thulp_workspace/
filter.rs1use crate::session::{Session, SessionStatus, SessionType, Timestamp};
7
8#[derive(Debug, Clone)]
24pub enum SessionFilter {
25 ByStatus(SessionStatus),
27
28 ByTypeName(String),
30
31 HasTag(String),
33
34 CreatedAfter(Timestamp),
36
37 CreatedBefore(Timestamp),
39
40 UpdatedAfter(Timestamp),
42
43 UpdatedBefore(Timestamp),
45
46 NameContains(String),
48
49 HasParent,
51
52 WithParent(crate::session::SessionId),
54
55 IsRoot,
57
58 And(Vec<SessionFilter>),
60
61 Or(Vec<SessionFilter>),
63
64 Not(Box<SessionFilter>),
66
67 All,
69}
70
71impl SessionFilter {
72 pub fn matches(&self, session: &Session) -> bool {
74 match self {
75 SessionFilter::ByStatus(status) => session.status() == *status,
76
77 SessionFilter::ByTypeName(type_name) => {
78 let session_type_name = session_type_name(&session.metadata.session_type);
79 session_type_name.eq_ignore_ascii_case(type_name)
80 }
81
82 SessionFilter::HasTag(tag) => session.metadata.tags.iter().any(|t| t == tag),
83
84 SessionFilter::CreatedAfter(timestamp) => {
85 session.metadata.created_at.as_millis() > timestamp.as_millis()
86 }
87
88 SessionFilter::CreatedBefore(timestamp) => {
89 session.metadata.created_at.as_millis() < timestamp.as_millis()
90 }
91
92 SessionFilter::UpdatedAfter(timestamp) => {
93 session.metadata.updated_at.as_millis() > timestamp.as_millis()
94 }
95
96 SessionFilter::UpdatedBefore(timestamp) => {
97 session.metadata.updated_at.as_millis() < timestamp.as_millis()
98 }
99
100 SessionFilter::NameContains(text) => session
101 .metadata
102 .name
103 .to_lowercase()
104 .contains(&text.to_lowercase()),
105
106 SessionFilter::HasParent => session.metadata.parent_session.is_some(),
107
108 SessionFilter::WithParent(parent_id) => {
109 session.metadata.parent_session.as_ref() == Some(parent_id)
110 }
111
112 SessionFilter::IsRoot => session.metadata.parent_session.is_none(),
113
114 SessionFilter::And(filters) => filters.iter().all(|f| f.matches(session)),
115
116 SessionFilter::Or(filters) => filters.iter().any(|f| f.matches(session)),
117
118 SessionFilter::Not(filter) => !filter.matches(session),
119
120 SessionFilter::All => true,
121 }
122 }
123
124 pub fn and(self, other: SessionFilter) -> SessionFilter {
126 match self {
127 SessionFilter::And(mut filters) => {
128 filters.push(other);
129 SessionFilter::And(filters)
130 }
131 _ => SessionFilter::And(vec![self, other]),
132 }
133 }
134
135 pub fn or(self, other: SessionFilter) -> SessionFilter {
137 match self {
138 SessionFilter::Or(mut filters) => {
139 filters.push(other);
140 SessionFilter::Or(filters)
141 }
142 _ => SessionFilter::Or(vec![self, other]),
143 }
144 }
145
146 pub fn negate(self) -> SessionFilter {
148 SessionFilter::Not(Box::new(self))
149 }
150
151 pub fn active() -> Self {
153 SessionFilter::ByStatus(SessionStatus::Active)
154 }
155
156 pub fn completed() -> Self {
158 SessionFilter::ByStatus(SessionStatus::Completed)
159 }
160
161 pub fn failed() -> Self {
163 SessionFilter::ByStatus(SessionStatus::Failed)
164 }
165
166 pub fn conversations() -> Self {
168 SessionFilter::ByTypeName("conversation".to_string())
169 }
170
171 pub fn teacher_demos() -> Self {
173 SessionFilter::ByTypeName("teacher_demo".to_string())
174 }
175
176 pub fn evaluations() -> Self {
178 SessionFilter::ByTypeName("evaluation".to_string())
179 }
180
181 pub fn refinements() -> Self {
183 SessionFilter::ByTypeName("refinement".to_string())
184 }
185
186 pub fn agent_sessions() -> Self {
188 SessionFilter::ByTypeName("agent".to_string())
189 }
190}
191
192fn session_type_name(session_type: &SessionType) -> &'static str {
194 match session_type {
195 SessionType::TeacherDemo { .. } => "teacher_demo",
196 SessionType::Evaluation { .. } => "evaluation",
197 SessionType::Refinement { .. } => "refinement",
198 SessionType::Conversation { .. } => "conversation",
199 SessionType::Agent { .. } => "agent",
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::session::{Session, SessionId, SessionType};
207
208 fn create_test_session(name: &str) -> Session {
209 Session::new(
210 name,
211 SessionType::Conversation {
212 purpose: "Test".to_string(),
213 },
214 )
215 }
216
217 #[test]
218 fn test_filter_by_status() {
219 let mut session = create_test_session("Test");
220
221 let filter = SessionFilter::ByStatus(SessionStatus::Active);
222 assert!(filter.matches(&session));
223
224 session.complete();
225 assert!(!filter.matches(&session));
226
227 let completed_filter = SessionFilter::ByStatus(SessionStatus::Completed);
228 assert!(completed_filter.matches(&session));
229 }
230
231 #[test]
232 fn test_filter_by_type_name() {
233 let conversation = Session::new(
234 "Test",
235 SessionType::Conversation {
236 purpose: "Test".to_string(),
237 },
238 );
239
240 let evaluation = Session::new(
241 "Test",
242 SessionType::Evaluation {
243 skill_name: "test".to_string(),
244 test_cases: 10,
245 },
246 );
247
248 let conv_filter = SessionFilter::ByTypeName("conversation".to_string());
249 assert!(conv_filter.matches(&conversation));
250 assert!(!conv_filter.matches(&evaluation));
251
252 let eval_filter = SessionFilter::ByTypeName("evaluation".to_string());
253 assert!(eval_filter.matches(&evaluation));
254 assert!(!eval_filter.matches(&conversation));
255 }
256
257 #[test]
258 fn test_filter_has_tag() {
259 let mut session = create_test_session("Test");
260 session.metadata.tags.push("important".to_string());
261
262 let filter = SessionFilter::HasTag("important".to_string());
263 assert!(filter.matches(&session));
264
265 let no_tag_filter = SessionFilter::HasTag("other".to_string());
266 assert!(!no_tag_filter.matches(&session));
267 }
268
269 #[test]
270 fn test_filter_name_contains() {
271 let session = create_test_session("My Test Session");
272
273 let filter = SessionFilter::NameContains("test".to_string());
274 assert!(filter.matches(&session));
275
276 let no_match = SessionFilter::NameContains("other".to_string());
277 assert!(!no_match.matches(&session));
278 }
279
280 #[test]
281 fn test_filter_and() {
282 let mut session = create_test_session("Test");
283 session.metadata.tags.push("important".to_string());
284
285 let filter = SessionFilter::active().and(SessionFilter::HasTag("important".to_string()));
286
287 assert!(filter.matches(&session));
288
289 session.complete();
290 assert!(!filter.matches(&session));
291 }
292
293 #[test]
294 fn test_filter_or() {
295 let mut session = create_test_session("Test");
296
297 let filter = SessionFilter::completed().or(SessionFilter::active());
298
299 assert!(filter.matches(&session)); session.complete();
302 assert!(filter.matches(&session)); session.fail();
305 assert!(!filter.matches(&session)); }
307
308 #[test]
309 fn test_filter_not() {
310 let session = create_test_session("Test");
311
312 let filter = SessionFilter::completed().negate();
313 assert!(filter.matches(&session)); let mut completed = create_test_session("Test");
316 completed.complete();
317 assert!(!filter.matches(&completed)); }
319
320 #[test]
321 fn test_filter_parent() {
322 let mut session = create_test_session("Test");
323
324 let has_parent = SessionFilter::HasParent;
325 let is_root = SessionFilter::IsRoot;
326
327 assert!(!has_parent.matches(&session));
328 assert!(is_root.matches(&session));
329
330 session.metadata.parent_session = Some(SessionId::new());
331 assert!(has_parent.matches(&session));
332 assert!(!is_root.matches(&session));
333 }
334
335 #[test]
336 fn test_filter_all() {
337 let session = create_test_session("Test");
338 assert!(SessionFilter::All.matches(&session));
339 }
340
341 #[test]
342 fn test_convenience_constructors() {
343 let session = Session::new(
344 "Test",
345 SessionType::Conversation {
346 purpose: "Test".to_string(),
347 },
348 );
349
350 assert!(SessionFilter::conversations().matches(&session));
351 assert!(!SessionFilter::evaluations().matches(&session));
352 assert!(SessionFilter::active().matches(&session));
353 }
354}