use crate::alert_storage::StoredAlert;
use crate::alert_analyzer::AlertPattern;
use smartcore::ensemble::random_forest_classifier::RandomForestClassifier;
use smartcore::linalg::basic::matrix::DenseMatrix;
use std::collections::HashMap;
use smartcore::cluster::dbscan::{DBSCAN, DBSCANParameters};
use smartcore::metrics::distance::euclidian::Euclidian;
use chrono::{DateTime, Utc, Duration, Timelike, Datelike};
use std::collections::HashSet;
pub struct AlertMLAnalyzer {
model: Option<RandomForestClassifier<f64, i32, DenseMatrix<f64>, Vec<i32>>>,
#[allow(dead_code)]
feature_names: Vec<String>,
#[allow(dead_code)]
pattern_history: HashMap<String, Vec<AlertPattern>>,
}
impl AlertMLAnalyzer {
pub fn new() -> Self {
Self {
model: None,
feature_names: vec![
"hour_of_day".to_string(),
"day_of_week".to_string(),
"severity_level".to_string(),
"metric_type".to_string(),
"threshold_ratio".to_string(),
],
pattern_history: HashMap::new(),
}
}
pub fn train(&mut self, alerts: &[StoredAlert], patterns: &[AlertPattern]) {
let (features, labels) = self.prepare_training_data(alerts, patterns);
if features.is_empty() {
return;
}
let x = DenseMatrix::from_2d_vec(&features);
let y = labels;
let model = RandomForestClassifier::fit(
&x, &y,
Default::default()
).unwrap();
self.model = Some(model);
}
fn prepare_training_data(&self, alerts: &[StoredAlert], patterns: &[AlertPattern])
-> (Vec<Vec<f64>>, Vec<i32>)
{
let mut features = Vec::new();
let mut labels = Vec::new();
for alert in alerts {
features.push(self.extract_features(alert));
let is_pattern = patterns.iter().any(|p| {
p.affected_metrics.contains(&alert.metric_name)
});
labels.push(if is_pattern { 1 } else { 0 });
}
(features, labels)
}
fn extract_features(&self, alert: &StoredAlert) -> Vec<f64> {
vec![
alert.created_at.hour() as f64,
alert.created_at.weekday().num_days_from_monday() as f64,
self.severity_to_number(&alert.severity),
self.hash_metric_type(&alert.metric_name),
alert.current_value / alert.threshold,
]
}
fn severity_to_number(&self, severity: &str) -> f64 {
match severity {
"Critical" => 3.0,
"Warning" => 2.0,
"Info" => 1.0,
_ => 0.0,
}
}
fn hash_metric_type(&self, metric: &str) -> f64 {
let hash = metric.chars().fold(0u64, |acc, c| {
acc.wrapping_add(c as u64)
});
(hash % 100) as f64 / 100.0
}
pub fn analyze_patterns(&mut self, alerts: &[StoredAlert]) -> Vec<AlertCluster> {
let features = alerts.iter()
.map(|alert| self.extract_features(alert))
.collect::<Vec<_>>();
if features.is_empty() {
return Vec::new();
}
let features_matrix = DenseMatrix::from_2d_vec(&features);
let params = DBSCANParameters::default()
.with_eps(0.3)
.with_min_samples(3)
.with_distance(Euclidian::default());
let dbscan: DBSCAN<f64, i32, DenseMatrix<f64>, Vec<i32>, Euclidian<f64>> =
DBSCAN::fit(&features_matrix, params).unwrap();
let labels = dbscan.predict(&features_matrix).unwrap().to_vec();
self.create_alert_clusters(alerts, &labels)
}
fn create_alert_clusters(&self, alerts: &[StoredAlert], labels: &[i32]) -> Vec<AlertCluster> {
let mut clusters = HashMap::new();
for (idx, &label) in labels.iter().enumerate() {
if label >= 0 { clusters.entry(label)
.or_insert_with(Vec::new)
.push(alerts[idx].clone());
}
}
clusters.into_iter()
.map(|(label, cluster_alerts)| {
let (center, radius) = self.calculate_cluster_metrics(&cluster_alerts);
AlertCluster {
id: format!("cluster_{}", label),
alerts: cluster_alerts.clone(),
center,
radius,
characteristics: self.analyze_cluster_characteristics(&cluster_alerts),
}
})
.collect()
}
fn calculate_cluster_metrics(&self, alerts: &[StoredAlert]) -> (ClusterCenter, f64) {
let mut avg_time = Duration::zero();
let mut avg_value = 0.0;
let mut avg_threshold = 0.0;
for alert in alerts {
avg_time = avg_time + (alert.created_at - alerts[0].created_at);
avg_value += alert.current_value;
avg_threshold += alert.threshold;
}
let count = alerts.len() as f64;
let center = ClusterCenter {
time_offset: avg_time / alerts.len() as i32,
value: avg_value / count,
threshold: avg_threshold / count,
};
let radius = alerts.iter()
.map(|alert| {
let time_diff = (alert.created_at - alerts[0].created_at - center.time_offset)
.num_seconds() as f64;
let value_diff = alert.current_value - center.value;
let threshold_diff = alert.threshold - center.threshold;
(time_diff.powi(2) + value_diff.powi(2) + threshold_diff.powi(2)).sqrt()
})
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or(0.0);
(center, radius)
}
fn analyze_cluster_characteristics(&self, alerts: &[StoredAlert]) -> ClusterCharacteristics {
let mut metrics = HashSet::new();
let mut severities = HashSet::new();
let mut time_range: Option<(DateTime<Utc>, DateTime<Utc>)> = None;
for alert in alerts {
metrics.insert(alert.metric_name.clone());
severities.insert(alert.severity.clone());
time_range = Some(match time_range {
Some((start, end)) => (
start.min(alert.created_at),
end.max(alert.created_at)
),
None => (alert.created_at, alert.created_at)
});
}
let (start_time, end_time) = time_range.unwrap_or((Utc::now(), Utc::now()));
let duration = end_time - start_time;
ClusterCharacteristics {
unique_metrics: metrics.len(),
unique_severities: severities.len(),
duration,
alert_count: alerts.len(),
severity_distribution: self.calculate_severity_distribution(alerts),
}
}
fn calculate_severity_distribution(&self, alerts: &[StoredAlert]) -> HashMap<String, f64> {
let mut distribution = HashMap::new();
let total = alerts.len() as f64;
for alert in alerts {
*distribution.entry(alert.severity.clone()).or_insert(0.0) += 1.0;
}
for count in distribution.values_mut() {
*count /= total;
}
distribution
}
pub fn predict_cluster(&self, alert: &StoredAlert) -> Option<String> {
if let Some(model) = &self.model {
let features = self.extract_features(alert);
let x = DenseMatrix::from_2d_vec(&vec![features]);
if let Ok(prediction) = model.predict(&x) {
if prediction[0] == 1 {
return Some("cluster_id".to_string());
}
}
}
None
}
}
#[derive(Debug)]
pub struct AlertCluster {
pub id: String,
pub alerts: Vec<StoredAlert>,
pub center: ClusterCenter,
pub radius: f64,
pub characteristics: ClusterCharacteristics,
}
#[derive(Debug)]
pub struct ClusterCenter {
pub time_offset: Duration,
pub value: f64,
pub threshold: f64,
}
#[derive(Debug)]
pub struct ClusterCharacteristics {
pub unique_metrics: usize,
pub unique_severities: usize,
pub duration: Duration,
pub alert_count: usize,
pub severity_distribution: HashMap<String, f64>,
}