1use crate::experience::{Experience, ExperienceType};
9use crate::types::Timestamp;
10
11#[derive(Clone, Debug)]
34pub struct SearchFilter {
35 pub domains: Option<Vec<String>>,
39
40 pub experience_types: Option<Vec<ExperienceType>>,
45
46 pub min_importance: Option<f32>,
48
49 pub min_confidence: Option<f32>,
51
52 pub since: Option<Timestamp>,
54
55 pub exclude_archived: bool,
57}
58
59impl Default for SearchFilter {
60 fn default() -> Self {
61 Self {
62 domains: None,
63 experience_types: None,
64 min_importance: None,
65 min_confidence: None,
66 since: None,
67 exclude_archived: true,
68 }
69 }
70}
71
72impl SearchFilter {
73 pub fn matches(&self, experience: &Experience) -> bool {
75 if self.exclude_archived && experience.archived {
77 return false;
78 }
79
80 if let Some(ref domains) = self.domains {
82 let has_match = experience
83 .domain
84 .iter()
85 .any(|d| domains.iter().any(|f| f == d));
86 if !has_match {
87 return false;
88 }
89 }
90
91 if let Some(ref types) = self.experience_types {
93 let exp_tag = experience.experience_type.type_tag();
94 let has_match = types.iter().any(|t| t.type_tag() == exp_tag);
95 if !has_match {
96 return false;
97 }
98 }
99
100 if let Some(min) = self.min_importance {
102 if experience.importance < min {
103 return false;
104 }
105 }
106
107 if let Some(min) = self.min_confidence {
109 if experience.confidence < min {
110 return false;
111 }
112 }
113
114 if let Some(since) = self.since {
116 if experience.timestamp < since {
117 return false;
118 }
119 }
120
121 true
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use crate::types::{AgentId, CollectiveId, ExperienceId};
129
130 fn test_experience() -> Experience {
132 let timestamp = Timestamp::now();
133 Experience {
134 id: ExperienceId::new(),
135 collective_id: CollectiveId::new(),
136 content: "test content".to_string(),
137 embedding: vec![0.1; 384],
138 experience_type: ExperienceType::Fact {
139 statement: "test".to_string(),
140 source: String::new(),
141 },
142 importance: 0.5,
143 confidence: 0.8,
144 applications: std::collections::BTreeMap::new(),
145 domain: vec!["rust".to_string(), "testing".to_string()],
146 related_files: vec![],
147 source_agent: AgentId::new("agent-1"),
148 source_task: None,
149 timestamp,
150 last_reinforced: timestamp,
151 archived: false,
152 }
153 }
154
155 #[test]
156 fn test_default_filter_excludes_archived() {
157 let filter = SearchFilter::default();
158 assert!(filter.exclude_archived);
159
160 let mut exp = test_experience();
161 assert!(filter.matches(&exp));
162
163 exp.archived = true;
164 assert!(!filter.matches(&exp));
165 }
166
167 #[test]
168 fn test_default_filter_matches_all_non_archived() {
169 let filter = SearchFilter::default();
170 let exp = test_experience();
171 assert!(filter.matches(&exp));
172 }
173
174 #[test]
175 fn test_domain_filter() {
176 let filter = SearchFilter {
177 domains: Some(vec!["rust".to_string()]),
178 ..SearchFilter::default()
179 };
180
181 let exp = test_experience(); assert!(filter.matches(&exp));
183
184 let filter_no_match = SearchFilter {
185 domains: Some(vec!["python".to_string()]),
186 ..SearchFilter::default()
187 };
188 assert!(!filter_no_match.matches(&exp));
189 }
190
191 #[test]
192 fn test_experience_type_filter() {
193 let filter = SearchFilter {
194 experience_types: Some(vec![ExperienceType::Fact {
195 statement: String::new(),
196 source: String::new(),
197 }]),
198 ..SearchFilter::default()
199 };
200
201 let exp = test_experience(); assert!(filter.matches(&exp));
203
204 let filter_no_match = SearchFilter {
205 experience_types: Some(vec![ExperienceType::Generic { category: None }]),
206 ..SearchFilter::default()
207 };
208 assert!(!filter_no_match.matches(&exp));
209 }
210
211 #[test]
212 fn test_importance_filter() {
213 let filter = SearchFilter {
214 min_importance: Some(0.7),
215 ..SearchFilter::default()
216 };
217
218 let mut exp = test_experience(); assert!(!filter.matches(&exp));
220
221 exp.importance = 0.8;
222 assert!(filter.matches(&exp));
223 }
224
225 #[test]
226 fn test_confidence_filter() {
227 let filter = SearchFilter {
228 min_confidence: Some(0.9),
229 ..SearchFilter::default()
230 };
231
232 let exp = test_experience(); assert!(!filter.matches(&exp));
234 }
235
236 #[test]
237 fn test_since_filter() {
238 let before = Timestamp::now();
239 let exp = test_experience();
240
241 let filter = SearchFilter {
243 since: Some(before),
244 ..SearchFilter::default()
245 };
246 assert!(filter.matches(&exp));
247 }
248
249 #[test]
250 fn test_combined_filters() {
251 let filter = SearchFilter {
252 domains: Some(vec!["rust".to_string()]),
253 min_importance: Some(0.3),
254 min_confidence: Some(0.5),
255 exclude_archived: true,
256 ..SearchFilter::default()
257 };
258
259 let exp = test_experience(); assert!(filter.matches(&exp));
261 }
262}