Skip to main content

hippmem_write/
explain.rs

1//! Explain + Inspect API (M6-002): retrieval-result diagnostics and
2//! explanation.
3
4use hippmem_core::ids::MemoryId;
5use hippmem_core::model::links::{ActivationStep, MatchDimension, MemoryWarning};
6use hippmem_core::model::unit::MemoryUnit;
7
8/// Explanation output: answers "why was this memory recalled".
9#[derive(Debug, Clone)]
10pub struct Explanation {
11    pub memory_id: MemoryId,
12    /// Activation trace.
13    pub trace: Vec<ActivationStep>,
14    /// Dimensions that hit.
15    pub dimensions: Vec<MatchDimension>,
16    /// Risk warnings.
17    pub warnings: Vec<MemoryWarning>,
18    /// Text explanation.
19    pub summary: String,
20}
21
22/// Generate an explanation for a retrieval result.
23pub fn explain_result(
24    unit: &MemoryUnit,
25    trace: &[ActivationStep],
26    dimensions: &[MatchDimension],
27    warnings: &[MemoryWarning],
28) -> Explanation {
29    let dim_strs: Vec<String> = dimensions.iter().map(|d| format!("{:?}", d)).collect();
30    let warn_strs: Vec<String> = warnings.iter().map(|w| format!("{:?}", w)).collect();
31
32    let summary = format!(
33        "Memory {} was recalled via {:?} dimension(s) ({}), hops={}. Warnings: {}.",
34        unit.id.as_u128(),
35        dimensions.len(),
36        dim_strs.join(", "),
37        trace.iter().map(|s| s.hop).max().unwrap_or(0),
38        if warn_strs.is_empty() {
39            "none".to_string()
40        } else {
41            warn_strs.join(", ")
42        },
43    );
44
45    Explanation {
46        memory_id: unit.id,
47        trace: trace.to_vec(),
48        dimensions: dimensions.to_vec(),
49        warnings: warnings.to_vec(),
50        summary,
51    }
52}
53
54/// Inspect query: diagnose store state.
55#[derive(Debug)]
56pub struct InspectReport {
57    pub memory_count: usize,
58    pub link_count: usize,
59    pub stage_distribution: Vec<(String, usize)>,
60}
61
62/// Generate an inspect report.
63pub fn inspect_units(units: &[MemoryUnit]) -> InspectReport {
64    let memory_count = units.len();
65    let link_count: usize = units.iter().map(|u| u.links.len()).sum();
66    let mut stage_dist: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
67    for unit in units {
68        let key = format!("{:?}", unit.stage);
69        *stage_dist.entry(key).or_insert(0) += 1;
70    }
71    let stage_distribution: Vec<(String, usize)> = stage_dist.into_iter().collect();
72
73    InspectReport {
74        memory_count,
75        link_count,
76        stage_distribution,
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn explanation_generates_summary() {
86        let unit = MemoryUnit {
87            schema_version: 1,
88            id: MemoryId(42),
89            created_at: hippmem_core::time::Timestamp(0),
90            updated_at: hippmem_core::time::Timestamp(0),
91            content: hippmem_core::model::unit::MemoryContent {
92                raw: "test".into(),
93                summary: None,
94                normalized: None,
95                language: hippmem_core::model::unit::Language::Zh,
96                content_type: hippmem_core::model::unit::ContentType::UserStatement,
97            },
98            context: hippmem_core::model::unit::WriteContext {
99                conversation_id: None,
100                session_id: None,
101                project_id: None,
102                task_id: None,
103                user_id: None,
104                local_time: hippmem_core::time::Timestamp(0),
105                preceding_memory_ids: vec![],
106                source_refs: vec![],
107            },
108            understanding: hippmem_core::model::understanding::MemoryUnderstanding {
109                entities: vec![],
110                events: vec![],
111                goals: vec![],
112                decisions: vec![],
113                preferences: vec![],
114                emotions: vec![],
115                causal_claims: vec![],
116                contradictions: vec![],
117                topics: vec![],
118                importance: hippmem_core::score::UnitScore::new(0.5),
119                confidence: hippmem_core::score::UnitScore::new(0.5),
120            },
121            association_keys: hippmem_core::model::links::AssociationKeys {
122                entity_keys: vec![],
123                temporal_keys: vec![],
124                lexical_signature: hippmem_core::model::links::LexicalSignature { simhash: [0; 4] },
125                semantic_signature: hippmem_core::model::links::SemanticSignature {
126                    lexical_simhash: [0; 4],
127                    dense_embedding_ref: None,
128                    binary_code: [0, 0],
129                    topic_minhash: [0u32; 16],
130                },
131                topic_keys: vec![],
132                emotion_keys: vec![],
133                goal_keys: vec![],
134                event_keys: vec![],
135                causal_keys: vec![],
136            },
137            links: vec![],
138            activation: hippmem_core::model::links::ActivationState {
139                last_retrieved_at: None,
140                retrieval_count: 0,
141                co_activations: vec![],
142                usage_score: hippmem_core::score::UnitScore::new(0.5),
143            },
144            lifecycle: hippmem_core::model::unit::MemoryLifecycle::Active,
145            provenance: hippmem_core::model::unit::Provenance {
146                origin: hippmem_core::model::unit::SourceKind::Conversation,
147                generated_by: hippmem_core::model::unit::GeneratedBy::UserDirect,
148                reliability: hippmem_core::score::UnitScore::new(0.5),
149                evidence_refs: vec![],
150                revision_history: vec![],
151            },
152            stage: hippmem_core::model::unit::MemoryStage::Indexed,
153        };
154        let exp = explain_result(&unit, &[], &[], &[]);
155        assert!(exp.summary.contains("42"));
156    }
157
158    #[test]
159    fn inspect_counts_units() {
160        let report = inspect_units(&[]);
161        assert_eq!(report.memory_count, 0);
162    }
163}