mockforge_core/ab_testing/
analytics.rs

1//! Analytics for A/B testing
2//!
3//! This module provides analytics and reporting functionality for A/B tests.
4
5use crate::ab_testing::types::{ABTestConfig, VariantAnalytics};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// Summary report for an A/B test
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ABTestReport {
12    /// Test configuration
13    pub test_config: ABTestConfig,
14    /// Analytics for each variant
15    pub variant_analytics: HashMap<String, VariantAnalytics>,
16    /// Total requests across all variants
17    pub total_requests: u64,
18    /// Test start time
19    pub start_time: Option<chrono::DateTime<chrono::Utc>>,
20    /// Test end time (if ended)
21    pub end_time: Option<chrono::DateTime<chrono::Utc>>,
22    /// Whether the test is currently active
23    pub is_active: bool,
24}
25
26impl ABTestReport {
27    /// Create a new A/B test report
28    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    /// Get the best performing variant (highest success rate)
50    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    /// Get the worst performing variant (lowest success rate)
59    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    /// Calculate statistical significance (simplified - would need proper statistical test in production)
68    pub fn statistical_significance(&self) -> f64 {
69        // Simplified calculation - in production, use proper statistical tests
70        // like chi-square or t-test
71        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        // Simple comparison of success rates
81        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        // Difference between best and worst
86        (max_rate - min_rate) * 100.0
87    }
88}
89
90/// Comparison between two variants
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct VariantComparison {
93    /// First variant ID
94    pub variant_a_id: String,
95    /// Second variant ID
96    pub variant_b_id: String,
97    /// Success rate difference (A - B)
98    pub success_rate_diff: f64,
99    /// Response time difference in milliseconds (A - B)
100    pub response_time_diff_ms: f64,
101    /// Error rate difference (A - B)
102    pub error_rate_diff: f64,
103    /// Request count difference (A - B)
104    pub request_count_diff: i64,
105}
106
107impl VariantComparison {
108    /// Create a comparison between two variants
109    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}