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)]
674mod tests {
675 use super::*;
676
677 #[test]
678 fn test_no_drift_when_disabled() {
679 let config = DriftConfig::default();
680 let controller = DriftController::new(config, 42, 12);
681
682 let adjustments = controller.compute_adjustments(6);
683 assert!(!controller.is_enabled());
684 assert!((adjustments.amount_mean_multiplier - 1.0).abs() < 0.001);
685 assert!((adjustments.anomaly_rate_adjustment).abs() < 0.001);
686 }
687
688 #[test]
689 fn test_gradual_drift() {
690 let config = DriftConfig {
691 enabled: true,
692 amount_mean_drift: 0.02,
693 anomaly_rate_drift: 0.001,
694 drift_type: DriftType::Gradual,
695 ..Default::default()
696 };
697 let controller = DriftController::new(config, 42, 12);
698
699 let adj0 = controller.compute_adjustments(0);
701 assert!((adj0.amount_mean_multiplier - 1.0).abs() < 0.001);
702
703 let adj6 = controller.compute_adjustments(6);
705 assert!(adj6.amount_mean_multiplier > 1.10);
706 assert!(adj6.amount_mean_multiplier < 1.15);
707
708 let adj12 = controller.compute_adjustments(12);
710 assert!(adj12.amount_mean_multiplier > 1.20);
711 assert!(adj12.amount_mean_multiplier < 1.30);
712 }
713
714 #[test]
715 fn test_drift_start_period() {
716 let config = DriftConfig {
717 enabled: true,
718 amount_mean_drift: 0.02,
719 drift_start_period: 3,
720 drift_type: DriftType::Gradual,
721 ..Default::default()
722 };
723 let controller = DriftController::new(config, 42, 12);
724
725 let adj2 = controller.compute_adjustments(2);
727 assert!((adj2.amount_mean_multiplier - 1.0).abs() < 0.001);
728
729 let adj3 = controller.compute_adjustments(3);
731 assert!((adj3.amount_mean_multiplier - 1.0).abs() < 0.001);
732
733 let adj6 = controller.compute_adjustments(6);
735 assert!(adj6.amount_mean_multiplier > 1.0);
736 }
737
738 #[test]
739 fn test_seasonal_factor() {
740 let config = DriftConfig {
741 enabled: true,
742 seasonal_drift: true,
743 drift_type: DriftType::Gradual,
744 ..Default::default()
745 };
746 let controller = DriftController::new(config, 42, 12);
747
748 let adj_dec = controller.compute_adjustments(11);
750 assert!(adj_dec.seasonal_factor > 1.2);
751
752 let adj_jan = controller.compute_adjustments(0);
754 assert!(adj_jan.seasonal_factor < 0.9);
755 }
756
757 #[test]
758 fn test_sudden_drift_reproducibility() {
759 let config = DriftConfig {
760 enabled: true,
761 sudden_drift_probability: 0.5,
762 sudden_drift_magnitude: 1.5,
763 drift_type: DriftType::Sudden,
764 ..Default::default()
765 };
766
767 let controller1 = DriftController::new(config.clone(), 42, 12);
769 let controller2 = DriftController::new(config, 42, 12);
770
771 assert_eq!(
772 controller1.sudden_drift_periods(),
773 controller2.sudden_drift_periods()
774 );
775 }
776
777 #[test]
778 fn test_regime_change() {
779 let config = DriftConfig {
780 enabled: true,
781 drift_type: DriftType::Regime,
782 regime_changes: vec![RegimeChange::new(6, RegimeChangeType::Acquisition)],
783 ..Default::default()
784 };
785 let controller = DriftController::new(config, 42, 12);
786
787 let adj_before = controller.compute_adjustments(5);
789 assert!((adj_before.volume_multiplier - 1.0).abs() < 0.001);
790
791 let adj_after = controller.compute_adjustments(6);
793 assert!(adj_after.volume_multiplier > 1.3); assert!(adj_after.amount_mean_multiplier > 1.1); assert!(adj_after
796 .active_regime_changes
797 .contains(&RegimeChangeType::Acquisition));
798 }
799
800 #[test]
801 fn test_regime_change_gradual_transition() {
802 let config = DriftConfig {
803 enabled: true,
804 drift_type: DriftType::Regime,
805 regime_changes: vec![RegimeChange {
806 period: 6,
807 change_type: RegimeChangeType::PriceIncrease,
808 description: None,
809 effects: vec![],
810 transition_periods: 4, }],
812 ..Default::default()
813 };
814 let controller = DriftController::new(config, 42, 12);
815
816 let adj_start = controller.compute_adjustments(6);
818 let adj_mid = controller.compute_adjustments(8);
820 let adj_end = controller.compute_adjustments(10);
822
823 assert!(adj_start.amount_mean_multiplier < adj_mid.amount_mean_multiplier);
825 assert!(adj_mid.amount_mean_multiplier < adj_end.amount_mean_multiplier);
826 }
827
828 #[test]
829 fn test_economic_cycle() {
830 let config = DriftConfig {
831 enabled: true,
832 drift_type: DriftType::EconomicCycle,
833 economic_cycle: EconomicCycleConfig {
834 enabled: true,
835 cycle_length: 12, amplitude: 0.20,
837 phase_offset: 0,
838 recession_periods: vec![],
839 recession_severity: 0.75,
840 },
841 ..Default::default()
842 };
843 let controller = DriftController::new(config, 42, 24);
844
845 let adj_0 = controller.compute_adjustments(0);
847 let adj_3 = controller.compute_adjustments(3);
849 let adj_9 = controller.compute_adjustments(9);
851
852 assert!(adj_3.economic_cycle_factor > adj_0.economic_cycle_factor);
854 assert!(adj_9.economic_cycle_factor < adj_0.economic_cycle_factor);
856 }
857
858 #[test]
859 fn test_economic_cycle_recession() {
860 let config = DriftConfig {
861 enabled: true,
862 drift_type: DriftType::EconomicCycle,
863 economic_cycle: EconomicCycleConfig {
864 enabled: true,
865 cycle_length: 12,
866 amplitude: 0.10,
867 phase_offset: 0,
868 recession_periods: vec![6, 7, 8],
869 recession_severity: 0.70,
870 },
871 ..Default::default()
872 };
873 let controller = DriftController::new(config, 42, 12);
874
875 let adj_5 = controller.compute_adjustments(5);
877 assert!(!adj_5.in_recession);
878
879 let adj_7 = controller.compute_adjustments(7);
881 assert!(adj_7.in_recession);
882 assert!(adj_7.economic_cycle_factor < adj_5.economic_cycle_factor);
883 }
884
885 #[test]
886 fn test_parameter_drift_linear() {
887 let config = DriftConfig {
888 enabled: true,
889 drift_type: DriftType::Gradual,
890 parameter_drifts: vec![ParameterDrift {
891 parameter: "discount_rate".to_string(),
892 drift_type: ParameterDriftType::Linear,
893 initial_value: 0.02,
894 target_or_rate: 0.001, start_period: 0,
896 end_period: None,
897 steepness: 0.1,
898 }],
899 ..Default::default()
900 };
901 let controller = DriftController::new(config, 42, 12);
902
903 let adj_0 = controller.compute_adjustments(0);
904 let adj_6 = controller.compute_adjustments(6);
905
906 let rate_0 = adj_0.parameter_values.get("discount_rate").unwrap();
907 let rate_6 = adj_6.parameter_values.get("discount_rate").unwrap();
908
909 assert!((rate_0 - 0.02).abs() < 0.0001);
911 assert!((rate_6 - 0.026).abs() < 0.0001);
912 }
913
914 #[test]
915 fn test_parameter_drift_logistic() {
916 let config = DriftConfig {
917 enabled: true,
918 drift_type: DriftType::Gradual,
919 parameter_drifts: vec![ParameterDrift {
920 parameter: "market_share".to_string(),
921 drift_type: ParameterDriftType::Logistic,
922 initial_value: 0.10, target_or_rate: 0.40, start_period: 0,
925 end_period: Some(24), steepness: 0.3,
927 }],
928 ..Default::default()
929 };
930 let controller = DriftController::new(config, 42, 36);
931
932 let adj_0 = controller.compute_adjustments(0);
933 let adj_12 = controller.compute_adjustments(12);
934 let adj_24 = controller.compute_adjustments(24);
935
936 let share_0 = *adj_0.parameter_values.get("market_share").unwrap();
937 let share_12 = *adj_12.parameter_values.get("market_share").unwrap();
938 let share_24 = *adj_24.parameter_values.get("market_share").unwrap();
939
940 assert!(share_0 < 0.15); assert!(share_12 > 0.20 && share_12 < 0.30); assert!(share_24 > 0.35); }
945
946 #[test]
947 fn test_combined_drift_adjustments() {
948 let adj = DriftAdjustments {
949 amount_mean_multiplier: 1.2,
950 seasonal_factor: 1.1,
951 economic_cycle_factor: 0.9,
952 volume_multiplier: 1.3,
953 ..DriftAdjustments::none()
954 };
955
956 assert!((adj.combined_amount_multiplier() - 1.188).abs() < 0.001);
958
959 assert!((adj.combined_volume_multiplier() - 1.287).abs() < 0.001);
961 }
962
963 #[test]
964 fn test_regime_change_volume_multipliers() {
965 assert!(RegimeChange::new(0, RegimeChangeType::Acquisition).volume_multiplier() > 1.0);
966 assert!(RegimeChange::new(0, RegimeChangeType::Divestiture).volume_multiplier() < 1.0);
967 assert!(
968 (RegimeChange::new(0, RegimeChangeType::PolicyChange).volume_multiplier() - 1.0).abs()
969 < 0.001
970 );
971 }
972}