1use crate::core::structured_context::{ConceptItem, DecisionItem, QuestionItem, QuestionStatus};
25use crate::graph::entity_graph::EntityAnalysis;
26use chrono::{DateTime, Utc};
27use serde::{Deserialize, Serialize};
28use uuid::Uuid;
29
30#[derive(Serialize, Deserialize, Clone, Debug)]
32pub struct StructuredSummaryView {
33 pub session_id: Uuid,
35 pub generated_at: DateTime<Utc>,
37
38 pub key_decisions: Vec<DecisionSummary>,
41 pub open_questions: Vec<QuestionSummary>,
43 pub key_concepts: Vec<ConceptSummary>,
45
46 pub important_entities: Vec<String>,
49 pub entity_summaries: Vec<EntitySummary>,
51
52 pub session_stats: SessionStats,
55}
56
57#[derive(Serialize, Deserialize, Clone, Debug)]
59pub struct DecisionSummary {
60 pub description: String,
62 pub context: String,
64 pub alternatives: Vec<String>,
66 pub confidence: f32,
68 pub timestamp: DateTime<Utc>,
70 pub confidence_level: ConfidenceLevel,
72}
73
74impl DecisionSummary {
75 pub fn from_decision_item(decision: &DecisionItem) -> Self {
77 let confidence_level = match decision.confidence {
78 f if f >= 0.8 => ConfidenceLevel::High,
79 f if f >= 0.6 => ConfidenceLevel::Medium,
80 f if f >= 0.4 => ConfidenceLevel::Low,
81 _ => ConfidenceLevel::VeryLow,
82 };
83
84 Self {
85 description: decision.description.clone(),
86 context: decision.context.clone(),
87 alternatives: decision.alternatives.clone(),
88 confidence: decision.confidence,
89 timestamp: decision.timestamp,
90 confidence_level,
91 }
92 }
93}
94
95#[derive(Serialize, Deserialize, Clone, Debug)]
97pub struct QuestionSummary {
98 pub question: String,
100 pub context: String,
102 pub status: QuestionStatus,
104 pub timestamp: DateTime<Utc>,
106 pub last_updated: DateTime<Utc>,
108 pub days_open: i64,
110 pub urgency_level: UrgencyLevel,
112}
113
114impl QuestionSummary {
115 pub fn from_question_item(question: &QuestionItem) -> Self {
117 let now = Utc::now();
118 let days_open = (now - question.timestamp).num_days();
119
120 let urgency_level = match (&question.status, days_open) {
121 (QuestionStatus::Open, days) if days > 7 => UrgencyLevel::High,
122 (QuestionStatus::Open, days) if days > 3 => UrgencyLevel::Medium,
123 (QuestionStatus::Open, _) => UrgencyLevel::Low,
124 (QuestionStatus::InProgress, days) if days > 14 => UrgencyLevel::High,
125 (QuestionStatus::InProgress, _) => UrgencyLevel::Medium,
126 _ => UrgencyLevel::Low,
127 };
128
129 Self {
130 question: question.question.clone(),
131 context: question.context.clone(),
132 status: question.status.clone(),
133 timestamp: question.timestamp,
134 last_updated: question.last_updated,
135 days_open,
136 urgency_level,
137 }
138 }
139}
140
141#[derive(Serialize, Deserialize, Clone, Debug)]
143pub struct ConceptSummary {
144 pub name: String,
146 pub definition: String,
148 pub examples: Vec<String>,
150 pub related_concepts: Vec<String>,
152 pub timestamp: DateTime<Utc>,
154 pub complexity_level: ComplexityLevel,
156}
157
158impl ConceptSummary {
159 pub fn from_concept_item(concept: &ConceptItem) -> Self {
161 let complexity_level = match (concept.examples.len(), concept.related_concepts.len()) {
162 (examples, related) if examples > 3 && related > 5 => ComplexityLevel::High,
163 (examples, related) if examples > 1 && related > 2 => ComplexityLevel::Medium,
164 _ => ComplexityLevel::Low,
165 };
166
167 Self {
168 name: concept.name.clone(),
169 definition: concept.definition.clone(),
170 examples: concept.examples.clone(),
171 related_concepts: concept.related_concepts.clone(),
172 timestamp: concept.timestamp,
173 complexity_level,
174 }
175 }
176}
177
178#[derive(Serialize, Deserialize, Clone, Debug)]
180pub struct EntitySummary {
181 pub entity_name: String,
183 pub importance_score: f32,
185 pub mention_count: u32,
187 pub relationship_count: usize,
189 pub first_seen: DateTime<Utc>,
191 pub last_seen: DateTime<Utc>,
193 pub importance_level: ImportanceLevel,
195 pub recency_level: RecencyLevel,
197}
198
199impl EntitySummary {
200 pub fn from_entity_analysis(analysis: &EntityAnalysis) -> Self {
202 let importance_level = match analysis.importance_score {
203 score if score >= 2.0 => ImportanceLevel::Critical,
204 score if score >= 1.5 => ImportanceLevel::High,
205 score if score >= 1.0 => ImportanceLevel::Medium,
206 score if score >= 0.5 => ImportanceLevel::Low,
207 _ => ImportanceLevel::Minimal,
208 };
209
210 let now = Utc::now();
211 let days_since_last_seen = (now - analysis.last_seen).num_days();
212 let recency_level = match days_since_last_seen {
213 0 => RecencyLevel::Today,
214 1..=3 => RecencyLevel::Recent,
215 4..=7 => RecencyLevel::ThisWeek,
216 8..=30 => RecencyLevel::ThisMonth,
217 _ => RecencyLevel::Old,
218 };
219
220 Self {
221 entity_name: analysis.entity_name.clone(),
222 importance_score: analysis.importance_score,
223 mention_count: analysis.mention_count,
224 relationship_count: analysis.relationship_count,
225 first_seen: analysis.first_seen,
226 last_seen: analysis.last_seen,
227 importance_level,
228 recency_level,
229 }
230 }
231}
232
233#[derive(Serialize, Deserialize, Clone, Debug)]
235pub struct SessionStats {
236 pub session_id: Uuid,
238 pub hot_context_size: usize,
240 pub warm_context_size: usize,
242 pub cold_context_size: usize,
244 pub total_updates: usize,
246 pub entity_count: usize,
248 pub decision_count: usize,
250 pub open_question_count: usize,
252 pub concept_count: usize,
254 pub code_reference_count: usize,
256 pub created_at: DateTime<Utc>,
258 pub last_updated: DateTime<Utc>,
260 pub session_duration: SessionDuration,
262 pub activity_level: ActivityLevel,
264}
265
266pub struct SessionStatsBuilder {
268 session_id: Uuid,
269 hot_context_size: usize,
270 warm_context_size: usize,
271 cold_context_size: usize,
272 total_updates: usize,
273 entity_count: usize,
274 decision_count: usize,
275 open_question_count: usize,
276 concept_count: usize,
277 code_reference_count: usize,
278 created_at: DateTime<Utc>,
279 last_updated: DateTime<Utc>,
280}
281
282impl SessionStatsBuilder {
283 pub fn new(session_id: Uuid, created_at: DateTime<Utc>, last_updated: DateTime<Utc>) -> Self {
285 Self {
286 session_id,
287 hot_context_size: 0,
288 warm_context_size: 0,
289 cold_context_size: 0,
290 total_updates: 0,
291 entity_count: 0,
292 decision_count: 0,
293 open_question_count: 0,
294 concept_count: 0,
295 code_reference_count: 0,
296 created_at,
297 last_updated,
298 }
299 }
300
301 pub fn with_context_sizes(mut self, hot: usize, warm: usize, cold: usize) -> Self {
303 self.hot_context_size = hot;
304 self.warm_context_size = warm;
305 self.cold_context_size = cold;
306 self
307 }
308
309 pub fn with_counts(mut self, updates: usize, entities: usize, decisions: usize) -> Self {
311 self.total_updates = updates;
312 self.entity_count = entities;
313 self.decision_count = decisions;
314 self
315 }
316
317 pub fn with_references(mut self, questions: usize, concepts: usize, code_refs: usize) -> Self {
319 self.open_question_count = questions;
320 self.concept_count = concepts;
321 self.code_reference_count = code_refs;
322 self
323 }
324
325 pub fn build(self) -> SessionStats {
327 SessionStats::from_builder(self)
328 }
329}
330
331impl SessionStats {
332 fn from_builder(builder: SessionStatsBuilder) -> Self {
333 let duration_hours = (builder.last_updated - builder.created_at).num_hours();
334 let session_duration = match duration_hours {
335 0..=1 => SessionDuration::Short,
336 2..=4 => SessionDuration::Medium,
337 5..=8 => SessionDuration::Long,
338 _ => SessionDuration::Extended,
339 };
340
341 let activity_level = match (builder.total_updates, duration_hours.max(1)) {
342 (updates, hours) if updates as i64 / hours > 10 => ActivityLevel::VeryHigh,
343 (updates, hours) if updates as i64 / hours > 5 => ActivityLevel::High,
344 (updates, hours) if updates as i64 / hours > 2 => ActivityLevel::Medium,
345 (updates, hours) if updates as i64 / hours > 0 => ActivityLevel::Low,
346 _ => ActivityLevel::Minimal,
347 };
348
349 Self {
350 session_id: builder.session_id,
351 hot_context_size: builder.hot_context_size,
352 warm_context_size: builder.warm_context_size,
353 cold_context_size: builder.cold_context_size,
354 total_updates: builder.total_updates,
355 entity_count: builder.entity_count,
356 decision_count: builder.decision_count,
357 open_question_count: builder.open_question_count,
358 concept_count: builder.concept_count,
359 code_reference_count: builder.code_reference_count,
360 created_at: builder.created_at,
361 last_updated: builder.last_updated,
362 session_duration,
363 activity_level,
364 }
365 }
366}
367
368#[derive(Serialize, Deserialize, Clone, Debug)]
370pub enum ConfidenceLevel {
371 VeryLow,
373 Low,
375 Medium,
377 High,
379}
380
381#[derive(Serialize, Deserialize, Clone, Debug)]
383pub enum UrgencyLevel {
384 Low,
386 Medium,
388 High,
390}
391
392#[derive(Serialize, Deserialize, Clone, Debug)]
394pub enum ComplexityLevel {
395 Low,
397 Medium,
399 High,
401}
402
403#[derive(Serialize, Deserialize, Clone, Debug)]
405pub enum ImportanceLevel {
406 Minimal,
408 Low,
410 Medium,
412 High,
414 Critical,
416}
417
418#[derive(Serialize, Deserialize, Clone, Debug)]
420pub enum RecencyLevel {
421 Today,
423 Recent,
425 ThisWeek,
427 ThisMonth,
429 Old,
431}
432
433#[derive(Serialize, Deserialize, Clone, Debug)]
435pub enum SessionDuration {
436 Short,
438 Medium,
440 Long,
442 Extended,
444}
445
446#[derive(Serialize, Deserialize, Clone, Debug)]
448pub enum ActivityLevel {
449 Minimal,
451 Low,
453 Medium,
455 High,
457 VeryHigh,
459}
460
461#[cfg(test)]
462mod tests {
463 use super::*;
464
465 #[test]
466 fn test_confidence_level_mapping() {
467 let decision = DecisionItem {
468 description: "Test decision".to_string(),
469 context: "Test context".to_string(),
470 alternatives: vec![],
471 confidence: 0.9,
472 timestamp: Utc::now(),
473 };
474
475 let summary = DecisionSummary::from_decision_item(&decision);
476 assert!(matches!(summary.confidence_level, ConfidenceLevel::High));
477 }
478
479 #[test]
480 fn test_urgency_level_calculation() {
481 let old_timestamp = Utc::now() - chrono::Duration::days(10);
482 let question = QuestionItem {
483 question: "Test question".to_string(),
484 context: "Test context".to_string(),
485 status: QuestionStatus::Open,
486 timestamp: old_timestamp,
487 last_updated: old_timestamp,
488 };
489
490 let summary = QuestionSummary::from_question_item(&question);
491 assert!(summary.days_open >= 10);
492 assert!(matches!(summary.urgency_level, UrgencyLevel::High));
493 }
494
495 #[test]
496 fn test_session_duration_calculation() {
497 let created = Utc::now() - chrono::Duration::hours(3);
498 let updated = Utc::now();
499
500 let stats = SessionStatsBuilder::new(Uuid::new_v4(), created, updated)
501 .with_context_sizes(10, 5, 2)
502 .with_counts(15, 20, 3)
503 .with_references(2, 5, 1)
504 .build();
505
506 assert!(matches!(stats.session_duration, SessionDuration::Medium));
507 }
508}