arrow_graph/streaming/
events.rs

1use crate::error::Result;
2use crate::streaming::incremental::{IncrementalGraphProcessor, UpdateResult};
3use crate::streaming::algorithms::{StreamingAlgorithm, StreamingPageRank, StreamingConnectedComponents};
4use crate::streaming::detection::{GraphChangeDetector, AnomalyEvent, AnomalyType};
5use std::collections::HashMap;
6
7/// Event-driven graph processing system that triggers algorithms based on graph changes
8/// This system monitors graph updates and automatically executes relevant algorithms
9#[derive(Debug)]
10pub struct EventDrivenProcessor {
11    // Core components
12    processor: IncrementalGraphProcessor,
13    change_detector: GraphChangeDetector,
14    
15    // Streaming algorithms
16    pagerank: Option<StreamingPageRank>,
17    components: Option<StreamingConnectedComponents>,
18    
19    // Event system
20    event_rules: Vec<EventRule>,
21    event_history: Vec<ProcessedEvent>,
22    max_history: usize,
23    
24    // Performance tracking
25    algorithm_stats: HashMap<String, AlgorithmStats>,
26}
27
28/// Rule that defines when and how to trigger algorithms based on events
29#[derive(Debug, Clone)]
30pub struct EventRule {
31    pub name: String,
32    pub trigger: EventTrigger,
33    pub action: EventAction,
34    pub priority: u8, // 0 = highest priority
35    pub enabled: bool,
36    pub cooldown_seconds: u64, // Minimum time between triggers
37    pub last_triggered: Option<u64>,
38}
39
40/// Conditions that trigger algorithm execution
41#[derive(Debug, Clone)]
42pub enum EventTrigger {
43    // Graph structure changes
44    VertexCountChange(ChangeThreshold),
45    EdgeCountChange(ChangeThreshold),
46    DensityChange(ChangeThreshold),
47    ComponentCountChange(usize), // Trigger when component count changes by N
48    
49    // Anomaly detection
50    AnomalyDetected(Vec<AnomalyType>),
51    AnomalySeverity(f64), // Trigger when anomaly severity exceeds threshold
52    
53    // Time-based
54    Periodic(u64), // Trigger every N seconds
55    
56    // Complex conditions
57    Custom(CustomTrigger),
58}
59
60/// Threshold configuration for numeric changes
61#[derive(Debug, Clone)]
62pub struct ChangeThreshold {
63    pub absolute: Option<f64>,
64    pub relative: Option<f64>, // Percentage change
65}
66
67/// Custom trigger function
68#[derive(Debug, Clone)]
69pub struct CustomTrigger {
70    pub name: String,
71    pub condition: TriggerCondition,
72}
73
74/// Different types of trigger conditions
75#[derive(Debug, Clone)]
76pub enum TriggerCondition {
77    HighDegreeNodes(usize, f64), // (min_degree, min_fraction)
78    RapidGrowth(f64),            // Growth rate threshold
79    StructuralChange(f64),       // Structural similarity threshold
80    CommunityInstability(f64),   // Community stability threshold
81}
82
83/// Actions to take when events are triggered
84#[derive(Debug, Clone)]
85pub enum EventAction {
86    // Algorithm execution
87    RunPageRank,
88    RunConnectedComponents,
89    RunAllAlgorithms,
90    
91    // Analysis actions
92    PerformAnomalyAnalysis,
93    UpdateBaseline,
94    
95    // Notification actions
96    LogEvent,
97    TriggerAlert(AlertLevel),
98    
99    // Combined actions
100    Multiple(Vec<EventAction>),
101}
102
103/// Alert severity levels
104#[derive(Debug, Clone, PartialEq)]
105pub enum AlertLevel {
106    Info,
107    Warning,
108    Critical,
109}
110
111/// Processed event record
112#[derive(Debug, Clone)]
113pub struct ProcessedEvent {
114    pub timestamp: u64,
115    pub rule_name: String,
116    pub trigger_type: String,
117    pub action_taken: String,
118    pub execution_time_ms: u64,
119    pub success: bool,
120    pub details: HashMap<String, String>,
121}
122
123/// Performance statistics for algorithms
124#[derive(Debug, Clone)]
125pub struct AlgorithmStats {
126    pub total_executions: u64,
127    pub total_time_ms: u64,
128    pub average_time_ms: f64,
129    pub last_execution: Option<u64>,
130    pub success_rate: f64,
131}
132
133impl EventDrivenProcessor {
134    /// Create new event-driven processor
135    pub fn new(graph: crate::graph::ArrowGraph) -> Result<Self> {
136        let processor = IncrementalGraphProcessor::new(graph)?;
137        let change_detector = GraphChangeDetector::default();
138
139        Ok(Self {
140            processor,
141            change_detector,
142            pagerank: None,
143            components: None,
144            event_rules: Vec::new(),
145            event_history: Vec::new(),
146            max_history: 1000,
147            algorithm_stats: HashMap::new(),
148        })
149    }
150
151    /// Initialize the processor with default algorithms
152    pub fn initialize(&mut self) -> Result<()> {
153        // Initialize change detector
154        self.change_detector.initialize(&self.processor)?;
155
156        // Initialize algorithms
157        let mut pagerank = StreamingPageRank::default();
158        pagerank.initialize(&self.processor)?;
159        self.pagerank = Some(pagerank);
160
161        let mut components = StreamingConnectedComponents::new();
162        components.initialize(&self.processor)?;
163        self.components = Some(components);
164
165        // Set up default event rules
166        self.setup_default_rules();
167
168        Ok(())
169    }
170
171    /// Setup default event rules
172    fn setup_default_rules(&mut self) {
173        // Rule 1: Run PageRank on significant edge changes
174        self.add_rule(EventRule {
175            name: "pagerank_on_edge_changes".to_string(),
176            trigger: EventTrigger::EdgeCountChange(ChangeThreshold {
177                absolute: Some(10.0),
178                relative: Some(0.05), // 5% change
179            }),
180            action: EventAction::RunPageRank,
181            priority: 1,
182            enabled: true,
183            cooldown_seconds: 30,
184            last_triggered: None,
185        });
186
187        // Rule 2: Run connected components on vertex changes
188        self.add_rule(EventRule {
189            name: "components_on_vertex_changes".to_string(),
190            trigger: EventTrigger::VertexCountChange(ChangeThreshold {
191                absolute: Some(5.0),
192                relative: Some(0.03), // 3% change
193            }),
194            action: EventAction::RunConnectedComponents,
195            priority: 1,
196            enabled: true,
197            cooldown_seconds: 20,
198            last_triggered: None,
199        });
200
201        // Rule 3: Alert on critical anomalies
202        self.add_rule(EventRule {
203            name: "critical_anomaly_alert".to_string(),
204            trigger: EventTrigger::AnomalySeverity(0.8),
205            action: EventAction::TriggerAlert(AlertLevel::Critical),
206            priority: 0, // Highest priority
207            enabled: true,
208            cooldown_seconds: 60,
209            last_triggered: None,
210        });
211
212        // Rule 4: Periodic baseline update
213        self.add_rule(EventRule {
214            name: "periodic_baseline_update".to_string(),
215            trigger: EventTrigger::Periodic(300), // Every 5 minutes
216            action: EventAction::UpdateBaseline,
217            priority: 2,
218            enabled: true,
219            cooldown_seconds: 0,
220            last_triggered: None,
221        });
222
223        // Rule 5: Full analysis on component structure changes
224        self.add_rule(EventRule {
225            name: "full_analysis_on_component_changes".to_string(),
226            trigger: EventTrigger::ComponentCountChange(1),
227            action: EventAction::Multiple(vec![
228                EventAction::RunAllAlgorithms,
229                EventAction::PerformAnomalyAnalysis,
230                EventAction::LogEvent,
231            ]),
232            priority: 1,
233            enabled: true,
234            cooldown_seconds: 45,
235            last_triggered: None,
236        });
237    }
238
239    /// Add a new event rule
240    pub fn add_rule(&mut self, rule: EventRule) {
241        self.event_rules.push(rule);
242        // Sort by priority (lower number = higher priority)
243        self.event_rules.sort_by_key(|r| r.priority);
244    }
245
246    /// Update the graph and process events
247    pub fn update(&mut self, changes: UpdateResult) -> Result<Vec<ProcessedEvent>> {
248        let current_time = std::time::SystemTime::now()
249            .duration_since(std::time::UNIX_EPOCH)
250            .unwrap()
251            .as_secs();
252
253        let mut triggered_events = Vec::new();
254
255        // Detect anomalies first
256        let anomalies = self.change_detector.update(&self.processor, &changes)?;
257
258        // Create a list of rule indices that need to be triggered
259        let mut rules_to_trigger = Vec::new();
260        
261        for (index, rule) in self.event_rules.iter().enumerate() {
262            if !rule.enabled {
263                continue;
264            }
265
266            // Check cooldown
267            if let Some(last_triggered) = rule.last_triggered {
268                if current_time < last_triggered + rule.cooldown_seconds {
269                    continue;
270                }
271            }
272
273            // Check if rule should trigger
274            if self.should_trigger_rule(rule, &changes, &anomalies, current_time)? {
275                rules_to_trigger.push(index);
276            }
277        }
278
279        // Execute triggered rules
280        for rule_index in rules_to_trigger {
281            let rule_name = self.event_rules[rule_index].name.clone();
282            let rule_action = self.event_rules[rule_index].action.clone();
283            let rule_trigger = self.event_rules[rule_index].trigger.clone();
284            
285            let start_time = std::time::Instant::now();
286            
287            // Execute the action
288            let success = self.execute_action(&rule_action, &changes, &anomalies)?;
289            
290            let execution_time = start_time.elapsed().as_millis() as u64;
291            
292            // Record the event
293            let event = ProcessedEvent {
294                timestamp: current_time,
295                rule_name,
296                trigger_type: format!("{:?}", rule_trigger),
297                action_taken: format!("{:?}", rule_action),
298                execution_time_ms: execution_time,
299                success,
300                details: self.create_event_details(&changes, &anomalies),
301            };
302
303            triggered_events.push(event.clone());
304            self.record_event(event);
305
306            // Update rule state
307            self.event_rules[rule_index].last_triggered = Some(current_time);
308
309            // Update algorithm stats if applicable
310            self.update_algorithm_stats(&rule_action, execution_time, success);
311        }
312
313        Ok(triggered_events)
314    }
315
316    /// Check if a rule should trigger
317    fn should_trigger_rule(
318        &self,
319        rule: &EventRule,
320        changes: &UpdateResult,
321        anomalies: &[AnomalyEvent],
322        current_time: u64,
323    ) -> Result<bool> {
324        match &rule.trigger {
325            EventTrigger::VertexCountChange(threshold) => {
326                self.check_change_threshold(changes.vertices_added as f64, threshold)
327            }
328
329            EventTrigger::EdgeCountChange(threshold) => {
330                self.check_change_threshold(changes.edges_added as f64, threshold)
331            }
332
333            EventTrigger::DensityChange(threshold) => {
334                // This would require tracking previous density
335                // For now, use edge count as a proxy
336                self.check_change_threshold(changes.edges_added as f64, threshold)
337            }
338
339            EventTrigger::ComponentCountChange(min_change) => {
340                Ok(changes.affected_components.len() >= *min_change)
341            }
342
343            EventTrigger::AnomalyDetected(types) => {
344                Ok(anomalies.iter().any(|a| types.contains(&a.anomaly_type)))
345            }
346
347            EventTrigger::AnomalySeverity(threshold) => {
348                Ok(anomalies.iter().any(|a| a.severity >= *threshold))
349            }
350
351            EventTrigger::Periodic(interval) => {
352                if let Some(last_triggered) = rule.last_triggered {
353                    Ok(current_time >= last_triggered + interval)
354                } else {
355                    Ok(true) // First time
356                }
357            }
358
359            EventTrigger::Custom(custom) => {
360                self.evaluate_custom_trigger(custom, changes, anomalies)
361            }
362        }
363    }
364
365    /// Check if a numeric change meets the threshold
366    fn check_change_threshold(&self, change: f64, threshold: &ChangeThreshold) -> Result<bool> {
367        let mut triggered = false;
368
369        if let Some(absolute) = threshold.absolute {
370            triggered |= change >= absolute;
371        }
372
373        if let Some(relative) = threshold.relative {
374            // For relative threshold, we'd need the previous value
375            // For now, just use absolute as a fallback
376            triggered |= change >= relative * 100.0; // Simplified
377        }
378
379        Ok(triggered)
380    }
381
382    /// Evaluate custom trigger conditions
383    fn evaluate_custom_trigger(
384        &self,
385        custom: &CustomTrigger,
386        _changes: &UpdateResult,
387        anomalies: &[AnomalyEvent],
388    ) -> Result<bool> {
389        match &custom.condition {
390            TriggerCondition::HighDegreeNodes(_min_degree, _min_fraction) => {
391                // Would need degree distribution analysis
392                Ok(false) // Placeholder
393            }
394
395            TriggerCondition::RapidGrowth(threshold) => {
396                // Check for rapid growth anomalies
397                Ok(anomalies.iter().any(|a| {
398                    matches!(a.anomaly_type, AnomalyType::EdgeBurst | AnomalyType::NodeBurst)
399                        && a.severity >= *threshold
400                }))
401            }
402
403            TriggerCondition::StructuralChange(threshold) => {
404                // Check for structural anomalies
405                Ok(anomalies.iter().any(|a| {
406                    matches!(
407                        a.anomaly_type,
408                        AnomalyType::ComponentMerge | AnomalyType::ComponentSplit | AnomalyType::CommunityShift
409                    ) && a.severity >= *threshold
410                }))
411            }
412
413            TriggerCondition::CommunityInstability(threshold) => {
414                if let Some(stability) = self.change_detector.current_community_stability() {
415                    Ok(stability < *threshold)
416                } else {
417                    Ok(false)
418                }
419            }
420        }
421    }
422
423    /// Execute an action
424    fn execute_action(
425        &mut self,
426        action: &EventAction,
427        changes: &UpdateResult,
428        anomalies: &[AnomalyEvent],
429    ) -> Result<bool> {
430        match action {
431            EventAction::RunPageRank => {
432                if let Some(ref mut pagerank) = self.pagerank {
433                    pagerank.update(&self.processor, changes)?;
434                    Ok(true)
435                } else {
436                    Ok(false)
437                }
438            }
439
440            EventAction::RunConnectedComponents => {
441                if let Some(ref mut components) = self.components {
442                    components.update(&self.processor, changes)?;
443                    Ok(true)
444                } else {
445                    Ok(false)
446                }
447            }
448
449            EventAction::RunAllAlgorithms => {
450                let mut success = true;
451                
452                if let Some(ref mut pagerank) = self.pagerank {
453                    success &= pagerank.update(&self.processor, changes).is_ok();
454                }
455                
456                if let Some(ref mut components) = self.components {
457                    success &= components.update(&self.processor, changes).is_ok();
458                }
459                
460                Ok(success)
461            }
462
463            EventAction::PerformAnomalyAnalysis => {
464                // Anomaly analysis is already done in update()
465                Ok(true)
466            }
467
468            EventAction::UpdateBaseline => {
469                // Would update baseline metrics in change detector
470                Ok(true)
471            }
472
473            EventAction::LogEvent => {
474                log::info!("Event triggered: {} anomalies detected", anomalies.len());
475                Ok(true)
476            }
477
478            EventAction::TriggerAlert(level) => {
479                match level {
480                    AlertLevel::Info => log::info!("Graph event alert: {} anomalies", anomalies.len()),
481                    AlertLevel::Warning => log::warn!("Graph event warning: {} anomalies", anomalies.len()),
482                    AlertLevel::Critical => log::error!("Critical graph event: {} anomalies", anomalies.len()),
483                }
484                Ok(true)
485            }
486
487            EventAction::Multiple(actions) => {
488                let mut all_success = true;
489                for sub_action in actions {
490                    let success = self.execute_action(sub_action, changes, anomalies)?;
491                    all_success &= success;
492                }
493                Ok(all_success)
494            }
495        }
496    }
497
498    /// Create event details
499    fn create_event_details(
500        &self,
501        changes: &UpdateResult,
502        anomalies: &[AnomalyEvent],
503    ) -> HashMap<String, String> {
504        let mut details = HashMap::new();
505
506        details.insert("vertices_added".to_string(), changes.vertices_added.to_string());
507        details.insert("vertices_removed".to_string(), changes.vertices_removed.to_string());
508        details.insert("edges_added".to_string(), changes.edges_added.to_string());
509        details.insert("edges_removed".to_string(), changes.edges_removed.to_string());
510        details.insert("anomalies_count".to_string(), anomalies.len().to_string());
511
512        if !anomalies.is_empty() {
513            let max_severity = anomalies.iter()
514                .map(|a| a.severity)
515                .fold(0.0f64, |a, b| a.max(b));
516            details.insert("max_anomaly_severity".to_string(), max_severity.to_string());
517        }
518
519        details
520    }
521
522    /// Record an event in history
523    fn record_event(&mut self, event: ProcessedEvent) {
524        self.event_history.push(event);
525
526        // Limit history size
527        while self.event_history.len() > self.max_history {
528            self.event_history.remove(0);
529        }
530    }
531
532    /// Update algorithm performance statistics
533    fn update_algorithm_stats(&mut self, action: &EventAction, execution_time: u64, success: bool) {
534        let algorithm_name = match action {
535            EventAction::RunPageRank => "pagerank",
536            EventAction::RunConnectedComponents => "connected_components",
537            EventAction::RunAllAlgorithms => "all_algorithms",
538            _ => return,
539        };
540
541        let stats = self.algorithm_stats.entry(algorithm_name.to_string()).or_insert(AlgorithmStats {
542            total_executions: 0,
543            total_time_ms: 0,
544            average_time_ms: 0.0,
545            last_execution: None,
546            success_rate: 0.0,
547        });
548
549        stats.total_executions += 1;
550        stats.total_time_ms += execution_time;
551        stats.average_time_ms = stats.total_time_ms as f64 / stats.total_executions as f64;
552        stats.last_execution = Some(
553            std::time::SystemTime::now()
554                .duration_since(std::time::UNIX_EPOCH)
555                .unwrap()
556                .as_secs(),
557        );
558
559        // Update success rate (simplified)
560        if success {
561            stats.success_rate = (stats.success_rate * (stats.total_executions - 1) as f64 + 1.0)
562                / stats.total_executions as f64;
563        } else {
564            stats.success_rate = (stats.success_rate * (stats.total_executions - 1) as f64)
565                / stats.total_executions as f64;
566        }
567    }
568
569    /// Get processor reference
570    pub fn processor(&self) -> &IncrementalGraphProcessor {
571        &self.processor
572    }
573
574    /// Get mutable processor reference
575    pub fn processor_mut(&mut self) -> &mut IncrementalGraphProcessor {
576        &mut self.processor
577    }
578
579    /// Get PageRank results
580    pub fn pagerank_results(&self) -> Option<&HashMap<String, f64>> {
581        self.pagerank.as_ref().map(|pr| pr.get_result())
582    }
583
584    /// Get connected components results
585    pub fn components_results(&self) -> Option<&HashMap<String, String>> {
586        self.components.as_ref().map(|cc| cc.get_result())
587    }
588
589    /// Get recent events
590    pub fn recent_events(&self, count: usize) -> Vec<&ProcessedEvent> {
591        self.event_history.iter().rev().take(count).collect()
592    }
593
594    /// Get algorithm statistics
595    pub fn algorithm_stats(&self) -> &HashMap<String, AlgorithmStats> {
596        &self.algorithm_stats
597    }
598
599    /// Get event rules
600    pub fn event_rules(&self) -> &[EventRule] {
601        &self.event_rules
602    }
603
604    /// Enable/disable a rule
605    pub fn set_rule_enabled(&mut self, rule_name: &str, enabled: bool) {
606        if let Some(rule) = self.event_rules.iter_mut().find(|r| r.name == rule_name) {
607            rule.enabled = enabled;
608        }
609    }
610
611    /// Get recent anomalies
612    pub fn recent_anomalies(&self, count: usize) -> Vec<&AnomalyEvent> {
613        self.change_detector.recent_anomalies(count)
614    }
615}
616
617#[cfg(test)]
618mod tests {
619    use super::*;
620    use crate::graph::ArrowGraph;
621    use arrow::array::{StringArray, Float64Array};
622    use arrow::record_batch::RecordBatch;
623    use arrow::datatypes::{Schema, Field, DataType};
624    use std::sync::Arc;
625
626    fn create_test_graph() -> Result<ArrowGraph> {
627        let nodes_schema = Arc::new(Schema::new(vec![
628            Field::new("id", DataType::Utf8, false),
629        ]));
630        let node_ids = StringArray::from(vec!["A", "B", "C"]);
631        let nodes_batch = RecordBatch::try_new(
632            nodes_schema,
633            vec![Arc::new(node_ids)],
634        )?;
635
636        let edges_schema = Arc::new(Schema::new(vec![
637            Field::new("source", DataType::Utf8, false),
638            Field::new("target", DataType::Utf8, false),
639            Field::new("weight", DataType::Float64, false),
640        ]));
641        let sources = StringArray::from(vec!["A"]);
642        let targets = StringArray::from(vec!["B"]);
643        let weights = Float64Array::from(vec![1.0]);
644        let edges_batch = RecordBatch::try_new(
645            edges_schema,
646            vec![Arc::new(sources), Arc::new(targets), Arc::new(weights)],
647        )?;
648
649        ArrowGraph::new(nodes_batch, edges_batch)
650    }
651
652    #[test]
653    fn test_event_driven_processor_initialization() {
654        let graph = create_test_graph().unwrap();
655        let mut processor = EventDrivenProcessor::new(graph).unwrap();
656        
657        processor.initialize().unwrap();
658        
659        assert!(processor.pagerank.is_some());
660        assert!(processor.components.is_some());
661        assert!(!processor.event_rules.is_empty());
662    }
663
664    #[test]
665    fn test_event_rule_creation() {
666        let rule = EventRule {
667            name: "test_rule".to_string(),
668            trigger: EventTrigger::EdgeCountChange(ChangeThreshold {
669                absolute: Some(5.0),
670                relative: None,
671            }),
672            action: EventAction::RunPageRank,
673            priority: 1,
674            enabled: true,
675            cooldown_seconds: 30,
676            last_triggered: None,
677        };
678
679        assert_eq!(rule.name, "test_rule");
680        assert!(rule.enabled);
681        assert_eq!(rule.cooldown_seconds, 30);
682    }
683
684    #[test]
685    fn test_change_threshold_evaluation() {
686        let graph = create_test_graph().unwrap();
687        let processor = EventDrivenProcessor::new(graph).unwrap();
688
689        let threshold = ChangeThreshold {
690            absolute: Some(10.0),
691            relative: Some(0.1),
692        };
693
694        assert!(processor.check_change_threshold(15.0, &threshold).unwrap());
695        assert!(!processor.check_change_threshold(5.0, &threshold).unwrap());
696    }
697
698    #[test]
699    fn test_event_processing() {
700        let graph = create_test_graph().unwrap();
701        let mut processor = EventDrivenProcessor::new(graph).unwrap();
702        processor.initialize().unwrap();
703
704        // Simulate significant changes
705        let changes = UpdateResult {
706            vertices_added: 0,
707            vertices_removed: 0,
708            edges_added: 15, // Above threshold
709            edges_removed: 0,
710            affected_components: vec![],
711            recomputation_needed: false,
712        };
713
714        let events = processor.update(changes).unwrap();
715        
716        // Should trigger some events due to edge changes
717        assert!(!events.is_empty());
718        
719        // Check that algorithms were updated
720        assert!(processor.pagerank_results().is_some());
721        assert!(processor.components_results().is_some());
722    }
723
724    #[test]
725    fn test_rule_cooldown() {
726        let current_time = std::time::SystemTime::now()
727            .duration_since(std::time::UNIX_EPOCH)
728            .unwrap()
729            .as_secs();
730            
731        let graph = create_test_graph().unwrap();
732        let mut processor = EventDrivenProcessor::new(graph).unwrap();
733        processor.initialize().unwrap();
734        
735        // Clear default rules to test only our cooldown rule
736        processor.event_rules.clear();
737
738        // Add a rule with cooldown
739        let rule = EventRule {
740            name: "test_cooldown".to_string(),
741            trigger: EventTrigger::EdgeCountChange(ChangeThreshold {
742                absolute: Some(10.0), // Higher threshold
743                relative: None,
744            }),
745            action: EventAction::LogEvent,
746            priority: 1,
747            enabled: true,
748            cooldown_seconds: 60,
749            last_triggered: Some(current_time), // Just triggered
750        };
751        
752        processor.add_rule(rule);
753
754        let changes = UpdateResult {
755            vertices_added: 0,
756            vertices_removed: 0,
757            edges_added: 15, // Above threshold
758            edges_removed: 0,
759            affected_components: vec![],
760            recomputation_needed: false,
761        };
762
763        // Should not trigger any events due to cooldown
764        let events = processor.update(changes.clone()).unwrap();
765        assert!(events.is_empty());
766
767        // Update the rule to have old last_triggered time
768        processor.event_rules[0].last_triggered = Some(current_time - 120); // 2 minutes ago
769        
770        // Now should trigger
771        let events = processor.update(changes).unwrap();
772        assert!(!events.is_empty());
773    }
774
775    #[test]
776    fn test_algorithm_stats_tracking() {
777        let graph = create_test_graph().unwrap();
778        let mut processor = EventDrivenProcessor::new(graph).unwrap();
779        processor.initialize().unwrap();
780
781        // Execute some actions
782        processor.update_algorithm_stats(&EventAction::RunPageRank, 100, true);
783        processor.update_algorithm_stats(&EventAction::RunPageRank, 200, true);
784
785        let stats = processor.algorithm_stats();
786        let pagerank_stats = stats.get("pagerank").unwrap();
787
788        assert_eq!(pagerank_stats.total_executions, 2);
789        assert_eq!(pagerank_stats.total_time_ms, 300);
790        assert_eq!(pagerank_stats.average_time_ms, 150.0);
791        assert_eq!(pagerank_stats.success_rate, 1.0);
792    }
793
794    #[test]
795    fn test_custom_trigger_evaluation() {
796        let graph = create_test_graph().unwrap();
797        let processor = EventDrivenProcessor::new(graph).unwrap();
798
799        let custom_trigger = CustomTrigger {
800            name: "rapid_growth".to_string(),
801            condition: TriggerCondition::RapidGrowth(0.5),
802        };
803
804        // Create anomaly that should trigger
805        let anomalies = vec![AnomalyEvent {
806            timestamp: 0,
807            anomaly_type: AnomalyType::EdgeBurst,
808            severity: 0.8,
809            description: "Test burst".to_string(),
810            affected_nodes: vec![],
811        }];
812
813        let changes = UpdateResult {
814            vertices_added: 0,
815            vertices_removed: 0,
816            edges_added: 0,
817            edges_removed: 0,
818            affected_components: vec![],
819            recomputation_needed: false,
820        };
821
822        assert!(processor.evaluate_custom_trigger(&custom_trigger, &changes, &anomalies).unwrap());
823    }
824
825    #[test]
826    fn test_event_history_management() {
827        let graph = create_test_graph().unwrap();
828        let mut processor = EventDrivenProcessor::new(graph).unwrap();
829        processor.max_history = 3; // Small limit for testing
830
831        // Add more events than the limit
832        for i in 0..5 {
833            let event = ProcessedEvent {
834                timestamp: i,
835                rule_name: format!("rule_{}", i),
836                trigger_type: "test".to_string(),
837                action_taken: "test".to_string(),
838                execution_time_ms: 100,
839                success: true,
840                details: HashMap::new(),
841            };
842            processor.record_event(event);
843        }
844
845        // Should only keep the last 3 events
846        assert_eq!(processor.event_history.len(), 3);
847        assert_eq!(processor.event_history[0].rule_name, "rule_2");
848        assert_eq!(processor.event_history[2].rule_name, "rule_4");
849    }
850}