spawn-access-control 0.1.12

A Rust library for access control management with WebAssembly support, including role-based access control (RBAC), permissions, and audit logging.
Documentation
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 { // -1 noise points
                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>,
}