Skip to main content

lean_ctx/core/knowledge/
fact.rs

1use chrono::{DateTime, Utc};
2
3use super::types::{FidelityScore, KnowledgeFact};
4
5impl KnowledgeFact {
6    pub fn is_current(&self) -> bool {
7        self.valid_until.is_none()
8    }
9
10    /// Stable, intrinsic quality metric (0.0..1.0).
11    ///
12    /// Based only on confidence, confirmation count, and feedback balance.
13    /// Deliberately excludes volatile signals (retrieval count, recency) to
14    /// keep recall output deterministic. For display ordering use
15    /// `salience_score()` which adds recency and category weighting.
16    pub fn quality_score(&self) -> f32 {
17        let confidence = self.confidence.clamp(0.0, 1.0);
18        let confirmations_norm = (self.confirmation_count.min(5) as f32) / 5.0;
19        let balance = self.feedback_up as i32 - self.feedback_down as i32;
20        let feedback_effect = (balance as f32 / 4.0).tanh() * 0.1;
21
22        // IMPORTANT: quality_score must be stable across repeated recall calls.
23        // Retrieval signals (retrieval_count/last_retrieved) are persisted, but should not change
24        // the displayed "quality" score, otherwise recall output becomes non-deterministic.
25        (0.8 * confidence + 0.2 * confirmations_norm + feedback_effect).clamp(0.0, 1.0)
26    }
27
28    pub fn was_valid_at(&self, at: DateTime<Utc>) -> bool {
29        let after_start = self.valid_from.is_none_or(|from| at >= from);
30        let before_end = self.valid_until.is_none_or(|until| at <= until);
31        after_start && before_end
32    }
33
34    /// Compute structural fidelity score (0.0 - 1.0).
35    /// Based on: has source, confirmations, confidence, freshness, feedback.
36    pub fn compute_structural_fidelity(&self) -> f64 {
37        let mut score: f64 = 0.0;
38        if !self.source_session.is_empty() && self.source_session != "unknown" {
39            score += 0.2;
40        }
41        if self.confirmation_count >= 2 {
42            score += 0.2;
43        }
44        if self.confidence > 0.7 {
45            score += 0.2;
46        }
47        let days_since_confirmed = Utc::now()
48            .signed_duration_since(self.last_confirmed)
49            .num_days();
50        if days_since_confirmed < 14 {
51            score += 0.2;
52        } else if days_since_confirmed < 30 {
53            score += 0.1;
54        }
55        if self.feedback_up > self.feedback_down {
56            score += 0.2;
57        } else if self.feedback_up > 0 {
58            score += 0.1;
59        }
60        score.min(1.0)
61    }
62
63    pub fn update_fidelity(&mut self) {
64        let structural = self.compute_structural_fidelity();
65        self.fidelity = Some(FidelityScore {
66            structural,
67            semantic: structural,
68            computed_at: Utc::now(),
69        });
70    }
71}