///|
/// Data-quality summary for a timestamped sensor stream.
pub struct DataQualityReport {
total : Int
valid : Int
missing : Int
non_finite : Int
non_monotonic_timestamps : Int
duplicate_timestamps : Int
finite_fraction : Double
} derive(Debug)
///|
pub fn DataQualityReport::total(self : DataQualityReport) -> Int {
self.total
}
///|
pub fn DataQualityReport::valid(self : DataQualityReport) -> Int {
self.valid
}
///|
pub fn DataQualityReport::missing(self : DataQualityReport) -> Int {
self.missing
}
///|
pub fn DataQualityReport::non_finite(self : DataQualityReport) -> Int {
self.non_finite
}
///|
pub fn DataQualityReport::non_monotonic_timestamps(
self : DataQualityReport,
) -> Int {
self.non_monotonic_timestamps
}
///|
pub fn DataQualityReport::duplicate_timestamps(self : DataQualityReport) -> Int {
self.duplicate_timestamps
}
///|
pub fn DataQualityReport::finite_fraction(self : DataQualityReport) -> Double {
self.finite_fraction
}
///|
pub fn assess_sensor_samples(
samples : Array[SensorSample],
) -> DataQualityReport {
let mut valid = 0
let mut missing = 0
let mut non_finite = 0
let mut non_monotonic = 0
let mut duplicate = 0
let mut previous_timestamp : Int? = None
for sample in samples {
if sample.missing() {
missing = missing + 1
} else if vector_is_finite(sample.measurement()) {
valid = valid + 1
} else {
non_finite = non_finite + 1
}
match previous_timestamp {
None => ()
Some(previous) => {
if sample.timestamp() < previous {
non_monotonic = non_monotonic + 1
}
if sample.timestamp() == previous {
duplicate = duplicate + 1
}
}
}
previous_timestamp = Some(sample.timestamp())
}
let total = samples.length()
{
total,
valid,
missing,
non_finite,
non_monotonic_timestamps: non_monotonic,
duplicate_timestamps: duplicate,
finite_fraction: if total == 0 {
0.0
} else {
valid.to_double() / total.to_double()
},
}
}
///|
pub struct PacketQuality {
sensor : String
report : DataQualityReport
accepted : Bool
reason : String
} derive(Debug)
///|
pub fn PacketQuality::sensor(self : PacketQuality) -> String {
self.sensor
}
///|
pub fn PacketQuality::report(self : PacketQuality) -> DataQualityReport {
self.report
}
///|
pub fn PacketQuality::accepted(self : PacketQuality) -> Bool {
self.accepted
}
///|
pub fn PacketQuality::reason(self : PacketQuality) -> String {
self.reason
}
///|
pub fn assess_packet(
packet : ObservationPacket,
minimum_finite_fraction : Double,
) -> PacketQuality {
let values = packet.values()
let finite = vector_is_finite(values)
let report : DataQualityReport = {
total: 1,
valid: if packet.is_valid() && finite {
1
} else {
0
},
missing: if values.length() == 0 {
1
} else {
0
},
non_finite: if finite {
0
} else {
1
},
non_monotonic_timestamps: 0,
duplicate_timestamps: 0,
finite_fraction: if packet.is_valid() && finite {
1.0
} else {
0.0
},
}
let threshold = if minimum_finite_fraction < 0.0 {
0.0
} else {
minimum_finite_fraction
}
let accepted = report.finite_fraction() >= threshold && packet.is_valid()
{
sensor: packet.sensor(),
report,
accepted,
reason: if accepted {
"ok"
} else if !finite {
"non_finite"
} else if values.length() == 0 {
"missing"
} else {
"invalid_shape"
},
}
}
///|
pub fn quality_weight(report : DataQualityReport) -> Double {
if report.total() == 0 {
0.0
} else {
report.finite_fraction()
}
}
///|
pub fn quality_adjusted_covariance(
covariance : Matrix,
report : DataQualityReport,
floor : Double,
) -> Matrix {
let safe_floor = if floor <= 0.0 { 0.000001 } else { floor }
let weight = quality_weight(report)
if weight <= 0.000000000001 {
covariance.add_diagonal(1000000.0)
} else {
covariance.scale(1.0 / weight).add_diagonal(safe_floor)
}
}