1use chrono::Timelike;
5#[allow(unused_imports)]
6use gestura_core_foundation::error::AppError;
7use std::collections::{BTreeMap, HashMap};
8use std::sync::Arc;
9use tokio::sync::RwLock;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
13pub enum EventType {
14 AppLaunch,
15 AppClose,
16 VoiceCommand,
17 GesturePerformed,
18 RingConnected,
19 RingDisconnected,
20 SettingsChanged,
21 ErrorOccurred,
22 FeatureUsed(String),
23 Custom(String),
24}
25
26#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
28pub struct UsageEvent {
29 pub event_id: String,
30 pub event_type: EventType,
31 pub timestamp: chrono::DateTime<chrono::Utc>,
32 pub user_id: Option<String>,
33 pub session_id: String,
34 pub properties: HashMap<String, serde_json::Value>,
35 pub duration_ms: Option<u64>,
36}
37
38#[derive(Debug, Clone, serde::Serialize)]
40pub struct AnalyticsInsights {
41 pub total_events: usize,
42 pub unique_users: usize,
43 pub active_sessions: usize,
44 pub most_used_features: Vec<(String, usize)>,
45 pub usage_patterns: UsagePatterns,
46 pub performance_metrics: PerformanceMetrics,
47 pub error_analysis: ErrorAnalysis,
48 pub time_period: TimePeriod,
49}
50
51#[derive(Debug, Clone, serde::Serialize)]
53pub struct UsagePatterns {
54 pub peak_usage_hours: Vec<u8>, pub average_session_duration_minutes: f64,
56 pub most_common_gestures: Vec<(String, usize)>,
57 pub voice_command_frequency: f64, pub feature_adoption_rate: HashMap<String, f64>, }
60
61#[derive(Debug, Clone, serde::Serialize)]
63pub struct PerformanceMetrics {
64 pub average_response_time_ms: f64,
65 pub gesture_recognition_accuracy: f64,
66 pub voice_recognition_accuracy: f64,
67 pub system_stability_score: f64, }
69
70#[derive(Debug, Clone, serde::Serialize)]
72pub struct ErrorAnalysis {
73 pub total_errors: usize,
74 pub error_rate: f64, pub most_common_errors: Vec<(String, usize)>,
76 pub error_trends: Vec<(chrono::DateTime<chrono::Utc>, usize)>,
77}
78
79#[derive(Debug, Clone, serde::Serialize)]
81pub struct TimePeriod {
82 pub start: chrono::DateTime<chrono::Utc>,
83 pub end: chrono::DateTime<chrono::Utc>,
84 pub duration_days: i64,
85}
86
87#[derive(Debug, Clone)]
89pub struct AnalyticsConfig {
90 pub enable_collection: bool,
91 pub anonymize_data: bool,
92 pub retention_days: i64,
93 pub batch_size: usize,
94 pub flush_interval_seconds: u64,
95 pub privacy_mode: PrivacyMode,
96}
97
98#[derive(Debug, Clone, PartialEq)]
100pub enum PrivacyMode {
101 Full, Limited, Anonymous, Disabled, }
106
107impl Default for AnalyticsConfig {
108 fn default() -> Self {
109 Self {
110 enable_collection: true,
111 anonymize_data: true,
112 retention_days: 30,
113 batch_size: 100,
114 flush_interval_seconds: 300, privacy_mode: PrivacyMode::Anonymous,
116 }
117 }
118}
119
120pub struct UsageAnalytics {
122 events: Arc<RwLock<Vec<UsageEvent>>>,
123 config: Arc<RwLock<AnalyticsConfig>>,
124 session_cache: Arc<RwLock<HashMap<String, SessionInfo>>>,
125 insights_cache: Arc<RwLock<Option<AnalyticsInsights>>>,
126 last_flush: Arc<RwLock<chrono::DateTime<chrono::Utc>>>,
127}
128
129#[derive(Debug, Clone)]
131struct SessionInfo {
132 #[allow(dead_code)]
133 session_id: String,
134 #[allow(dead_code)]
135 user_id: Option<String>,
136 start_time: chrono::DateTime<chrono::Utc>,
137 last_activity: chrono::DateTime<chrono::Utc>,
138 event_count: usize,
139}
140
141impl UsageAnalytics {
142 pub fn new(config: AnalyticsConfig) -> Self {
144 Self {
145 events: Arc::new(RwLock::new(Vec::new())),
146 config: Arc::new(RwLock::new(config)),
147 session_cache: Arc::new(RwLock::new(HashMap::new())),
148 insights_cache: Arc::new(RwLock::new(None)),
149 last_flush: Arc::new(RwLock::new(chrono::Utc::now())),
150 }
151 }
152
153 pub async fn track_event(&self, mut event: UsageEvent) -> Result<(), AppError> {
155 let config = self.config.read().await;
156
157 if !config.enable_collection || config.privacy_mode == PrivacyMode::Disabled {
158 return Ok(());
159 }
160
161 if config.anonymize_data || config.privacy_mode == PrivacyMode::Anonymous {
163 event.user_id = None;
164 event.properties.remove("device_id");
166 event.properties.remove("ip_address");
167 event.properties.remove("user_agent");
168 }
169
170 self.update_session_info(&event).await;
172
173 let mut events = self.events.write().await;
175 events.push(event);
176
177 if events.len() >= config.batch_size {
179 drop(events);
180 drop(config);
181 self.flush_events().await?;
182 }
183
184 Ok(())
185 }
186
187 pub async fn track_app_launch(
189 &self,
190 session_id: String,
191 user_id: Option<String>,
192 ) -> Result<(), AppError> {
193 let event = UsageEvent {
194 event_id: uuid::Uuid::new_v4().to_string(),
195 event_type: EventType::AppLaunch,
196 timestamp: chrono::Utc::now(),
197 user_id,
198 session_id,
199 properties: HashMap::from([
200 (
201 "version".to_string(),
202 serde_json::Value::String("1.0.0".to_string()),
203 ),
204 (
205 "platform".to_string(),
206 serde_json::Value::String(std::env::consts::OS.to_string()),
207 ),
208 ]),
209 duration_ms: None,
210 };
211
212 self.track_event(event).await
213 }
214
215 pub async fn track_voice_command(
217 &self,
218 session_id: String,
219 user_id: Option<String>,
220 command: &str,
221 confidence: f32,
222 processing_time_ms: u64,
223 ) -> Result<(), AppError> {
224 let event = UsageEvent {
225 event_id: uuid::Uuid::new_v4().to_string(),
226 event_type: EventType::VoiceCommand,
227 timestamp: chrono::Utc::now(),
228 user_id,
229 session_id,
230 properties: HashMap::from([
231 (
232 "command_length".to_string(),
233 serde_json::Value::Number(serde_json::Number::from(command.len())),
234 ),
235 (
236 "confidence".to_string(),
237 serde_json::Value::Number(
238 serde_json::Number::from_f64(confidence as f64).unwrap(),
239 ),
240 ),
241 (
242 "processing_time_ms".to_string(),
243 serde_json::Value::Number(serde_json::Number::from(processing_time_ms)),
244 ),
245 ]),
246 duration_ms: Some(processing_time_ms),
247 };
248
249 self.track_event(event).await
250 }
251
252 pub async fn track_gesture(
254 &self,
255 session_id: String,
256 user_id: Option<String>,
257 gesture_type: &str,
258 confidence: f32,
259 ) -> Result<(), AppError> {
260 let event = UsageEvent {
261 event_id: uuid::Uuid::new_v4().to_string(),
262 event_type: EventType::GesturePerformed,
263 timestamp: chrono::Utc::now(),
264 user_id,
265 session_id,
266 properties: HashMap::from([
267 (
268 "gesture_type".to_string(),
269 serde_json::Value::String(gesture_type.to_string()),
270 ),
271 (
272 "confidence".to_string(),
273 serde_json::Value::Number(
274 serde_json::Number::from_f64(confidence as f64).unwrap(),
275 ),
276 ),
277 ]),
278 duration_ms: None,
279 };
280
281 self.track_event(event).await
282 }
283
284 pub async fn track_error(
286 &self,
287 session_id: String,
288 user_id: Option<String>,
289 error_type: &str,
290 error_message: &str,
291 ) -> Result<(), AppError> {
292 let event = UsageEvent {
293 event_id: uuid::Uuid::new_v4().to_string(),
294 event_type: EventType::ErrorOccurred,
295 timestamp: chrono::Utc::now(),
296 user_id,
297 session_id,
298 properties: HashMap::from([
299 (
300 "error_type".to_string(),
301 serde_json::Value::String(error_type.to_string()),
302 ),
303 (
304 "error_message".to_string(),
305 serde_json::Value::String(error_message.to_string()),
306 ),
307 ]),
308 duration_ms: None,
309 };
310
311 self.track_event(event).await
312 }
313
314 pub async fn generate_insights(
316 &self,
317 days_back: Option<i64>,
318 ) -> Result<AnalyticsInsights, AppError> {
319 let days = days_back.unwrap_or(7);
320 let start_time = chrono::Utc::now() - chrono::Duration::days(days);
321 let end_time = chrono::Utc::now();
322
323 let events = self.events.read().await;
324 let filtered_events: Vec<&UsageEvent> = events
325 .iter()
326 .filter(|e| e.timestamp >= start_time && e.timestamp <= end_time)
327 .collect();
328
329 if filtered_events.is_empty() {
330 return Ok(AnalyticsInsights {
331 total_events: 0,
332 unique_users: 0,
333 active_sessions: 0,
334 most_used_features: Vec::new(),
335 usage_patterns: UsagePatterns {
336 peak_usage_hours: Vec::new(),
337 average_session_duration_minutes: 0.0,
338 most_common_gestures: Vec::new(),
339 voice_command_frequency: 0.0,
340 feature_adoption_rate: HashMap::new(),
341 },
342 performance_metrics: PerformanceMetrics {
343 average_response_time_ms: 0.0,
344 gesture_recognition_accuracy: 0.0,
345 voice_recognition_accuracy: 0.0,
346 system_stability_score: 1.0,
347 },
348 error_analysis: ErrorAnalysis {
349 total_errors: 0,
350 error_rate: 0.0,
351 most_common_errors: Vec::new(),
352 error_trends: Vec::new(),
353 },
354 time_period: TimePeriod {
355 start: start_time,
356 end: end_time,
357 duration_days: days,
358 },
359 });
360 }
361
362 let total_events = filtered_events.len();
364 let unique_users = filtered_events
365 .iter()
366 .filter_map(|e| e.user_id.as_ref())
367 .collect::<std::collections::HashSet<_>>()
368 .len();
369 let unique_sessions = filtered_events
370 .iter()
371 .map(|e| &e.session_id)
372 .collect::<std::collections::HashSet<_>>()
373 .len();
374
375 let mut feature_counts = HashMap::new();
377 for event in &filtered_events {
378 let feature_name = match &event.event_type {
379 EventType::VoiceCommand => "voice_commands",
380 EventType::GesturePerformed => "gestures",
381 EventType::RingConnected => "ring_connection",
382 EventType::SettingsChanged => "settings",
383 EventType::FeatureUsed(name) => name,
384 _ => "other",
385 };
386 *feature_counts.entry(feature_name.to_string()).or_insert(0) += 1;
387 }
388
389 let mut most_used_features: Vec<(String, usize)> = feature_counts.into_iter().collect();
390 most_used_features.sort_by(|a, b| b.1.cmp(&a.1));
391 most_used_features.truncate(10);
392
393 let usage_patterns = self.analyze_usage_patterns(&filtered_events).await;
395
396 let performance_metrics = self.analyze_performance(&filtered_events).await;
398
399 let error_analysis = self.analyze_errors(&filtered_events).await;
401
402 let insights = AnalyticsInsights {
403 total_events,
404 unique_users,
405 active_sessions: unique_sessions,
406 most_used_features,
407 usage_patterns,
408 performance_metrics,
409 error_analysis,
410 time_period: TimePeriod {
411 start: start_time,
412 end: end_time,
413 duration_days: days,
414 },
415 };
416
417 let mut cache = self.insights_cache.write().await;
419 *cache = Some(insights.clone());
420
421 Ok(insights)
422 }
423
424 async fn analyze_usage_patterns(&self, events: &[&UsageEvent]) -> UsagePatterns {
426 let mut hour_counts = BTreeMap::new();
428 for event in events {
429 let hour = event.timestamp.hour() as u8;
430 *hour_counts.entry(hour).or_insert(0) += 1;
431 }
432
433 let mut peak_hours: Vec<(u8, usize)> = hour_counts.into_iter().collect();
434 peak_hours.sort_by(|a, b| b.1.cmp(&a.1));
435 let peak_usage_hours = peak_hours.into_iter().take(3).map(|(h, _)| h).collect();
436
437 let sessions = self.session_cache.read().await;
439 let avg_duration = if !sessions.is_empty() {
440 let total_duration: i64 = sessions
441 .values()
442 .map(|s| (s.last_activity - s.start_time).num_minutes())
443 .sum();
444 total_duration as f64 / sessions.len() as f64
445 } else {
446 0.0
447 };
448
449 let mut gesture_counts = HashMap::new();
451 for event in events {
452 if let EventType::GesturePerformed = event.event_type
453 && let Some(gesture_type) = event.properties.get("gesture_type")
454 && let Some(gesture_str) = gesture_type.as_str()
455 {
456 *gesture_counts.entry(gesture_str.to_string()).or_insert(0) += 1;
457 }
458 }
459
460 let mut most_common_gestures: Vec<(String, usize)> = gesture_counts.into_iter().collect();
461 most_common_gestures.sort_by(|a, b| b.1.cmp(&a.1));
462 most_common_gestures.truncate(5);
463
464 let voice_commands = events
466 .iter()
467 .filter(|e| matches!(e.event_type, EventType::VoiceCommand))
468 .count();
469 let unique_sessions = events
470 .iter()
471 .map(|e| &e.session_id)
472 .collect::<std::collections::HashSet<_>>()
473 .len();
474 let voice_command_frequency = if unique_sessions > 0 {
475 voice_commands as f64 / unique_sessions as f64
476 } else {
477 0.0
478 };
479
480 UsagePatterns {
481 peak_usage_hours,
482 average_session_duration_minutes: avg_duration,
483 most_common_gestures,
484 voice_command_frequency,
485 feature_adoption_rate: HashMap::new(), }
487 }
488
489 async fn analyze_performance(&self, events: &[&UsageEvent]) -> PerformanceMetrics {
491 let mut response_times = Vec::new();
492 let mut gesture_confidences = Vec::new();
493 let mut voice_confidences = Vec::new();
494
495 for event in events {
496 if let Some(duration) = event.duration_ms {
497 response_times.push(duration as f64);
498 }
499
500 if let Some(confidence) = event.properties.get("confidence")
501 && let Some(conf_val) = confidence.as_f64()
502 {
503 match event.event_type {
504 EventType::GesturePerformed => gesture_confidences.push(conf_val),
505 EventType::VoiceCommand => voice_confidences.push(conf_val),
506 _ => {}
507 }
508 }
509 }
510
511 let avg_response_time = if !response_times.is_empty() {
512 response_times.iter().sum::<f64>() / response_times.len() as f64
513 } else {
514 0.0
515 };
516
517 let gesture_accuracy = if !gesture_confidences.is_empty() {
518 gesture_confidences.iter().sum::<f64>() / gesture_confidences.len() as f64
519 } else {
520 0.0
521 };
522
523 let voice_accuracy = if !voice_confidences.is_empty() {
524 voice_confidences.iter().sum::<f64>() / voice_confidences.len() as f64
525 } else {
526 0.0
527 };
528
529 let error_count = events
531 .iter()
532 .filter(|e| matches!(e.event_type, EventType::ErrorOccurred))
533 .count();
534 let stability_score = if !events.is_empty() {
535 1.0 - (error_count as f64 / events.len() as f64)
536 } else {
537 1.0
538 };
539
540 PerformanceMetrics {
541 average_response_time_ms: avg_response_time,
542 gesture_recognition_accuracy: gesture_accuracy,
543 voice_recognition_accuracy: voice_accuracy,
544 system_stability_score: stability_score,
545 }
546 }
547
548 async fn analyze_errors(&self, events: &[&UsageEvent]) -> ErrorAnalysis {
550 let error_events: Vec<&UsageEvent> = events
551 .iter()
552 .filter(|e| matches!(e.event_type, EventType::ErrorOccurred))
553 .cloned()
554 .collect();
555
556 let total_errors = error_events.len();
557 let unique_sessions = events
558 .iter()
559 .map(|e| &e.session_id)
560 .collect::<std::collections::HashSet<_>>()
561 .len();
562 let error_rate = if unique_sessions > 0 {
563 total_errors as f64 / unique_sessions as f64
564 } else {
565 0.0
566 };
567
568 let mut error_counts = HashMap::new();
570 for event in &error_events {
571 if let Some(error_type) = event.properties.get("error_type")
572 && let Some(error_str) = error_type.as_str()
573 {
574 *error_counts.entry(error_str.to_string()).or_insert(0) += 1;
575 }
576 }
577
578 let mut most_common_errors: Vec<(String, usize)> = error_counts.into_iter().collect();
579 most_common_errors.sort_by(|a, b| b.1.cmp(&a.1));
580 most_common_errors.truncate(5);
581
582 let mut daily_errors = BTreeMap::new();
584 for event in &error_events {
585 let date = event.timestamp.date_naive();
586 *daily_errors.entry(date).or_insert(0) += 1;
587 }
588
589 let error_trends: Vec<(chrono::DateTime<chrono::Utc>, usize)> = daily_errors
590 .into_iter()
591 .map(|(date, count)| (date.and_hms_opt(0, 0, 0).unwrap().and_utc(), count))
592 .collect();
593
594 ErrorAnalysis {
595 total_errors,
596 error_rate,
597 most_common_errors,
598 error_trends,
599 }
600 }
601
602 async fn update_session_info(&self, event: &UsageEvent) {
604 let mut sessions = self.session_cache.write().await;
605
606 let session_info =
607 sessions
608 .entry(event.session_id.clone())
609 .or_insert_with(|| SessionInfo {
610 session_id: event.session_id.clone(),
611 user_id: event.user_id.clone(),
612 start_time: event.timestamp,
613 last_activity: event.timestamp,
614 event_count: 0,
615 });
616
617 session_info.last_activity = event.timestamp;
618 session_info.event_count += 1;
619 }
620
621 async fn flush_events(&self) -> Result<(), AppError> {
623 let mut events = self.events.write().await;
624 let _config = self.config.read().await;
625
626 if events.is_empty() {
627 return Ok(());
628 }
629
630 tracing::info!("Flushing {} analytics events", events.len());
632
633 events.clear();
635
636 let mut last_flush = self.last_flush.write().await;
637 *last_flush = chrono::Utc::now();
638
639 Ok(())
640 }
641
642 pub async fn cleanup_old_data(&self) -> Result<usize, AppError> {
644 let config = self.config.read().await;
645 let cutoff_date = chrono::Utc::now() - chrono::Duration::days(config.retention_days);
646
647 let mut events = self.events.write().await;
648 let initial_count = events.len();
649
650 events.retain(|event| event.timestamp > cutoff_date);
651
652 let removed_count = initial_count - events.len();
653 if removed_count > 0 {
654 tracing::info!("Cleaned up {} old analytics events", removed_count);
655 }
656
657 Ok(removed_count)
658 }
659
660 pub async fn update_config(&self, new_config: AnalyticsConfig) {
662 let mut config = self.config.write().await;
663 *config = new_config;
664 }
665
666 pub async fn get_config(&self) -> AnalyticsConfig {
668 let config = self.config.read().await;
669 config.clone()
670 }
671
672 pub async fn get_cached_insights(&self) -> Option<AnalyticsInsights> {
674 let cache = self.insights_cache.read().await;
675 cache.clone()
676 }
677}
678
679static USAGE_ANALYTICS: tokio::sync::OnceCell<UsageAnalytics> = tokio::sync::OnceCell::const_new();
681
682pub async fn get_usage_analytics() -> &'static UsageAnalytics {
684 USAGE_ANALYTICS
685 .get_or_init(|| async { UsageAnalytics::new(AnalyticsConfig::default()) })
686 .await
687}
688
689#[cfg(test)]
690mod tests {
691 use super::*;
692
693 #[tokio::test]
694 async fn test_event_tracking() {
695 let config = AnalyticsConfig {
697 enable_collection: true,
698 anonymize_data: false,
699 privacy_mode: PrivacyMode::Full,
700 ..Default::default()
701 };
702 let analytics = UsageAnalytics::new(config);
703
704 analytics
705 .track_app_launch("session1".to_string(), Some("user1".to_string()))
706 .await
707 .unwrap();
708 analytics
709 .track_voice_command(
710 "session1".to_string(),
711 Some("user1".to_string()),
712 "test command",
713 0.9,
714 100,
715 )
716 .await
717 .unwrap();
718
719 let insights = analytics.generate_insights(Some(1)).await.unwrap();
720 assert_eq!(insights.total_events, 2);
721 assert_eq!(insights.unique_users, 1);
722 }
723
724 #[tokio::test]
725 async fn test_privacy_mode() {
726 let config = AnalyticsConfig {
727 privacy_mode: PrivacyMode::Anonymous,
728 ..Default::default()
729 };
730
731 let analytics = UsageAnalytics::new(config);
732
733 let event = UsageEvent {
734 event_id: "test".to_string(),
735 event_type: EventType::VoiceCommand,
736 timestamp: chrono::Utc::now(),
737 user_id: Some("user123".to_string()),
738 session_id: "session1".to_string(),
739 properties: HashMap::new(),
740 duration_ms: None,
741 };
742
743 analytics.track_event(event.clone()).await.unwrap();
744
745 let events = analytics.events.read().await;
747 assert!(events[0].user_id.is_none());
748 }
749
750 #[tokio::test]
751 async fn test_insights_generation() {
752 let analytics = UsageAnalytics::new(AnalyticsConfig::default());
753
754 for i in 0..10 {
756 analytics
757 .track_gesture(
758 "session1".to_string(),
759 Some("user1".to_string()),
760 "tap",
761 0.8 + (i as f32 * 0.01),
762 )
763 .await
764 .unwrap();
765 }
766
767 let insights = analytics.generate_insights(Some(1)).await.unwrap();
768 assert_eq!(insights.total_events, 10);
769 assert!(insights.performance_metrics.gesture_recognition_accuracy > 0.8);
770 }
771}