Skip to main content

lean_ctx/core/
evidence_classification.rs

1//! Deterministic classification of savings evidence.
2
3use crate::core::savings_ledger::event::{
4    EvidenceClass, MECHANISM_CACHING, MECHANISM_COMPRESSION, MECHANISM_ROUTING, MeasurementMethod,
5};
6
7/// Classify the measurement method and evidence strength for a savings event.
8pub fn classify(
9    mechanism: &str,
10    has_tokenizer_count: bool,
11    is_holdout: bool,
12) -> (MeasurementMethod, EvidenceClass) {
13    if is_holdout {
14        (MeasurementMethod::Holdout, EvidenceClass::Statistical)
15    } else {
16        match mechanism {
17            MECHANISM_COMPRESSION if has_tokenizer_count => {
18                (MeasurementMethod::DirectCount, EvidenceClass::Measured)
19            }
20            MECHANISM_COMPRESSION => (MeasurementMethod::DirectCount, EvidenceClass::Approximated),
21            MECHANISM_ROUTING => (
22                MeasurementMethod::BaselineEstimate,
23                EvidenceClass::Approximated,
24            ),
25            MECHANISM_CACHING => (
26                MeasurementMethod::ProviderReconciled,
27                EvidenceClass::Measured,
28            ),
29            _ => (MeasurementMethod::Unknown, EvidenceClass::Unclassified),
30        }
31    }
32}
33
34/// Return the default confidence associated with an evidence class.
35pub fn confidence_for_class(class: &EvidenceClass) -> f64 {
36    match class {
37        EvidenceClass::Measured => 1.0,
38        EvidenceClass::Approximated => 0.85,
39        EvidenceClass::Statistical => 0.7,
40        EvidenceClass::Declared => 0.3,
41        EvidenceClass::Unclassified => 0.0,
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn compression_with_tokenizer_count_is_measured() {
51        assert_eq!(
52            classify(MECHANISM_COMPRESSION, true, false),
53            (MeasurementMethod::DirectCount, EvidenceClass::Measured)
54        );
55    }
56
57    #[test]
58    fn compression_without_tokenizer_count_is_approximated() {
59        assert_eq!(
60            classify(MECHANISM_COMPRESSION, false, false),
61            (MeasurementMethod::DirectCount, EvidenceClass::Approximated)
62        );
63    }
64
65    #[test]
66    fn routing_is_baseline_estimate_and_approximated() {
67        assert_eq!(
68            classify(MECHANISM_ROUTING, true, false),
69            (
70                MeasurementMethod::BaselineEstimate,
71                EvidenceClass::Approximated
72            )
73        );
74    }
75
76    #[test]
77    fn caching_is_provider_reconciled_and_measured() {
78        assert_eq!(
79            classify(MECHANISM_CACHING, false, false),
80            (
81                MeasurementMethod::ProviderReconciled,
82                EvidenceClass::Measured
83            )
84        );
85    }
86
87    #[test]
88    fn holdout_takes_precedence_and_is_statistical() {
89        assert_eq!(
90            classify(MECHANISM_COMPRESSION, true, true),
91            (MeasurementMethod::Holdout, EvidenceClass::Statistical)
92        );
93    }
94
95    #[test]
96    fn unknown_mechanism_is_unclassified() {
97        assert_eq!(
98            classify("other", true, false),
99            (MeasurementMethod::Unknown, EvidenceClass::Unclassified)
100        );
101    }
102
103    #[test]
104    fn confidence_covers_every_evidence_class() {
105        assert_eq!(confidence_for_class(&EvidenceClass::Measured), 1.0);
106        assert_eq!(confidence_for_class(&EvidenceClass::Approximated), 0.85);
107        assert_eq!(confidence_for_class(&EvidenceClass::Statistical), 0.7);
108        assert_eq!(confidence_for_class(&EvidenceClass::Declared), 0.3);
109        assert_eq!(confidence_for_class(&EvidenceClass::Unclassified), 0.0);
110    }
111}