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.map_or(true, |t| t <= chrono::Utc::now())
35 && test_config.end_time.map_or(true, |t| t >= chrono::Utc::now());
36
37 Self {
38 test_config,
39 variant_analytics,
40 total_requests,
41 start_time: None,
42 end_time: None,
43 is_active,
44 }
45 }
46
47 pub fn best_variant(&self) -> Option<&VariantAnalytics> {
49 self.variant_analytics.values().max_by(|a, b| {
50 a.success_rate()
51 .partial_cmp(&b.success_rate())
52 .unwrap_or(std::cmp::Ordering::Equal)
53 })
54 }
55
56 pub fn worst_variant(&self) -> Option<&VariantAnalytics> {
58 self.variant_analytics.values().min_by(|a, b| {
59 a.success_rate()
60 .partial_cmp(&b.success_rate())
61 .unwrap_or(std::cmp::Ordering::Equal)
62 })
63 }
64
65 pub fn statistical_significance(&self) -> f64 {
67 if self.variant_analytics.len() < 2 {
70 return 0.0;
71 }
72
73 let variants: Vec<&VariantAnalytics> = self.variant_analytics.values().collect();
74 if variants.len() < 2 {
75 return 0.0;
76 }
77
78 let success_rates: Vec<f64> = variants.iter().map(|v| v.success_rate()).collect();
80 let max_rate = success_rates.iter().fold(0.0f64, |a, &b| a.max(b));
81 let min_rate = success_rates.iter().fold(1.0f64, |a, &b| a.min(b));
82
83 (max_rate - min_rate) * 100.0
85 }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct VariantComparison {
91 pub variant_a_id: String,
93 pub variant_b_id: String,
95 pub success_rate_diff: f64,
97 pub response_time_diff_ms: f64,
99 pub error_rate_diff: f64,
101 pub request_count_diff: i64,
103}
104
105impl VariantComparison {
106 pub fn new(variant_a: &VariantAnalytics, variant_b: &VariantAnalytics) -> Self {
108 Self {
109 variant_a_id: variant_a.variant_id.clone(),
110 variant_b_id: variant_b.variant_id.clone(),
111 success_rate_diff: variant_a.success_rate() - variant_b.success_rate(),
112 response_time_diff_ms: variant_a.avg_response_time_ms - variant_b.avg_response_time_ms,
113 error_rate_diff: variant_a.error_rate() - variant_b.error_rate(),
114 request_count_diff: variant_a.request_count as i64 - variant_b.request_count as i64,
115 }
116 }
117}