use ulid::Ulid;
pub const TRUST_SCORES: &[(&str, f32)] = &[
("human-decision", 0.95),
("qa-event", 0.75),
("agent-log", 0.50),
("web-scraped", 0.35),
];
pub trait TrustLookup {
fn get_trust(&self, id: &Ulid) -> Option<f32>;
}
pub fn resolve_provenance(section_hint: Option<&str>) -> &'static str {
match section_hint {
Some(hint) => TRUST_SCORES
.iter()
.find(|(k, _)| *k == hint)
.map(|(k, _)| *k)
.unwrap_or("agent-log"),
None => "agent-log",
}
}
pub fn trust_for(provenance: &str) -> Option<f32> {
TRUST_SCORES
.iter()
.find(|(k, _)| *k == provenance)
.map(|(_, v)| *v)
}
pub fn compute_distill_trust(sources: &[Ulid], index: &dyn TrustLookup, confidence: f32) -> f32 {
let trusts: Vec<f32> = sources
.iter()
.filter_map(|id| index.get_trust(id))
.collect();
if trusts.is_empty() {
return 0.5;
}
let mean = trusts.iter().sum::<f32>() / trusts.len() as f32;
(mean * confidence).clamp(0.0, 1.0)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
struct FakeIndex(HashMap<Ulid, f32>);
impl TrustLookup for FakeIndex {
fn get_trust(&self, id: &Ulid) -> Option<f32> {
self.0.get(id).copied()
}
}
#[test]
fn trust_scores_known_values() {
assert_eq!(trust_for("human-decision"), Some(0.95));
assert_eq!(trust_for("qa-event"), Some(0.75));
assert_eq!(trust_for("agent-log"), Some(0.50));
assert_eq!(trust_for("web-scraped"), Some(0.35));
assert_eq!(trust_for("unknown"), None);
assert_eq!(trust_for("human-validated"), None); assert_eq!(trust_for("distilled"), None); }
#[test]
fn distill_empty_sources_is_neutral() {
let idx = FakeIndex(HashMap::new());
assert_eq!(compute_distill_trust(&[], &idx, 0.87), 0.5);
}
#[test]
fn distill_empty_sources_confidence_zero_is_neutral() {
let idx = FakeIndex(HashMap::new());
assert_eq!(compute_distill_trust(&[], &idx, 0.0), 0.5);
}
#[test]
fn distill_mean_times_confidence() {
let (a, b) = (Ulid::new(), Ulid::new());
let mut m = HashMap::new();
m.insert(a, 0.95);
m.insert(b, 0.75);
let idx = FakeIndex(m);
let got = compute_distill_trust(&[a, b], &idx, 0.80);
assert!(
(got - 0.85_f32 * 0.80_f32).abs() < 1e-6,
"attendu ≈ 0.68, obtenu {got}"
);
}
#[test]
fn distill_clamp_high() {
let id = Ulid::new();
let mut m = HashMap::new();
m.insert(id, 1.0_f32);
let idx = FakeIndex(m);
let got = compute_distill_trust(&[id], &idx, 1.0);
assert!(got <= 1.0, "clamp haut violé : {got}");
assert!((got - 1.0_f32).abs() < 1e-6);
}
#[test]
fn distill_clamp_low() {
let id = Ulid::new();
let mut m = HashMap::new();
m.insert(id, 0.95_f32);
let idx = FakeIndex(m);
let got = compute_distill_trust(&[id], &idx, 0.0);
assert!((got - 0.0_f32).abs() < 1e-6, "clamp bas violé : {got}");
}
#[test]
fn distill_unknown_sources_counted_as_missing_not_zero() {
let (a, b) = (Ulid::new(), Ulid::new());
let idx = FakeIndex(HashMap::new()); let got = compute_distill_trust(&[a, b], &idx, 0.9);
assert_eq!(got, 0.5, "toutes sources inconnues → neutre 0.5");
}
}