spawn_access_control/
time_series_analyzer.rs

1use crate::alert_storage::StoredAlert;
2use chrono::{DateTime, Utc, Duration, Timelike, Datelike};
3use serde::Serialize;
4use ndarray::{Array1, ArrayView1};
5
6#[derive(Debug, Serialize)]
7pub struct TimeSeriesAnalysis {
8    pub trend: TrendAnalysis,
9    pub seasonality: SeasonalityAnalysis,
10    pub forecasts: Vec<AlertForecast>,
11}
12
13#[derive(Debug, Serialize)]
14pub struct TrendAnalysis {
15    pub direction: TrendDirection,
16    pub slope: f64,
17    pub confidence: f64,
18}
19
20#[derive(Debug, Serialize)]
21pub enum TrendDirection {
22    Increasing,
23    Decreasing,
24    Stable,
25}
26
27#[derive(Debug, Serialize)]
28pub struct SeasonalityAnalysis {
29    pub has_daily_pattern: bool,
30    pub has_weekly_pattern: bool,
31    pub daily_peak_hours: Vec<u32>,
32    pub weekly_peak_days: Vec<u32>,
33    pub confidence: f64,
34}
35
36#[derive(Debug, Serialize)]
37pub struct AlertForecast {
38    pub timestamp: DateTime<Utc>,
39    pub expected_value: f64,
40    pub confidence_interval: (f64, f64),
41    pub probability: f64,
42}
43
44pub struct TimeSeriesAnalyzer {
45    config: TimeSeriesConfig,
46}
47
48#[derive(Clone)]
49pub struct TimeSeriesConfig {
50    pub min_data_points: usize,
51    pub forecast_horizon: Duration,
52    pub seasonality_threshold: f64,
53    pub trend_threshold: f64,
54}
55
56impl TimeSeriesAnalyzer {
57    pub fn new(config: TimeSeriesConfig) -> Self {
58        Self { config }
59    }
60
61    pub fn analyze(&self, alerts: &[StoredAlert]) -> Option<TimeSeriesAnalysis> {
62        if alerts.len() < self.config.min_data_points {
63            return None;
64        }
65
66        let trend = self.analyze_trend(alerts);
67        let seasonality = self.analyze_seasonality(alerts);
68        let forecasts = self.generate_forecasts(alerts, &trend, &seasonality);
69
70        Some(TimeSeriesAnalysis {
71            trend,
72            seasonality,
73            forecasts,
74        })
75    }
76
77    fn analyze_trend(&self, alerts: &[StoredAlert]) -> TrendAnalysis {
78        let timestamps: Vec<i64> = alerts.iter()
79            .map(|a| a.created_at.timestamp())
80            .collect();
81        let values: Vec<f64> = alerts.iter()
82            .map(|a| a.current_value)
83            .collect();
84
85        let x = Array1::from_vec(timestamps);
86        let y = Array1::from_vec(values);
87        
88        let x_mean = x.mean().unwrap() as f64;
89        let y_mean = y.mean().unwrap();
90        
91        let numerator: f64 = x.iter()
92            .zip(y.iter())
93            .map(|(&x_i, &y_i)| {
94                let x_f64 = x_i as f64;
95                (x_f64 - x_mean) * (y_i - y_mean)
96            })
97            .sum();
98        
99        let denominator: f64 = x.iter()
100            .map(|&x_i| {
101                let x_f64 = x_i as f64;
102                (x_f64 - x_mean).powi(2)
103            })
104            .sum();
105
106        let slope = numerator / denominator;
107        let x_view = x.view();
108        let y_view = y.view();
109        let confidence = self.calculate_trend_confidence(&x_view, &y_view, slope, x_mean, y_mean);
110
111        TrendAnalysis {
112            direction: if slope.abs() < self.config.trend_threshold {
113                TrendDirection::Stable
114            } else if slope > 0.0 {
115                TrendDirection::Increasing
116            } else {
117                TrendDirection::Decreasing
118            },
119            slope,
120            confidence,
121        }
122    }
123
124    fn analyze_seasonality(&self, alerts: &[StoredAlert]) -> SeasonalityAnalysis {
125        let mut hourly_counts = vec![0; 24];
126        let mut daily_counts = vec![0; 7];
127
128        for alert in alerts {
129            let hour = alert.created_at.hour() as usize;
130            let day = alert.created_at.weekday().num_days_from_monday() as usize;
131            
132            hourly_counts[hour] += 1;
133            daily_counts[day] += 1;
134        }
135
136        // Peak saatleri ve günleri bul
137        let daily_peaks = self.find_peaks(&hourly_counts, 3);
138        let weekly_peaks = self.find_peaks(&daily_counts, 2);
139
140        // Seasonality testi
141        let hourly_variance = self.calculate_variance(&hourly_counts);
142        let daily_variance = self.calculate_variance(&daily_counts);
143
144        SeasonalityAnalysis {
145            has_daily_pattern: hourly_variance > self.config.seasonality_threshold,
146            has_weekly_pattern: daily_variance > self.config.seasonality_threshold,
147            daily_peak_hours: daily_peaks.into_iter().map(|i| i as u32).collect(),
148            weekly_peak_days: weekly_peaks.into_iter().map(|i| i as u32).collect(),
149            confidence: (hourly_variance + daily_variance) / 2.0,
150        }
151    }
152
153    fn generate_forecasts(
154        &self,
155        alerts: &[StoredAlert],
156        trend: &TrendAnalysis,
157        seasonality: &SeasonalityAnalysis,
158    ) -> Vec<AlertForecast> {
159        let mut forecasts = Vec::new();
160        let last_time = alerts.last().unwrap().created_at;
161        
162        // ARIMA benzeri basit bir tahmin modeli
163        for i in 1..=24 { // 24 saatlik tahmin
164            let forecast_time = last_time + Duration::hours(i);
165            let base_value = self.calculate_base_forecast(alerts, &forecast_time);
166            
167            // Trend etkisini ekle
168            let trend_effect = trend.slope * (i as f64);
169            
170            // Seasonality etkisini ekle
171            let seasonal_effect = if seasonality.has_daily_pattern {
172                self.calculate_seasonal_effect(forecast_time.hour(), &seasonality.daily_peak_hours)
173            } else {
174                0.0
175            };
176
177            let expected_value = base_value + trend_effect + seasonal_effect;
178            let uncertainty = self.calculate_uncertainty(i as f64);
179
180            forecasts.push(AlertForecast {
181                timestamp: forecast_time,
182                expected_value,
183                confidence_interval: (
184                    expected_value - uncertainty,
185                    expected_value + uncertainty
186                ),
187                probability: self.calculate_alert_probability(
188                    expected_value,
189                    uncertainty,
190                    alerts
191                ),
192            });
193        }
194
195        forecasts
196    }
197
198    fn calculate_base_forecast(&self, alerts: &[StoredAlert], forecast_time: &DateTime<Utc>) -> f64 {
199        // Son 24 saatteki benzer saatlerin ortalamasını al
200        let hour = forecast_time.hour();
201        let recent_alerts: Vec<_> = alerts.iter()
202            .filter(|a| a.created_at.hour() == hour)
203            .filter(|a| a.created_at + Duration::days(1) > *forecast_time)
204            .collect();
205
206        if recent_alerts.is_empty() {
207            alerts.last().unwrap().current_value
208        } else {
209            recent_alerts.iter()
210                .map(|a| a.current_value)
211                .sum::<f64>() / recent_alerts.len() as f64
212        }
213    }
214
215    fn calculate_seasonal_effect(&self, hour: u32, peak_hours: &[u32]) -> f64 {
216        if peak_hours.contains(&hour) {
217            10.0 // Peak saatlerde yüksek etki
218        } else {
219            0.0
220        }
221    }
222
223    fn calculate_uncertainty(&self, hours_ahead: f64) -> f64 {
224        // Belirsizlik zamanla artar
225        5.0 + (hours_ahead / 24.0) * 10.0
226    }
227
228    fn calculate_alert_probability(&self, value: f64, uncertainty: f64, history: &[StoredAlert]) -> f64 {
229        // Basit bir olasılık hesabı
230        let threshold = history.iter()
231            .map(|a| a.threshold)
232            .sum::<f64>() / history.len() as f64;
233
234        if value > threshold {
235            0.8 - (uncertainty / value)
236        } else {
237            0.2 * (value / threshold)
238        }
239    }
240
241    fn find_peaks(&self, values: &[i32], count: usize) -> Vec<usize> {
242        let mut peaks: Vec<(usize, i32)> = values.iter()
243            .enumerate()
244            .map(|(i, &v)| (i, v))
245            .collect();
246        
247        peaks.sort_by_key(|&(_, v)| std::cmp::Reverse(v));
248        peaks.iter()
249            .take(count)
250            .map(|&(i, _)| i)
251            .collect()
252    }
253
254    fn calculate_variance(&self, values: &[i32]) -> f64 {
255        let mean = values.iter().sum::<i32>() as f64 / values.len() as f64;
256        let variance = values.iter()
257            .map(|&x| {
258                let diff = x as f64 - mean;
259                diff * diff
260            })
261            .sum::<f64>() / values.len() as f64;
262        
263        variance.sqrt() / mean // Normalized variance
264    }
265
266    fn calculate_trend_confidence(
267        &self,
268        x: &ArrayView1<i64>,
269        y: &ArrayView1<f64>,
270        slope: f64,
271        x_mean: f64,
272        y_mean: f64,
273    ) -> f64 {
274        let y_pred: Vec<f64> = x.iter()
275            .map(|&x_i| slope * ((x_i as f64) - x_mean) + y_mean)
276            .collect();
277
278        let ss_res: f64 = y.iter()
279            .zip(y_pred.iter())
280            .map(|(&y_i, &f_i)| (y_i - f_i).powi(2))
281            .sum();
282
283        let ss_tot: f64 = y.iter()
284            .map(|&y_i| (y_i - y_mean).powi(2))
285            .sum();
286
287        1.0 - (ss_res / ss_tot)
288    }
289}