1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct RuleMetrics {
13 pub rule_id: String,
15 pub usage_count: u64,
17 pub success_count: u64,
19 pub failure_count: u64,
21 pub success_rate: f32,
23 pub confidence: f32,
25 pub first_applied: Option<DateTime<Utc>>,
27 pub last_applied: Option<DateTime<Utc>>,
29 pub avg_application_time_ms: f64,
31}
32
33impl RuleMetrics {
34 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 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 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 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 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct AnalyticsInsights {
96 pub total_rules: usize,
98 pub total_applications: u64,
100 pub avg_success_rate: f32,
102 pub avg_confidence: f32,
104 pub most_used_rule: Option<String>,
106 pub least_used_rule: Option<String>,
108 pub top_performing_rules: Vec<String>,
110 pub bottom_performing_rules: Vec<String>,
112}
113
114pub struct AnalyticsEngine {
116 metrics: Arc<RwLock<HashMap<String, RuleMetrics>>>,
118}
119
120impl AnalyticsEngine {
121 pub fn new() -> Self {
123 Self {
124 metrics: Arc::new(RwLock::new(HashMap::new())),
125 }
126 }
127
128 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 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 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 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 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 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 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 pub async fn clear_metrics(&self) -> Result<()> {
247 self.metrics.write().await.clear();
248 Ok(())
249 }
250
251 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}