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#[derive(Debug)]
10pub struct EventDrivenProcessor {
11 processor: IncrementalGraphProcessor,
13 change_detector: GraphChangeDetector,
14
15 pagerank: Option<StreamingPageRank>,
17 components: Option<StreamingConnectedComponents>,
18
19 event_rules: Vec<EventRule>,
21 event_history: Vec<ProcessedEvent>,
22 max_history: usize,
23
24 algorithm_stats: HashMap<String, AlgorithmStats>,
26}
27
28#[derive(Debug, Clone)]
30pub struct EventRule {
31 pub name: String,
32 pub trigger: EventTrigger,
33 pub action: EventAction,
34 pub priority: u8, pub enabled: bool,
36 pub cooldown_seconds: u64, pub last_triggered: Option<u64>,
38}
39
40#[derive(Debug, Clone)]
42pub enum EventTrigger {
43 VertexCountChange(ChangeThreshold),
45 EdgeCountChange(ChangeThreshold),
46 DensityChange(ChangeThreshold),
47 ComponentCountChange(usize), AnomalyDetected(Vec<AnomalyType>),
51 AnomalySeverity(f64), Periodic(u64), Custom(CustomTrigger),
58}
59
60#[derive(Debug, Clone)]
62pub struct ChangeThreshold {
63 pub absolute: Option<f64>,
64 pub relative: Option<f64>, }
66
67#[derive(Debug, Clone)]
69pub struct CustomTrigger {
70 pub name: String,
71 pub condition: TriggerCondition,
72}
73
74#[derive(Debug, Clone)]
76pub enum TriggerCondition {
77 HighDegreeNodes(usize, f64), RapidGrowth(f64), StructuralChange(f64), CommunityInstability(f64), }
82
83#[derive(Debug, Clone)]
85pub enum EventAction {
86 RunPageRank,
88 RunConnectedComponents,
89 RunAllAlgorithms,
90
91 PerformAnomalyAnalysis,
93 UpdateBaseline,
94
95 LogEvent,
97 TriggerAlert(AlertLevel),
98
99 Multiple(Vec<EventAction>),
101}
102
103#[derive(Debug, Clone, PartialEq)]
105pub enum AlertLevel {
106 Info,
107 Warning,
108 Critical,
109}
110
111#[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#[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 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 pub fn initialize(&mut self) -> Result<()> {
153 self.change_detector.initialize(&self.processor)?;
155
156 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 self.setup_default_rules();
167
168 Ok(())
169 }
170
171 fn setup_default_rules(&mut self) {
173 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), }),
180 action: EventAction::RunPageRank,
181 priority: 1,
182 enabled: true,
183 cooldown_seconds: 30,
184 last_triggered: None,
185 });
186
187 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), }),
194 action: EventAction::RunConnectedComponents,
195 priority: 1,
196 enabled: true,
197 cooldown_seconds: 20,
198 last_triggered: None,
199 });
200
201 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, enabled: true,
208 cooldown_seconds: 60,
209 last_triggered: None,
210 });
211
212 self.add_rule(EventRule {
214 name: "periodic_baseline_update".to_string(),
215 trigger: EventTrigger::Periodic(300), action: EventAction::UpdateBaseline,
217 priority: 2,
218 enabled: true,
219 cooldown_seconds: 0,
220 last_triggered: None,
221 });
222
223 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 pub fn add_rule(&mut self, rule: EventRule) {
241 self.event_rules.push(rule);
242 self.event_rules.sort_by_key(|r| r.priority);
244 }
245
246 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 let anomalies = self.change_detector.update(&self.processor, &changes)?;
257
258 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 if let Some(last_triggered) = rule.last_triggered {
268 if current_time < last_triggered + rule.cooldown_seconds {
269 continue;
270 }
271 }
272
273 if self.should_trigger_rule(rule, &changes, &anomalies, current_time)? {
275 rules_to_trigger.push(index);
276 }
277 }
278
279 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 let success = self.execute_action(&rule_action, &changes, &anomalies)?;
289
290 let execution_time = start_time.elapsed().as_millis() as u64;
291
292 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 self.event_rules[rule_index].last_triggered = Some(current_time);
308
309 self.update_algorithm_stats(&rule_action, execution_time, success);
311 }
312
313 Ok(triggered_events)
314 }
315
316 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 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) }
357 }
358
359 EventTrigger::Custom(custom) => {
360 self.evaluate_custom_trigger(custom, changes, anomalies)
361 }
362 }
363 }
364
365 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 triggered |= change >= relative * 100.0; }
378
379 Ok(triggered)
380 }
381
382 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 Ok(false) }
394
395 TriggerCondition::RapidGrowth(threshold) => {
396 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 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 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 Ok(true)
466 }
467
468 EventAction::UpdateBaseline => {
469 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 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 fn record_event(&mut self, event: ProcessedEvent) {
524 self.event_history.push(event);
525
526 while self.event_history.len() > self.max_history {
528 self.event_history.remove(0);
529 }
530 }
531
532 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 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 pub fn processor(&self) -> &IncrementalGraphProcessor {
571 &self.processor
572 }
573
574 pub fn processor_mut(&mut self) -> &mut IncrementalGraphProcessor {
576 &mut self.processor
577 }
578
579 pub fn pagerank_results(&self) -> Option<&HashMap<String, f64>> {
581 self.pagerank.as_ref().map(|pr| pr.get_result())
582 }
583
584 pub fn components_results(&self) -> Option<&HashMap<String, String>> {
586 self.components.as_ref().map(|cc| cc.get_result())
587 }
588
589 pub fn recent_events(&self, count: usize) -> Vec<&ProcessedEvent> {
591 self.event_history.iter().rev().take(count).collect()
592 }
593
594 pub fn algorithm_stats(&self) -> &HashMap<String, AlgorithmStats> {
596 &self.algorithm_stats
597 }
598
599 pub fn event_rules(&self) -> &[EventRule] {
601 &self.event_rules
602 }
603
604 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 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 let changes = UpdateResult {
706 vertices_added: 0,
707 vertices_removed: 0,
708 edges_added: 15, edges_removed: 0,
710 affected_components: vec![],
711 recomputation_needed: false,
712 };
713
714 let events = processor.update(changes).unwrap();
715
716 assert!(!events.is_empty());
718
719 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 processor.event_rules.clear();
737
738 let rule = EventRule {
740 name: "test_cooldown".to_string(),
741 trigger: EventTrigger::EdgeCountChange(ChangeThreshold {
742 absolute: Some(10.0), relative: None,
744 }),
745 action: EventAction::LogEvent,
746 priority: 1,
747 enabled: true,
748 cooldown_seconds: 60,
749 last_triggered: Some(current_time), };
751
752 processor.add_rule(rule);
753
754 let changes = UpdateResult {
755 vertices_added: 0,
756 vertices_removed: 0,
757 edges_added: 15, edges_removed: 0,
759 affected_components: vec![],
760 recomputation_needed: false,
761 };
762
763 let events = processor.update(changes.clone()).unwrap();
765 assert!(events.is_empty());
766
767 processor.event_rules[0].last_triggered = Some(current_time - 120); 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 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 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; 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 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}