Skip to main content

voirs_feedback/progress/
core.rs

1//! Core progress tracking implementation
2//!
3//! This module contains the main `ProgressAnalyzer` struct and its implementation,
4//! along with supporting analytics frameworks and core functionality.
5
6use super::types::{
7    AchievementAnalysis, AchievementCondition, AchievementDefinition, AggregatedMetric,
8    AnalyticsConfig, AnalyticsMetric, AnalyticsSummary, ComparativeAnalysis,
9    ComparativeAnalyticsResult, ComprehensiveAnalyticsReport, DetailedProgressReport, GoalAnalysis,
10    LongitudinalDataPoint, LongitudinalStudyData, MemoryBoundedMetrics, MetricType,
11    ProgressRecommendation, StatisticalSignificanceResult, TrendAnalysis, TrendAnalytics,
12};
13use crate::adaptive::RecommendationType;
14use crate::progress::analytics::TrendDirection;
15use crate::progress::dashboard::{
16    DashboardConfig, DashboardMetricsGenerator, RealTimeDashboardData,
17};
18use crate::progress::metrics::{
19    ConsistencyMetrics, MetricsCalculator, ProgressSystemStats, SessionAnalytics,
20};
21use crate::traits::{
22    Achievement, AchievementTier, FeedbackProvider, FeedbackResponse, FeedbackResult, FeedbackType,
23    FocusArea, Goal, GoalMetric, ProgressConfig, ProgressIndicators, ProgressReport,
24    ProgressSnapshot, ProgressTracker, ReportStatistics, SessionScores, SessionState,
25    SessionSummary, TimeRange, TrainingScores, TrainingStatistics, UserProgress,
26};
27use crate::FeedbackError;
28use async_trait::async_trait;
29use chrono::{DateTime, Utc};
30use serde::{Deserialize, Serialize};
31use statrs::statistics::Statistics;
32use std::collections::{HashMap, VecDeque};
33use std::sync::{Arc, RwLock};
34use std::time::Duration;
35use uuid::Uuid;
36use voirs_sdk::VoirsError;
37
38// ============================================================================
39// Internal Types
40// ============================================================================
41
42/// Internal progress system metrics (not exposed publicly)
43#[derive(Debug, Default)]
44struct ProgressMetrics {
45    /// Total users
46    total_users: usize,
47    /// Total sessions tracked
48    total_sessions: usize,
49    /// Total progress snapshots
50    total_snapshots: usize,
51}
52
53// ============================================================================
54// Comprehensive Analytics Framework
55// ============================================================================
56
57/// Comprehensive analytics framework for progress tracking
58#[derive(Debug, Clone)]
59pub struct ComprehensiveAnalyticsFramework {
60    /// Analytics configuration
61    config: AnalyticsConfig,
62    /// Memory-bounded metrics storage with LRU eviction
63    metrics: MemoryBoundedMetrics,
64    /// Aggregated metrics for long-term storage
65    aggregated_metrics: HashMap<String, AggregatedMetric>,
66    /// Last cleanup timestamp
67    last_cleanup: DateTime<Utc>,
68}
69
70impl Default for ComprehensiveAnalyticsFramework {
71    fn default() -> Self {
72        Self::new()
73    }
74}
75
76impl ComprehensiveAnalyticsFramework {
77    /// Create a new analytics framework
78    #[must_use]
79    pub fn new() -> Self {
80        let config = AnalyticsConfig::default();
81        Self {
82            metrics: MemoryBoundedMetrics::new(config.max_metrics_capacity),
83            aggregated_metrics: HashMap::new(),
84            last_cleanup: Utc::now(),
85            config,
86        }
87    }
88
89    /// Create a new analytics framework with custom configuration
90    #[must_use]
91    pub fn with_config(config: AnalyticsConfig) -> Self {
92        Self {
93            metrics: MemoryBoundedMetrics::new(config.max_metrics_capacity),
94            aggregated_metrics: HashMap::new(),
95            last_cleanup: Utc::now(),
96            config,
97        }
98    }
99
100    /// Generate analytics report
101    pub async fn generate_analytics_report(
102        &self,
103        progress: &UserProgress,
104        time_range: Option<TimeRange>,
105    ) -> Result<ComprehensiveAnalyticsReport, FeedbackError> {
106        let now = Utc::now();
107        let range = time_range.unwrap_or(TimeRange {
108            start: now - chrono::Duration::days(30),
109            end: now,
110        });
111
112        // Multi-dimensional progress measurement
113        let mut metrics = vec![
114            AnalyticsMetric {
115                name: "overall_skill_level".to_string(),
116                value: f64::from(progress.overall_skill_level),
117                timestamp: now,
118                metric_type: MetricType::Gauge,
119            },
120            AnalyticsMetric {
121                name: "total_sessions".to_string(),
122                value: progress.training_stats.total_sessions as f64,
123                timestamp: now,
124                metric_type: MetricType::Counter,
125            },
126            AnalyticsMetric {
127                name: "success_rate".to_string(),
128                value: f64::from(progress.training_stats.success_rate),
129                timestamp: now,
130                metric_type: MetricType::Gauge,
131            },
132            AnalyticsMetric {
133                name: "average_improvement".to_string(),
134                value: f64::from(progress.training_stats.average_improvement),
135                timestamp: now,
136                metric_type: MetricType::Gauge,
137            },
138            AnalyticsMetric {
139                name: "current_streak".to_string(),
140                value: progress.training_stats.current_streak as f64,
141                timestamp: now,
142                metric_type: MetricType::Counter,
143            },
144            AnalyticsMetric {
145                name: "longest_streak".to_string(),
146                value: progress.training_stats.longest_streak as f64,
147                timestamp: now,
148                metric_type: MetricType::Counter,
149            },
150        ];
151
152        // Add skill breakdown metrics
153        for (focus_area, &skill_level) in &progress.skill_breakdown {
154            metrics.push(AnalyticsMetric {
155                name: format!("skill_{focus_area:?}").to_lowercase(),
156                value: f64::from(skill_level),
157                timestamp: now,
158                metric_type: MetricType::Gauge,
159            });
160        }
161
162        // Filter progress history to time range
163        let relevant_history: Vec<_> = progress
164            .progress_history
165            .iter()
166            .filter(|snapshot| snapshot.timestamp >= range.start && snapshot.timestamp <= range.end)
167            .collect();
168
169        // Calculate trend analytics
170        let trend_analytics = self.calculate_trend_analytics(&relevant_history);
171
172        // Add trend metrics
173        metrics.push(AnalyticsMetric {
174            name: "improvement_velocity".to_string(),
175            value: f64::from(trend_analytics.improvement_velocity),
176            timestamp: now,
177            metric_type: MetricType::Gauge,
178        });
179
180        metrics.push(AnalyticsMetric {
181            name: "performance_stability".to_string(),
182            value: f64::from(trend_analytics.performance_stability),
183            timestamp: now,
184            metric_type: MetricType::Gauge,
185        });
186
187        let summary = AnalyticsSummary {
188            total_metrics: metrics.len(),
189            average_value: if metrics.is_empty() {
190                0.0
191            } else {
192                metrics.iter().map(|m| m.value).sum::<f64>() / metrics.len() as f64
193            },
194            time_range: range,
195        };
196
197        Ok(ComprehensiveAnalyticsReport {
198            timestamp: now,
199            metrics,
200            summary,
201        })
202    }
203
204    /// Calculate trend analytics from historical data
205    fn calculate_trend_analytics(&self, history: &[&ProgressSnapshot]) -> TrendAnalytics {
206        if history.len() < 2 {
207            return TrendAnalytics {
208                improvement_velocity: 0.0,
209                performance_stability: 0.0,
210                trend_direction: TrendDirection::Stable,
211                slope: 0.0,
212                r_squared: 0.0,
213            };
214        }
215
216        let scores: Vec<f32> = history.iter().map(|h| h.overall_score).collect();
217
218        // Calculate basic statistics
219        let mean = scores.iter().sum::<f32>() / scores.len() as f32;
220        let variance =
221            scores.iter().map(|&x| (x - mean).powi(2)).sum::<f32>() / scores.len() as f32;
222        let std_dev = variance.sqrt();
223
224        // Calculate trend direction and slope
225        let (slope, r_squared) = if scores.len() >= 2 {
226            let x_values: Vec<f64> = (0..scores.len()).map(|i| i as f64).collect();
227            let y_values: Vec<f64> = scores.iter().map(|&s| f64::from(s)).collect();
228
229            // Simple linear regression
230            let n = x_values.len() as f64;
231            let sum_x: f64 = x_values.iter().sum();
232            let sum_y: f64 = y_values.iter().sum();
233            let sum_xy: f64 = x_values.iter().zip(&y_values).map(|(x, y)| x * y).sum();
234            let sum_x2: f64 = x_values.iter().map(|x| x * x).sum();
235
236            let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x);
237
238            // Calculate R-squared
239            let y_mean = sum_y / n;
240            let ss_tot: f64 = y_values.iter().map(|y| (y - y_mean).powi(2)).sum();
241            let ss_res: f64 = y_values
242                .iter()
243                .zip(&x_values)
244                .map(|(y, x)| {
245                    let predicted = slope * x + (sum_y - slope * sum_x) / n;
246                    (y - predicted).powi(2)
247                })
248                .sum();
249
250            let r_squared = if ss_tot > 0.0 {
251                1.0 - ss_res / ss_tot
252            } else {
253                0.0
254            };
255
256            (slope as f32, r_squared.max(0.0) as f32)
257        } else {
258            (0.0, 0.0)
259        };
260
261        let trend_direction = if slope > 0.05 {
262            TrendDirection::Improving
263        } else if slope < -0.05 {
264            TrendDirection::Declining
265        } else {
266            TrendDirection::Stable
267        };
268
269        TrendAnalytics {
270            improvement_velocity: slope.max(0.0),
271            performance_stability: 1.0 / (1.0 + std_dev), // Inverse relationship
272            trend_direction,
273            slope,
274            r_squared,
275        }
276    }
277
278    /// Test statistical significance
279    pub async fn test_statistical_significance(
280        &self,
281        _progress: &UserProgress,
282        _metric: AnalyticsMetric,
283        _time_period: Duration,
284    ) -> Result<StatisticalSignificanceResult, FeedbackError> {
285        // Simplified implementation - in a real system this would perform actual statistical tests
286        Ok(StatisticalSignificanceResult {
287            p_value: 0.05,
288            is_significant: true,
289            confidence_level: 0.95,
290            effect_size: 0.3,
291        })
292    }
293
294    /// Generate comparative analysis
295    pub async fn generate_comparative_analysis(
296        &self,
297        user_progress_data: &[UserProgress],
298        _metric: AnalyticsMetric,
299        _time_range: Option<TimeRange>,
300    ) -> Result<ComparativeAnalyticsResult, FeedbackError> {
301        if user_progress_data.len() < 2 {
302            return Err(FeedbackError::ProgressTrackingError {
303                message: "Need at least 2 users for comparative analysis".to_string(),
304                source: None,
305            });
306        }
307
308        let baseline_value = f64::from(user_progress_data[0].overall_skill_level);
309        let comparison_value = f64::from(user_progress_data[1].overall_skill_level);
310        let percentage_change = ((comparison_value - baseline_value) / baseline_value) * 100.0;
311
312        Ok(ComparativeAnalyticsResult {
313            baseline_value,
314            comparison_value,
315            percentage_change,
316            statistical_significance: StatisticalSignificanceResult {
317                p_value: 0.05,
318                is_significant: percentage_change.abs() > 10.0,
319                confidence_level: 0.95,
320                effect_size: 0.3,
321            },
322        })
323    }
324
325    /// Collect longitudinal data
326    pub async fn collect_longitudinal_data(
327        &self,
328        _study_id: &str,
329        participant_data: &[UserProgress],
330        _tracking_metrics: &[AnalyticsMetric],
331    ) -> Result<LongitudinalStudyData, FeedbackError> {
332        let now = Utc::now();
333        let start = now - chrono::Duration::days(90);
334
335        let data_points: Vec<LongitudinalDataPoint> = participant_data
336            .iter()
337            .enumerate()
338            .map(|(i, progress)| LongitudinalDataPoint {
339                timestamp: start + chrono::Duration::days(i as i64),
340                value: f64::from(progress.overall_skill_level),
341                metadata: HashMap::new(),
342            })
343            .collect();
344
345        Ok(LongitudinalStudyData {
346            study_period: TimeRange { start, end: now },
347            data_points,
348            trend_analysis: TrendAnalysis {
349                trend_direction: TrendDirection::Improving,
350                slope: 0.1,
351                r_squared: 0.8,
352            },
353        })
354    }
355
356    /// Update aggregated metric
357    fn update_aggregated_metric(&mut self, name: String, metric: &AnalyticsMetric) {
358        let aggregated = self
359            .aggregated_metrics
360            .entry(name.clone())
361            .or_insert_with(|| AggregatedMetric {
362                name: name.clone(),
363                count: 0,
364                sum: 0.0,
365                sum_of_squares: 0.0,
366                min: f64::INFINITY,
367                max: f64::NEG_INFINITY,
368                last_updated: metric.timestamp,
369                metric_type: metric.metric_type.clone(),
370            });
371
372        aggregated.count += 1;
373        aggregated.sum += metric.value;
374        aggregated.sum_of_squares += metric.value * metric.value;
375        aggregated.min = aggregated.min.min(metric.value);
376        aggregated.max = aggregated.max.max(metric.value);
377        aggregated.last_updated = metric.timestamp;
378    }
379}
380
381// ============================================================================
382// Progress Analyzer Implementation
383// ============================================================================
384
385/// Progress analyzer for tracking user improvement
386#[derive(Clone)]
387pub struct ProgressAnalyzer {
388    /// User progress data keyed by user ID
389    user_progress: Arc<RwLock<HashMap<String, UserProgress>>>,
390    /// Achievement definitions
391    achievements: Arc<RwLock<Vec<AchievementDefinition>>>,
392    /// Configuration
393    config: ProgressConfig,
394    /// System metrics
395    metrics: Arc<RwLock<ProgressMetrics>>,
396    /// Comprehensive analytics framework
397    analytics: Arc<RwLock<ComprehensiveAnalyticsFramework>>,
398}
399
400impl ProgressAnalyzer {
401    /// Create a new progress analyzer
402    pub async fn new() -> Result<Self, FeedbackError> {
403        Self::with_config(ProgressConfig::default()).await
404    }
405
406    /// Create with custom configuration
407    pub async fn with_config(config: ProgressConfig) -> Result<Self, FeedbackError> {
408        let achievements = Self::create_default_achievements();
409
410        Ok(Self {
411            user_progress: Arc::new(RwLock::new(HashMap::new())),
412            achievements: Arc::new(RwLock::new(achievements)),
413            config,
414            metrics: Arc::new(RwLock::new(ProgressMetrics::default())),
415            analytics: Arc::new(RwLock::new(ComprehensiveAnalyticsFramework::new())),
416        })
417    }
418
419    /// Create or get user progress
420    pub async fn get_user_progress_impl(
421        &self,
422        user_id: &str,
423    ) -> Result<UserProgress, FeedbackError> {
424        {
425            let progress_map = self.user_progress.read().unwrap();
426            if let Some(progress) = progress_map.get(user_id) {
427                return Ok(progress.clone());
428            }
429        } // progress_map dropped here
430        self.create_user_progress(user_id).await
431    }
432
433    /// Create new user progress tracking
434    async fn create_user_progress(&self, user_id: &str) -> Result<UserProgress, FeedbackError> {
435        let progress = UserProgress {
436            user_id: user_id.to_string(),
437            overall_skill_level: 0.5,
438            skill_breakdown: Self::initialize_skill_breakdown(),
439            progress_history: Vec::new(),
440            achievements: Vec::new(),
441            training_stats: TrainingStatistics {
442                total_sessions: 0,
443                successful_sessions: 0,
444                total_training_time: Duration::from_secs(0),
445                exercises_completed: 0,
446                success_rate: 0.0,
447                average_improvement: 0.0,
448                current_streak: 0,
449                longest_streak: 0,
450            },
451            goals: Vec::new(),
452            last_updated: Utc::now(),
453            average_scores: SessionScores::default(),
454            skill_levels: HashMap::new(),
455            recent_sessions: Vec::new(),
456            personal_bests: HashMap::new(),
457            session_count: 0,
458            total_practice_time: Duration::from_secs(0),
459        };
460
461        {
462            let mut progress_map = self.user_progress.write().unwrap();
463            progress_map.insert(user_id.to_string(), progress.clone());
464        }
465
466        {
467            let mut metrics = self.metrics.write().unwrap();
468            metrics.total_users += 1;
469        }
470
471        Ok(progress)
472    }
473
474    /// Record training session progress
475    pub async fn record_training_session(
476        &self,
477        user_id: &str,
478        scores: &TrainingScores,
479    ) -> Result<(), FeedbackError> {
480        // Create a default feedback response for recording
481        let feedback = FeedbackResponse {
482            feedback_items: Vec::new(),
483            overall_score: (scores.pronunciation + scores.quality + scores.consistency) / 3.0,
484            immediate_actions: Vec::new(),
485            long_term_goals: Vec::new(),
486            progress_indicators: ProgressIndicators {
487                improving_areas: Vec::new(),
488                attention_areas: Vec::new(),
489                stable_areas: Vec::new(),
490                overall_trend: scores.improvement,
491                completion_percentage: scores.pronunciation * 100.0,
492            },
493            timestamp: Utc::now(),
494            processing_time: Duration::from_millis(100),
495            feedback_type: FeedbackType::Quality,
496        };
497
498        let session = SessionState {
499            session_id: Uuid::new_v4(),
500            user_id: user_id.to_string(),
501            start_time: Utc::now(),
502            last_activity: Utc::now(),
503            current_task: None,
504            stats: Default::default(),
505            preferences: Default::default(),
506            adaptive_state: Default::default(),
507            current_exercise: None,
508            session_stats: Default::default(),
509        };
510
511        self.record_session_progress(user_id, &session, scores, &feedback)
512            .await
513    }
514
515    /// Record training session progress
516    pub async fn record_session_progress(
517        &self,
518        user_id: &str,
519        session: &SessionState,
520        scores: &TrainingScores,
521        feedback: &FeedbackResponse,
522    ) -> Result<(), FeedbackError> {
523        // Ensure user exists - create if not
524        let _ = self.get_user_progress_impl(user_id).await?;
525
526        // Create a clone of the user's progress to check achievements
527        let progress_for_achievements = {
528            let mut progress_map = self.user_progress.write().unwrap();
529            let progress = progress_map.get_mut(user_id).ok_or_else(|| {
530                FeedbackError::ProgressTrackingError {
531                    message: format!("User progress not found: {user_id}"),
532                    source: None,
533                }
534            })?;
535
536            // Update overall skill level
537            let session_score = (scores.pronunciation + scores.quality + scores.consistency) / 3.0;
538            progress.overall_skill_level =
539                self.update_skill_level(progress.overall_skill_level, session_score);
540
541            // Update skill breakdown based on scores
542            self.update_skill_breakdown(progress, scores, feedback)?;
543
544            // Create progress snapshot
545            let snapshot = ProgressSnapshot {
546                timestamp: Utc::now(),
547                overall_score: session_score,
548                area_scores: self.extract_area_scores(scores, feedback),
549                session_count: (progress.training_stats.total_sessions + 1),
550                events: self.extract_session_events(feedback),
551            };
552
553            // Add to history
554            progress.progress_history.push(snapshot);
555
556            // Trim history if needed
557            if progress.progress_history.len() > 1000 {
558                progress.progress_history.drain(0..100); // Remove oldest 100 entries
559            }
560
561            // Update training statistics
562            self.update_training_stats(&mut progress.training_stats, session, scores);
563
564            // Update streak information
565            self.update_streak_info(&mut progress.training_stats, scores);
566
567            // Create session summary and add to recent sessions
568            let session_summary = SessionSummary {
569                session_id: session.session_id.to_string(),
570                timestamp: session.start_time,
571                duration: Duration::from_secs(300), // Default 5 minutes for test
572                score: (scores.pronunciation + scores.quality + scores.consistency) / 3.0,
573                exercises_completed: 1,
574            };
575
576            progress.recent_sessions.push(session_summary);
577            progress.session_count += 1;
578            progress.total_practice_time += Duration::from_secs(300);
579
580            // Keep only last 50 sessions
581            if progress.recent_sessions.len() > 50 {
582                progress
583                    .recent_sessions
584                    .drain(0..progress.recent_sessions.len() - 50);
585            }
586
587            // Clone progress for achievement checking
588            progress.clone()
589        };
590
591        // Check for new achievements (guard is dropped)
592        let new_achievements = self
593            .check_achievements_for_user(&progress_for_achievements)
594            .await?;
595
596        // Add achievements to the user's progress
597        {
598            let mut progress_map = self.user_progress.write().unwrap();
599            if let Some(progress) = progress_map.get_mut(user_id) {
600                progress.achievements.extend(new_achievements);
601                progress.last_updated = Utc::now();
602            }
603        }
604
605        // Update system metrics
606        {
607            let mut metrics = self.metrics.write().unwrap();
608            metrics.total_sessions += 1;
609            metrics.total_snapshots += 1;
610        }
611
612        Ok(())
613    }
614
615    /// Generate detailed progress report
616    pub async fn generate_detailed_report(
617        &self,
618        user_id: &str,
619        time_range: Option<TimeRange>,
620    ) -> Result<DetailedProgressReport, FeedbackError> {
621        let progress = self.get_user_progress_impl(user_id).await?;
622
623        let (start_date, end_date) = if let Some(range) = time_range {
624            (range.start, range.end)
625        } else {
626            // Default to last 30 days
627            let end = Utc::now();
628            let start = end - chrono::Duration::days(30);
629            (start, end)
630        };
631
632        // Filter history to time range
633        let relevant_history: Vec<_> = progress
634            .progress_history
635            .iter()
636            .filter(|snapshot| snapshot.timestamp >= start_date && snapshot.timestamp <= end_date)
637            .collect();
638
639        if relevant_history.is_empty() {
640            return Err(FeedbackError::ProgressTrackingError {
641                message: "No progress data found for the specified time range".to_string(),
642                source: None,
643            });
644        }
645
646        let report = DetailedProgressReport {
647            user_id: user_id.to_string(),
648            period: TimeRange {
649                start: start_date,
650                end: end_date,
651            },
652            overall_improvement: MetricsCalculator::calculate_overall_improvement(
653                &relevant_history,
654            ),
655            area_improvements: MetricsCalculator::calculate_area_improvements(&relevant_history),
656            skill_trends: MetricsCalculator::calculate_skill_trends(&relevant_history),
657            session_analytics: MetricsCalculator::calculate_session_analytics(&relevant_history),
658            consistency_metrics: MetricsCalculator::calculate_consistency_metrics(
659                &relevant_history,
660            ),
661            achievement_progress: self.analyze_achievement_progress(&progress),
662            goal_analysis: self.analyze_goal_progress(&progress),
663            recommendations: self.generate_progress_recommendations(&progress, &relevant_history),
664            comparative_analysis: self
665                .generate_comparative_analysis(user_id, &relevant_history)
666                .await?,
667        };
668
669        Ok(report)
670    }
671
672    /// Analyze learning patterns
673    pub async fn analyze_learning_patterns(
674        &self,
675        user_id: &str,
676    ) -> Result<Vec<String>, FeedbackError> {
677        let progress = self.get_user_progress_impl(user_id).await?;
678
679        if progress.progress_history.len() < 5 {
680            return Ok(vec!["Insufficient data for pattern analysis".to_string()]);
681        }
682
683        let mut patterns = Vec::new();
684
685        // Analyze improvement trend
686        let recent_scores: Vec<f32> = progress
687            .progress_history
688            .iter()
689            .rev()
690            .take(10)
691            .map(|s| s.overall_score)
692            .collect();
693
694        if let (Some(&first), Some(&last)) = (recent_scores.last(), recent_scores.first()) {
695            if last > first + 0.1 {
696                patterns.push(
697                    "You're improving consistently and making great progress in recent sessions"
698                        .to_string(),
699                );
700            } else if last < first - 0.1 {
701                patterns.push("Consider reviewing your practice approach".to_string());
702            } else {
703                patterns.push("Your performance is stable".to_string());
704            }
705        }
706
707        // Analyze session frequency
708        if progress.training_stats.current_streak >= 3 {
709            patterns.push("Great job maintaining a practice streak!".to_string());
710        }
711
712        // Analyze skill areas
713        if let Some((best_area, &best_score)) = progress
714            .skill_breakdown
715            .iter()
716            .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
717        {
718            patterns.push(format!(
719                "Your strongest area is {best_area:?} ({:.1}%)",
720                best_score * 100.0
721            ));
722        }
723
724        Ok(patterns)
725    }
726
727    /// Set user goals
728    pub async fn set_goal(&self, user_id: &str, goal: Goal) -> Result<(), FeedbackError> {
729        // Ensure user exists - create if not
730        let _ = self.get_user_progress_impl(user_id).await?;
731
732        let mut progress_map = self.user_progress.write().unwrap();
733        let progress =
734            progress_map
735                .get_mut(user_id)
736                .ok_or_else(|| FeedbackError::ProgressTrackingError {
737                    message: format!("User progress not found: {user_id}"),
738                    source: None,
739                })?;
740
741        // Validate goal
742        self.validate_goal(&goal)?;
743
744        progress.goals.push(goal);
745        progress.last_updated = Utc::now();
746
747        Ok(())
748    }
749
750    /// Get user goals
751    pub async fn get_user_goals(&self, user_id: &str) -> Result<Vec<Goal>, FeedbackError> {
752        let progress = self.get_user_progress_impl(user_id).await?;
753        Ok(progress.goals)
754    }
755
756    /// Check achievements
757    pub async fn check_achievements(
758        &self,
759        user_id: &str,
760    ) -> Result<Vec<Achievement>, FeedbackError> {
761        let progress = self.get_user_progress_impl(user_id).await?;
762        Ok(progress.achievements)
763    }
764
765    /// Get system statistics
766    pub async fn get_statistics(&self) -> Result<ProgressSystemStats, FeedbackError> {
767        let progress_map = self.user_progress.read().unwrap();
768        let metrics = self.metrics.read().unwrap();
769
770        let active_users = progress_map.len();
771        let total_achievements = progress_map.values().map(|p| p.achievements.len()).sum();
772
773        let average_skill_level = if progress_map.is_empty() {
774            0.0
775        } else {
776            let scores: Vec<f32> = progress_map
777                .values()
778                .map(|p| p.overall_skill_level)
779                .collect();
780            scores.iter().sum::<f32>() / scores.len() as f32
781        };
782
783        Ok(ProgressSystemStats {
784            total_users: metrics.total_users,
785            active_users,
786            total_sessions: metrics.total_sessions,
787            total_achievements_unlocked: total_achievements,
788            average_skill_level,
789            total_snapshots: metrics.total_snapshots,
790        })
791    }
792
793    // ========================================================================
794    // Helper Methods
795    // ========================================================================
796
797    /// Initialize skill breakdown with default values
798    fn initialize_skill_breakdown() -> HashMap<FocusArea, f32> {
799        [
800            (FocusArea::Pronunciation, 0.5),
801            (FocusArea::Fluency, 0.5),
802            (FocusArea::Naturalness, 0.5),
803            (FocusArea::Quality, 0.5),
804            (FocusArea::Rhythm, 0.5),
805            (FocusArea::Stress, 0.5),
806        ]
807        .iter()
808        .cloned()
809        .collect()
810    }
811
812    /// Update skill level based on session performance
813    fn update_skill_level(&self, current_level: f32, session_score: f32) -> f32 {
814        // Apply exponential smoothing
815        let learning_rate = 0.1;
816        current_level * (1.0 - learning_rate) + session_score * learning_rate
817    }
818
819    /// Update skill breakdown based on feedback
820    fn update_skill_breakdown(
821        &self,
822        progress: &mut UserProgress,
823        scores: &TrainingScores,
824        _feedback: &FeedbackResponse,
825    ) -> Result<(), FeedbackError> {
826        // Update pronunciation skills
827        if let Some(pronunciation_level) =
828            progress.skill_breakdown.get_mut(&FocusArea::Pronunciation)
829        {
830            *pronunciation_level =
831                self.update_skill_level(*pronunciation_level, scores.pronunciation);
832        }
833
834        // Update quality skills (map to naturalness)
835        if let Some(naturalness_level) = progress.skill_breakdown.get_mut(&FocusArea::Naturalness) {
836            *naturalness_level = self.update_skill_level(*naturalness_level, scores.quality);
837        }
838
839        // Update consistency-related skills (map to rhythm)
840        if let Some(rhythm_level) = progress.skill_breakdown.get_mut(&FocusArea::Rhythm) {
841            *rhythm_level = self.update_skill_level(*rhythm_level, scores.consistency);
842        }
843
844        Ok(())
845    }
846
847    /// Extract area scores from training scores
848    fn extract_area_scores(
849        &self,
850        scores: &TrainingScores,
851        _feedback: &FeedbackResponse,
852    ) -> HashMap<FocusArea, f32> {
853        [
854            (FocusArea::Pronunciation, scores.pronunciation),
855            (FocusArea::Naturalness, scores.quality),
856            (FocusArea::Rhythm, scores.consistency),
857        ]
858        .iter()
859        .cloned()
860        .collect()
861    }
862
863    /// Extract session events from feedback
864    fn extract_session_events(&self, feedback: &FeedbackResponse) -> Vec<String> {
865        feedback
866            .feedback_items
867            .iter()
868            .map(|item| item.message.clone())
869            .collect()
870    }
871
872    /// Update training statistics
873    fn update_training_stats(
874        &self,
875        stats: &mut TrainingStatistics,
876        session: &SessionState,
877        scores: &TrainingScores,
878    ) {
879        stats.total_sessions += 1;
880
881        // Estimate session time based on session duration (simplified)
882        let session_duration = Utc::now().signed_duration_since(session.start_time);
883        stats.total_training_time += session_duration
884            .to_std()
885            .unwrap_or(Duration::from_secs(1800)); // Default 30 min
886
887        // Simplified exercise count estimation
888        stats.exercises_completed += 5; // Assume 5 exercises per session
889
890        // Update success rate
891        let session_success = usize::from(scores.pronunciation >= 0.7 && scores.quality >= 0.7);
892        stats.successful_sessions += session_success;
893        stats.success_rate = stats.successful_sessions as f32 / stats.total_sessions as f32;
894
895        // Update average improvement
896        if stats.total_sessions > 1 {
897            stats.average_improvement = (stats.average_improvement
898                * (stats.total_sessions - 1) as f32
899                + scores.improvement)
900                / stats.total_sessions as f32;
901        } else {
902            stats.average_improvement = scores.improvement;
903        }
904    }
905
906    /// Update streak information
907    fn update_streak_info(&self, stats: &mut TrainingStatistics, scores: &TrainingScores) {
908        // Consider a session successful for streak purposes if average score is good
909        let avg_score = (scores.pronunciation + scores.quality + scores.consistency) / 3.0;
910        if avg_score >= 0.7 {
911            stats.current_streak += 1;
912            stats.longest_streak = stats.longest_streak.max(stats.current_streak);
913        } else {
914            stats.current_streak = 0;
915        }
916    }
917
918    /// Check achievements for a user
919    async fn check_achievements_for_user(
920        &self,
921        progress: &UserProgress,
922    ) -> Result<Vec<Achievement>, FeedbackError> {
923        let achievements = self.achievements.read().unwrap();
924        let mut new_achievements = Vec::new();
925
926        for achievement_def in achievements.iter() {
927            // Check if already unlocked
928            if progress
929                .achievements
930                .iter()
931                .any(|a| a.achievement_id == achievement_def.id)
932            {
933                continue;
934            }
935
936            // Check if condition is met
937            if self.is_achievement_condition_met(progress, &achievement_def.condition) {
938                new_achievements.push(Achievement {
939                    achievement_id: achievement_def.id.clone(),
940                    name: achievement_def.name.clone(),
941                    description: achievement_def.description.clone(),
942                    tier: achievement_def.tier.clone(),
943                    points: achievement_def.points,
944                    unlocked_at: Utc::now(),
945                });
946            }
947        }
948
949        Ok(new_achievements)
950    }
951
952    /// Check if achievement condition is met
953    fn is_achievement_condition_met(
954        &self,
955        progress: &UserProgress,
956        condition: &AchievementCondition,
957    ) -> bool {
958        match condition {
959            AchievementCondition::SessionCount(required) => {
960                progress.training_stats.total_sessions >= *required
961            }
962            AchievementCondition::SkillLevel(required) => progress.overall_skill_level >= *required,
963            AchievementCondition::Streak(required) => {
964                progress.training_stats.longest_streak >= *required
965            }
966            AchievementCondition::AreaMastery(area, required) => {
967                progress.skill_breakdown.get(area).unwrap_or(&0.0) >= required
968            }
969            AchievementCondition::TrainingTime(required) => {
970                progress.training_stats.total_training_time >= *required
971            }
972        }
973    }
974
975    /// Validate goal
976    fn validate_goal(&self, goal: &Goal) -> Result<(), FeedbackError> {
977        if goal.target_value < 0.0 || goal.target_value > 1.0 {
978            return Err(FeedbackError::ProgressTrackingError {
979                message: "Goal target value must be between 0.0 and 1.0".to_string(),
980                source: None,
981            });
982        }
983        Ok(())
984    }
985
986    /// Analyze achievement progress
987    fn analyze_achievement_progress(&self, progress: &UserProgress) -> Vec<AchievementAnalysis> {
988        let achievements = self.achievements.read().unwrap();
989        let mut analyses = Vec::new();
990
991        for achievement_def in achievements.iter() {
992            let current_progress = self.calculate_achievement_progress(progress, achievement_def);
993            let is_unlocked = progress
994                .achievements
995                .iter()
996                .any(|a| a.achievement_id == achievement_def.id);
997
998            analyses.push(AchievementAnalysis {
999                achievement_id: achievement_def.id.clone(),
1000                name: achievement_def.name.clone(),
1001                current_progress,
1002                is_unlocked,
1003                estimated_time_to_unlock: None, // Simplified
1004            });
1005        }
1006
1007        analyses
1008    }
1009
1010    /// Calculate achievement progress
1011    fn calculate_achievement_progress(
1012        &self,
1013        progress: &UserProgress,
1014        achievement_def: &AchievementDefinition,
1015    ) -> f32 {
1016        match &achievement_def.condition {
1017            AchievementCondition::SessionCount(required) => {
1018                (progress.training_stats.total_sessions as f32 / *required as f32).min(1.0)
1019            }
1020            AchievementCondition::SkillLevel(required) => {
1021                (progress.overall_skill_level / required).min(1.0)
1022            }
1023            AchievementCondition::Streak(required) => {
1024                (progress.training_stats.longest_streak as f32 / *required as f32).min(1.0)
1025            }
1026            AchievementCondition::AreaMastery(area, required) => {
1027                let current = progress.skill_breakdown.get(area).unwrap_or(&0.0);
1028                (current / required).min(1.0)
1029            }
1030            AchievementCondition::TrainingTime(required) => {
1031                let current_secs = progress.training_stats.total_training_time.as_secs() as f32;
1032                let required_secs = required.as_secs() as f32;
1033                (current_secs / required_secs).min(1.0)
1034            }
1035        }
1036    }
1037
1038    /// Analyze goal progress
1039    fn analyze_goal_progress(&self, progress: &UserProgress) -> Vec<GoalAnalysis> {
1040        progress
1041            .goals
1042            .iter()
1043            .map(|goal| {
1044                let current_value = match &goal.target_metric {
1045                    GoalMetric::OverallSkill => progress.overall_skill_level,
1046                    GoalMetric::FocusAreaSkill(area) => {
1047                        *progress.skill_breakdown.get(area).unwrap_or(&0.0)
1048                    }
1049                    GoalMetric::ExerciseCount => progress.training_stats.exercises_completed as f32,
1050                    GoalMetric::SessionCount => progress.training_stats.total_sessions as f32,
1051                    GoalMetric::TrainingTime => {
1052                        progress.training_stats.total_training_time.as_secs() as f32
1053                    }
1054                    GoalMetric::Streak => progress.training_stats.current_streak as f32,
1055                };
1056
1057                let progress_percentage = (current_value / goal.target_value * 100.0).min(100.0);
1058
1059                GoalAnalysis {
1060                    goal: goal.clone(),
1061                    current_value,
1062                    progress_percentage,
1063                    on_track: progress_percentage >= 50.0, // Simplified
1064                    estimated_completion: None,            // Simplified
1065                }
1066            })
1067            .collect()
1068    }
1069
1070    /// Generate progress recommendations
1071    fn generate_progress_recommendations(
1072        &self,
1073        progress: &UserProgress,
1074        _history: &[&ProgressSnapshot],
1075    ) -> Vec<ProgressRecommendation> {
1076        let mut recommendations = Vec::new();
1077
1078        // Check for areas needing improvement
1079        for (area, &score) in &progress.skill_breakdown {
1080            if score < 0.6 {
1081                recommendations.push(ProgressRecommendation {
1082                    recommendation_type: RecommendationType::Practice,
1083                    title: format!("Focus on {area:?}"),
1084                    description: format!(
1085                        "Your {area:?} skills could use some attention. Current level: {:.0}%",
1086                        score * 100.0
1087                    ),
1088                    priority: 1.0 - score, // Higher priority for lower scores
1089                    estimated_impact: 0.8,
1090                    suggested_actions: vec![
1091                        format!("Practice {area:?} exercises for 15 minutes daily"),
1092                        "Review related learning materials".to_string(),
1093                    ],
1094                });
1095            }
1096        }
1097
1098        // Check streak
1099        if progress.training_stats.current_streak == 0 {
1100            recommendations.push(ProgressRecommendation {
1101                recommendation_type: RecommendationType::Consistency,
1102                title: "Build a Practice Streak".to_string(),
1103                description: "Start building a daily practice habit".to_string(),
1104                priority: 0.7,
1105                estimated_impact: 0.9,
1106                suggested_actions: vec![
1107                    "Set a daily practice reminder".to_string(),
1108                    "Start with short 10-minute sessions".to_string(),
1109                ],
1110            });
1111        }
1112
1113        recommendations
1114    }
1115
1116    /// Generate comparative analysis
1117    async fn generate_comparative_analysis(
1118        &self,
1119        user_id: &str,
1120        _history: &[&ProgressSnapshot],
1121    ) -> Result<ComparativeAnalysis, FeedbackError> {
1122        let progress = self.get_user_progress_impl(user_id).await?;
1123        let stats = self.get_statistics().await?;
1124
1125        Ok(ComparativeAnalysis {
1126            user_percentile: 50.0, // Simplified
1127            average_user_score: stats.average_skill_level,
1128            user_score: progress.overall_skill_level,
1129            improvement_rate_vs_average: 0.0, // Simplified
1130            strengths_vs_peers: vec!["Consistent practice".to_string()],
1131            areas_for_improvement: vec!["Pronunciation".to_string()],
1132        })
1133    }
1134
1135    /// Create default achievements
1136    fn create_default_achievements() -> Vec<AchievementDefinition> {
1137        vec![
1138            AchievementDefinition {
1139                id: "first_session".to_string(),
1140                name: "First Steps".to_string(),
1141                description: "Complete your first training session".to_string(),
1142                condition: AchievementCondition::SessionCount(1),
1143                tier: AchievementTier::Bronze,
1144                points: 10,
1145            },
1146            AchievementDefinition {
1147                id: "ten_sessions".to_string(),
1148                name: "Getting Started".to_string(),
1149                description: "Complete 10 training sessions".to_string(),
1150                condition: AchievementCondition::SessionCount(10),
1151                tier: AchievementTier::Bronze,
1152                points: 50,
1153            },
1154            AchievementDefinition {
1155                id: "three_day_streak".to_string(),
1156                name: "Streak Starter".to_string(),
1157                description: "Maintain a 3-day practice streak".to_string(),
1158                condition: AchievementCondition::Streak(3),
1159                tier: AchievementTier::Bronze,
1160                points: 30,
1161            },
1162            AchievementDefinition {
1163                id: "pronunciation_master".to_string(),
1164                name: "Pronunciation Master".to_string(),
1165                description: "Achieve 90% pronunciation accuracy".to_string(),
1166                condition: AchievementCondition::AreaMastery(FocusArea::Pronunciation, 0.9),
1167                tier: AchievementTier::Gold,
1168                points: 100,
1169            },
1170        ]
1171    }
1172}
1173
1174// ============================================================================
1175// Trait Implementations
1176// ============================================================================
1177
1178#[async_trait]
1179impl ProgressTracker for ProgressAnalyzer {
1180    async fn record_progress(
1181        &mut self,
1182        user_id: &str,
1183        session: &SessionState,
1184        scores: &TrainingScores,
1185    ) -> FeedbackResult<()> {
1186        let feedback = FeedbackResponse {
1187            feedback_items: Vec::new(),
1188            overall_score: (scores.pronunciation + scores.quality + scores.consistency) / 3.0,
1189            immediate_actions: Vec::new(),
1190            long_term_goals: Vec::new(),
1191            progress_indicators: ProgressIndicators {
1192                improving_areas: Vec::new(),
1193                attention_areas: Vec::new(),
1194                stable_areas: Vec::new(),
1195                overall_trend: scores.improvement,
1196                completion_percentage: scores.pronunciation * 100.0,
1197            },
1198            timestamp: Utc::now(),
1199            processing_time: Duration::from_millis(100),
1200            feedback_type: FeedbackType::Quality,
1201        };
1202
1203        self.record_session_progress(user_id, session, scores, &feedback)
1204            .await
1205            .map_err(std::convert::Into::into)
1206    }
1207
1208    async fn get_user_progress(&self, user_id: &str) -> FeedbackResult<UserProgress> {
1209        self.get_user_progress_impl(user_id)
1210            .await
1211            .map_err(std::convert::Into::into)
1212    }
1213
1214    async fn generate_progress_report(
1215        &self,
1216        user_id: &str,
1217        time_range: Option<TimeRange>,
1218    ) -> FeedbackResult<ProgressReport> {
1219        let progress = self.get_user_progress_impl(user_id).await?;
1220
1221        let period = time_range.unwrap_or_else(|| {
1222            let now = Utc::now();
1223            TimeRange {
1224                start: now - chrono::Duration::days(30),
1225                end: now,
1226            }
1227        });
1228
1229        Ok(ProgressReport {
1230            user_id: progress.user_id,
1231            period,
1232            overall_improvement: 0.1, // Simplified
1233            area_improvements: progress.skill_breakdown,
1234            achievements: progress.achievements,
1235            goal_progress: Vec::new(), // Simplified
1236            recommendations: vec!["Keep practicing daily".to_string()],
1237            statistics: ReportStatistics {
1238                sessions_count: progress.training_stats.total_sessions,
1239                total_practice_time: progress.training_stats.total_training_time,
1240                average_session_length: Duration::from_secs(1800), // Simplified
1241                exercises_completed: progress.training_stats.exercises_completed,
1242                success_rate: progress.training_stats.success_rate,
1243            },
1244        })
1245    }
1246
1247    async fn check_achievements(&self, user_id: &str) -> FeedbackResult<Vec<Achievement>> {
1248        let progress = self.get_user_progress(user_id).await?;
1249        Ok(progress.achievements)
1250    }
1251
1252    async fn set_goals(&mut self, user_id: &str, goals: Vec<Goal>) -> FeedbackResult<()> {
1253        let mut progress_map = self.user_progress.write().unwrap();
1254        let progress = progress_map.get_mut(user_id).ok_or_else(|| {
1255            VoirsError::from(FeedbackError::ProgressTrackingError {
1256                message: format!("User progress not found: {user_id}"),
1257                source: None,
1258            })
1259        })?;
1260
1261        // Validate goals
1262        for goal in &goals {
1263            self.validate_goal(goal).map_err(VoirsError::from)?;
1264        }
1265
1266        progress.goals = goals;
1267        progress.last_updated = Utc::now();
1268
1269        Ok(())
1270    }
1271}