ricecoder_learning/
analytics_engine.rs

1/// Analytics engine for tracking and analyzing rule metrics
2use crate::error::{LearningError, Result};
3use crate::models::{Rule, RuleScope};
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::sync::Arc;
8use tokio::sync::RwLock;
9
10/// Metrics for a single rule
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct RuleMetrics {
13    /// Rule ID
14    pub rule_id: String,
15    /// Number of times the rule has been applied
16    pub usage_count: u64,
17    /// Number of successful applications
18    pub success_count: u64,
19    /// Number of failed applications
20    pub failure_count: u64,
21    /// Success rate (0.0 to 1.0)
22    pub success_rate: f32,
23    /// Confidence score (0.0 to 1.0)
24    pub confidence: f32,
25    /// When the rule was first applied
26    pub first_applied: Option<DateTime<Utc>>,
27    /// When the rule was last applied
28    pub last_applied: Option<DateTime<Utc>>,
29    /// Average time to apply the rule (in milliseconds)
30    pub avg_application_time_ms: f64,
31}
32
33impl RuleMetrics {
34    /// Create new metrics for a rule
35    pub fn new(rule_id: String) -> Self {
36        Self {
37            rule_id,
38            usage_count: 0,
39            success_count: 0,
40            failure_count: 0,
41            success_rate: 0.0,
42            confidence: 0.5,
43            first_applied: None,
44            last_applied: None,
45            avg_application_time_ms: 0.0,
46        }
47    }
48
49    /// Record a successful application
50    pub fn record_success(&mut self, application_time_ms: f64) {
51        self.usage_count += 1;
52        self.success_count += 1;
53        self.update_success_rate();
54        self.update_application_time(application_time_ms);
55        self.update_last_applied();
56    }
57
58    /// Record a failed application
59    pub fn record_failure(&mut self, application_time_ms: f64) {
60        self.usage_count += 1;
61        self.failure_count += 1;
62        self.update_success_rate();
63        self.update_application_time(application_time_ms);
64        self.update_last_applied();
65    }
66
67    /// Update success rate based on current counts
68    fn update_success_rate(&mut self) {
69        if self.usage_count > 0 {
70            self.success_rate = self.success_count as f32 / self.usage_count as f32;
71        }
72    }
73
74    /// Update average application time
75    fn update_application_time(&mut self, new_time_ms: f64) {
76        if self.usage_count == 1 {
77            self.avg_application_time_ms = new_time_ms;
78        } else {
79            let total_time = self.avg_application_time_ms * (self.usage_count - 1) as f64;
80            self.avg_application_time_ms = (total_time + new_time_ms) / self.usage_count as f64;
81        }
82    }
83
84    /// Update last applied timestamp
85    fn update_last_applied(&mut self) {
86        self.last_applied = Some(Utc::now());
87        if self.first_applied.is_none() {
88            self.first_applied = Some(Utc::now());
89        }
90    }
91}
92
93/// Analytics insights about rule usage
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct AnalyticsInsights {
96    /// Total number of rules tracked
97    pub total_rules: usize,
98    /// Total number of rule applications
99    pub total_applications: u64,
100    /// Average success rate across all rules
101    pub avg_success_rate: f32,
102    /// Average confidence score across all rules
103    pub avg_confidence: f32,
104    /// Most frequently used rule ID
105    pub most_used_rule: Option<String>,
106    /// Least frequently used rule ID
107    pub least_used_rule: Option<String>,
108    /// Rules with highest success rate
109    pub top_performing_rules: Vec<String>,
110    /// Rules with lowest success rate
111    pub bottom_performing_rules: Vec<String>,
112}
113
114/// Analytics engine for tracking rule metrics and generating insights
115pub struct AnalyticsEngine {
116    /// Metrics for each rule
117    metrics: Arc<RwLock<HashMap<String, RuleMetrics>>>,
118}
119
120impl AnalyticsEngine {
121    /// Create a new analytics engine
122    pub fn new() -> Self {
123        Self {
124            metrics: Arc::new(RwLock::new(HashMap::new())),
125        }
126    }
127
128    /// Record a rule application
129    pub async fn record_application(
130        &self,
131        rule_id: String,
132        success: bool,
133        application_time_ms: f64,
134    ) -> Result<()> {
135        let mut metrics = self.metrics.write().await;
136        let rule_metrics = metrics
137            .entry(rule_id.clone())
138            .or_insert_with(|| RuleMetrics::new(rule_id));
139
140        if success {
141            rule_metrics.record_success(application_time_ms);
142        } else {
143            rule_metrics.record_failure(application_time_ms);
144        }
145
146        Ok(())
147    }
148
149    /// Get metrics for a specific rule
150    pub async fn get_rule_metrics(&self, rule_id: &str) -> Result<Option<RuleMetrics>> {
151        let metrics = self.metrics.read().await;
152        Ok(metrics.get(rule_id).cloned())
153    }
154
155    /// Get all metrics
156    pub async fn get_all_metrics(&self) -> Result<Vec<RuleMetrics>> {
157        let metrics = self.metrics.read().await;
158        Ok(metrics.values().cloned().collect())
159    }
160
161    /// Update rule confidence based on validation results
162    pub async fn update_confidence(&self, rule_id: &str, new_confidence: f32) -> Result<()> {
163        if !(0.0..=1.0).contains(&new_confidence) {
164            return Err(LearningError::AnalyticsError(
165                "Confidence must be between 0.0 and 1.0".to_string(),
166            ));
167        }
168
169        let mut metrics = self.metrics.write().await;
170        if let Some(rule_metrics) = metrics.get_mut(rule_id) {
171            rule_metrics.confidence = new_confidence;
172            Ok(())
173        } else {
174            Err(LearningError::AnalyticsError(format!(
175                "Rule {} not found in metrics",
176                rule_id
177            )))
178        }
179    }
180
181    /// Generate analytics insights
182    pub async fn generate_insights(&self) -> Result<AnalyticsInsights> {
183        let metrics = self.metrics.read().await;
184
185        if metrics.is_empty() {
186            return Ok(AnalyticsInsights {
187                total_rules: 0,
188                total_applications: 0,
189                avg_success_rate: 0.0,
190                avg_confidence: 0.0,
191                most_used_rule: None,
192                least_used_rule: None,
193                top_performing_rules: Vec::new(),
194                bottom_performing_rules: Vec::new(),
195            });
196        }
197
198        let total_rules = metrics.len();
199        let total_applications: u64 = metrics.values().map(|m| m.usage_count).sum();
200        let avg_success_rate: f32 = metrics.values().map(|m| m.success_rate).sum::<f32>()
201            / total_rules as f32;
202        let avg_confidence: f32 =
203            metrics.values().map(|m| m.confidence).sum::<f32>() / total_rules as f32;
204
205        // Find most and least used rules
206        let most_used_rule = metrics
207            .values()
208            .max_by_key(|m| m.usage_count)
209            .map(|m| m.rule_id.clone());
210        let least_used_rule = metrics
211            .values()
212            .filter(|m| m.usage_count > 0)
213            .min_by_key(|m| m.usage_count)
214            .map(|m| m.rule_id.clone());
215
216        // Find top and bottom performing rules
217        let mut sorted_by_success: Vec<_> = metrics.values().collect();
218        sorted_by_success.sort_by(|a, b| b.success_rate.partial_cmp(&a.success_rate).unwrap());
219
220        let top_performing_rules: Vec<String> = sorted_by_success
221            .iter()
222            .take(5)
223            .map(|m| m.rule_id.clone())
224            .collect();
225
226        let bottom_performing_rules: Vec<String> = sorted_by_success
227            .iter()
228            .rev()
229            .take(5)
230            .map(|m| m.rule_id.clone())
231            .collect();
232
233        Ok(AnalyticsInsights {
234            total_rules,
235            total_applications,
236            avg_success_rate,
237            avg_confidence,
238            most_used_rule,
239            least_used_rule,
240            top_performing_rules,
241            bottom_performing_rules,
242        })
243    }
244
245    /// Clear all metrics
246    pub async fn clear_metrics(&self) -> Result<()> {
247        self.metrics.write().await.clear();
248        Ok(())
249    }
250
251    /// Get metrics for rules in a specific scope
252    pub async fn get_metrics_by_scope(
253        &self,
254        rules: &[Rule],
255        scope: RuleScope,
256    ) -> Result<Vec<RuleMetrics>> {
257        let metrics = self.metrics.read().await;
258        let scope_metrics: Vec<RuleMetrics> = rules
259            .iter()
260            .filter(|r| r.scope == scope)
261            .filter_map(|r| metrics.get(&r.id).cloned())
262            .collect();
263
264        Ok(scope_metrics)
265    }
266}
267
268impl Default for AnalyticsEngine {
269    fn default() -> Self {
270        Self::new()
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277
278    #[test]
279    fn test_rule_metrics_creation() {
280        let metrics = RuleMetrics::new("rule_1".to_string());
281        assert_eq!(metrics.rule_id, "rule_1");
282        assert_eq!(metrics.usage_count, 0);
283        assert_eq!(metrics.success_count, 0);
284        assert_eq!(metrics.failure_count, 0);
285        assert_eq!(metrics.success_rate, 0.0);
286        assert_eq!(metrics.confidence, 0.5);
287    }
288
289    #[test]
290    fn test_rule_metrics_record_success() {
291        let mut metrics = RuleMetrics::new("rule_1".to_string());
292        metrics.record_success(10.0);
293
294        assert_eq!(metrics.usage_count, 1);
295        assert_eq!(metrics.success_count, 1);
296        assert_eq!(metrics.failure_count, 0);
297        assert_eq!(metrics.success_rate, 1.0);
298        assert_eq!(metrics.avg_application_time_ms, 10.0);
299        assert!(metrics.first_applied.is_some());
300        assert!(metrics.last_applied.is_some());
301    }
302
303    #[test]
304    fn test_rule_metrics_record_failure() {
305        let mut metrics = RuleMetrics::new("rule_1".to_string());
306        metrics.record_failure(5.0);
307
308        assert_eq!(metrics.usage_count, 1);
309        assert_eq!(metrics.success_count, 0);
310        assert_eq!(metrics.failure_count, 1);
311        assert_eq!(metrics.success_rate, 0.0);
312    }
313
314    #[test]
315    fn test_rule_metrics_mixed_results() {
316        let mut metrics = RuleMetrics::new("rule_1".to_string());
317        metrics.record_success(10.0);
318        metrics.record_success(12.0);
319        metrics.record_failure(8.0);
320
321        assert_eq!(metrics.usage_count, 3);
322        assert_eq!(metrics.success_count, 2);
323        assert_eq!(metrics.failure_count, 1);
324        assert!((metrics.success_rate - 2.0 / 3.0).abs() < 0.01);
325    }
326
327    #[test]
328    fn test_rule_metrics_average_time() {
329        let mut metrics = RuleMetrics::new("rule_1".to_string());
330        metrics.record_success(10.0);
331        metrics.record_success(20.0);
332        metrics.record_success(30.0);
333
334        assert!((metrics.avg_application_time_ms - 20.0).abs() < 0.01);
335    }
336
337    #[tokio::test]
338    async fn test_analytics_engine_record_application() {
339        let engine = AnalyticsEngine::new();
340        engine
341            .record_application("rule_1".to_string(), true, 10.0)
342            .await
343            .unwrap();
344
345        let metrics = engine.get_rule_metrics("rule_1").await.unwrap();
346        assert!(metrics.is_some());
347        let metrics = metrics.unwrap();
348        assert_eq!(metrics.usage_count, 1);
349        assert_eq!(metrics.success_count, 1);
350    }
351
352    #[tokio::test]
353    async fn test_analytics_engine_get_all_metrics() {
354        let engine = AnalyticsEngine::new();
355        engine
356            .record_application("rule_1".to_string(), true, 10.0)
357            .await
358            .unwrap();
359        engine
360            .record_application("rule_2".to_string(), false, 5.0)
361            .await
362            .unwrap();
363
364        let all_metrics = engine.get_all_metrics().await.unwrap();
365        assert_eq!(all_metrics.len(), 2);
366    }
367
368    #[tokio::test]
369    async fn test_analytics_engine_update_confidence() {
370        let engine = AnalyticsEngine::new();
371        engine
372            .record_application("rule_1".to_string(), true, 10.0)
373            .await
374            .unwrap();
375
376        engine
377            .update_confidence("rule_1", 0.8)
378            .await
379            .unwrap();
380
381        let metrics = engine.get_rule_metrics("rule_1").await.unwrap().unwrap();
382        assert_eq!(metrics.confidence, 0.8);
383    }
384
385    #[tokio::test]
386    async fn test_analytics_engine_invalid_confidence() {
387        let engine = AnalyticsEngine::new();
388        engine
389            .record_application("rule_1".to_string(), true, 10.0)
390            .await
391            .unwrap();
392
393        let result = engine.update_confidence("rule_1", 1.5).await;
394        assert!(result.is_err());
395    }
396
397    #[tokio::test]
398    async fn test_analytics_engine_generate_insights() {
399        let engine = AnalyticsEngine::new();
400        engine
401            .record_application("rule_1".to_string(), true, 10.0)
402            .await
403            .unwrap();
404        engine
405            .record_application("rule_1".to_string(), true, 12.0)
406            .await
407            .unwrap();
408        engine
409            .record_application("rule_2".to_string(), false, 5.0)
410            .await
411            .unwrap();
412
413        let insights = engine.generate_insights().await.unwrap();
414        assert_eq!(insights.total_rules, 2);
415        assert_eq!(insights.total_applications, 3);
416        assert!(insights.most_used_rule.is_some());
417    }
418
419    #[tokio::test]
420    async fn test_analytics_engine_clear_metrics() {
421        let engine = AnalyticsEngine::new();
422        engine
423            .record_application("rule_1".to_string(), true, 10.0)
424            .await
425            .unwrap();
426
427        engine.clear_metrics().await.unwrap();
428
429        let all_metrics = engine.get_all_metrics().await.unwrap();
430        assert_eq!(all_metrics.len(), 0);
431    }
432
433    #[tokio::test]
434    async fn test_analytics_engine_empty_insights() {
435        let engine = AnalyticsEngine::new();
436        let insights = engine.generate_insights().await.unwrap();
437
438        assert_eq!(insights.total_rules, 0);
439        assert_eq!(insights.total_applications, 0);
440        assert_eq!(insights.avg_success_rate, 0.0);
441        assert_eq!(insights.avg_confidence, 0.0);
442        assert!(insights.most_used_rule.is_none());
443    }
444}