///|
/// A candidate device with a deterministic score and reason.
pub(all) struct DeviceCandidate {
device_id : String
device : Device
score : Double
reason : SelectionReason
}
///|
/// A human-readable explanation for one feature in a plan.
pub(all) struct PlanExplanation {
feature_id : String
device_id : String
reason : SelectionReason
message : String
}
///|
/// Aggregate quality score for a plan.
pub(all) struct QualityScore {
coverage : Double
capability : Bool
warning_count : Int
score : Double
}
///|
fn candidate_is_before(left : DeviceCandidate, right : DeviceCandidate) -> Bool {
if left.score < right.score {
true
} else if left.score > right.score {
false
} else {
left.device_id.compare(right.device_id) > 0
}
}
///|
fn insert_candidate(
candidates : Array[DeviceCandidate],
candidate : DeviceCandidate,
) -> Array[DeviceCandidate] {
let result = []
let mut inserted = false
for item in candidates {
if !inserted && candidate_is_before(candidate, item) {
result.push(candidate)
inserted = true
}
result.push(item)
}
if !inserted {
result.push(candidate)
}
result
}
///|
/// Rank reachable devices that support the requested feature kind.
pub fn rank_devices(
feature~ : Feature,
devices~ : Array[Device],
) -> Array[DeviceCandidate] {
let result = []
for device in devices {
if device.can_measure(feature) {
let score = device.accuracy +
(device.reach - feature.nominal.abs()) * 0.0001
let candidate = DeviceCandidate::{
device_id: device.id,
device,
score,
reason: SelectionReason::Supported,
}
let reordered = insert_candidate(result, candidate)
result.clear()
for item in reordered {
result.push(item)
}
}
}
result
}
///|
/// Select the first deterministic candidate, if one exists.
pub fn choose_device(
feature~ : Feature,
devices~ : Array[Device],
) -> DeviceCandidate? {
let candidates = rank_devices(feature~, devices~)
candidates.get(0)
}
///|
/// Explain each planned feature and each plan-level warning.
pub fn explain_plan(plan : MeasurementPlan) -> Array[PlanExplanation] {
let result = []
for step in plan.steps {
result.push({
feature_id: step.feature_id,
device_id: step.device_id,
reason: SelectionReason::Supported,
message: "selected " + step.device_id,
})
}
if plan.steps.length() == 0 {
for warning in plan.summary.warnings {
result.push({
feature_id: warning,
device_id: "",
reason: SelectionReason::NoCandidate,
message: warning,
})
}
}
result
}
///|
/// Score coverage and capability while keeping warning count visible.
pub fn plan_quality(plan : MeasurementPlan) -> QualityScore {
let warning_count = plan.summary.warnings.length()
let multiplier = if plan.summary.capability { 1.0 } else { 0.5 }
let score = plan.summary.coverage * multiplier
{
coverage: plan.summary.coverage,
capability: plan.summary.capability,
warning_count,
score,
}
}