smos_application/testkit/
facts.rs1use std::collections::{HashMap, HashSet};
24use std::sync::{Arc, Mutex};
25
26use smos_domain::{Fact, FactId, FactStatus, Heat, MemoryKey, SessionId, Timestamp};
27
28use crate::errors::RepoError;
29use crate::ports::FactRepository;
30use crate::types::SearchHit;
31
32#[derive(Default, Clone)]
33pub struct InMemoryFacts {
34 store: Arc<Mutex<HashMap<String, Fact>>>,
35 dedup_hits: Arc<Mutex<Vec<SearchHit>>>,
39 search_hits: Arc<Mutex<Vec<SearchHit>>>,
42}
43
44impl InMemoryFacts {
45 pub fn seed(&self, fact: Fact) {
48 self.store
49 .lock()
50 .unwrap()
51 .insert(fact.id().as_str().to_string(), fact);
52 }
53
54 pub fn get_clone(&self, id: &FactId) -> Option<Fact> {
56 self.store.lock().unwrap().get(id.as_str()).cloned()
57 }
58
59 pub fn script_dedup_hits(&self, hits: Vec<SearchHit>) {
61 *self.dedup_hits.lock().unwrap() = hits;
62 }
63
64 pub fn script_search_hits(&self, hits: Vec<SearchHit>) {
68 *self.search_hits.lock().unwrap() = hits;
69 }
70
71 pub fn is_empty(&self) -> bool {
72 self.store.lock().unwrap().is_empty()
73 }
74
75 pub fn contains(&self, id: &FactId) -> bool {
76 self.store.lock().unwrap().contains_key(id.as_str())
77 }
78}
79
80impl FactRepository for InMemoryFacts {
81 async fn save(&self, fact: &Fact) -> Result<(), RepoError> {
82 self.store
83 .lock()
84 .unwrap()
85 .insert(fact.id().as_str().to_string(), fact.clone());
86 Ok(())
87 }
88
89 async fn get(&self, id: &FactId, _memory_key: &MemoryKey) -> Result<Option<Fact>, RepoError> {
90 Ok(self.get_clone(id))
91 }
92
93 async fn list_accepted(&self, _memory_key: &MemoryKey) -> Result<Vec<Fact>, RepoError> {
94 Ok(self
95 .store
96 .lock()
97 .unwrap()
98 .values()
99 .filter(|f| f.status() == FactStatus::Accepted)
100 .cloned()
101 .collect())
102 }
103
104 async fn list_pending(&self, _memory_key: &MemoryKey) -> Result<Vec<Fact>, RepoError> {
105 Ok(self
106 .store
107 .lock()
108 .unwrap()
109 .values()
110 .filter(|f| f.status() == FactStatus::Pending)
111 .cloned()
112 .collect())
113 }
114
115 async fn list_memory_keys_for_session(
116 &self,
117 session_id: &SessionId,
118 ) -> Result<Vec<MemoryKey>, RepoError> {
119 let mut out: Vec<MemoryKey> = Vec::new();
120 let mut seen: HashSet<String> = HashSet::new();
121 for fact in self.store.lock().unwrap().values() {
122 if !fact.source_sessions().iter().any(|s| s == session_id) {
123 continue;
124 }
125 let mk_str = fact.memory_key().as_str().to_string();
126 if seen.insert(mk_str) {
127 out.push(fact.memory_key().clone());
128 }
129 }
130 Ok(out)
131 }
132
133 async fn list_memory_keys(&self) -> Result<Vec<MemoryKey>, RepoError> {
134 let mut out: Vec<MemoryKey> = Vec::new();
135 let mut seen: HashSet<String> = HashSet::new();
136 for fact in self.store.lock().unwrap().values() {
137 let mk_str = fact.memory_key().as_str().to_string();
138 if seen.insert(mk_str) {
139 out.push(fact.memory_key().clone());
140 }
141 }
142 Ok(out)
143 }
144
145 async fn search_similar(
146 &self,
147 _embedding: Vec<f32>,
148 _memory_key: &MemoryKey,
149 _limit: usize,
150 ) -> Result<Vec<SearchHit>, RepoError> {
151 Ok(self.search_hits.lock().unwrap().clone())
152 }
153
154 async fn search_for_dedup(
155 &self,
156 _embedding: Vec<f32>,
157 _memory_key: &MemoryKey,
158 _limit: usize,
159 ) -> Result<Vec<SearchHit>, RepoError> {
160 Ok(self.dedup_hits.lock().unwrap().clone())
161 }
162
163 async fn update_heat_batch(
164 &self,
165 _ids: &[FactId],
166 _memory_key: &MemoryKey,
167 _heat_base: Heat,
168 _last_access: Timestamp,
169 ) -> Result<(), RepoError> {
170 Ok(())
171 }
172}