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 chrono::{DateTime, Utc, Duration, Timelike, Datelike};
use serde::Serialize;
use ndarray::{Array1, ArrayView1};

#[derive(Debug, Serialize)]
pub struct TimeSeriesAnalysis {
    pub trend: TrendAnalysis,
    pub seasonality: SeasonalityAnalysis,
    pub forecasts: Vec<AlertForecast>,
}

#[derive(Debug, Serialize)]
pub struct TrendAnalysis {
    pub direction: TrendDirection,
    pub slope: f64,
    pub confidence: f64,
}

#[derive(Debug, Serialize)]
pub enum TrendDirection {
    Increasing,
    Decreasing,
    Stable,
}

#[derive(Debug, Serialize)]
pub struct SeasonalityAnalysis {
    pub has_daily_pattern: bool,
    pub has_weekly_pattern: bool,
    pub daily_peak_hours: Vec<u32>,
    pub weekly_peak_days: Vec<u32>,
    pub confidence: f64,
}

#[derive(Debug, Serialize)]
pub struct AlertForecast {
    pub timestamp: DateTime<Utc>,
    pub expected_value: f64,
    pub confidence_interval: (f64, f64),
    pub probability: f64,
}

pub struct TimeSeriesAnalyzer {
    config: TimeSeriesConfig,
}

#[derive(Clone)]
pub struct TimeSeriesConfig {
    pub min_data_points: usize,
    pub forecast_horizon: Duration,
    pub seasonality_threshold: f64,
    pub trend_threshold: f64,
}

impl TimeSeriesAnalyzer {
    pub fn new(config: TimeSeriesConfig) -> Self {
        Self { config }
    }

    pub fn analyze(&self, alerts: &[StoredAlert]) -> Option<TimeSeriesAnalysis> {
        if alerts.len() < self.config.min_data_points {
            return None;
        }

        let trend = self.analyze_trend(alerts);
        let seasonality = self.analyze_seasonality(alerts);
        let forecasts = self.generate_forecasts(alerts, &trend, &seasonality);

        Some(TimeSeriesAnalysis {
            trend,
            seasonality,
            forecasts,
        })
    }

    fn analyze_trend(&self, alerts: &[StoredAlert]) -> TrendAnalysis {
        let timestamps: Vec<i64> = alerts.iter()
            .map(|a| a.created_at.timestamp())
            .collect();
        let values: Vec<f64> = alerts.iter()
            .map(|a| a.current_value)
            .collect();

        let x = Array1::from_vec(timestamps);
        let y = Array1::from_vec(values);
        
        let x_mean = x.mean().unwrap() as f64;
        let y_mean = y.mean().unwrap();
        
        let numerator: f64 = x.iter()
            .zip(y.iter())
            .map(|(&x_i, &y_i)| {
                let x_f64 = x_i as f64;
                (x_f64 - x_mean) * (y_i - y_mean)
            })
            .sum();
        
        let denominator: f64 = x.iter()
            .map(|&x_i| {
                let x_f64 = x_i as f64;
                (x_f64 - x_mean).powi(2)
            })
            .sum();

        let slope = numerator / denominator;
        let x_view = x.view();
        let y_view = y.view();
        let confidence = self.calculate_trend_confidence(&x_view, &y_view, slope, x_mean, y_mean);

        TrendAnalysis {
            direction: if slope.abs() < self.config.trend_threshold {
                TrendDirection::Stable
            } else if slope > 0.0 {
                TrendDirection::Increasing
            } else {
                TrendDirection::Decreasing
            },
            slope,
            confidence,
        }
    }

    fn analyze_seasonality(&self, alerts: &[StoredAlert]) -> SeasonalityAnalysis {
        let mut hourly_counts = vec![0; 24];
        let mut daily_counts = vec![0; 7];

        for alert in alerts {
            let hour = alert.created_at.hour() as usize;
            let day = alert.created_at.weekday().num_days_from_monday() as usize;
            
            hourly_counts[hour] += 1;
            daily_counts[day] += 1;
        }

        // Peak saatleri ve günleri bul
        let daily_peaks = self.find_peaks(&hourly_counts, 3);
        let weekly_peaks = self.find_peaks(&daily_counts, 2);

        // Seasonality testi
        let hourly_variance = self.calculate_variance(&hourly_counts);
        let daily_variance = self.calculate_variance(&daily_counts);

        SeasonalityAnalysis {
            has_daily_pattern: hourly_variance > self.config.seasonality_threshold,
            has_weekly_pattern: daily_variance > self.config.seasonality_threshold,
            daily_peak_hours: daily_peaks.into_iter().map(|i| i as u32).collect(),
            weekly_peak_days: weekly_peaks.into_iter().map(|i| i as u32).collect(),
            confidence: (hourly_variance + daily_variance) / 2.0,
        }
    }

