///|
/// End-to-end stream pipeline: preprocess packets, fuse a state estimate, and
/// maintain lifecycle/health metadata for production telemetry.
pub struct PipelineEvent {
fusion : FusionEvent
lifecycle : TrackLifecycle
sensor_score : Double
rolling_mean : Double
rolling_variance : Double
} derive(Debug)
///|
pub fn PipelineEvent::fusion(self : PipelineEvent) -> FusionEvent {
self.fusion
}
///|
pub fn PipelineEvent::lifecycle(self : PipelineEvent) -> TrackLifecycle {
self.lifecycle
}
///|
pub fn PipelineEvent::sensor_score(self : PipelineEvent) -> Double {
self.sensor_score
}
///|
pub fn PipelineEvent::rolling_mean(self : PipelineEvent) -> Double {
self.rolling_mean
}
///|
pub fn PipelineEvent::rolling_variance(self : PipelineEvent) -> Double {
self.rolling_variance
}
///|
pub struct SensorPipeline {
fusion : SensorFusion
tracker : TrackManager
health : Map[String, SensorHealth]
windows : Map[String, RollingWindow]
window_capacity : Int
}
///|
pub fn SensorPipeline::new(
fusion : SensorFusion,
confirmation_hits : Int,
deletion_misses : Int,
window_capacity : Int,
) -> SensorPipeline {
{
fusion,
tracker: TrackManager::new(confirmation_hits, deletion_misses),
health: {},
windows: {},
window_capacity: if window_capacity < 1 {
1
} else {
window_capacity
},
}
}
///|
fn SensorPipeline::window_for(
self : SensorPipeline,
sensor : String,
) -> RollingWindow {
match self.windows.get(sensor) {
Some(window) => window
None => {
let window = RollingWindow::new(self.window_capacity)
self.windows[sensor] = window
window
}
}
}
///|
fn SensorPipeline::health_for(
self : SensorPipeline,
sensor : String,
) -> SensorHealth {
match self.health.get(sensor) {
Some(value) => value
None => {
let value = SensorHealth::new(sensor, 0.2, 0.1)
self.health[sensor] = value
value
}
}
}
///|
pub fn SensorPipeline::process(
self : SensorPipeline,
packet : ObservationPacket,
) -> PipelineEvent {
let sensor = packet.sensor()
let window = self.window_for(sensor)
let value = packet.values()
if value.length() > 0 {
window.push(value[0])
}
let health = self.health_for(sensor)
let fusion = self.fusion.process(packet)
health.observe(fusion.result())
let lifecycle = self.tracker.observe(fusion.result())
{
fusion,
lifecycle,
sensor_score: health.score(),
rolling_mean: window.mean(),
rolling_variance: window.variance(),
}
}
///|
pub fn SensorPipeline::process_missing(
self : SensorPipeline,
timestamp : Int,
sensor : String,
) -> PipelineEvent {
let window = self.window_for(sensor)
let health = self.health_for(sensor)
let fusion = self.fusion.process_missing(timestamp, sensor)
health.observe(fusion.result())
let lifecycle = self.tracker.observe(fusion.result())
{
fusion,
lifecycle,
sensor_score: health.score(),
rolling_mean: window.mean(),
rolling_variance: window.variance(),
}
}
///|
pub fn SensorPipeline::run(
self : SensorPipeline,
packets : Array[ObservationPacket],
) -> Array[PipelineEvent] {
let events : Array[PipelineEvent] = []
for packet in packets {
events.push(self.process(packet))
}
events
}
///|
pub fn SensorPipeline::lifecycle(self : SensorPipeline) -> TrackLifecycle {
self.tracker.lifecycle()
}
///|
pub fn SensorPipeline::sensor_health(
self : SensorPipeline,
sensor : String,
) -> SensorHealth {
self.health_for(sensor)
}
///|
pub fn SensorPipeline::window(
self : SensorPipeline,
sensor : String,
) -> RollingWindow {
self.window_for(sensor)
}
///|
pub struct PipelineReport {
events : Int
accepted : Int
rejected : Int
missing : Int
final_lifecycle : TrackLifecycle
final_sensor_score : Double
} derive(Debug)
///|
pub fn PipelineReport::events(self : PipelineReport) -> Int {
self.events
}
///|
pub fn PipelineReport::accepted(self : PipelineReport) -> Int {
self.accepted
}
///|
pub fn PipelineReport::rejected(self : PipelineReport) -> Int {
self.rejected
}
///|
pub fn PipelineReport::missing(self : PipelineReport) -> Int {
self.missing
}
///|
pub fn PipelineReport::final_lifecycle(self : PipelineReport) -> TrackLifecycle {
self.final_lifecycle
}
///|
pub fn PipelineReport::final_sensor_score(self : PipelineReport) -> Double {
self.final_sensor_score
}
///|
pub fn summarize_pipeline(
events : Array[PipelineEvent],
sensor : String,
) -> PipelineReport {
let mut accepted = 0
let mut rejected = 0
let mut missing = 0
let mut lifecycle = Tentative
let mut score = 1.0
for event in events {
lifecycle = event.lifecycle()
score = event.sensor_score()
match event.fusion().result() {
Accepted => accepted = accepted + 1
MissingMeasurement => missing = missing + 1
RejectedByGate | InvalidMeasurement | SingularInnovation =>
rejected = rejected + 1
}
}
let _ = sensor
{
events: accepted + rejected + missing,
accepted,
rejected,
missing,
final_lifecycle: lifecycle,
final_sensor_score: score,
}
}