use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfidenceComponents {
pub raw: f32,
pub calibrated: f32,
pub corroboration: f32,
pub trust: f32,
pub recency: f32,
}
impl ConfidenceComponents {
pub fn combine(&self) -> f32 {
let c = self.raw * self.calibrated * self.corroboration * self.trust * self.recency;
c.clamp(0.0, 1.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CalibrationTable {
pub values: std::collections::BTreeMap<String, f32>,
}
impl CalibrationTable {
pub fn get(&self, extractor_id: &str) -> Option<f32> {
self.values.get(extractor_id).copied()
}
pub fn set(&mut self, extractor_id: &str, value: f32) {
self.values
.insert(extractor_id.to_string(), value.clamp(0.1, 2.0));
}
pub fn to_json(&self) -> String {
serde_json::to_string(self).expect("calibration table serializable")
}
pub fn from_json(s: &str) -> Self {
serde_json::from_str(s).unwrap_or_default()
}
}
pub fn calibrate(extractor_id: &str, table: &CalibrationTable) -> f32 {
table.get(extractor_id).unwrap_or(0.8)
}
pub fn derive_calibration(precision: f64, fabrication_rate: f64) -> f32 {
let base = 0.8_f32;
let precision_factor = precision as f32;
let fabrication_penalty = 1.0 - fabrication_rate as f32;
(base * precision_factor * fabrication_penalty).clamp(0.1, 2.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn combine_clamps_to_unit_range() {
let c = ConfidenceComponents {
raw: 2.0,
calibrated: 2.0,
corroboration: 2.0,
trust: 2.0,
recency: 2.0,
};
assert_eq!(c.combine(), 1.0);
let c = ConfidenceComponents {
raw: 0.0,
calibrated: 0.5,
corroboration: 0.5,
trust: 0.5,
recency: 0.5,
};
assert_eq!(c.combine(), 0.0);
}
#[test]
fn calibrate_returns_prior_for_unknown() {
let table = CalibrationTable::default();
assert_eq!(calibrate("unknown", &table), 0.8);
}
#[test]
fn calibrate_returns_stored_value() {
let mut table = CalibrationTable::default();
table.set("ext1", 1.2);
assert_eq!(calibrate("ext1", &table), 1.2);
}
#[test]
fn calibration_table_roundtrip() {
let mut table = CalibrationTable::default();
table.set("ext1", 1.0);
table.set("ext2", 0.5);
let json = table.to_json();
let restored = CalibrationTable::from_json(&json);
assert_eq!(restored.get("ext1"), Some(1.0));
assert_eq!(restored.get("ext2"), Some(0.5));
}
#[test]
fn derive_calibration_monotonic_in_precision() {
let low = derive_calibration(0.5, 0.0);
let high = derive_calibration(0.95, 0.0);
assert!(high > low);
}
#[test]
fn derive_calibration_penalizes_fabrication() {
let clean = derive_calibration(0.9, 0.0);
let dirty = derive_calibration(0.9, 0.1);
assert!(clean > dirty);
}
}