zeph_memory/
recall_view.rs1use crate::graph::types::GraphFact;
10use crate::types::MessageId;
11
12pub use zeph_common::memory::RecallView;
13
14#[derive(Debug, Clone)]
45pub struct RecalledFact {
46 pub fact: GraphFact,
48 pub activation_score: Option<f32>,
53 pub provenance_message_id: Option<MessageId>,
55 pub provenance_snippet: Option<String>,
57 pub neighbors: Vec<GraphFact>,
59}
60
61impl RecalledFact {
62 #[must_use]
64 pub fn from_graph_fact(fact: GraphFact) -> Self {
65 Self {
66 fact,
67 activation_score: None,
68 provenance_message_id: None,
69 provenance_snippet: None,
70 neighbors: Vec::new(),
71 }
72 }
73
74 #[must_use]
80 pub fn from_activated_fact(af: crate::graph::activation::ActivatedFact) -> Self {
81 let activation_score = af.activation_score;
82 let edge = af.edge;
83 let fact = GraphFact {
84 entity_name: String::new(),
85 relation: edge.canonical_relation.clone(),
86 target_name: String::new(),
87 fact: edge.fact.clone(),
88 entity_match_score: activation_score,
89 hop_distance: 0,
90 confidence: edge.confidence,
91 valid_from: if edge.valid_from.is_empty() {
92 None
93 } else {
94 Some(edge.valid_from.clone())
95 },
96 edge_type: edge.edge_type,
97 retrieval_count: edge.retrieval_count,
98 edge_id: Some(edge.id),
99 };
100 Self {
101 fact,
102 activation_score: Some(activation_score),
103 provenance_message_id: edge.source_message_id,
104 provenance_snippet: None,
105 neighbors: Vec::new(),
106 }
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::graph::types::{EdgeType, GraphFact};
114 use crate::types::MessageId;
115
116 fn make_fact() -> GraphFact {
117 GraphFact {
118 entity_name: "Rust".to_string(),
119 relation: "uses".to_string(),
120 target_name: "LLVM".to_string(),
121 fact: "Rust uses LLVM".to_string(),
122 entity_match_score: 0.9,
123 hop_distance: 0,
124 confidence: 0.95,
125 valid_from: None,
126 edge_type: EdgeType::Semantic,
127 retrieval_count: 0,
128 edge_id: None,
129 }
130 }
131
132 #[test]
133 fn from_graph_fact_no_enrichment() {
134 let rf = RecalledFact::from_graph_fact(make_fact());
135 assert!(rf.activation_score.is_none());
136 assert!(rf.provenance_message_id.is_none());
137 assert!(rf.provenance_snippet.is_none());
138 assert!(rf.neighbors.is_empty());
139 }
140
141 #[test]
142 fn recall_view_default_is_head() {
143 assert_eq!(RecallView::default(), RecallView::Head);
144 }
145
146 fn head_fact() -> RecalledFact {
149 RecalledFact::from_graph_fact(GraphFact {
150 entity_name: "Rust".to_string(),
151 relation: "uses".to_string(),
152 target_name: "LLVM".to_string(),
153 fact: "Rust uses LLVM for code generation".to_string(),
154 entity_match_score: 0.9,
155 hop_distance: 0,
156 confidence: 0.95,
157 valid_from: Some("2026-01-01".to_string()),
158 edge_type: EdgeType::Semantic,
159 retrieval_count: 1,
160 edge_id: Some(10),
161 })
162 }
163
164 #[test]
165 fn snapshot_head_no_enrichment() {
166 let rf = head_fact();
167 insta::assert_debug_snapshot!("head_view", rf);
168 }
169
170 #[test]
171 fn snapshot_zoom_in_with_provenance() {
172 let mut rf = head_fact();
173 rf.provenance_message_id = Some(MessageId(42));
174 rf.provenance_snippet = Some("The Rust compiler uses LLVM as its backend".to_string());
175 insta::assert_debug_snapshot!("zoom_in_view", rf);
176 }
177
178 #[test]
179 fn snapshot_zoom_out_with_neighbors() {
180 let mut rf = head_fact();
181 rf.neighbors.push(GraphFact {
182 entity_name: "LLVM".to_string(),
183 relation: "supports".to_string(),
184 target_name: "WebAssembly".to_string(),
185 fact: "LLVM supports WebAssembly output".to_string(),
186 entity_match_score: 0.5,
187 hop_distance: 1,
188 confidence: 0.8,
189 valid_from: None,
190 edge_type: EdgeType::Semantic,
191 retrieval_count: 0,
192 edge_id: Some(11),
193 });
194 insta::assert_debug_snapshot!("zoom_out_view", rf);
195 }
196
197 #[test]
198 fn snapshot_sa_fact_with_activation_score() {
199 let rf = RecalledFact {
201 fact: GraphFact {
202 entity_name: String::new(),
203 relation: "uses".to_string(),
204 target_name: String::new(),
205 fact: "Rust uses LLVM for compilation".to_string(),
206 entity_match_score: 0.82,
207 hop_distance: 0,
208 confidence: 0.9,
209 valid_from: Some("2026-01-01".to_string()),
210 edge_type: EdgeType::Semantic,
211 retrieval_count: 0,
212 edge_id: Some(55),
213 },
214 activation_score: Some(0.82),
215 provenance_message_id: None,
216 provenance_snippet: None,
217 neighbors: Vec::new(),
218 };
219 insta::assert_debug_snapshot!("sa_head_view", rf);
220 }
221}