mockforge_core/ab_testing/
analytics.rs1use crate::ab_testing::types::{ABTestConfig, VariantAnalytics};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ABTestReport {
12 pub test_config: ABTestConfig,
14 pub variant_analytics: HashMap<String, VariantAnalytics>,
16 pub total_requests: u64,
18 pub start_time: Option<chrono::DateTime<chrono::Utc>>,
20 pub end_time: Option<chrono::DateTime<chrono::Utc>>,
22 pub is_active: bool,
24}
25
26impl ABTestReport {
27 pub fn new(
29 test_config: ABTestConfig,
30 variant_analytics: HashMap<String, VariantAnalytics>,
31 ) -> Self {
32 let total_requests: u64 = variant_analytics.values().map(|a| a.request_count).sum();
33 let is_active = test_config.enabled
34 && (test_config.start_time.is_none()
35 || test_config.start_time.unwrap() <= chrono::Utc::now())
36 && (test_config.end_time.is_none()
37 || test_config.end_time.unwrap() >= chrono::Utc::now());
38
39 Self {
40 test_config,
41 variant_analytics,
42 total_requests,
43 start_time: None,
44 end_time: None,
45 is_active,
46 }
47 }
48
49 pub fn best_variant(&self) -> Option<&VariantAnalytics> {
51 self.variant_analytics.values().max_by(|a, b| {
52 a.success_rate()
53 .partial_cmp(&b.success_rate())
54 .unwrap_or(std::cmp::Ordering::Equal)
55 })
56 }
57
58 pub fn worst_variant(&self) -> Option<&VariantAnalytics> {
60 self.variant_analytics.values().min_by(|a, b| {
61 a.success_rate()
62 .partial_cmp(&b.success_rate())
63 .unwrap_or(std::cmp::Ordering::Equal)
64 })
65 }
66
67 pub fn statistical_significance(&self) -> f64 {
69 if self.variant_analytics.len() < 2 {
72 return 0.0;
73 }
74
75 let variants: Vec<&VariantAnalytics> = self.variant_analytics.values().collect();
76 if variants.len() < 2 {
77 return 0.0;
78 }
79
80 let success_rates: Vec<f64> = variants.iter().map(|v| v.success_rate()).collect();
82 let max_rate = success_rates.iter().fold(0.0f64, |a, &b| a.max(b));
83 let min_rate = success_rates.iter().fold(1.0f64, |a, &b| a.min(b));
84
85 (max_rate - min_rate) * 100.0
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct VariantComparison {
93 pub variant_a_id: String,
95 pub variant_b_id: String,
97 pub success_rate_diff: f64,
99 pub response_time_diff_ms: f64,
101 pub error_rate_diff: f64,
103 pub request_count_diff: i64,
105}
106
107impl VariantComparison {
108 pub fn new(variant_a: &VariantAnalytics, variant_b: &VariantAnalytics) -> Self {
110 Self {
111 variant_a_id: variant_a.variant_id.clone(),
112 variant_b_id: variant_b.variant_id.clone(),
113 success_rate_diff: variant_a.success_rate() - variant_b.success_rate(),
114 response_time_diff_ms: variant_a.avg_response_time_ms - variant_b.avg_response_time_ms,
115 error_rate_diff: variant_a.error_rate() - variant_b.error_rate(),
116 request_count_diff: variant_a.request_count as i64 - variant_b.request_count as i64,
117 }
118 }
119}