pub mod ml;
pub mod rules;
pub mod statistical;
pub mod trend;
use crate::error::Result;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Anomaly {
pub timestamp: DateTime<Utc>,
pub metric_name: String,
pub observed_value: f64,
pub expected_value: f64,
pub score: f64,
pub severity: AnomalySeverity,
pub anomaly_type: AnomalyType,
pub description: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AnomalySeverity {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AnomalyType {
Spike,
Drop,
UpwardTrend,
DownwardTrend,
Pattern,
MissingData,
}
pub trait AnomalyDetector: Send + Sync {
fn detect(&self, data: &[DataPoint]) -> Result<Vec<Anomaly>>;
fn update_baseline(&mut self, data: &[DataPoint]) -> Result<()>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataPoint {
pub timestamp: DateTime<Utc>,
pub value: f64,
}
impl DataPoint {
pub fn new(timestamp: DateTime<Utc>, value: f64) -> Self {
Self { timestamp, value }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Baseline {
pub mean: f64,
pub std_dev: f64,
pub min: f64,
pub max: f64,
pub count: usize,
}
impl Baseline {
pub fn from_data(data: &[DataPoint]) -> Result<Self> {
if data.is_empty() {
return Err(crate::error::ObservabilityError::AnomalyDetectionError(
"Cannot calculate baseline from empty data".to_string(),
));
}
let values: Vec<f64> = data.iter().map(|d| d.value).collect();
let count = values.len();
let sum: f64 = values.iter().sum();
let mean = sum / count as f64;
let variance: f64 = values
.iter()
.map(|v| {
let diff = v - mean;
diff * diff
})
.sum::<f64>()
/ count as f64;
let std_dev = variance.sqrt();
let min = values.iter().cloned().fold(f64::INFINITY, f64::min);
let max = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
Ok(Self {
mean,
std_dev,
min,
max,
count,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_baseline_calculation() {
let data = vec![
DataPoint::new(Utc::now(), 10.0),
DataPoint::new(Utc::now(), 20.0),
DataPoint::new(Utc::now(), 30.0),
];
let baseline = Baseline::from_data(&data).expect("Failed to calculate baseline");
assert_eq!(baseline.mean, 20.0);
assert_eq!(baseline.min, 10.0);
assert_eq!(baseline.max, 30.0);
}
}