1use rand::prelude::*;
12use rand_chacha::ChaCha8Rng;
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
17#[serde(rename_all = "snake_case")]
18pub enum DriftType {
19 #[default]
21 Gradual,
22 Sudden,
24 Recurring,
26 Mixed,
28 Regime,
30 EconomicCycle,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "snake_case")]
37#[derive(Default)]
38pub enum RegimeChangeType {
39 Acquisition,
41 Divestiture,
43 PriceIncrease,
45 PriceDecrease,
47 ProductLaunch,
49 ProductDiscontinuation,
51 #[default]
53 PolicyChange,
54 CompetitorEntry,
56 Custom,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct RegimeEffect {
63 pub field: String,
65 pub multiplier: f64,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct RegimeChange {
72 pub period: u32,
74 pub change_type: RegimeChangeType,
76 #[serde(default)]
78 pub description: Option<String>,
79 #[serde(default)]
81 pub effects: Vec<RegimeEffect>,
82 #[serde(default)]
84 pub transition_periods: u32,
85}
86
87impl RegimeChange {
88 pub fn new(period: u32, change_type: RegimeChangeType) -> Self {
90 Self {
91 period,
92 change_type,
93 description: None,
94 effects: Vec::new(),
95 transition_periods: 0,
96 }
97 }
98
99 pub fn volume_multiplier(&self) -> f64 {
101 match self.change_type {
102 RegimeChangeType::Acquisition => 1.35,
103 RegimeChangeType::Divestiture => 0.70,
104 RegimeChangeType::PriceIncrease => 0.95,
105 RegimeChangeType::PriceDecrease => 1.10,
106 RegimeChangeType::ProductLaunch => 1.20,
107 RegimeChangeType::ProductDiscontinuation => 0.85,
108 RegimeChangeType::PolicyChange => 1.0,
109 RegimeChangeType::CompetitorEntry => 0.90,
110 RegimeChangeType::Custom => self
111 .effects
112 .iter()
113 .find(|e| e.field == "transaction_volume")
114 .map(|e| e.multiplier)
115 .unwrap_or(1.0),
116 }
117 }
118
119 pub fn amount_mean_multiplier(&self) -> f64 {
121 match self.change_type {
122 RegimeChangeType::Acquisition => 1.15,
123 RegimeChangeType::Divestiture => 0.90,
124 RegimeChangeType::PriceIncrease => 1.25,
125 RegimeChangeType::PriceDecrease => 0.80,
126 RegimeChangeType::ProductLaunch => 0.90, RegimeChangeType::ProductDiscontinuation => 1.10, RegimeChangeType::PolicyChange => 1.0,
129 RegimeChangeType::CompetitorEntry => 0.95,
130 RegimeChangeType::Custom => self
131 .effects
132 .iter()
133 .find(|e| e.field == "amount_mean")
134 .map(|e| e.multiplier)
135 .unwrap_or(1.0),
136 }
137 }
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct EconomicCycleConfig {
143 pub enabled: bool,
145 #[serde(default = "default_cycle_length")]
147 pub cycle_length: u32,
148 #[serde(default = "default_amplitude")]
150 pub amplitude: f64,
151 #[serde(default)]
153 pub phase_offset: u32,
154 #[serde(default)]
156 pub recession_periods: Vec<u32>,
157 #[serde(default = "default_recession_severity")]
159 pub recession_severity: f64,
160}
161
162fn default_cycle_length() -> u32 {
163 48 }
165
166fn default_amplitude() -> f64 {
167 0.15 }
169
170fn default_recession_severity() -> f64 {
171 0.75 }
173
174impl Default for EconomicCycleConfig {
175 fn default() -> Self {
176 Self {
177 enabled: false,
178 cycle_length: 48,
179 amplitude: 0.15,
180 phase_offset: 0,
181 recession_periods: Vec::new(),
182 recession_severity: 0.75,
183 }
184 }
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
189#[serde(rename_all = "snake_case")]
190pub enum ParameterDriftType {
191 #[default]
193 Linear,
194 Exponential,
196 Logistic,
198 Step,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct ParameterDrift {
205 pub parameter: String,
207 pub drift_type: ParameterDriftType,
209 pub initial_value: f64,
211 pub target_or_rate: f64,
213 #[serde(default)]
215 pub start_period: u32,
216 #[serde(default)]
218 pub end_period: Option<u32>,
219 #[serde(default = "default_steepness")]
221 pub steepness: f64,
222}
223
224fn default_steepness() -> f64 {
225 0.1
226}
227
228impl Default for ParameterDrift {
229 fn default() -> Self {
230 Self {
231 parameter: String::new(),
232 drift_type: ParameterDriftType::Linear,
233 initial_value: 1.0,
234 target_or_rate: 0.01,
235 start_period: 0,
236 end_period: None,
237 steepness: 0.1,
238 }
239 }
240}
241
242impl ParameterDrift {
243 pub fn value_at(&self, period: u32) -> f64 {
245 if period < self.start_period {
246 return self.initial_value;
247 }
248
249 let effective_period = period - self.start_period;
250
251 match self.drift_type {
252 ParameterDriftType::Linear => {
253 self.initial_value + self.target_or_rate * (effective_period as f64)
254 }
255 ParameterDriftType::Exponential => {
256 self.initial_value * (1.0 + self.target_or_rate).powi(effective_period as i32)
257 }
258 ParameterDriftType::Logistic => {
259 let end_period = self.end_period.unwrap_or(self.start_period + 24);
260 let midpoint = (self.start_period + end_period) as f64 / 2.0;
261 let t = period as f64;
262 let range = self.target_or_rate - self.initial_value;
263 self.initial_value + range / (1.0 + (-self.steepness * (t - midpoint)).exp())
264 }
265 ParameterDriftType::Step => {
266 if let Some(end) = self.end_period {
267 if period >= end {
268 return self.target_or_rate;
269 }
270 }
271 self.initial_value
272 }
273 }
274 }
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct DriftConfig {
280 pub enabled: bool,
282 pub amount_mean_drift: f64,
284 pub amount_variance_drift: f64,
286 pub anomaly_rate_drift: f64,
288 pub concept_drift_rate: f64,
290 pub sudden_drift_probability: f64,
292 pub sudden_drift_magnitude: f64,
294 pub seasonal_drift: bool,
296 pub drift_start_period: u32,
298 pub drift_type: DriftType,
300 #[serde(default)]
302 pub regime_changes: Vec<RegimeChange>,
303 #[serde(default)]
305 pub economic_cycle: EconomicCycleConfig,
306 #[serde(default)]
308 pub parameter_drifts: Vec<ParameterDrift>,
309}
310
311impl Default for DriftConfig {
312 fn default() -> Self {
313 Self {
314 enabled: false,
315 amount_mean_drift: 0.02,
316 amount_variance_drift: 0.0,
317 anomaly_rate_drift: 0.0,
318 concept_drift_rate: 0.01,
319 sudden_drift_probability: 0.0,
320 sudden_drift_magnitude: 2.0,
321 seasonal_drift: false,
322 drift_start_period: 0,
323 drift_type: DriftType::Gradual,
324 regime_changes: Vec::new(),
325 economic_cycle: EconomicCycleConfig::default(),
326 parameter_drifts: Vec::new(),
327 }
328 }
329}
330
331impl DriftConfig {
332 pub fn with_regime_changes(regime_changes: Vec<RegimeChange>) -> Self {
334 Self {
335 enabled: true,
336 drift_type: DriftType::Regime,
337 regime_changes,
338 ..Default::default()
339 }
340 }
341
342 pub fn with_economic_cycle(cycle_config: EconomicCycleConfig) -> Self {
344 Self {
345 enabled: true,
346 drift_type: DriftType::EconomicCycle,
347 economic_cycle: cycle_config,
348 ..Default::default()
349 }
350 }
351}
352
353#[derive(Debug, Clone, Default)]
355pub struct DriftAdjustments {
356 pub amount_mean_multiplier: f64,
358 pub amount_variance_multiplier: f64,
360 pub anomaly_rate_adjustment: f64,
362 pub concept_drift_factor: f64,
364 pub sudden_drift_occurred: bool,
366 pub seasonal_factor: f64,
368 pub volume_multiplier: f64,
370 pub economic_cycle_factor: f64,
372 pub in_recession: bool,
374 pub active_regime_changes: Vec<RegimeChangeType>,
376 pub parameter_values: std::collections::HashMap<String, f64>,
378}
379
380impl DriftAdjustments {
381 pub fn none() -> Self {
383 Self {
384 amount_mean_multiplier: 1.0,
385 amount_variance_multiplier: 1.0,
386 anomaly_rate_adjustment: 0.0,
387 concept_drift_factor: 0.0,
388 sudden_drift_occurred: false,
389 seasonal_factor: 1.0,
390 volume_multiplier: 1.0,
391 economic_cycle_factor: 1.0,
392 in_recession: false,
393 active_regime_changes: Vec::new(),
394 parameter_values: std::collections::HashMap::new(),
395 }
396 }
397
398 pub fn combined_amount_multiplier(&self) -> f64 {
400 self.amount_mean_multiplier * self.seasonal_factor * self.economic_cycle_factor
401 }
402
403 pub fn combined_volume_multiplier(&self) -> f64 {
405 self.volume_multiplier * self.seasonal_factor * self.economic_cycle_factor
406 }
407}
408
409pub struct DriftController {
411 config: DriftConfig,
412 rng: ChaCha8Rng,
413 sudden_drift_periods: Vec<u32>,
415 total_periods: u32,
417}
418
419impl DriftController {
420 pub fn new(config: DriftConfig, seed: u64, total_periods: u32) -> Self {
422 let mut controller = Self {
423 config,
424 rng: ChaCha8Rng::seed_from_u64(seed),
425 sudden_drift_periods: Vec::new(),
426 total_periods,
427 };
428
429 if controller.config.enabled
431 && (controller.config.drift_type == DriftType::Sudden
432 || controller.config.drift_type == DriftType::Mixed)
433 {
434 controller.precompute_sudden_drifts();
435 }
436
437 controller
438 }
439
440 fn precompute_sudden_drifts(&mut self) {
442 for period in 0..self.total_periods {
443 if period >= self.config.drift_start_period
444 && self.rng.gen::<f64>() < self.config.sudden_drift_probability
445 {
446 self.sudden_drift_periods.push(period);
447 }
448 }
449 }
450
451 pub fn is_enabled(&self) -> bool {
453 self.config.enabled
454 }
455
456 pub fn compute_adjustments(&self, period: u32) -> DriftAdjustments {
458 if !self.config.enabled {
459 return DriftAdjustments::none();
460 }
461
462 if period < self.config.drift_start_period {
464 return DriftAdjustments::none();
465 }
466
467 let effective_period = period - self.config.drift_start_period;
468 let mut adjustments = DriftAdjustments::none();
469
470 match self.config.drift_type {
471 DriftType::Gradual => {
472 self.apply_gradual_drift(&mut adjustments, effective_period);
473 }
474 DriftType::Sudden => {
475 self.apply_sudden_drift(&mut adjustments, period);
476 }
477 DriftType::Recurring => {
478 self.apply_recurring_drift(&mut adjustments, effective_period);
479 }
480 DriftType::Mixed => {
481 self.apply_gradual_drift(&mut adjustments, effective_period);
483 self.apply_sudden_drift(&mut adjustments, period);
484 }
485 DriftType::Regime => {
486 self.apply_regime_drift(&mut adjustments, period);
487 }
488 DriftType::EconomicCycle => {
489 self.apply_economic_cycle(&mut adjustments, period);
490 }
491 }
492
493 if self.config.seasonal_drift {
495 adjustments.seasonal_factor = self.compute_seasonal_factor(period);
496 }
497
498 self.apply_parameter_drifts(&mut adjustments, period);
500
501 adjustments
502 }
503
504 fn apply_gradual_drift(&self, adjustments: &mut DriftAdjustments, effective_period: u32) {
506 let p = effective_period as f64;
507
508 adjustments.amount_mean_multiplier = (1.0 + self.config.amount_mean_drift).powf(p);
510
511 adjustments.amount_variance_multiplier = (1.0 + self.config.amount_variance_drift).powf(p);
512
513 adjustments.anomaly_rate_adjustment = self.config.anomaly_rate_drift * p;
515
516 adjustments.concept_drift_factor = (self.config.concept_drift_rate * p).min(1.0);
518 }
519
520 fn apply_sudden_drift(&self, adjustments: &mut DriftAdjustments, period: u32) {
522 let events_occurred: usize = self
524 .sudden_drift_periods
525 .iter()
526 .filter(|&&p| p <= period)
527 .count();
528
529 if events_occurred > 0 {
530 adjustments.sudden_drift_occurred = self.sudden_drift_periods.contains(&period);
531
532 let cumulative_magnitude = self
534 .config
535 .sudden_drift_magnitude
536 .powi(events_occurred as i32);
537
538 adjustments.amount_mean_multiplier *= cumulative_magnitude;
539 adjustments.amount_variance_multiplier *= cumulative_magnitude.sqrt();
540 }
542 }
543
544 fn apply_recurring_drift(&self, adjustments: &mut DriftAdjustments, effective_period: u32) {
546 let cycle_position = (effective_period % 12) as f64;
548 let cycle_radians = (cycle_position / 12.0) * 2.0 * std::f64::consts::PI;
549
550 let seasonal_amplitude = self.config.concept_drift_rate;
552 adjustments.amount_mean_multiplier = 1.0 + seasonal_amplitude * cycle_radians.sin();
553
554 adjustments.amount_variance_multiplier =
556 1.0 + (seasonal_amplitude * 0.5) * (cycle_radians + std::f64::consts::FRAC_PI_2).sin();
557 }
558
559 fn compute_seasonal_factor(&self, period: u32) -> f64 {
561 let month = period % 12;
563
564 match month {
566 0 | 1 => 0.85, 2 => 0.90, 3 | 4 => 0.95, 5 => 1.0, 6 | 7 => 0.95, 8 => 1.0, 9 => 1.10, 10 => 1.20, 11 => 1.30, _ => 1.0,
576 }
577 }
578
579 pub fn sudden_drift_periods(&self) -> &[u32] {
581 &self.sudden_drift_periods
582 }
583
584 pub fn config(&self) -> &DriftConfig {
586 &self.config
587 }
588
589 fn apply_regime_drift(&self, adjustments: &mut DriftAdjustments, period: u32) {
591 let mut volume_mult = 1.0;
592 let mut amount_mult = 1.0;
593
594 for regime_change in &self.config.regime_changes {
595 if period >= regime_change.period {
596 let periods_since = period - regime_change.period;
598 let transition_factor = if regime_change.transition_periods == 0 {
599 1.0
600 } else {
601 (periods_since as f64 / regime_change.transition_periods as f64).min(1.0)
602 };
603
604 let vol_change = regime_change.volume_multiplier() - 1.0;
606 let amt_change = regime_change.amount_mean_multiplier() - 1.0;
607
608 volume_mult *= 1.0 + vol_change * transition_factor;
609 amount_mult *= 1.0 + amt_change * transition_factor;
610
611 adjustments
612 .active_regime_changes
613 .push(regime_change.change_type);
614 }
615 }
616
617 adjustments.volume_multiplier = volume_mult;
618 adjustments.amount_mean_multiplier *= amount_mult;
619 }
620
621 fn apply_economic_cycle(&self, adjustments: &mut DriftAdjustments, period: u32) {
623 let cycle = &self.config.economic_cycle;
624 if !cycle.enabled {
625 return;
626 }
627
628 let adjusted_period = period + cycle.phase_offset;
630 let cycle_position =
631 (adjusted_period % cycle.cycle_length) as f64 / cycle.cycle_length as f64;
632
633 let cycle_radians = cycle_position * 2.0 * std::f64::consts::PI;
635 let cycle_factor = 1.0 + cycle.amplitude * cycle_radians.sin();
636
637 let in_recession = cycle.recession_periods.contains(&period);
639 adjustments.in_recession = in_recession;
640
641 let final_factor = if in_recession {
643 cycle_factor * cycle.recession_severity
644 } else {
645 cycle_factor
646 };
647
648 adjustments.economic_cycle_factor = final_factor;
649 adjustments.amount_mean_multiplier *= final_factor;
650 adjustments.volume_multiplier = final_factor;
651 }
652
653 fn apply_parameter_drifts(&self, adjustments: &mut DriftAdjustments, period: u32) {
655 for param_drift in &self.config.parameter_drifts {
656 let value = param_drift.value_at(period);
657 adjustments
658 .parameter_values
659 .insert(param_drift.parameter.clone(), value);
660 }
661 }
662
663 pub fn regime_changes_until(&self, period: u32) -> Vec<&RegimeChange> {
665 self.config
666 .regime_changes
667 .iter()
668 .filter(|rc| rc.period <= period)
669 .collect()
670 }
671}
672
673#[cfg(test)]
674#[allow(clippy::unwrap_used)]
675mod tests {
676 use super::*;
677
678 #[test]
679 fn test_no_drift_when_disabled() {
680 let config = DriftConfig::default();
681 let controller = DriftController::new(config, 42, 12);
682
683 let adjustments = controller.compute_adjustments(6);
684 assert!(!controller.is_enabled());
685 assert!((adjustments.amount_mean_multiplier - 1.0).abs() < 0.001);
686 assert!((adjustments.anomaly_rate_adjustment).abs() < 0.001);
687 }
688
689 #[test]
690 fn test_gradual_drift() {
691 let config = DriftConfig {
692 enabled: true,
693 amount_mean_drift: 0.02,
694 anomaly_rate_drift: 0.001,
695 drift_type: DriftType::Gradual,
696 ..Default::default()
697 };
698 let controller = DriftController::new(config, 42, 12);
699
700 let adj0 = controller.compute_adjustments(0);
702 assert!((adj0.amount_mean_multiplier - 1.0).abs() < 0.001);
703
704 let adj6 = controller.compute_adjustments(6);
706 assert!(adj6.amount_mean_multiplier > 1.10);
707 assert!(adj6.amount_mean_multiplier < 1.15);
708
709 let adj12 = controller.compute_adjustments(12);
711 assert!(adj12.amount_mean_multiplier > 1.20);
712 assert!(adj12.amount_mean_multiplier < 1.30);
713 }
714
715 #[test]
716 fn test_drift_start_period() {
717 let config = DriftConfig {
718 enabled: true,
719 amount_mean_drift: 0.02,
720 drift_start_period: 3,
721 drift_type: DriftType::Gradual,
722 ..Default::default()
723 };
724 let controller = DriftController::new(config, 42, 12);
725
726 let adj2 = controller.compute_adjustments(2);
728 assert!((adj2.amount_mean_multiplier - 1.0).abs() < 0.001);
729
730 let adj3 = controller.compute_adjustments(3);
732 assert!((adj3.amount_mean_multiplier - 1.0).abs() < 0.001);
733
734 let adj6 = controller.compute_adjustments(6);
736 assert!(adj6.amount_mean_multiplier > 1.0);
737 }
738
739 #[test]
740 fn test_seasonal_factor() {
741 let config = DriftConfig {
742 enabled: true,
743 seasonal_drift: true,
744 drift_type: DriftType::Gradual,
745 ..Default::default()
746 };
747 let controller = DriftController::new(config, 42, 12);
748
749 let adj_dec = controller.compute_adjustments(11);
751 assert!(adj_dec.seasonal_factor > 1.2);
752
753 let adj_jan = controller.compute_adjustments(0);
755 assert!(adj_jan.seasonal_factor < 0.9);
756 }
757
758 #[test]
759 fn test_sudden_drift_reproducibility() {
760 let config = DriftConfig {
761 enabled: true,
762 sudden_drift_probability: 0.5,
763 sudden_drift_magnitude: 1.5,
764 drift_type: DriftType::Sudden,
765 ..Default::default()
766 };
767
768 let controller1 = DriftController::new(config.clone(), 42, 12);
770 let controller2 = DriftController::new(config, 42, 12);
771
772 assert_eq!(
773 controller1.sudden_drift_periods(),
774 controller2.sudden_drift_periods()
775 );
776 }
777
778 #[test]
779 fn test_regime_change() {
780 let config = DriftConfig {
781 enabled: true,
782 drift_type: DriftType::Regime,
783 regime_changes: vec![RegimeChange::new(6, RegimeChangeType::Acquisition)],
784 ..Default::default()
785 };
786 let controller = DriftController::new(config, 42, 12);
787
788 let adj_before = controller.compute_adjustments(5);
790 assert!((adj_before.volume_multiplier - 1.0).abs() < 0.001);
791
792 let adj_after = controller.compute_adjustments(6);
794 assert!(adj_after.volume_multiplier > 1.3); assert!(adj_after.amount_mean_multiplier > 1.1); assert!(adj_after
797 .active_regime_changes
798 .contains(&RegimeChangeType::Acquisition));
799 }
800
801 #[test]
802 fn test_regime_change_gradual_transition() {
803 let config = DriftConfig {
804 enabled: true,
805 drift_type: DriftType::Regime,
806 regime_changes: vec![RegimeChange {
807 period: 6,
808 change_type: RegimeChangeType::PriceIncrease,
809 description: None,
810 effects: vec![],
811 transition_periods: 4, }],
813 ..Default::default()
814 };
815 let controller = DriftController::new(config, 42, 12);
816
817 let adj_start = controller.compute_adjustments(6);
819 let adj_mid = controller.compute_adjustments(8);
821 let adj_end = controller.compute_adjustments(10);
823
824 assert!(adj_start.amount_mean_multiplier < adj_mid.amount_mean_multiplier);
826 assert!(adj_mid.amount_mean_multiplier < adj_end.amount_mean_multiplier);
827 }
828
829 #[test]
830 fn test_economic_cycle() {
831 let config = DriftConfig {
832 enabled: true,
833 drift_type: DriftType::EconomicCycle,
834 economic_cycle: EconomicCycleConfig {
835 enabled: true,
836 cycle_length: 12, amplitude: 0.20,
838 phase_offset: 0,
839 recession_periods: vec![],
840 recession_severity: 0.75,
841 },
842 ..Default::default()
843 };
844 let controller = DriftController::new(config, 42, 24);
845
846 let adj_0 = controller.compute_adjustments(0);
848 let adj_3 = controller.compute_adjustments(3);
850 let adj_9 = controller.compute_adjustments(9);
852
853 assert!(adj_3.economic_cycle_factor > adj_0.economic_cycle_factor);
855 assert!(adj_9.economic_cycle_factor < adj_0.economic_cycle_factor);
857 }
858
859 #[test]
860 fn test_economic_cycle_recession() {
861 let config = DriftConfig {
862 enabled: true,
863 drift_type: DriftType::EconomicCycle,
864 economic_cycle: EconomicCycleConfig {
865 enabled: true,
866 cycle_length: 12,
867 amplitude: 0.10,
868 phase_offset: 0,
869 recession_periods: vec![6, 7, 8],
870 recession_severity: 0.70,
871 },
872 ..Default::default()
873 };
874 let controller = DriftController::new(config, 42, 12);
875
876 let adj_5 = controller.compute_adjustments(5);
878 assert!(!adj_5.in_recession);
879
880 let adj_7 = controller.compute_adjustments(7);
882 assert!(adj_7.in_recession);
883 assert!(adj_7.economic_cycle_factor < adj_5.economic_cycle_factor);
884 }
885
886 #[test]
887 fn test_parameter_drift_linear() {
888 let config = DriftConfig {
889 enabled: true,
890 drift_type: DriftType::Gradual,
891 parameter_drifts: vec![ParameterDrift {
892 parameter: "discount_rate".to_string(),
893 drift_type: ParameterDriftType::Linear,
894 initial_value: 0.02,
895 target_or_rate: 0.001, start_period: 0,
897 end_period: None,
898 steepness: 0.1,
899 }],
900 ..Default::default()
901 };
902 let controller = DriftController::new(config, 42, 12);
903
904 let adj_0 = controller.compute_adjustments(0);
905 let adj_6 = controller.compute_adjustments(6);
906
907 let rate_0 = adj_0.parameter_values.get("discount_rate").unwrap();
908 let rate_6 = adj_6.parameter_values.get("discount_rate").unwrap();
909
910 assert!((rate_0 - 0.02).abs() < 0.0001);
912 assert!((rate_6 - 0.026).abs() < 0.0001);
913 }
914
915 #[test]
916 fn test_parameter_drift_logistic() {
917 let config = DriftConfig {
918 enabled: true,
919 drift_type: DriftType::Gradual,
920 parameter_drifts: vec![ParameterDrift {
921 parameter: "market_share".to_string(),
922 drift_type: ParameterDriftType::Logistic,
923 initial_value: 0.10, target_or_rate: 0.40, start_period: 0,
926 end_period: Some(24), steepness: 0.3,
928 }],
929 ..Default::default()
930 };
931 let controller = DriftController::new(config, 42, 36);
932
933 let adj_0 = controller.compute_adjustments(0);
934 let adj_12 = controller.compute_adjustments(12);
935 let adj_24 = controller.compute_adjustments(24);
936
937 let share_0 = *adj_0.parameter_values.get("market_share").unwrap();
938 let share_12 = *adj_12.parameter_values.get("market_share").unwrap();
939 let share_24 = *adj_24.parameter_values.get("market_share").unwrap();
940
941 assert!(share_0 < 0.15); assert!(share_12 > 0.20 && share_12 < 0.30); assert!(share_24 > 0.35); }
946
947 #[test]
948 fn test_combined_drift_adjustments() {
949 let adj = DriftAdjustments {
950 amount_mean_multiplier: 1.2,
951 seasonal_factor: 1.1,
952 economic_cycle_factor: 0.9,
953 volume_multiplier: 1.3,
954 ..DriftAdjustments::none()
955 };
956
957 assert!((adj.combined_amount_multiplier() - 1.188).abs() < 0.001);
959
960 assert!((adj.combined_volume_multiplier() - 1.287).abs() < 0.001);
962 }
963
964 #[test]
965 fn test_regime_change_volume_multipliers() {
966 assert!(RegimeChange::new(0, RegimeChangeType::Acquisition).volume_multiplier() > 1.0);
967 assert!(RegimeChange::new(0, RegimeChangeType::Divestiture).volume_multiplier() < 1.0);
968 assert!(
969 (RegimeChange::new(0, RegimeChangeType::PolicyChange).volume_multiplier() - 1.0).abs()
970 < 0.001
971 );
972 }
973}