use std::collections::HashMap;
use std::time::{Duration, SystemTime};
use serde::{Deserialize, Serialize};
use super::regression_detector::RegressionDetectionConfig;
use super::baseline_manager::BaselineData;
use super::detection_results::StatisticalSignificance;
pub struct TrendAnalyzer {
config: RegressionDetectionConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrendAnalysis {
pub test_id: String,
pub implementation: String,
pub trend_direction: TrendDirection,
pub trend_strength: f64,
pub slope: f64,
pub r_squared: f64,
pub significance: StatisticalSignificance,
pub forecast: PerformanceForecast,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TrendDirection {
StronglyImproving,
Improving,
Stable,
Declining,
StronglyDeclining,
Volatile,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceForecast {
pub predicted_value: f64,
pub prediction_interval: (f64, f64),
pub confidence: f64,
pub time_horizon: Duration,
}
impl TrendAnalyzer {
pub fn new(config: RegressionDetectionConfig) -> Self {
Self { config }
}
pub fn analyze_trends(&self, baselines: &HashMap<String, BaselineData>) -> HashMap<String, TrendAnalysis> {
let mut trends = HashMap::new();
for (key, baseline) in baselines {
if baseline.measurements.len() < self.config.trend_window_size {
continue;
}
let trend = self.analyze_single_trend(baseline);
trends.insert(key.clone(), trend);
}
trends
}
fn analyze_single_trend(&self, baseline: &BaselineData) -> TrendAnalysis {
let recent_measurements: Vec<_> = baseline.measurements
.iter()
.rev()
.take(self.config.trend_window_size)
.collect();
let n = recent_measurements.len() as f64;
let x_values: Vec<f64> = (0..recent_measurements.len()).map(|i| i as f64).collect();
let y_values: Vec<f64> = recent_measurements.iter().map(|m| m.value).collect();
let x_mean = x_values.iter().sum::<f64>() / n;
let y_mean = y_values.iter().sum::<f64>() / n;
let numerator: f64 = x_values.iter().zip(&y_values)
.map(|(x, y)| (x - x_mean) * (y - y_mean))
.sum();
let denominator: f64 = x_values.iter()
.map(|x| (x - x_mean).powi(2))
.sum();
let slope = if denominator != 0.0 { numerator / denominator } else { 0.0 };
let y_pred: Vec<f64> = x_values.iter().map(|x| y_mean + slope * (x - x_mean)).collect();
let ss_tot: f64 = y_values.iter().map(|y| (y - y_mean).powi(2)).sum();
let ss_res: f64 = y_values.iter().zip(&y_pred).map(|(y, y_p)| (y - y_p).powi(2)).sum();
let r_squared = if ss_tot != 0.0 { 1.0 - (ss_res / ss_tot) } else { 0.0 };
let trend_direction = if slope.abs() < 0.1 {
TrendDirection::Stable
} else if slope > 2.0 {
TrendDirection::StronglyImproving
} else if slope > 0.5 {
TrendDirection::Improving
} else if slope < -2.0 {
TrendDirection::StronglyDeclining
} else if slope < -0.5 {
TrendDirection::Declining
} else {
TrendDirection::Volatile
};
let trend_strength = (slope.abs() * 10.0).min(100.0);
let predicted_value = y_mean + slope * n;
let prediction_error = (ss_res / (n - 2.0)).sqrt();
let prediction_interval = (
predicted_value - 1.96 * prediction_error,
predicted_value + 1.96 * prediction_error,
);
let forecast = PerformanceForecast {
predicted_value,
prediction_interval,
confidence: (r_squared * 100.0).min(95.0),
time_horizon: Duration::from_secs(86400), };
TrendAnalysis {
test_id: baseline.test_id.clone(),
implementation: baseline.implementation_id.clone(),
trend_direction,
trend_strength,
slope,
r_squared,
significance: StatisticalSignificance {
p_value: 0.05, is_significant: r_squared > 0.5,
test_method: "Linear regression".to_string(),
effect_size: slope,
},
forecast,
}
}
}