    fn generate_forecasts(
        &self,
        alerts: &[StoredAlert],
        trend: &TrendAnalysis,
        seasonality: &SeasonalityAnalysis,
    ) -> Vec<AlertForecast> {
        let mut forecasts = Vec::new();
        let last_time = alerts.last().unwrap().created_at;
        
        // ARIMA benzeri basit bir tahmin modeli
        for i in 1..=24 { // 24 saatlik tahmin
            let forecast_time = last_time + Duration::hours(i);
            let base_value = self.calculate_base_forecast(alerts, &forecast_time);
            
            // Trend etkisini ekle
            let trend_effect = trend.slope * (i as f64);
            
            // Seasonality etkisini ekle
            let seasonal_effect = if seasonality.has_daily_pattern {
                self.calculate_seasonal_effect(forecast_time.hour(), &seasonality.daily_peak_hours)
            } else {
                0.0
            };

            let expected_value = base_value + trend_effect + seasonal_effect;
            let uncertainty = self.calculate_uncertainty(i as f64);

            forecasts.push(AlertForecast {
                timestamp: forecast_time,
                expected_value,
                confidence_interval: (
                    expected_value - uncertainty,
                    expected_value + uncertainty
                ),
                probability: self.calculate_alert_probability(
                    expected_value,
                    uncertainty,
                    alerts
                ),
            });
        }

        forecasts
    }

    fn calculate_base_forecast(&self, alerts: &[StoredAlert], forecast_time: &DateTime<Utc>) -> f64 {
        // Son 24 saatteki benzer saatlerin ortalamasını al
        let hour = forecast_time.hour();
        let recent_alerts: Vec<_> = alerts.iter()
            .filter(|a| a.created_at.hour() == hour)
            .filter(|a| a.created_at + Duration::days(1) > *forecast_time)
            .collect();

        if recent_alerts.is_empty() {
            alerts.last().unwrap().current_value
        } else {
            recent_alerts.iter()
                .map(|a| a.current_value)
                .sum::<f64>() / recent_alerts.len() as f64
        }
    }

    fn calculate_seasonal_effect(&self, hour: u32, peak_hours: &[u32]) -> f64 {
        if peak_hours.contains(&hour) {
            10.0 // Peak saatlerde yüksek etki
        } else {
            0.0
        }
    }

    fn calculate_uncertainty(&self, hours_ahead: f64) -> f64 {
        // Belirsizlik zamanla artar
        5.0 + (hours_ahead / 24.0) * 10.0
    }

    fn calculate_alert_probability(&self, value: f64, uncertainty: f64, history: &[StoredAlert]) -> f64 {
        // Basit bir olasılık hesabı
        let threshold = history.iter()
            .map(|a| a.threshold)
            .sum::<f64>() / history.len() as f64;

        if value > threshold {
            0.8 - (uncertainty / value)
        } else {
            0.2 * (value / threshold)
        }
    }

    fn find_peaks(&self, values: &[i32], count: usize) -> Vec<usize> {
        let mut peaks: Vec<(usize, i32)> = values.iter()
            .enumerate()
            .map(|(i, &v)| (i, v))
            .collect();
        
        peaks.sort_by_key(|&(_, v)| std::cmp::Reverse(v));
        peaks.iter()
            .take(count)
            .map(|&(i, _)| i)
            .collect()
    }

    fn calculate_variance(&self, values: &[i32]) -> f64 {
        let mean = values.iter().sum::<i32>() as f64 / values.len() as f64;
        let variance = values.iter()
            .map(|&x| {
                let diff = x as f64 - mean;
                diff * diff
            })
            .sum::<f64>() / values.len() as f64;
        
        variance.sqrt() / mean // Normalized variance
    }

    fn calculate_trend_confidence(
        &self,
        x: &ArrayView1<i64>,
        y: &ArrayView1<f64>,
        slope: f64,
        x_mean: f64,
        y_mean: f64,
    ) -> f64 {
        let y_pred: Vec<f64> = x.iter()
            .map(|&x_i| slope * ((x_i as f64) - x_mean) + y_mean)
            .collect();

        let ss_res: f64 = y.iter()
            .zip(y_pred.iter())
            .map(|(&y_i, &f_i)| (y_i - f_i).powi(2))
            .sum();

        let ss_tot: f64 = y.iter()
            .map(|&y_i| (y_i - y_mean).powi(2))
            .sum();

        1.0 - (ss_res / ss_tot)
    }
}