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 Experience {
133 id: ExperienceId::new(),
134 collective_id: CollectiveId::new(),
135 content: "test content".to_string(),
136 embedding: vec![0.1; 384],
137 experience_type: ExperienceType::Fact {
138 statement: "test".to_string(),
139 source: String::new(),
140 },
141 importance: 0.5,
142 confidence: 0.8,
143 applications: 0,
144 domain: vec!["rust".to_string(), "testing".to_string()],
145 related_files: vec![],
146 source_agent: AgentId::new("agent-1"),
147 source_task: None,
148 timestamp: Timestamp::now(),
149 archived: false,
150 }
151 }
152
153 #[test]
154 fn test_default_filter_excludes_archived() {
155 let filter = SearchFilter::default();
156 assert!(filter.exclude_archived);
157
158 let mut exp = test_experience();
159 assert!(filter.matches(&exp));
160
161 exp.archived = true;
162 assert!(!filter.matches(&exp));
163 }
164
165 #[test]
166 fn test_default_filter_matches_all_non_archived() {
167 let filter = SearchFilter::default();
168 let exp = test_experience();
169 assert!(filter.matches(&exp));
170 }
171
172 #[test]
173 fn test_domain_filter() {
174 let filter = SearchFilter {
175 domains: Some(vec!["rust".to_string()]),
176 ..SearchFilter::default()
177 };
178
179 let exp = test_experience(); assert!(filter.matches(&exp));
181
182 let filter_no_match = SearchFilter {
183 domains: Some(vec!["python".to_string()]),
184 ..SearchFilter::default()
185 };
186 assert!(!filter_no_match.matches(&exp));
187 }
188
189 #[test]
190 fn test_experience_type_filter() {
191 let filter = SearchFilter {
192 experience_types: Some(vec![ExperienceType::Fact {
193 statement: String::new(),
194 source: String::new(),
195 }]),
196 ..SearchFilter::default()
197 };
198
199 let exp = test_experience(); assert!(filter.matches(&exp));
201
202 let filter_no_match = SearchFilter {
203 experience_types: Some(vec![ExperienceType::Generic { category: None }]),
204 ..SearchFilter::default()
205 };
206 assert!(!filter_no_match.matches(&exp));
207 }
208
209 #[test]
210 fn test_importance_filter() {
211 let filter = SearchFilter {
212 min_importance: Some(0.7),
213 ..SearchFilter::default()
214 };
215
216 let mut exp = test_experience(); assert!(!filter.matches(&exp));
218
219 exp.importance = 0.8;
220 assert!(filter.matches(&exp));
221 }
222
223 #[test]
224 fn test_confidence_filter() {
225 let filter = SearchFilter {
226 min_confidence: Some(0.9),
227 ..SearchFilter::default()
228 };
229
230 let exp = test_experience(); assert!(!filter.matches(&exp));
232 }
233
234 #[test]
235 fn test_since_filter() {
236 let before = Timestamp::now();
237 let exp = test_experience();
238
239 let filter = SearchFilter {
241 since: Some(before),
242 ..SearchFilter::default()
243 };
244 assert!(filter.matches(&exp));
245 }
246
247 #[test]
248 fn test_combined_filters() {
249 let filter = SearchFilter {
250 domains: Some(vec!["rust".to_string()]),
251 min_importance: Some(0.3),
252 min_confidence: Some(0.5),
253 exclude_archived: true,
254 ..SearchFilter::default()
255 };
256
257 let exp = test_experience(); assert!(filter.matches(&exp));
259 }
260}