use super::LeaderboardEntry;
use anyhow::Result;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LeaderboardStats {
pub total_entries: usize,
pub unique_models: usize,
pub unique_submitters: usize,
pub entries_by_category: HashMap<String, usize>,
pub average_metrics: AverageMetrics,
pub best_metrics: BestMetrics,
pub entries_by_precision: HashMap<String, usize>,
pub entries_by_platform: HashMap<String, usize>,
pub date_range: DateRange,
pub top_submitters: Vec<(String, usize)>,
pub top_benchmarks: Vec<(String, usize)>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AverageMetrics {
pub latency_ms: f64,
pub throughput: Option<f64>,
pub tokens_per_second: Option<f64>,
pub memory_mb: Option<f64>,
pub gpu_utilization: Option<f64>,
pub accuracy: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BestMetrics {
pub lowest_latency: MetricRecord,
pub highest_throughput: Option<MetricRecord>,
pub highest_tokens_per_second: Option<MetricRecord>,
pub lowest_memory: Option<MetricRecord>,
pub highest_accuracy: Option<MetricRecord>,
pub lowest_energy: Option<MetricRecord>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricRecord {
pub value: f64,
pub model_name: String,
pub model_version: String,
pub entry_id: uuid::Uuid,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DateRange {
pub start: DateTime<Utc>,
pub end: DateTime<Utc>,
}
impl LeaderboardStats {
pub fn from_entries(entries: &[LeaderboardEntry]) -> Result<Self> {
if entries.is_empty() {
return Ok(Self::empty());
}
let total_entries = entries.len();
let unique_models: std::collections::HashSet<_> =
entries.iter().map(|e| &e.model_name).collect();
let unique_submitters: std::collections::HashSet<_> =
entries.iter().map(|e| &e.submitter.name).collect();
let mut entries_by_category = HashMap::new();
for entry in entries {
*entries_by_category.entry(entry.category.to_string()).or_insert(0) += 1;
}
let mut entries_by_precision = HashMap::new();
for entry in entries {
let precision = format!("{:?}", entry.software.precision);
*entries_by_precision.entry(precision).or_insert(0) += 1;
}
let mut entries_by_platform = HashMap::new();
for entry in entries {
*entries_by_platform.entry(entry.hardware.platform.clone()).or_insert(0) += 1;
}
let average_metrics = Self::calculate_averages(entries);
let best_metrics = Self::find_best_metrics(entries);
let dates: Vec<_> = entries.iter().map(|e| e.timestamp).collect();
let date_range = DateRange {
start: *dates.iter().min().expect("Min failed"),
end: *dates.iter().max().expect("Max failed"),
};
let mut submitter_counts: HashMap<String, usize> = HashMap::new();
for entry in entries {
*submitter_counts.entry(entry.submitter.name.clone()).or_insert(0) += 1;
}
let mut top_submitters: Vec<_> = submitter_counts.into_iter().collect();
top_submitters.sort_by_key(|item| std::cmp::Reverse(item.1));
top_submitters.truncate(10);
let mut benchmark_counts: HashMap<String, usize> = HashMap::new();
for entry in entries {
*benchmark_counts.entry(entry.benchmark_name.clone()).or_insert(0) += 1;
}
let mut top_benchmarks: Vec<_> = benchmark_counts.into_iter().collect();
top_benchmarks.sort_by_key(|item| std::cmp::Reverse(item.1));
top_benchmarks.truncate(10);
Ok(Self {
total_entries,
unique_models: unique_models.len(),
unique_submitters: unique_submitters.len(),
entries_by_category,
average_metrics,
best_metrics,
entries_by_precision,
entries_by_platform,
date_range,
top_submitters,
top_benchmarks,
})
}
fn empty() -> Self {
Self {
total_entries: 0,
unique_models: 0,
unique_submitters: 0,
entries_by_category: HashMap::new(),
average_metrics: AverageMetrics {
latency_ms: 0.0,
throughput: None,
tokens_per_second: None,
memory_mb: None,
gpu_utilization: None,
accuracy: None,
},
best_metrics: BestMetrics {
lowest_latency: MetricRecord {
value: f64::MAX,
model_name: String::new(),
model_version: String::new(),
entry_id: uuid::Uuid::nil(),
timestamp: Utc::now(),
},
highest_throughput: None,
highest_tokens_per_second: None,
lowest_memory: None,
highest_accuracy: None,
lowest_energy: None,
},
entries_by_precision: HashMap::new(),
entries_by_platform: HashMap::new(),
date_range: DateRange {
start: Utc::now(),
end: Utc::now(),
},
top_submitters: Vec::new(),
top_benchmarks: Vec::new(),
}
}
fn calculate_averages(entries: &[LeaderboardEntry]) -> AverageMetrics {
let mut latency_sum = 0.0;
let mut throughput_sum = 0.0;
let mut throughput_count = 0;
let mut tps_sum = 0.0;
let mut tps_count = 0;
let mut memory_sum = 0.0;
let mut memory_count = 0;
let mut gpu_sum = 0.0;
let mut gpu_count = 0;
let mut accuracy_sum = 0.0;
let mut accuracy_count = 0;
for entry in entries {
latency_sum += entry.metrics.latency_ms;
if let Some(throughput) = entry.metrics.throughput {
throughput_sum += throughput;
throughput_count += 1;
}
if let Some(tps) = entry.metrics.tokens_per_second {
tps_sum += tps;
tps_count += 1;
}
if let Some(memory) = entry.metrics.memory_mb {
memory_sum += memory;
memory_count += 1;
}
if let Some(gpu) = entry.metrics.gpu_utilization {
gpu_sum += gpu;
gpu_count += 1;
}
if let Some(accuracy) = entry.metrics.accuracy {
accuracy_sum += accuracy;
accuracy_count += 1;
}
}
AverageMetrics {
latency_ms: latency_sum / entries.len() as f64,
throughput: if throughput_count > 0 {
Some(throughput_sum / throughput_count as f64)
} else {
None
},
tokens_per_second: if tps_count > 0 { Some(tps_sum / tps_count as f64) } else { None },
memory_mb: if memory_count > 0 { Some(memory_sum / memory_count as f64) } else { None },
gpu_utilization: if gpu_count > 0 { Some(gpu_sum / gpu_count as f64) } else { None },
accuracy: if accuracy_count > 0 {
Some(accuracy_sum / accuracy_count as f64)
} else {
None
},
}
}
fn find_best_metrics(entries: &[LeaderboardEntry]) -> BestMetrics {
let mut best = BestMetrics {
lowest_latency: MetricRecord {
value: f64::MAX,
model_name: String::new(),
model_version: String::new(),
entry_id: uuid::Uuid::nil(),
timestamp: Utc::now(),
},
highest_throughput: None,
highest_tokens_per_second: None,
lowest_memory: None,
highest_accuracy: None,
lowest_energy: None,
};
for entry in entries {
if entry.metrics.latency_ms < best.lowest_latency.value {
best.lowest_latency = MetricRecord {
value: entry.metrics.latency_ms,
model_name: entry.model_name.clone(),
model_version: entry.model_version.clone(),
entry_id: entry.id,
timestamp: entry.timestamp,
};
}
if let Some(throughput) = entry.metrics.throughput {
if best.highest_throughput.is_none()
|| throughput
> best
.highest_throughput
.as_ref()
.expect("highest_throughput must be initialized")
.value
{
best.highest_throughput = Some(MetricRecord {
value: throughput,
model_name: entry.model_name.clone(),
model_version: entry.model_version.clone(),
entry_id: entry.id,
timestamp: entry.timestamp,
});
}
}
if let Some(tps) = entry.metrics.tokens_per_second {
if best.highest_tokens_per_second.is_none()
|| tps
> best
.highest_tokens_per_second
.as_ref()
.expect("highest_tokens_per_second must be initialized")
.value
{
best.highest_tokens_per_second = Some(MetricRecord {
value: tps,
model_name: entry.model_name.clone(),
model_version: entry.model_version.clone(),
entry_id: entry.id,
timestamp: entry.timestamp,
});
}
}
if let Some(memory) = entry.metrics.memory_mb {
if best.lowest_memory.is_none()
|| memory
< best
.lowest_memory
.as_ref()
.expect("lowest_memory must be initialized")
.value
{
best.lowest_memory = Some(MetricRecord {
value: memory,
model_name: entry.model_name.clone(),
model_version: entry.model_version.clone(),
entry_id: entry.id,
timestamp: entry.timestamp,
});
}
}
if let Some(accuracy) = entry.metrics.accuracy {
if best.highest_accuracy.is_none()
|| accuracy
> best
.highest_accuracy
.as_ref()
.expect("highest_accuracy must be initialized")
.value
{
best.highest_accuracy = Some(MetricRecord {
value: accuracy,
model_name: entry.model_name.clone(),
model_version: entry.model_version.clone(),
entry_id: entry.id,
timestamp: entry.timestamp,
});
}
}
if let Some(energy) = entry.metrics.energy_watts {
if best.lowest_energy.is_none()
|| energy
< best
.lowest_energy
.as_ref()
.expect("lowest_energy must be initialized")
.value
{
best.lowest_energy = Some(MetricRecord {
value: energy,
model_name: entry.model_name.clone(),
model_version: entry.model_version.clone(),
entry_id: entry.id,
timestamp: entry.timestamp,
});
}
}
}
best
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrendAnalysis {
pub model_name: String,
pub metric_name: String,
pub period_days: usize,
pub data_points: Vec<TrendDataPoint>,
pub trend: PerformanceTrend,
pub daily_change_percent: f64,
pub r_squared: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrendDataPoint {
pub timestamp: DateTime<Utc>,
pub value: f64,
pub entry_id: uuid::Uuid,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum PerformanceTrend {
Improving,
Degrading,
Stable,
Insufficient,
}
impl TrendAnalysis {
pub fn analyze(entries: &[LeaderboardEntry], metric_name: &str) -> Result<Self> {
if entries.is_empty() {
anyhow::bail!("No entries provided for trend analysis");
}
let model_name = entries[0].model_name.clone();
let mut data_points = Vec::new();
for entry in entries {
let value = match metric_name {
"latency" => Some(entry.metrics.latency_ms),
"throughput" => entry.metrics.throughput,
"tokens_per_second" => entry.metrics.tokens_per_second,
"memory" => entry.metrics.memory_mb,
"gpu_utilization" => entry.metrics.gpu_utilization,
"accuracy" => entry.metrics.accuracy,
"energy" => entry.metrics.energy_watts,
_ => entry.metrics.custom_metrics.get(metric_name).copied(),
};
if let Some(val) = value {
data_points.push(TrendDataPoint {
timestamp: entry.timestamp,
value: val,
entry_id: entry.id,
});
}
}
if data_points.len() < 2 {
return Ok(Self {
model_name,
metric_name: metric_name.to_string(),
period_days: 0,
data_points,
trend: PerformanceTrend::Insufficient,
daily_change_percent: 0.0,
r_squared: 0.0,
});
}
data_points.sort_by_key(|p| p.timestamp);
let start = data_points
.first()
.expect("data_points must have at least one element")
.timestamp;
let end = data_points
.last()
.expect("data_points must have at least one element")
.timestamp;
let period_days = (end - start).num_days() as usize;
let (slope, r_squared) = Self::linear_regression(&data_points);
let trend = if r_squared < 0.3 {
PerformanceTrend::Stable
} else if slope > 0.01 {
if matches!(metric_name, "throughput" | "tokens_per_second" | "accuracy") {
PerformanceTrend::Improving
} else {
PerformanceTrend::Degrading
}
} else if slope < -0.01 {
if matches!(metric_name, "latency" | "memory" | "energy") {
PerformanceTrend::Improving
} else {
PerformanceTrend::Degrading
}
} else {
PerformanceTrend::Stable
};
let first_value =
data_points.first().expect("data_points must have at least one element").value;
let last_value =
data_points.last().expect("data_points must have at least one element").value;
let total_change_percent = ((last_value - first_value) / first_value) * 100.0;
let daily_change_percent =
if period_days > 0 { total_change_percent / period_days as f64 } else { 0.0 };
Ok(Self {
model_name,
metric_name: metric_name.to_string(),
period_days,
data_points,
trend,
daily_change_percent,
r_squared,
})
}
fn linear_regression(points: &[TrendDataPoint]) -> (f64, f64) {
let n = points.len() as f64;
if n < 2.0 {
return (0.0, 0.0);
}
let first_timestamp = points[0].timestamp;
let x_values: Vec<f64> = points
.iter()
.map(|p| (p.timestamp - first_timestamp).num_seconds() as f64 / 86400.0)
.collect();
let y_values: Vec<f64> = points.iter().map(|p| p.value).collect();
let x_mean = x_values.iter().sum::<f64>() / n;
let y_mean = y_values.iter().sum::<f64>() / n;
let mut numerator = 0.0;
let mut denominator = 0.0;
for i in 0..points.len() {
numerator += (x_values[i] - x_mean) * (y_values[i] - y_mean);
denominator += (x_values[i] - x_mean).powi(2);
}
if denominator == 0.0 {
return (0.0, 0.0);
}
let slope = numerator / denominator;
let mut ss_tot = 0.0;
let mut ss_res = 0.0;
for i in 0..points.len() {
let y_pred = slope * (x_values[i] - x_mean) + y_mean;
ss_tot += (y_values[i] - y_mean).powi(2);
ss_res += (y_values[i] - y_pred).powi(2);
}
let r_squared = if ss_tot == 0.0 { 0.0 } else { 1.0 - (ss_res / ss_tot) };
(slope, r_squared)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::leaderboard::*;
use chrono::Duration;
use std::collections::HashMap;
fn create_test_entries() -> Vec<LeaderboardEntry> {
vec![
LeaderboardEntry {
id: uuid::Uuid::new_v4(),
timestamp: Utc::now() - Duration::days(7),
model_name: "model1".to_string(),
model_version: "1.0".to_string(),
benchmark_name: "benchmark1".to_string(),
category: LeaderboardCategory::Inference,
hardware: HardwareInfo {
cpu: "Intel Xeon".to_string(),
cpu_cores: 32,
gpu: Some("NVIDIA A100".to_string()),
gpu_count: Some(1),
memory_gb: 256.0,
accelerator: Some(AcceleratorType::CUDA),
platform: "x86_64".to_string(),
},
software: SoftwareInfo {
framework_version: "0.1.0".to_string(),
rust_version: "1.75".to_string(),
os: "Linux".to_string(),
optimization_level: OptimizationLevel::O3,
precision: Precision::FP16,
quantization: None,
compiler_flags: vec![],
},
metrics: PerformanceMetrics {
latency_ms: 15.0,
latency_percentiles: LatencyPercentiles {
p50: 14.0,
p90: 18.0,
p95: 20.0,
p99: 25.0,
p999: 35.0,
},
throughput: Some(66.7),
tokens_per_second: None,
memory_mb: Some(1024.0),
peak_memory_mb: Some(1536.0),
gpu_utilization: Some(85.0),
accuracy: Some(0.90),
energy_watts: None,
custom_metrics: HashMap::new(),
},
metadata: HashMap::new(),
validated: true,
submitter: SubmitterInfo {
name: "User1".to_string(),
organization: None,
email: None,
github: None,
},
tags: vec![],
},
LeaderboardEntry {
id: uuid::Uuid::new_v4(),
timestamp: Utc::now(),
model_name: "model1".to_string(),
model_version: "1.1".to_string(),
benchmark_name: "benchmark1".to_string(),
category: LeaderboardCategory::Inference,
hardware: HardwareInfo {
cpu: "Intel Xeon".to_string(),
cpu_cores: 32,
gpu: Some("NVIDIA A100".to_string()),
gpu_count: Some(1),
memory_gb: 256.0,
accelerator: Some(AcceleratorType::CUDA),
platform: "x86_64".to_string(),
},
software: SoftwareInfo {
framework_version: "0.1.0".to_string(),
rust_version: "1.75".to_string(),
os: "Linux".to_string(),
optimization_level: OptimizationLevel::O3,
precision: Precision::FP16,
quantization: None,
compiler_flags: vec![],
},
metrics: PerformanceMetrics {
latency_ms: 10.0,
latency_percentiles: LatencyPercentiles {
p50: 9.0,
p90: 12.0,
p95: 14.0,
p99: 18.0,
p999: 25.0,
},
throughput: Some(100.0),
tokens_per_second: None,
memory_mb: Some(1024.0),
peak_memory_mb: Some(1536.0),
gpu_utilization: Some(90.0),
accuracy: Some(0.95),
energy_watts: None,
custom_metrics: HashMap::new(),
},
metadata: HashMap::new(),
validated: true,
submitter: SubmitterInfo {
name: "User1".to_string(),
organization: None,
email: None,
github: None,
},
tags: vec![],
},
]
}
#[test]
fn test_leaderboard_stats() {
let entries = create_test_entries();
let stats = LeaderboardStats::from_entries(&entries).expect("operation failed in test");
assert_eq!(stats.total_entries, 2);
assert_eq!(stats.unique_models, 1);
assert_eq!(stats.unique_submitters, 1);
assert_eq!(stats.entries_by_category["Inference"], 2);
assert_eq!(stats.average_metrics.latency_ms, 12.5);
assert_eq!(stats.best_metrics.lowest_latency.value, 10.0);
}
#[test]
fn test_trend_analysis() {
let entries = create_test_entries();
let trend = TrendAnalysis::analyze(&entries, "latency").expect("operation failed in test");
assert_eq!(trend.model_name, "model1");
assert_eq!(trend.metric_name, "latency");
assert_eq!(trend.data_points.len(), 2);
match trend.trend {
PerformanceTrend::Improving => {}, _ => panic!("Expected improving trend for latency"),
}
}
#[test]
fn test_empty_entries_stats() {
let entries: Vec<LeaderboardEntry> = Vec::new();
let stats = LeaderboardStats::from_entries(&entries).expect("operation failed in test");
assert_eq!(stats.total_entries, 0);
assert_eq!(stats.unique_models, 0);
assert_eq!(stats.unique_submitters, 0);
}
#[test]
fn test_stats_unique_submitters() {
let entries = create_test_entries();
let stats = LeaderboardStats::from_entries(&entries).expect("operation failed in test");
assert_eq!(stats.unique_submitters, 1);
}
#[test]
fn test_stats_entries_by_precision() {
let entries = create_test_entries();
let stats = LeaderboardStats::from_entries(&entries).expect("operation failed in test");
assert!(stats.entries_by_precision.contains_key("FP16"));
assert_eq!(stats.entries_by_precision["FP16"], 2);
}
#[test]
fn test_stats_entries_by_platform() {
let entries = create_test_entries();
let stats = LeaderboardStats::from_entries(&entries).expect("operation failed in test");
assert!(stats.entries_by_platform.contains_key("x86_64"));
assert_eq!(stats.entries_by_platform["x86_64"], 2);
}
#[test]
fn test_stats_top_submitters() {
let entries = create_test_entries();
let stats = LeaderboardStats::from_entries(&entries).expect("operation failed in test");
assert!(!stats.top_submitters.is_empty());
assert_eq!(stats.top_submitters[0].0, "User1");
assert_eq!(stats.top_submitters[0].1, 2);
}
#[test]
fn test_stats_top_benchmarks() {
let entries = create_test_entries();
let stats = LeaderboardStats::from_entries(&entries).expect("operation failed in test");
assert!(!stats.top_benchmarks.is_empty());
assert_eq!(stats.top_benchmarks[0].0, "benchmark1");
}
#[test]
fn test_stats_date_range() {
let entries = create_test_entries();
let stats = LeaderboardStats::from_entries(&entries).expect("operation failed in test");
assert!(stats.date_range.start < stats.date_range.end);
}
#[test]
fn test_stats_average_latency() {
let entries = create_test_entries();
let stats = LeaderboardStats::from_entries(&entries).expect("operation failed in test");
assert!((stats.average_metrics.latency_ms - 12.5).abs() < 1e-6);
}
#[test]
fn test_stats_average_throughput() {
let entries = create_test_entries();
let stats = LeaderboardStats::from_entries(&entries).expect("operation failed in test");
assert!(stats.average_metrics.throughput.is_some());
let avg = stats.average_metrics.throughput.expect("throughput should exist");
assert!((avg - 83.35).abs() < 1e-1);
}
#[test]
fn test_stats_average_accuracy() {
let entries = create_test_entries();
let stats = LeaderboardStats::from_entries(&entries).expect("operation failed in test");
assert!(stats.average_metrics.accuracy.is_some());
let avg = stats.average_metrics.accuracy.expect("accuracy should exist");
assert!((avg - 0.925).abs() < 1e-6);
}
#[test]
fn test_stats_best_lowest_latency() {
let entries = create_test_entries();
let stats = LeaderboardStats::from_entries(&entries).expect("operation failed in test");
assert!((stats.best_metrics.lowest_latency.value - 10.0).abs() < 1e-6);
}
#[test]
fn test_stats_best_highest_throughput() {
let entries = create_test_entries();
let stats = LeaderboardStats::from_entries(&entries).expect("operation failed in test");
assert!(stats.best_metrics.highest_throughput.is_some());
let best = stats.best_metrics.highest_throughput.as_ref().expect("should exist");
assert!((best.value - 100.0).abs() < 1e-6);
}
#[test]
fn test_stats_best_highest_accuracy() {
let entries = create_test_entries();
let stats = LeaderboardStats::from_entries(&entries).expect("operation failed in test");
assert!(stats.best_metrics.highest_accuracy.is_some());
let best = stats.best_metrics.highest_accuracy.as_ref().expect("should exist");
assert!((best.value - 0.95).abs() < 1e-6);
}
#[test]
fn test_stats_best_lowest_memory() {
let entries = create_test_entries();
let stats = LeaderboardStats::from_entries(&entries).expect("operation failed in test");
assert!(stats.best_metrics.lowest_memory.is_some());
let best = stats.best_metrics.lowest_memory.as_ref().expect("should exist");
assert!((best.value - 1024.0).abs() < 1e-6);
}
#[test]
fn test_trend_analysis_empty_entries() {
let entries: Vec<LeaderboardEntry> = Vec::new();
let result = TrendAnalysis::analyze(&entries, "latency");
assert!(result.is_err());
}
#[test]
fn test_trend_analysis_throughput() {
let entries = create_test_entries();
let trend =
TrendAnalysis::analyze(&entries, "throughput").expect("operation failed in test");
assert_eq!(trend.data_points.len(), 2);
}
#[test]
fn test_trend_analysis_accuracy() {
let entries = create_test_entries();
let trend = TrendAnalysis::analyze(&entries, "accuracy").expect("operation failed in test");
assert_eq!(trend.data_points.len(), 2);
}
#[test]
fn test_trend_analysis_unknown_metric() {
let entries = create_test_entries();
let trend = TrendAnalysis::analyze(&entries, "nonexistent_metric")
.expect("operation failed in test");
assert!(
trend.data_points.is_empty() || matches!(trend.trend, PerformanceTrend::Insufficient)
);
}
#[test]
fn test_trend_analysis_period_days() {
let entries = create_test_entries();
let trend = TrendAnalysis::analyze(&entries, "latency").expect("operation failed in test");
assert!(trend.period_days >= 6); }
#[test]
fn test_trend_analysis_daily_change() {
let entries = create_test_entries();
let trend = TrendAnalysis::analyze(&entries, "latency").expect("operation failed in test");
assert!(trend.daily_change_percent < 0.0);
}
#[test]
fn test_average_metrics_clone() {
let avg = AverageMetrics {
latency_ms: 10.0,
throughput: Some(100.0),
tokens_per_second: None,
memory_mb: Some(512.0),
gpu_utilization: None,
accuracy: Some(0.95),
};
let cloned = avg.clone();
assert!((cloned.latency_ms - 10.0).abs() < 1e-6);
assert_eq!(cloned.throughput, Some(100.0));
}
#[test]
fn test_metric_record_clone() {
let record = MetricRecord {
value: 42.0,
model_name: "test_model".to_string(),
model_version: "1.0".to_string(),
entry_id: uuid::Uuid::nil(),
timestamp: Utc::now(),
};
let cloned = record.clone();
assert!((cloned.value - 42.0).abs() < 1e-6);
assert_eq!(cloned.model_name, "test_model");
}
#[test]
fn test_performance_trend_variants() {
let _improving = PerformanceTrend::Improving;
let _degrading = PerformanceTrend::Degrading;
let _stable = PerformanceTrend::Stable;
let _insufficient = PerformanceTrend::Insufficient;
}
#[test]
fn test_performance_trend_clone() {
let trend = PerformanceTrend::Improving;
let cloned = trend;
assert!(matches!(cloned, PerformanceTrend::Improving));
}
#[test]
fn test_date_range_clone() {
let now = Utc::now();
let range = DateRange {
start: now - Duration::days(7),
end: now,
};
let cloned = range.clone();
assert_eq!(cloned.start, range.start);
assert_eq!(cloned.end, range.end);
}
#[test]
fn test_trend_data_point_clone() {
let point = TrendDataPoint {
timestamp: Utc::now(),
value: 42.0,
entry_id: uuid::Uuid::nil(),
};
let cloned = point.clone();
assert!((cloned.value - 42.0).abs() < 1e-6);
}
#[test]
fn test_linear_regression_single_point() {
let points = vec![TrendDataPoint {
timestamp: Utc::now(),
value: 10.0,
entry_id: uuid::Uuid::nil(),
}];
let (slope, r_squared) = TrendAnalysis::linear_regression(&points);
assert!((slope).abs() < 1e-6);
assert!((r_squared).abs() < 1e-6);
}
}