Skip to main content

ringkernel_procint/fabric/
anomaly_injection.rs

1//! Anomaly injection for synthetic process data.
2//!
3//! Provides configurable anomaly patterns for realistic test data.
4
5/// Anomaly configuration for event generation.
6#[derive(Debug, Clone)]
7pub struct AnomalyConfig {
8    /// Rate of bottleneck anomalies (0.0 - 1.0).
9    pub bottleneck_rate: f32,
10    /// Rate of rework loops (0.0 - 1.0).
11    pub rework_rate: f32,
12    /// Rate of long-running activities (0.0 - 1.0).
13    pub long_running_rate: f32,
14    /// Rate of skipped activities (0.0 - 1.0).
15    pub skip_rate: f32,
16    /// Bottleneck duration multiplier.
17    pub bottleneck_multiplier: f32,
18    /// Long-running duration multiplier.
19    pub long_running_multiplier: f32,
20    /// Maximum rework iterations.
21    pub max_rework_iterations: u32,
22}
23
24impl Default for AnomalyConfig {
25    fn default() -> Self {
26        Self {
27            bottleneck_rate: 0.05,
28            rework_rate: 0.03,
29            long_running_rate: 0.02,
30            skip_rate: 0.01,
31            bottleneck_multiplier: 5.0,
32            long_running_multiplier: 10.0,
33            max_rework_iterations: 3,
34        }
35    }
36}
37
38impl AnomalyConfig {
39    /// Create config with no anomalies (perfect conformance).
40    pub fn none() -> Self {
41        Self {
42            bottleneck_rate: 0.0,
43            rework_rate: 0.0,
44            long_running_rate: 0.0,
45            skip_rate: 0.0,
46            ..Default::default()
47        }
48    }
49
50    /// Create config with high anomaly rates for testing.
51    pub fn high() -> Self {
52        Self {
53            bottleneck_rate: 0.15,
54            rework_rate: 0.10,
55            long_running_rate: 0.08,
56            skip_rate: 0.05,
57            ..Default::default()
58        }
59    }
60
61    /// Total anomaly rate.
62    pub fn total_rate(&self) -> f32 {
63        self.bottleneck_rate + self.rework_rate + self.long_running_rate + self.skip_rate
64    }
65
66    /// Set bottleneck rate.
67    pub fn with_bottleneck_rate(mut self, rate: f32) -> Self {
68        self.bottleneck_rate = rate.clamp(0.0, 1.0);
69        self
70    }
71
72    /// Set rework rate.
73    pub fn with_rework_rate(mut self, rate: f32) -> Self {
74        self.rework_rate = rate.clamp(0.0, 1.0);
75        self
76    }
77
78    /// Set long-running rate.
79    pub fn with_long_running_rate(mut self, rate: f32) -> Self {
80        self.long_running_rate = rate.clamp(0.0, 1.0);
81        self
82    }
83
84    /// Set skip rate.
85    pub fn with_skip_rate(mut self, rate: f32) -> Self {
86        self.skip_rate = rate.clamp(0.0, 1.0);
87        self
88    }
89}
90
91/// Anomaly injector for modifying generated events.
92#[derive(Debug, Clone)]
93pub struct AnomalyInjector {
94    config: AnomalyConfig,
95}
96
97impl AnomalyInjector {
98    /// Create a new anomaly injector.
99    pub fn new(config: AnomalyConfig) -> Self {
100        Self { config }
101    }
102
103    /// Get the configuration.
104    pub fn config(&self) -> &AnomalyConfig {
105        &self.config
106    }
107
108    /// Check if bottleneck should be injected.
109    pub fn should_inject_bottleneck(&self, random: f32) -> bool {
110        random < self.config.bottleneck_rate
111    }
112
113    /// Check if rework should be injected.
114    pub fn should_inject_rework(&self, random: f32) -> bool {
115        random < self.config.bottleneck_rate + self.config.rework_rate
116            && random >= self.config.bottleneck_rate
117    }
118
119    /// Check if long-running should be injected.
120    pub fn should_inject_long_running(&self, random: f32) -> bool {
121        let threshold = self.config.bottleneck_rate + self.config.rework_rate;
122        random < threshold + self.config.long_running_rate && random >= threshold
123    }
124
125    /// Check if skip should be injected.
126    pub fn should_inject_skip(&self, random: f32) -> bool {
127        let threshold =
128            self.config.bottleneck_rate + self.config.rework_rate + self.config.long_running_rate;
129        random < threshold + self.config.skip_rate && random >= threshold
130    }
131
132    /// Apply bottleneck duration multiplier.
133    pub fn apply_bottleneck(&self, base_duration: u32) -> u32 {
134        (base_duration as f32 * self.config.bottleneck_multiplier) as u32
135    }
136
137    /// Apply long-running duration multiplier.
138    pub fn apply_long_running(&self, base_duration: u32) -> u32 {
139        (base_duration as f32 * self.config.long_running_multiplier) as u32
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_default_config() {
149        let config = AnomalyConfig::default();
150        assert!(config.total_rate() > 0.0);
151        assert!(config.total_rate() < 0.2);
152    }
153
154    #[test]
155    fn test_no_anomalies() {
156        let config = AnomalyConfig::none();
157        assert_eq!(config.total_rate(), 0.0);
158    }
159
160    #[test]
161    fn test_injector() {
162        let injector = AnomalyInjector::new(AnomalyConfig::default());
163
164        // Test bottleneck check
165        assert!(injector.should_inject_bottleneck(0.01));
166        assert!(!injector.should_inject_bottleneck(0.99));
167
168        // Test duration multiplier
169        let base = 1000;
170        let bottleneck = injector.apply_bottleneck(base);
171        assert!(bottleneck > base);
172    }
173}