1use crate::memory::error::{MemoryError, Result};
8use regex::Regex;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::sync::Arc;
12use std::time::{Duration, Instant};
13use tokio::sync::RwLock;
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
17pub enum TriggerEvent {
18 Security,
20 Error,
22 Performance,
24 BusinessCritical,
26 UserExperience,
28}
29
30impl TriggerEvent {
31 pub fn all_types() -> Vec<TriggerEvent> {
33 vec![
34 TriggerEvent::Security,
35 TriggerEvent::Error,
36 TriggerEvent::Performance,
37 TriggerEvent::BusinessCritical,
38 TriggerEvent::UserExperience,
39 ]
40 }
41
42 pub fn description(&self) -> &'static str {
44 match self {
45 TriggerEvent::Security => "Security vulnerabilities, threats, and incidents",
46 TriggerEvent::Error => "Error conditions and critical system failures",
47 TriggerEvent::Performance => "Performance bottlenecks and optimization needs",
48 TriggerEvent::BusinessCritical => "Strategic decisions and business-critical insights",
49 TriggerEvent::UserExperience => "User feedback and experience issues",
50 }
51 }
52
53 pub fn priority(&self) -> u8 {
55 match self {
56 TriggerEvent::Security => 100, TriggerEvent::Error => 90, TriggerEvent::Performance => 70, TriggerEvent::BusinessCritical => 80, TriggerEvent::UserExperience => 60, }
62 }
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct TriggerPattern {
68 pub regex: String,
70 #[serde(skip)]
72 pub compiled_regex: Option<Regex>,
73 pub keywords: Vec<String>,
75 pub context_boosters: Vec<String>,
77 pub confidence_threshold: f64,
79 pub enabled: bool,
81}
82
83impl TriggerPattern {
84 pub fn new(regex: String, keywords: Vec<String>) -> Result<Self> {
86 let compiled_regex = Some(
87 Regex::new(®ex)
88 .map_err(|e| MemoryError::Configuration(format!("Invalid regex pattern: {e}")))?,
89 );
90
91 Ok(TriggerPattern {
92 regex,
93 compiled_regex,
94 keywords,
95 context_boosters: Vec::new(),
96 confidence_threshold: 0.7,
97 enabled: true,
98 })
99 }
100
101 pub fn matches(&self, content: &str) -> bool {
103 if !self.enabled {
104 return false;
105 }
106
107 if let Some(ref regex) = self.compiled_regex {
109 if regex.is_match(content) {
110 return true;
111 }
112 }
113
114 let content_lower = content.to_lowercase();
116 self.keywords
117 .iter()
118 .any(|keyword| content_lower.contains(&keyword.to_lowercase()))
119 }
120
121 pub fn calculate_confidence(&self, content: &str) -> f64 {
123 if !self.matches(content) {
124 return 0.0;
125 }
126
127 let content_lower = content.to_lowercase();
128 let mut confidence = 0.4; let high_value_security_terms = [
132 "xss",
133 "injection",
134 "csrf",
135 "vulnerability",
136 "exploit",
137 "malware",
138 "phishing",
139 ];
140 let has_high_value_security = high_value_security_terms
141 .iter()
142 .any(|term| content_lower.contains(term));
143
144 let keyword_matches = self
146 .keywords
147 .iter()
148 .filter(|keyword| content_lower.contains(&keyword.to_lowercase()))
149 .count() as f64;
150 if keyword_matches > 0.0 {
151 confidence += 0.3 + (keyword_matches / self.keywords.len() as f64) * 0.2;
153
154 if has_high_value_security
156 && self
157 .keywords
158 .iter()
159 .any(|k| high_value_security_terms.contains(&k.as_str()))
160 {
161 confidence += 0.1;
162 }
163 }
164
165 let context_matches = self
167 .context_boosters
168 .iter()
169 .filter(|booster| content_lower.contains(&booster.to_lowercase()))
170 .count() as f64;
171 if !self.context_boosters.is_empty() && context_matches > 0.0 {
172 confidence += (context_matches / self.context_boosters.len() as f64) * 0.1;
173 }
174
175 confidence.min(1.0)
176 }
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct TriggerConfig {
182 pub patterns: HashMap<TriggerEvent, TriggerPattern>,
184 pub importance_multiplier: f64,
186 pub max_processing_time_ms: u64,
188 pub enable_ab_testing: bool,
190 pub user_customizations: HashMap<String, HashMap<TriggerEvent, TriggerPattern>>,
192}
193
194impl Default for TriggerConfig {
195 fn default() -> Self {
196 let mut patterns = HashMap::new();
197
198 if let Ok(mut security_pattern) = TriggerPattern::new(
200 r"(?i)(vulnerability|exploit|attack|breach|security|threat|malware|phishing|xss|injection|csrf)".to_string(),
201 vec![
202 "vulnerability".to_string(),
203 "exploit".to_string(),
204 "attack".to_string(),
205 "breach".to_string(),
206 "security".to_string(),
207 "threat".to_string(),
208 "malware".to_string(),
209 "phishing".to_string(),
210 "xss".to_string(),
211 "injection".to_string(),
212 "csrf".to_string(),
213 ]
214 ) {
215 security_pattern.confidence_threshold = 0.6; patterns.insert(TriggerEvent::Security, security_pattern);
217 }
218
219 if let Ok(mut error_pattern) = TriggerPattern::new(
221 r"(?i)(error|exception|failure|crash|panic|fatal|critical)".to_string(),
222 vec![
223 "error".to_string(),
224 "exception".to_string(),
225 "failure".to_string(),
226 "crash".to_string(),
227 "panic".to_string(),
228 "fatal".to_string(),
229 "critical".to_string(),
230 ],
231 ) {
232 error_pattern.confidence_threshold = 0.6;
233 patterns.insert(TriggerEvent::Error, error_pattern);
234 }
235
236 if let Ok(mut performance_pattern) = TriggerPattern::new(
238 r"(?i)(slow|latency|bottleneck|performance|optimization|memory leak|timeout)"
239 .to_string(),
240 vec![
241 "slow".to_string(),
242 "latency".to_string(),
243 "bottleneck".to_string(),
244 "performance".to_string(),
245 "optimization".to_string(),
246 ],
247 ) {
248 performance_pattern.confidence_threshold = 0.6;
249 patterns.insert(TriggerEvent::Performance, performance_pattern);
250 }
251
252 if let Ok(mut business_pattern) = TriggerPattern::new(
254 r"(?i)(revenue|profit|loss|critical|strategic|decision|customer|retention)".to_string(),
255 vec![
256 "revenue".to_string(),
257 "profit".to_string(),
258 "critical".to_string(),
259 "strategic".to_string(),
260 "decision".to_string(),
261 ],
262 ) {
263 business_pattern.confidence_threshold = 0.6;
264 patterns.insert(TriggerEvent::BusinessCritical, business_pattern);
265 }
266
267 if let Ok(mut ux_pattern) = TriggerPattern::new(
269 r"(?i)(user|usability|feedback|complaint|satisfaction|experience|ui|ux)".to_string(),
270 vec![
271 "user".to_string(),
272 "usability".to_string(),
273 "feedback".to_string(),
274 "complaint".to_string(),
275 "satisfaction".to_string(),
276 ],
277 ) {
278 ux_pattern.confidence_threshold = 0.6;
279 patterns.insert(TriggerEvent::UserExperience, ux_pattern);
280 }
281
282 TriggerConfig {
283 patterns,
284 importance_multiplier: 2.0,
285 max_processing_time_ms: 50,
286 enable_ab_testing: false,
287 user_customizations: HashMap::new(),
288 }
289 }
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize, Default)]
294pub struct TriggerMetrics {
295 pub triggers_by_type: HashMap<TriggerEvent, u64>,
297 pub processing_time_by_type: HashMap<TriggerEvent, Duration>,
299 pub accuracy_by_type: HashMap<TriggerEvent, f64>,
301 pub total_memories_processed: u64,
303 pub total_triggered_memories: u64,
305 pub average_processing_time: Duration,
307}
308
309#[derive(Debug, Clone)]
311pub struct TriggerDetectionResult {
312 pub triggered: bool,
314 pub trigger_type: Option<TriggerEvent>,
316 pub confidence: f64,
318 pub original_importance: f64,
320 pub boosted_importance: f64,
322 pub processing_time: Duration,
324}
325
326pub struct EventTriggeredScoringEngine {
328 config: Arc<RwLock<TriggerConfig>>,
329 metrics: Arc<RwLock<TriggerMetrics>>,
330}
331
332impl EventTriggeredScoringEngine {
333 pub fn new(config: TriggerConfig) -> Self {
335 Self {
336 config: Arc::new(RwLock::new(config)),
337 metrics: Arc::new(RwLock::new(TriggerMetrics::default())),
338 }
339 }
340
341 pub fn with_default_config() -> Self {
343 Self::new(TriggerConfig::default())
344 }
345
346 pub async fn analyze_content(
348 &self,
349 content: &str,
350 original_importance: f64,
351 user_id: Option<&str>,
352 ) -> Result<TriggerDetectionResult> {
353 let start_time = Instant::now();
354 let config = self.config.read().await;
355
356 let max_duration = Duration::from_millis(config.max_processing_time_ms);
358
359 let patterns = if let Some(user) = user_id {
361 config
362 .user_customizations
363 .get(user)
364 .unwrap_or(&config.patterns)
365 } else {
366 &config.patterns
367 };
368
369 let mut best_match: Option<(TriggerEvent, f64)> = None;
370
371 for (trigger_type, pattern) in patterns {
373 if start_time.elapsed() > max_duration {
375 break;
376 }
377
378 if pattern.matches(content) {
379 let confidence = pattern.calculate_confidence(content);
380 if confidence >= pattern.confidence_threshold {
381 if let Some((current_type, current_confidence)) = &best_match {
382 let confidence_diff = confidence - current_confidence;
384 let should_replace = if confidence_diff.abs() < 0.05 {
385 trigger_type.priority() > current_type.priority()
387 } else {
388 confidence > *current_confidence
390 };
391
392 if should_replace {
393 best_match = Some((trigger_type.clone(), confidence));
394 }
395 } else {
396 best_match = Some((trigger_type.clone(), confidence));
397 }
398 }
399 }
400 }
401
402 let processing_time = start_time.elapsed();
403
404 let result = if let Some((trigger_type, confidence)) = best_match {
406 let boosted_importance = original_importance * config.importance_multiplier;
407
408 self.update_metrics(&trigger_type, processing_time, true)
410 .await;
411
412 TriggerDetectionResult {
413 triggered: true,
414 trigger_type: Some(trigger_type),
415 confidence,
416 original_importance,
417 boosted_importance,
418 processing_time,
419 }
420 } else {
421 self.update_metrics_non_triggered(processing_time).await;
423
424 TriggerDetectionResult {
425 triggered: false,
426 trigger_type: None,
427 confidence: 0.0,
428 original_importance,
429 boosted_importance: original_importance,
430 processing_time,
431 }
432 };
433
434 Ok(result)
435 }
436
437 pub async fn update_config(&self, new_config: TriggerConfig) -> Result<()> {
439 let mut config = self.config.write().await;
440 *config = new_config;
441 Ok(())
442 }
443
444 pub async fn get_metrics(&self) -> TriggerMetrics {
446 self.metrics.read().await.clone()
447 }
448
449 pub async fn reset_metrics(&self) -> Result<()> {
451 let mut metrics = self.metrics.write().await;
452 *metrics = TriggerMetrics::default();
453 Ok(())
454 }
455
456 pub async fn add_user_customization(
458 &self,
459 user_id: String,
460 customizations: HashMap<TriggerEvent, TriggerPattern>,
461 ) -> Result<()> {
462 let mut config = self.config.write().await;
463 config.user_customizations.insert(user_id, customizations);
464 Ok(())
465 }
466
467 async fn update_metrics(
469 &self,
470 trigger_type: &TriggerEvent,
471 processing_time: Duration,
472 triggered: bool,
473 ) {
474 let mut metrics = self.metrics.write().await;
475
476 metrics.total_memories_processed += 1;
477 if triggered {
478 metrics.total_triggered_memories += 1;
479 *metrics
480 .triggers_by_type
481 .entry(trigger_type.clone())
482 .or_insert(0) += 1;
483 *metrics
484 .processing_time_by_type
485 .entry(trigger_type.clone())
486 .or_insert(Duration::ZERO) += processing_time;
487 }
488
489 let total_time = metrics.average_processing_time
491 * (metrics.total_memories_processed - 1) as u32
492 + processing_time;
493 metrics.average_processing_time = total_time / metrics.total_memories_processed as u32;
494 }
495
496 async fn update_metrics_non_triggered(&self, processing_time: Duration) {
497 let mut metrics = self.metrics.write().await;
498 metrics.total_memories_processed += 1;
499
500 let total_time = metrics.average_processing_time
502 * (metrics.total_memories_processed - 1) as u32
503 + processing_time;
504 metrics.average_processing_time = total_time / metrics.total_memories_processed as u32;
505 }
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511
512 #[test]
513 fn test_trigger_event_types() {
514 let all_types = TriggerEvent::all_types();
515 assert_eq!(all_types.len(), 5);
516 assert!(all_types.contains(&TriggerEvent::Security));
517 assert!(all_types.contains(&TriggerEvent::Error));
518 assert!(all_types.contains(&TriggerEvent::Performance));
519 assert!(all_types.contains(&TriggerEvent::BusinessCritical));
520 assert!(all_types.contains(&TriggerEvent::UserExperience));
521 }
522
523 #[test]
524 fn test_trigger_pattern_creation() {
525 let pattern = TriggerPattern::new(
526 r"(?i)(error|exception)".to_string(),
527 vec!["error".to_string(), "exception".to_string()],
528 )
529 .unwrap();
530
531 assert!(pattern.matches("An error occurred"));
532 assert!(pattern.matches("Exception thrown"));
533 assert!(!pattern.matches("Everything is fine"));
534 }
535
536 #[test]
537 fn test_confidence_calculation() {
538 let mut pattern = TriggerPattern::new(
539 r"(?i)(error|exception)".to_string(),
540 vec!["error".to_string(), "exception".to_string()],
541 )
542 .unwrap();
543
544 pattern.context_boosters = vec!["critical".to_string(), "fatal".to_string()];
545
546 let confidence1 = pattern.calculate_confidence("An error occurred");
547 let confidence2 = pattern.calculate_confidence("A critical error occurred");
548 let confidence3 = pattern.calculate_confidence("A critical fatal error occurred");
549
550 assert!(confidence2 > confidence1);
551 assert!(confidence3 > confidence2);
552 assert!(confidence1 > 0.0);
553 assert!(confidence3 <= 1.0);
554 }
555
556 #[tokio::test]
557 async fn test_event_triggered_scoring_engine() {
558 let engine = EventTriggeredScoringEngine::with_default_config();
559
560 let result = engine
562 .analyze_content(
563 "Security vulnerability detected in the authentication system",
564 0.5,
565 None,
566 )
567 .await
568 .unwrap();
569
570 assert!(result.triggered);
571 assert!(matches!(result.trigger_type, Some(TriggerEvent::Security)));
572 assert_eq!(result.boosted_importance, 1.0); assert!(result.confidence > 0.7);
574 assert!(result.processing_time.as_millis() < 50);
575 }
576
577 #[tokio::test]
578 async fn test_performance_within_limits() {
579 let engine = EventTriggeredScoringEngine::with_default_config();
580
581 let result = engine
582 .analyze_content(
583 "Performance bottleneck detected in database queries",
584 0.6,
585 None,
586 )
587 .await
588 .unwrap();
589
590 assert!(result.triggered);
591 assert!(matches!(
592 result.trigger_type,
593 Some(TriggerEvent::Performance)
594 ));
595 assert!(result.processing_time.as_millis() < 50);
596 }
597
598 #[tokio::test]
599 async fn test_metrics_tracking() {
600 let engine = EventTriggeredScoringEngine::with_default_config();
601
602 let _result1 = engine
604 .analyze_content("Error in system", 0.5, None)
605 .await
606 .unwrap();
607 let _result2 = engine
608 .analyze_content("Normal content", 0.5, None)
609 .await
610 .unwrap();
611 let _result3 = engine
612 .analyze_content("Security breach", 0.5, None)
613 .await
614 .unwrap();
615
616 let metrics = engine.get_metrics().await;
617 assert_eq!(metrics.total_memories_processed, 3);
618 assert_eq!(metrics.total_triggered_memories, 2);
619 assert!(metrics.triggers_by_type.contains_key(&TriggerEvent::Error));
620 assert!(metrics
621 .triggers_by_type
622 .contains_key(&TriggerEvent::Security));
623 }
624}