///|
/// One auditable planned operation and its observed verdict.
pub(all) struct AuditEntry {
feature_id : String
device_id : String
strategy : Strategy
uncertainty : Double
verdict : Verdict
}
///|
/// Trace of a complete offline planning and observation pass.
pub(all) struct AuditTrace {
part_id : String
entries : Array[AuditEntry]
warnings : Array[String]
coverage : Double
capability : Bool
}
///|
fn has_observation(
feature_id : String,
observations : Array[Observation],
) -> Bool {
let mut result = false
for observation in observations {
if observation.feature_id == feature_id {
result = true
}
}
result
}
///|
/// Build an audit trace when only step IDs and observations are available.
pub fn build_audit_trace(
plan~ : MeasurementPlan,
observations~ : Array[Observation],
) -> AuditTrace {
let entries = []
for step in plan.steps {
let verdict = if has_observation(step.feature_id, observations) {
Verdict::Pass
} else {
Verdict::Unmeasured
}
entries.push({
feature_id: step.feature_id,
device_id: step.device_id,
strategy: step.strategy,
uncertainty: step.uncertainty,
verdict,
})
}
{
part_id: plan.part_id,
entries,
warnings: plan.summary.warnings,
coverage: plan.summary.coverage,
capability: plan.summary.capability,
}
}
///|
/// Build a trace with full feature-aware classification.
pub fn build_audit_trace_with_features(
plan~ : MeasurementPlan,
features~ : Array[Feature],
observations~ : Array[Observation],
) -> AuditTrace {
let entries = []
for step in plan.steps {
let mut verdict = Verdict::Unmeasured
for feature in features {
if feature.id == step.feature_id {
let mut observation = None
for item in observations {
if item.feature_id == feature.id && observation is None {
observation = Some(item)
}
}
verdict = classify(feature, observation)
}
}
entries.push({
feature_id: step.feature_id,
device_id: step.device_id,
strategy: step.strategy,
uncertainty: step.uncertainty,
verdict,
})
}
{
part_id: plan.part_id,
entries,
warnings: plan.summary.warnings,
coverage: plan.summary.coverage,
capability: plan.summary.capability,
}
}
///|
/// Count passing, failing, and unmeasured entries in a trace.
pub fn audit_verdict_counts(trace : AuditTrace) -> (Int, Int, Int) {
let mut pass = 0
let mut fail = 0
let mut unmeasured = 0
for entry in trace.entries {
match entry.verdict {
Pass => pass += 1
Fail(_) => fail += 1
Unmeasured => unmeasured += 1
}
}
(pass, fail, unmeasured)
}