oxibrain_core/
confidence.rs1use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ConfidenceComponents {
13 pub raw: f32,
15 pub calibrated: f32,
17 pub corroboration: f32,
19 pub trust: f32,
21 pub recency: f32,
23}
24
25impl ConfidenceComponents {
26 pub fn combine(&self) -> f32 {
28 let c = self.raw * self.calibrated * self.corroboration * self.trust * self.recency;
29 c.clamp(0.0, 1.0)
30 }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, Default)]
35pub struct CalibrationTable {
36 pub values: std::collections::BTreeMap<String, f32>,
37}
38
39impl CalibrationTable {
40 pub fn get(&self, extractor_id: &str) -> Option<f32> {
43 self.values.get(extractor_id).copied()
44 }
45
46 pub fn set(&mut self, extractor_id: &str, value: f32) {
48 self.values
49 .insert(extractor_id.to_string(), value.clamp(0.1, 2.0));
50 }
51
52 pub fn to_json(&self) -> String {
54 serde_json::to_string(self).expect("calibration table serializable")
55 }
56
57 pub fn from_json(s: &str) -> Self {
59 serde_json::from_str(s).unwrap_or_default()
60 }
61}
62
63pub fn calibrate(extractor_id: &str, table: &CalibrationTable) -> f32 {
66 table.get(extractor_id).unwrap_or(0.8)
67}
68
69pub fn derive_calibration(precision: f64, fabrication_rate: f64) -> f32 {
73 let base = 0.8_f32;
74 let precision_factor = precision as f32;
75 let fabrication_penalty = 1.0 - fabrication_rate as f32;
76 (base * precision_factor * fabrication_penalty).clamp(0.1, 2.0)
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn combine_clamps_to_unit_range() {
85 let c = ConfidenceComponents {
86 raw: 2.0,
87 calibrated: 2.0,
88 corroboration: 2.0,
89 trust: 2.0,
90 recency: 2.0,
91 };
92 assert_eq!(c.combine(), 1.0);
93
94 let c = ConfidenceComponents {
95 raw: 0.0,
96 calibrated: 0.5,
97 corroboration: 0.5,
98 trust: 0.5,
99 recency: 0.5,
100 };
101 assert_eq!(c.combine(), 0.0);
102 }
103
104 #[test]
105 fn calibrate_returns_prior_for_unknown() {
106 let table = CalibrationTable::default();
107 assert_eq!(calibrate("unknown", &table), 0.8);
108 }
109
110 #[test]
111 fn calibrate_returns_stored_value() {
112 let mut table = CalibrationTable::default();
113 table.set("ext1", 1.2);
114 assert_eq!(calibrate("ext1", &table), 1.2);
115 }
116
117 #[test]
118 fn calibration_table_roundtrip() {
119 let mut table = CalibrationTable::default();
120 table.set("ext1", 1.0);
121 table.set("ext2", 0.5);
122 let json = table.to_json();
123 let restored = CalibrationTable::from_json(&json);
124 assert_eq!(restored.get("ext1"), Some(1.0));
125 assert_eq!(restored.get("ext2"), Some(0.5));
126 }
127
128 #[test]
129 fn derive_calibration_monotonic_in_precision() {
130 let low = derive_calibration(0.5, 0.0);
131 let high = derive_calibration(0.95, 0.0);
132 assert!(high > low);
133 }
134
135 #[test]
136 fn derive_calibration_penalizes_fabrication() {
137 let clean = derive_calibration(0.9, 0.0);
138 let dirty = derive_calibration(0.9, 0.1);
139 assert!(clean > dirty);
140 }
141}