use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tracing::{debug, info, span, Level};
use crate::annotations::{EvidenceItem, TripleAnnotation};
#[derive(Debug, Clone)]
pub struct TrustConfig {
pub confidence_weight: f64,
pub source_weight: f64,
pub evidence_weight: f64,
pub freshness_weight: f64,
pub provenance_weight: f64,
pub decay_half_life_days: f64,
pub min_trust_threshold: f64,
pub propagation_damping: f64,
pub enable_bayesian_update: bool,
}
impl Default for TrustConfig {
fn default() -> Self {
Self {
confidence_weight: 0.3,
source_weight: 0.25,
evidence_weight: 0.25,
freshness_weight: 0.1,
provenance_weight: 0.1,
decay_half_life_days: 365.0, min_trust_threshold: 0.1,
propagation_damping: 0.9,
enable_bayesian_update: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceReputation {
pub source_id: String,
pub reputation: f64,
pub annotation_count: usize,
pub avg_confidence: f64,
pub verification_count: usize,
pub failure_count: usize,
pub last_updated: DateTime<Utc>,
}
impl SourceReputation {
pub fn new(source_id: String) -> Self {
Self {
source_id,
reputation: 0.5, annotation_count: 0,
avg_confidence: 0.0,
verification_count: 0,
failure_count: 0,
last_updated: Utc::now(),
}
}
pub fn update_for_annotation(&mut self, confidence: f64) {
let new_avg = (self.avg_confidence * self.annotation_count as f64 + confidence)
/ (self.annotation_count + 1) as f64;
self.avg_confidence = new_avg;
self.annotation_count += 1;
self.last_updated = Utc::now();
self.recalculate_reputation();
}
pub fn record_verification(&mut self, is_correct: bool) {
if is_correct {
self.verification_count += 1;
} else {
self.failure_count += 1;
}
self.last_updated = Utc::now();
self.recalculate_reputation();
}
fn recalculate_reputation(&mut self) {
let total_verifications = self.verification_count + self.failure_count;
if total_verifications > 0 {
let success_rate = self.verification_count as f64 / total_verifications as f64;
self.reputation = 0.7 * success_rate + 0.3 * self.avg_confidence;
} else {
self.reputation = self.avg_confidence;
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrustScoreBreakdown {
pub total_score: f64,
pub confidence_component: f64,
pub source_component: f64,
pub evidence_component: f64,
pub freshness_component: f64,
pub provenance_component: f64,
pub computed_at: DateTime<Utc>,
}
pub struct TrustScorer {
config: TrustConfig,
source_reputations: HashMap<String, SourceReputation>,
score_cache: HashMap<String, TrustScoreBreakdown>,
stats: TrustScoringStatistics,
}
#[derive(Debug, Clone, Default)]
pub struct TrustScoringStatistics {
pub scores_computed: usize,
pub cache_hits: usize,
pub cache_misses: usize,
pub avg_trust_score: f64,
pub high_trust_count: usize,
pub low_trust_count: usize,
}
impl TrustScorer {
pub fn new(config: TrustConfig) -> Self {
info!("Creating trust scorer");
Self {
config,
source_reputations: HashMap::new(),
score_cache: HashMap::new(),
stats: TrustScoringStatistics::default(),
}
}
pub fn calculate_trust(&mut self, annotation: &TripleAnnotation) -> TrustScoreBreakdown {
let span = span!(Level::DEBUG, "calculate_trust");
let _enter = span.enter();
let cache_key = format!("{:?}", annotation);
if let Some(cached) = self.score_cache.get(&cache_key) {
self.stats.cache_hits += 1;
return cached.clone();
}
self.stats.cache_misses += 1;
let confidence_component = self.compute_confidence_component(annotation);
let source_component = self.compute_source_component(annotation);
let evidence_component = self.compute_evidence_component(annotation);
let freshness_component = self.compute_freshness_component(annotation);
let provenance_component = self.compute_provenance_component(annotation);
let total_score = self.config.confidence_weight * confidence_component
+ self.config.source_weight * source_component
+ self.config.evidence_weight * evidence_component
+ self.config.freshness_weight * freshness_component
+ self.config.provenance_weight * provenance_component;
let breakdown = TrustScoreBreakdown {
total_score,
confidence_component,
source_component,
evidence_component,
freshness_component,
provenance_component,
computed_at: Utc::now(),
};
self.stats.scores_computed += 1;
self.stats.avg_trust_score =
(self.stats.avg_trust_score * (self.stats.scores_computed - 1) as f64 + total_score)
/ self.stats.scores_computed as f64;
if total_score > 0.7 {
self.stats.high_trust_count += 1;
} else if total_score < 0.3 {
self.stats.low_trust_count += 1;
}
self.score_cache.insert(cache_key, breakdown.clone());
debug!("Computed trust score: {:.3}", total_score);
breakdown
}
fn compute_confidence_component(&self, annotation: &TripleAnnotation) -> f64 {
annotation.confidence.unwrap_or(0.5)
}
fn compute_source_component(&mut self, annotation: &TripleAnnotation) -> f64 {
if let Some(ref source) = annotation.source {
let reputation = self
.source_reputations
.entry(source.clone())
.or_insert_with(|| SourceReputation::new(source.clone()));
if let Some(confidence) = annotation.confidence {
reputation.update_for_annotation(confidence);
}
reputation.reputation
} else {
0.5 }
}
fn compute_evidence_component(&self, annotation: &TripleAnnotation) -> f64 {
if annotation.evidence.is_empty() {
return 0.3; }
let total_strength: f64 = annotation.evidence.iter().map(|e| e.strength).sum();
let avg_strength = total_strength / annotation.evidence.len() as f64;
let evidence_count_factor = (annotation.evidence.len() as f64).ln() / 3.0;
(avg_strength + evidence_count_factor.min(0.2)).min(1.0)
}
fn compute_freshness_component(&self, annotation: &TripleAnnotation) -> f64 {
if let Some(timestamp) = annotation.timestamp {
let age_days = (Utc::now() - timestamp).num_days() as f64;
0.5_f64.powf(age_days / self.config.decay_half_life_days)
} else {
0.5 }
}
fn compute_provenance_component(&self, annotation: &TripleAnnotation) -> f64 {
if annotation.provenance.is_empty() {
return 0.3; }
let has_agent = annotation.provenance.iter().any(|p| !p.agent.is_empty());
let has_method = annotation.provenance.iter().any(|p| p.method.is_some());
let has_activity = annotation.provenance.iter().any(|p| p.activity.is_some());
let completeness = (has_agent as u8 + has_method as u8 + has_activity as u8) as f64 / 3.0;
let count_factor = (annotation.provenance.len() as f64).ln() / 3.0;
(completeness + count_factor.min(0.2)).min(1.0)
}
pub fn propagate_confidence(&self, annotations: &[TripleAnnotation]) -> Vec<f64> {
let span = span!(Level::DEBUG, "propagate_confidence");
let _enter = span.enter();
if annotations.is_empty() {
return Vec::new();
}
let n = annotations.len();
let mut confidence_values = vec![0.5; n];
for (i, ann) in annotations.iter().enumerate() {
confidence_values[i] = ann.confidence.unwrap_or(0.5);
}
let max_iterations = 10;
let convergence_threshold = 0.001;
for iteration in 0..max_iterations {
let mut new_values = confidence_values.clone();
for i in 0..n {
let current = confidence_values[i];
let mut neighbor_sum = 0.0;
let mut neighbor_count = 0;
for meta in &annotations[i].meta_annotations {
neighbor_sum += meta.annotation.confidence.unwrap_or(0.5);
neighbor_count += 1;
}
if neighbor_count > 0 {
let neighbor_avg = neighbor_sum / neighbor_count as f64;
new_values[i] = current * (1.0 - self.config.propagation_damping)
+ neighbor_avg * self.config.propagation_damping;
}
}
let max_change = confidence_values
.iter()
.zip(new_values.iter())
.map(|(old, new)| (old - new).abs())
.fold(0.0, f64::max);
confidence_values = new_values;
if max_change < convergence_threshold {
debug!(
"Confidence propagation converged after {} iterations",
iteration + 1
);
break;
}
}
confidence_values
}
pub fn record_verification(&mut self, source: &str, is_correct: bool) {
let reputation = self
.source_reputations
.entry(source.to_string())
.or_insert_with(|| SourceReputation::new(source.to_string()));
reputation.record_verification(is_correct);
}
pub fn get_source_reputation(&self, source: &str) -> Option<&SourceReputation> {
self.source_reputations.get(source)
}
pub fn bayesian_update(&self, prior_confidence: f64, evidence: &[EvidenceItem]) -> f64 {
if !self.config.enable_bayesian_update || evidence.is_empty() {
return prior_confidence;
}
let mut posterior = prior_confidence;
for ev in evidence {
let likelihood_ratio = ev.strength;
let numerator = likelihood_ratio * posterior;
let denominator =
likelihood_ratio * posterior + (1.0 - likelihood_ratio) * (1.0 - posterior);
posterior = if denominator > 0.0 {
numerator / denominator
} else {
posterior
};
}
posterior.clamp(0.0, 1.0)
}
pub fn clear_cache(&mut self) {
self.score_cache.clear();
}
pub fn statistics(&self) -> &TrustScoringStatistics {
&self.stats
}
pub fn get_all_reputations(&self) -> &HashMap<String, SourceReputation> {
&self.source_reputations
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trust_scorer_creation() {
let config = TrustConfig::default();
let scorer = TrustScorer::new(config);
let stats = scorer.statistics();
assert_eq!(stats.scores_computed, 0);
}
#[test]
fn test_basic_trust_calculation() {
let config = TrustConfig::default();
let mut scorer = TrustScorer::new(config);
let mut annotation = TripleAnnotation::new()
.with_confidence(0.9)
.with_source("http://example.org/source".to_string());
annotation.quality_score = Some(0.8);
let breakdown = scorer.calculate_trust(&annotation);
assert!(breakdown.total_score > 0.0 && breakdown.total_score <= 1.0);
assert_eq!(breakdown.confidence_component, 0.9);
}
#[test]
fn test_evidence_component() {
let config = TrustConfig::default();
let mut scorer = TrustScorer::new(config);
let mut annotation = TripleAnnotation::new().with_confidence(0.8);
annotation.evidence.push(EvidenceItem {
evidence_type: "experimental".to_string(),
reference: "http://example.org/study1".to_string(),
strength: 0.9,
description: Some("Strong experimental evidence".to_string()),
});
let breakdown = scorer.calculate_trust(&annotation);
assert!(breakdown.evidence_component > 0.3);
}
#[test]
fn test_source_reputation() {
let config = TrustConfig::default();
let mut scorer = TrustScorer::new(config);
let source = "http://example.org/reliable_source";
for i in 0..5 {
let confidence = 0.85 + (i as f64 * 0.02); let annotation = TripleAnnotation::new()
.with_confidence(confidence)
.with_source(source.to_string());
scorer.calculate_trust(&annotation);
}
let reputation = scorer.get_source_reputation(source).unwrap();
assert!(reputation.reputation > 0.5);
assert_eq!(reputation.annotation_count, 5);
}
#[test]
fn test_verification_updates_reputation() {
let config = TrustConfig::default();
let mut scorer = TrustScorer::new(config);
let source = "http://example.org/source";
let annotation = TripleAnnotation::new()
.with_confidence(0.8)
.with_source(source.to_string());
scorer.calculate_trust(&annotation);
let initial_reputation = scorer.get_source_reputation(source).unwrap().reputation;
for _ in 0..5 {
scorer.record_verification(source, true);
}
let updated_reputation = scorer.get_source_reputation(source).unwrap().reputation;
assert!(updated_reputation > initial_reputation);
}
#[test]
fn test_confidence_propagation() {
let config = TrustConfig::default();
let scorer = TrustScorer::new(config);
let annotations = vec![
TripleAnnotation::new().with_confidence(0.9),
TripleAnnotation::new().with_confidence(0.5),
TripleAnnotation::new().with_confidence(0.7),
];
let propagated = scorer.propagate_confidence(&annotations);
assert_eq!(propagated.len(), 3);
assert!(propagated.iter().all(|&c| (0.0..=1.0).contains(&c)));
}
#[test]
fn test_bayesian_update() {
let config = TrustConfig::default();
let scorer = TrustScorer::new(config);
let prior = 0.5;
let evidence = vec![EvidenceItem {
evidence_type: "experimental".to_string(),
reference: "http://example.org/study1".to_string(),
strength: 0.9,
description: None,
}];
let posterior = scorer.bayesian_update(prior, &evidence);
assert!(posterior > prior);
assert!((0.0..=1.0).contains(&posterior));
}
#[test]
fn test_freshness_decay() {
let config = TrustConfig::default();
let mut scorer = TrustScorer::new(config);
let old_annotation = TripleAnnotation {
confidence: Some(0.9),
timestamp: Some(Utc::now() - chrono::Duration::days(730)), ..Default::default()
};
let recent_annotation = TripleAnnotation {
confidence: Some(0.9),
timestamp: Some(Utc::now()),
..Default::default()
};
let old_breakdown = scorer.calculate_trust(&old_annotation);
let recent_breakdown = scorer.calculate_trust(&recent_annotation);
assert!(recent_breakdown.freshness_component > old_breakdown.freshness_component);
}
#[test]
fn test_cache_effectiveness() {
let config = TrustConfig::default();
let mut scorer = TrustScorer::new(config);
let annotation = TripleAnnotation::new().with_confidence(0.9);
scorer.calculate_trust(&annotation);
assert_eq!(scorer.statistics().cache_hits, 0);
assert_eq!(scorer.statistics().cache_misses, 1);
scorer.calculate_trust(&annotation);
assert_eq!(scorer.statistics().cache_hits, 1);
assert_eq!(scorer.statistics().cache_misses, 1);
}
}