use std::sync::OnceLock;
use omena_cascade::{
CascadeDeclaration, cascade_margin_for_outcome, cascade_property_for_key,
summarize_cascade_margin_schema_v0,
};
use omena_query_checker_orchestrator::{
OmenaCheckerCascadeDeclarationInputV0, OmenaCheckerCascadeEvaluationV0,
};
use super::super::OmenaQueryCascadeConfidenceV0;
use super::runtime_state::query_runtime_cascade_declaration_from_input;
pub(super) fn summarize_query_cascade_confidence_for_evaluation(
evaluation: &OmenaCheckerCascadeEvaluationV0,
declarations: &[OmenaCheckerCascadeDeclarationInputV0],
) -> Option<OmenaQueryCascadeConfidenceV0> {
if !matches!(
evaluation.rule_code_name,
"unreachable-declaration" | "dead-cascade-layer"
) {
return None;
}
let margin = query_cascade_margin_for_evaluation(evaluation, declarations)?;
let abs_distance = margin.signed_distance.unsigned_abs();
let dominant_axis_weight_basis_points =
query_cascade_confidence_axis_weight_basis_points(margin.dominant_axis);
let sigmoid_temperature_basis_points = 1_200u16;
let confidence_score_basis_points = query_cascade_confidence_score_basis_points(
abs_distance,
dominant_axis_weight_basis_points,
sigmoid_temperature_basis_points,
);
Some(OmenaQueryCascadeConfidenceV0 {
schema_version: "0",
product: "omena-query.cascade-confidence",
feature_gate: "cascade-confidence-v0",
confidence_kind: "fixtureWitnessTierWeightedSigmoid",
claim_level: "fixtureWitnessResearchHint",
theorem_claimed: false,
public_safety_claim_ready: false,
calibration_stage: "fixtureWitnessTierWeightSigmoidV0",
margin_product: margin.product,
margin_kind: margin.margin_kind,
dominant_axis: margin.dominant_axis,
dominant_axis_weight_basis_points,
sigmoid_temperature_basis_points,
signed_distance: margin.signed_distance,
abs_distance,
confidence_score_basis_points,
confidence_bucket: query_cascade_confidence_bucket(confidence_score_basis_points),
winner_declaration_id: margin.winner_declaration_id,
challenger_declaration_id: margin.challenger_declaration_id,
})
}
fn query_cascade_margin_for_evaluation(
evaluation: &OmenaCheckerCascadeEvaluationV0,
declarations: &[OmenaCheckerCascadeDeclarationInputV0],
) -> Option<omena_cascade::CascadeMarginV0> {
let anchor_id = evaluation.declaration_ids.first()?;
let anchor = declarations
.iter()
.find(|declaration| declaration.declaration_id == *anchor_id)?;
let site_declarations = declarations
.iter()
.filter(|declaration| {
declaration.selector == anchor.selector
&& declaration
.property
.to_property_name()
.same_as(&anchor.property.to_property_name())
&& declaration.condition_context == anchor.condition_context
})
.map(query_diagnostic_cascade_declaration_from_input)
.collect::<Vec<_>>();
if site_declarations.len() < 2 {
return None;
}
let outcome = cascade_property_for_key(
site_declarations,
&anchor.property.to_property_name().canonical_key(),
);
cascade_margin_for_outcome(&outcome)
}
fn query_diagnostic_cascade_declaration_from_input(
input: &OmenaCheckerCascadeDeclarationInputV0,
) -> CascadeDeclaration {
let mut declaration = query_runtime_cascade_declaration_from_input(input);
declaration.id = input.declaration_id.clone();
declaration
}
fn query_cascade_confidence_axis_weight_basis_points(axis: &str) -> u16 {
static AXIS_ORDER: OnceLock<Vec<&'static str>> = OnceLock::new();
let axis_order = AXIS_ORDER.get_or_init(|| summarize_cascade_margin_schema_v0().axis_order);
axis_order
.iter()
.position(|candidate| *candidate == axis)
.and_then(|position| u16::try_from((axis_order.len() - position) * 1_000).ok())
.unwrap_or(500)
}
fn query_cascade_confidence_score_basis_points(
abs_distance: u64,
axis_weight_basis_points: u16,
sigmoid_temperature_basis_points: u16,
) -> u16 {
let signed_input = (abs_distance as f64 * f64::from(axis_weight_basis_points))
/ f64::from(sigmoid_temperature_basis_points);
let confidence = 1.0 / (1.0 + (-signed_input).exp());
(confidence * 10_000.0).round().clamp(0.0, 10_000.0) as u16
}
fn query_cascade_confidence_bucket(score_basis_points: u16) -> &'static str {
match score_basis_points {
0..=5_999 => "narrow",
6_000..=8_499 => "moderate",
_ => "clear",
}
}