use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RobustnessMetrics {
pub fault_count: u64,
pub recovery_count: u64,
pub recovery_rate: f64,
pub consistency_score: Option<f64>,
}
impl RobustnessMetrics {
pub fn from_fault_data(fault_count: u64, recovery_count: u64) -> Self {
let recovery_rate = if fault_count > 0 {
recovery_count as f64 / fault_count as f64
} else {
1.0 };
Self {
fault_count,
recovery_count,
recovery_rate,
consistency_score: None,
}
}
pub fn calculate_consistency(values: &[f64]) -> Option<f64> {
if values.len() < 2 {
return None;
}
let n = values.len() as f64;
let mean: f64 = values.iter().sum::<f64>() / n;
if mean.abs() < 1e-9 {
return None; }
let variance: f64 = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (n - 1.0);
let std_dev = variance.sqrt();
let cv = std_dev / mean.abs();
Some((1.0 - cv).clamp(0.0, 1.0))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_recovery_rate() {
let metrics = RobustnessMetrics::from_fault_data(10, 8);
assert!((metrics.recovery_rate - 0.8).abs() < 0.01);
}
#[test]
fn test_no_faults() {
let metrics = RobustnessMetrics::from_fault_data(0, 0);
assert!((metrics.recovery_rate - 1.0).abs() < 0.01);
}
#[test]
fn test_consistency_perfect() {
let values = vec![0.8, 0.8, 0.8, 0.8];
let score = RobustnessMetrics::calculate_consistency(&values);
assert!(score.unwrap() > 0.99);
}
#[test]
fn test_consistency_variable() {
let values = vec![0.5, 0.7, 0.9, 1.1];
let score = RobustnessMetrics::calculate_consistency(&values);
assert!(score.unwrap() < 0.9);
}
#[test]
fn test_consistency_insufficient_data() {
let values = vec![0.8];
let score = RobustnessMetrics::calculate_consistency(&values);
assert!(score.is_none());
}
}