1use chrono::{DateTime, Duration, Timelike, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10use super::{ContextContent, ContextType, GatheredContext};
11
12pub struct PartnershipAnalyzer {
14 contexts: Vec<GatheredContext>,
15}
16
17impl PartnershipAnalyzer {
18 pub fn new(contexts: Vec<GatheredContext>) -> Self {
19 Self { contexts }
20 }
21
22 pub fn analyze_partnership(&self) -> PartnershipAnalysis {
24 let sessions = self.detect_collaborative_sessions();
25 let conversation_flows = self.analyze_conversation_flows();
26 let interaction_patterns = self.analyze_interaction_patterns();
27 let collaboration_metrics = self.calculate_collaboration_metrics(&sessions);
28 let relationship_evolution = self.analyze_relationship_evolution();
29 let shared_understanding = self.measure_shared_understanding();
30
31 PartnershipAnalysis {
32 total_interactions: self.contexts.len(),
33 collaborative_sessions: sessions,
34 conversation_flows,
35 interaction_patterns,
36 collaboration_metrics,
37 relationship_evolution,
38 shared_understanding,
39 partnership_health: self.calculate_partnership_health(&collaboration_metrics),
40 recommendations: self.generate_recommendations(&collaboration_metrics),
41 }
42 }
43
44 fn detect_collaborative_sessions(&self) -> Vec<CollaborativeSession> {
46 let mut sessions = Vec::new();
47 let mut current_session: Option<CollaborativeSession> = None;
48
49 let session_gap = Duration::minutes(30);
51
52 let mut sorted_contexts = self.contexts.clone();
53 sorted_contexts.sort_by_key(|c| c.timestamp);
54
55 for context in sorted_contexts {
56 if let Some(ref mut session) = current_session {
57 let gap = context.timestamp - session.end_time;
58
59 if gap > session_gap || context.ai_tool != session.primary_tool {
60 sessions.push(session.clone());
62 current_session = Some(CollaborativeSession::new(context));
63 } else {
64 session.update(context);
66 }
67 } else {
68 current_session = Some(CollaborativeSession::new(context));
70 }
71 }
72
73 if let Some(session) = current_session {
74 sessions.push(session);
75 }
76
77 sessions
78 }
79
80 fn analyze_conversation_flows(&self) -> Vec<ConversationFlow> {
82 let mut flows = Vec::new();
83
84 let chat_contexts: Vec<_> = self
86 .contexts
87 .iter()
88 .filter(|c| matches!(c.content_type, ContextType::ChatHistory))
89 .collect();
90
91 for context in chat_contexts {
92 if let ContextContent::Json(json) = &context.content {
93 if let Some(messages) = json.get("messages").and_then(|m| m.as_array()) {
94 let flow = self.analyze_message_flow(messages, &context.ai_tool);
95 flows.push(flow);
96 }
97 }
98 }
99
100 flows
101 }
102
103 fn analyze_message_flow(&self, messages: &[serde_json::Value], tool: &str) -> ConversationFlow {
105 let mut turn_count = 0;
106 let mut question_count = 0;
107 let mut clarification_count = 0;
108 let mut completion_count = 0;
109 let mut context_switches = 0;
110 let mut last_topic = String::new();
111
112 for (i, msg) in messages.iter().enumerate() {
113 if let Some(content) = msg.get("content").and_then(|c| c.as_str()) {
114 turn_count += 1;
115
116 if content.contains('?') {
118 question_count += 1;
119 }
120
121 if content.contains("clarify")
123 || content.contains("what do you mean")
124 || content.contains("could you explain")
125 {
126 clarification_count += 1;
127 }
128
129 if content.contains("done")
131 || content.contains("complete")
132 || content.contains("finished")
133 || content.contains("works")
134 {
135 completion_count += 1;
136 }
137
138 let current_topic = self.extract_topic(content);
140 if i > 0 && current_topic != last_topic {
141 context_switches += 1;
142 }
143 last_topic = current_topic;
144 }
145 }
146
147 ConversationFlow {
148 tool: tool.to_string(),
149 turn_count,
150 question_count,
151 clarification_count,
152 completion_count,
153 context_switches,
154 flow_smoothness: self.calculate_flow_smoothness(
155 turn_count,
156 clarification_count,
157 context_switches,
158 ),
159 }
160 }
161
162 fn calculate_flow_smoothness(
164 &self,
165 turns: usize,
166 clarifications: usize,
167 switches: usize,
168 ) -> f32 {
169 if turns == 0 {
170 return 0.0;
171 }
172
173 let disruptions = clarifications + switches;
174
175 1.0 - (disruptions as f32 / turns as f32).min(1.0)
176 }
177
178 fn extract_topic(&self, content: &str) -> String {
180 let keywords = [
182 "function", "file", "error", "bug", "feature", "test", "deploy", "design",
183 ];
184
185 for keyword in keywords {
186 if content.to_lowercase().contains(keyword) {
187 return keyword.to_string();
188 }
189 }
190
191 "general".to_string()
192 }
193
194 fn analyze_interaction_patterns(&self) -> InteractionPatterns {
196 let mut tool_preferences = HashMap::new();
197 let mut time_of_day_activity = vec![0; 24]; let mut response_patterns = ResponsePatterns::default();
199
200 for context in &self.contexts {
201 *tool_preferences.entry(context.ai_tool.clone()).or_insert(0) += 1;
203
204 let hour = context.timestamp.hour() as usize;
206 time_of_day_activity[hour] += 1;
207
208 if let ContextContent::Json(json) = &context.content {
210 if let Some(messages) = json.get("messages").and_then(|m| m.as_array()) {
211 self.analyze_response_patterns(messages, &mut response_patterns);
212 }
213 }
214 }
215
216 let peak_hours = self.find_peak_hours(&time_of_day_activity);
218
219 InteractionPatterns {
220 tool_preferences,
221 peak_collaboration_hours: peak_hours,
222 average_session_length: self.calculate_average_session_length(),
223 response_patterns,
224 }
225 }
226
227 fn analyze_response_patterns(
229 &self,
230 messages: &[serde_json::Value],
231 patterns: &mut ResponsePatterns,
232 ) {
233 let mut last_was_human = false;
234
235 for msg in messages.iter() {
236 if let Some(role) = msg.get("role").and_then(|r| r.as_str()) {
237 if role == "user" {
238 last_was_human = true;
239 } else if role == "assistant" && last_was_human {
240 if let Some(content) = msg.get("content").and_then(|c| c.as_str()) {
242 if content.len() > 500 {
244 patterns.detailed_responses += 1;
245 } else if content.len() < 100 {
246 patterns.brief_responses += 1;
247 }
248
249 if content.contains("```") {
250 patterns.code_heavy_responses += 1;
251 }
252
253 if content.contains("Let me") || content.contains("I'll") {
254 patterns.proactive_responses += 1;
255 }
256 }
257 last_was_human = false;
258 }
259 }
260 }
261
262 patterns.total_responses += messages.len() / 2; }
264
265 fn find_peak_hours(&self, activity: &[usize]) -> Vec<usize> {
267 let mut hours_with_activity: Vec<(usize, usize)> = activity
268 .iter()
269 .enumerate()
270 .map(|(hour, &count)| (hour, count))
271 .filter(|(_, count)| *count > 0)
272 .collect();
273
274 hours_with_activity.sort_by_key(|(_, count)| std::cmp::Reverse(*count));
275
276 hours_with_activity
277 .into_iter()
278 .take(3)
279 .map(|(hour, _)| hour)
280 .collect()
281 }
282
283 fn calculate_average_session_length(&self) -> Duration {
285 let sessions = self.detect_collaborative_sessions();
286 if sessions.is_empty() {
287 return Duration::zero();
288 }
289
290 let total_duration: Duration = sessions.iter().map(|s| s.end_time - s.start_time).sum();
291
292 let avg_seconds = total_duration.num_seconds() / sessions.len() as i64;
293 Duration::seconds(avg_seconds)
294 }
295
296 fn calculate_collaboration_metrics(
298 &self,
299 sessions: &[CollaborativeSession],
300 ) -> CollaborationMetrics {
301 let total_sessions = sessions.len();
302 let mut productive_sessions = 0;
303 let mut stuck_sessions = 0;
304 let mut learning_sessions = 0;
305
306 for session in sessions {
307 if session.outcomes_achieved > 0 {
308 productive_sessions += 1;
309 }
310 if session.clarifications_needed > 2 {
311 stuck_sessions += 1;
312 }
313 if session.new_concepts_introduced > 0 {
314 learning_sessions += 1;
315 }
316 }
317
318 CollaborationMetrics {
319 productivity_rate: if total_sessions > 0 {
320 productive_sessions as f32 / total_sessions as f32
321 } else {
322 0.0
323 },
324 learning_rate: if total_sessions > 0 {
325 learning_sessions as f32 / total_sessions as f32
326 } else {
327 0.0
328 },
329 stuck_rate: if total_sessions > 0 {
330 stuck_sessions as f32 / total_sessions as f32
331 } else {
332 0.0
333 },
334 collaboration_depth: self.calculate_collaboration_depth(sessions),
335 mutual_understanding: self.calculate_mutual_understanding(sessions),
336 }
337 }
338
339 fn calculate_collaboration_depth(&self, sessions: &[CollaborativeSession]) -> f32 {
341 if sessions.is_empty() {
342 return 0.0;
343 }
344
345 let avg_interactions = sessions.iter().map(|s| s.interaction_count).sum::<usize>() as f32
346 / sessions.len() as f32;
347
348 (avg_interactions / 20.0).min(1.0)
350 }
351
352 fn calculate_mutual_understanding(&self, sessions: &[CollaborativeSession]) -> f32 {
354 if sessions.is_empty() {
355 return 0.0;
356 }
357
358 let understanding_factors = sessions
359 .iter()
360 .map(|s| {
361 let clarity =
362 1.0 - (s.clarifications_needed as f32 / s.interaction_count.max(1) as f32);
363 let efficiency = s.outcomes_achieved as f32 / s.interaction_count.max(1) as f32;
364 (clarity + efficiency) / 2.0
365 })
366 .sum::<f32>();
367
368 understanding_factors / sessions.len() as f32
369 }
370
371 fn analyze_relationship_evolution(&self) -> RelationshipEvolution {
373 let sessions = self.detect_collaborative_sessions();
374 if sessions.len() < 2 {
375 return RelationshipEvolution::default();
376 }
377
378 let mid_point = sessions.len() / 2;
380 let early_sessions = &sessions[..mid_point];
381 let recent_sessions = &sessions[mid_point..];
382
383 let early_metrics = self.calculate_collaboration_metrics(early_sessions);
384 let recent_metrics = self.calculate_collaboration_metrics(recent_sessions);
385
386 RelationshipEvolution {
387 productivity_trend: recent_metrics.productivity_rate - early_metrics.productivity_rate,
388 understanding_trend: recent_metrics.mutual_understanding
389 - early_metrics.mutual_understanding,
390 complexity_trend: recent_sessions
391 .iter()
392 .map(|s| s.new_concepts_introduced)
393 .sum::<usize>() as f32
394 / early_sessions
395 .iter()
396 .map(|s| s.new_concepts_introduced)
397 .sum::<usize>()
398 .max(1) as f32,
399 trust_indicators: self.calculate_trust_indicators(&sessions),
400 }
401 }
402
403 fn calculate_trust_indicators(&self, sessions: &[CollaborativeSession]) -> TrustIndicators {
405 let mut autonomy_given = 0;
406 let mut suggestions_followed = 0;
407
408 for session in sessions {
410 if session.interaction_count > 5 && session.clarifications_needed < 2 {
411 autonomy_given += 1;
412 }
413 if session.outcomes_achieved > 0 {
414 suggestions_followed += 1;
415 }
416 }
417
418 TrustIndicators {
419 autonomy_level: autonomy_given as f32 / sessions.len().max(1) as f32,
420 correction_acceptance: 0.8, suggestion_adoption_rate: suggestions_followed as f32 / sessions.len().max(1) as f32,
422 }
423 }
424
425 fn measure_shared_understanding(&self) -> SharedUnderstanding {
427 let mut shared_vocabulary = HashMap::new();
428 let mut communication_efficiency = 0.0;
429
430 for context in &self.contexts {
432 if let ContextContent::Text(text) = &context.content {
433 for word in text.split_whitespace() {
435 if word.len() > 5 && !word.chars().all(|c| c.is_lowercase()) {
436 *shared_vocabulary.entry(word.to_string()).or_insert(0) += 1;
437 }
438 }
439 }
440 }
441
442 let sessions = self.detect_collaborative_sessions();
444 if sessions.len() > 1 {
445 let early_clarifications = sessions[..sessions.len() / 2]
446 .iter()
447 .map(|s| s.clarifications_needed)
448 .sum::<usize>() as f32;
449 let recent_clarifications = sessions[sessions.len() / 2..]
450 .iter()
451 .map(|s| s.clarifications_needed)
452 .sum::<usize>() as f32;
453
454 communication_efficiency =
455 1.0 - (recent_clarifications / early_clarifications.max(1.0)).min(1.0);
456 }
457
458 SharedUnderstanding {
459 vocabulary_size: shared_vocabulary.len(),
460 concept_alignment: 0.75, communication_efficiency,
462 domain_expertise_areas: self.identify_expertise_areas(&shared_vocabulary),
463 }
464 }
465
466 fn identify_expertise_areas(&self, vocabulary: &HashMap<String, usize>) -> Vec<String> {
468 let domains = vec![
470 ("rust", vec!["impl", "trait", "async", "tokio", "cargo"]),
471 (
472 "javascript",
473 vec!["const", "async", "await", "npm", "react"],
474 ),
475 ("python", vec!["def", "import", "pip", "django", "flask"]),
476 (
477 "ai",
478 vec!["model", "training", "neural", "embedding", "transformer"],
479 ),
480 (
481 "database",
482 vec!["query", "table", "index", "postgres", "sqlite"],
483 ),
484 ];
485
486 let mut identified_domains = Vec::new();
487
488 for (domain, keywords) in domains {
489 let matches = keywords
490 .iter()
491 .filter(|k| vocabulary.contains_key(&k.to_string()))
492 .count();
493
494 if matches >= 2 {
495 identified_domains.push(domain.to_string());
496 }
497 }
498
499 identified_domains
500 }
501
502 fn calculate_partnership_health(&self, metrics: &CollaborationMetrics) -> PartnershipHealth {
504 let score = (metrics.productivity_rate * 0.3
505 + metrics.learning_rate * 0.2
506 + (1.0 - metrics.stuck_rate) * 0.2
507 + metrics.collaboration_depth * 0.15
508 + metrics.mutual_understanding * 0.15)
509 .min(1.0);
510
511 let status = match score {
512 s if s >= 0.8 => "Thriving",
513 s if s >= 0.6 => "Healthy",
514 s if s >= 0.4 => "Developing",
515 _ => "Needs Attention",
516 };
517
518 PartnershipHealth {
519 overall_score: score,
520 status: status.to_string(),
521 strengths: self.identify_strengths(metrics),
522 areas_for_improvement: self.identify_improvements(metrics),
523 }
524 }
525
526 fn identify_strengths(&self, metrics: &CollaborationMetrics) -> Vec<String> {
528 let mut strengths = Vec::new();
529
530 if metrics.productivity_rate > 0.7 {
531 strengths.push("High productivity in achieving goals".to_string());
532 }
533 if metrics.learning_rate > 0.5 {
534 strengths.push("Continuous learning and growth".to_string());
535 }
536 if metrics.mutual_understanding > 0.7 {
537 strengths.push("Strong mutual understanding".to_string());
538 }
539 if metrics.collaboration_depth > 0.6 {
540 strengths.push("Deep, meaningful collaborations".to_string());
541 }
542
543 strengths
544 }
545
546 fn identify_improvements(&self, metrics: &CollaborationMetrics) -> Vec<String> {
548 let mut improvements = Vec::new();
549
550 if metrics.stuck_rate > 0.3 {
551 improvements.push("Reduce getting stuck - try breaking down complex tasks".to_string());
552 }
553 if metrics.productivity_rate < 0.5 {
554 improvements.push("Focus on completing more outcomes per session".to_string());
555 }
556 if metrics.mutual_understanding < 0.5 {
557 improvements.push("Improve clarity in communication".to_string());
558 }
559
560 improvements
561 }
562
563 fn generate_recommendations(&self, metrics: &CollaborationMetrics) -> Vec<String> {
565 let mut recommendations = Vec::new();
566
567 if metrics.stuck_rate > 0.2 {
569 recommendations.push(
570 "💡 When stuck, try: 1) Break down the problem, 2) Provide more context, 3) Ask for alternative approaches".to_string()
571 );
572 }
573
574 if metrics.learning_rate < 0.3 {
575 recommendations.push(
576 "📚 Explore new areas together - try asking about unfamiliar technologies or concepts".to_string()
577 );
578 }
579
580 if metrics.collaboration_depth < 0.5 {
581 recommendations.push(
582 "🤝 Deepen collaboration by working on longer-term projects together".to_string(),
583 );
584 }
585
586 if metrics.mutual_understanding < 0.6 {
587 recommendations.push(
588 "🗣️ Improve understanding by being more specific about requirements and constraints".to_string()
589 );
590 }
591
592 recommendations.push("🌟 Keep building on your collaborative strengths!".to_string());
594
595 recommendations
596 }
597}
598
599#[derive(Debug, Clone, Serialize, Deserialize)]
601pub struct CollaborativeSession {
602 pub start_time: DateTime<Utc>,
603 pub end_time: DateTime<Utc>,
604 pub primary_tool: String,
605 pub interaction_count: usize,
606 pub clarifications_needed: usize,
607 pub outcomes_achieved: usize,
608 pub new_concepts_introduced: usize,
609 pub session_mood: SessionMood,
610}
611
612impl CollaborativeSession {
613 fn new(context: GatheredContext) -> Self {
614 let mut session = Self {
615 start_time: context.timestamp,
616 end_time: context.timestamp,
617 primary_tool: context.ai_tool.clone(),
618 interaction_count: 0,
619 clarifications_needed: 0,
620 outcomes_achieved: 0,
621 new_concepts_introduced: 0,
622 session_mood: SessionMood::Neutral,
623 };
624
625 session.update(context);
627 session
628 }
629
630 fn update(&mut self, context: GatheredContext) {
631 self.end_time = context.timestamp;
632 self.interaction_count += 1;
633
634 if let ContextContent::Json(json) = &context.content {
636 if let Some(messages) = json.get("messages").and_then(|m| m.as_array()) {
637 for msg in messages {
638 if let Some(content) = msg.get("content").and_then(|c| c.as_str()) {
639 if content.contains("done")
641 || content.contains("complete")
642 || content.contains("finished")
643 || content.contains("works")
644 || content.contains("successfully")
645 {
646 self.outcomes_achieved += 1;
647 }
648
649 if content.contains("?")
651 || content.contains("clarify")
652 || content.contains("what do you mean")
653 {
654 self.clarifications_needed += 1;
655 }
656
657 if content.contains("What's")
659 || content.contains("How does")
660 || content.contains("explain")
661 || content.contains("is a ")
662 {
663 self.new_concepts_introduced += 1;
664 }
665 }
666 }
667 }
668 }
669
670 if self.outcomes_achieved > self.clarifications_needed {
672 self.session_mood = SessionMood::Productive;
673 } else if self.clarifications_needed > 3 {
674 self.session_mood = SessionMood::Stuck;
675 }
676 }
677}
678
679#[derive(Debug, Clone, Serialize, Deserialize)]
680pub enum SessionMood {
681 Frustrated,
682 Stuck,
683 Neutral,
684 Productive,
685 Excited,
686}
687
688#[derive(Debug, Clone, Serialize, Deserialize)]
690pub struct ConversationFlow {
691 pub tool: String,
692 pub turn_count: usize,
693 pub question_count: usize,
694 pub clarification_count: usize,
695 pub completion_count: usize,
696 pub context_switches: usize,
697 pub flow_smoothness: f32,
698}
699
700#[derive(Debug, Clone, Serialize, Deserialize)]
702pub struct InteractionPatterns {
703 pub tool_preferences: HashMap<String, usize>,
704 pub peak_collaboration_hours: Vec<usize>,
705 pub average_session_length: Duration,
706 pub response_patterns: ResponsePatterns,
707}
708
709#[derive(Debug, Clone, Default, Serialize, Deserialize)]
710pub struct ResponsePatterns {
711 pub total_responses: usize,
712 pub detailed_responses: usize,
713 pub brief_responses: usize,
714 pub code_heavy_responses: usize,
715 pub proactive_responses: usize,
716}
717
718#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
720pub struct CollaborationMetrics {
721 pub productivity_rate: f32,
722 pub learning_rate: f32,
723 pub stuck_rate: f32,
724 pub collaboration_depth: f32,
725 pub mutual_understanding: f32,
726}
727
728#[derive(Debug, Clone, Default, Serialize, Deserialize)]
730pub struct RelationshipEvolution {
731 pub productivity_trend: f32, pub understanding_trend: f32,
733 pub complexity_trend: f32, pub trust_indicators: TrustIndicators,
735}
736
737#[derive(Debug, Clone, Default, Serialize, Deserialize)]
738pub struct TrustIndicators {
739 pub autonomy_level: f32,
740 pub correction_acceptance: f32,
741 pub suggestion_adoption_rate: f32,
742}
743
744#[derive(Debug, Clone, Serialize, Deserialize)]
746pub struct SharedUnderstanding {
747 pub vocabulary_size: usize,
748 pub concept_alignment: f32,
749 pub communication_efficiency: f32,
750 pub domain_expertise_areas: Vec<String>,
751}
752
753#[derive(Debug, Clone, Serialize, Deserialize)]
755pub struct PartnershipHealth {
756 pub overall_score: f32,
757 pub status: String,
758 pub strengths: Vec<String>,
759 pub areas_for_improvement: Vec<String>,
760}
761
762#[derive(Debug, Clone, Serialize, Deserialize)]
764pub struct PartnershipAnalysis {
765 pub total_interactions: usize,
766 pub collaborative_sessions: Vec<CollaborativeSession>,
767 pub conversation_flows: Vec<ConversationFlow>,
768 pub interaction_patterns: InteractionPatterns,
769 pub collaboration_metrics: CollaborationMetrics,
770 pub relationship_evolution: RelationshipEvolution,
771 pub shared_understanding: SharedUnderstanding,
772 pub partnership_health: PartnershipHealth,
773 pub recommendations: Vec<String>,
774}