Skip to main content

datasynth_core/models/
technology_transition.rs

1//! Technology transition models for pattern drift simulation.
2//!
3//! Provides comprehensive technology change modeling including:
4//! - ERP migrations with cutover phases
5//! - Module implementations
6//! - Integration upgrades
7
8use chrono::NaiveDate;
9use serde::{Deserialize, Serialize};
10
11/// Technology transition event type with associated configuration.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(tag = "type", rename_all = "snake_case")]
14pub enum TechnologyTransitionType {
15    /// Full ERP system migration.
16    ErpMigration(ErpMigrationConfig),
17    /// New module implementation.
18    ModuleImplementation(ModuleImplementationConfig),
19    /// Integration or interface upgrade.
20    IntegrationUpgrade(IntegrationUpgradeConfig),
21}
22
23impl TechnologyTransitionType {
24    /// Get the event type name.
25    pub fn type_name(&self) -> &'static str {
26        match self {
27            Self::ErpMigration(_) => "erp_migration",
28            Self::ModuleImplementation(_) => "module_implementation",
29            Self::IntegrationUpgrade(_) => "integration_upgrade",
30        }
31    }
32
33    /// Get the error rate impact for this transition.
34    pub fn error_rate_impact(&self) -> f64 {
35        match self {
36            Self::ErpMigration(c) => c.migration_issues.combined_error_rate(),
37            Self::ModuleImplementation(c) => c.implementation_error_rate,
38            Self::IntegrationUpgrade(c) => c.transition_error_rate,
39        }
40    }
41
42    /// Get the transition duration in months.
43    pub fn transition_months(&self) -> u32 {
44        match self {
45            Self::ErpMigration(c) => c.phases.total_duration_months(),
46            Self::ModuleImplementation(c) => c.rollout_months,
47            Self::IntegrationUpgrade(c) => c.transition_months,
48        }
49    }
50}
51
52/// Configuration for ERP migration.
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ErpMigrationConfig {
55    /// Source system identifier.
56    pub source_system: String,
57    /// Target system identifier.
58    pub target_system: String,
59    /// Migration phases configuration.
60    #[serde(default)]
61    pub phases: MigrationPhases,
62    /// Migration issue configuration.
63    #[serde(default)]
64    pub migration_issues: MigrationIssueConfig,
65    /// Data migration strategy.
66    #[serde(default)]
67    pub data_migration_strategy: DataMigrationStrategy,
68    /// Entities being migrated.
69    #[serde(default)]
70    pub migrated_entities: Vec<String>,
71    /// Legacy system decommission date.
72    #[serde(default)]
73    pub decommission_date: Option<NaiveDate>,
74}
75
76impl Default for ErpMigrationConfig {
77    fn default() -> Self {
78        Self {
79            source_system: "SAP_R3".to_string(),
80            target_system: "SAP_S4HANA".to_string(),
81            phases: MigrationPhases::default(),
82            migration_issues: MigrationIssueConfig::default(),
83            data_migration_strategy: DataMigrationStrategy::BigBang,
84            migrated_entities: Vec::new(),
85            decommission_date: None,
86        }
87    }
88}
89
90/// Migration phases configuration.
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct MigrationPhases {
93    /// Preparation phase start.
94    #[serde(default)]
95    pub preparation_start: Option<NaiveDate>,
96    /// Data migration start.
97    #[serde(default)]
98    pub data_migration_start: Option<NaiveDate>,
99    /// Parallel run period start.
100    #[serde(default)]
101    pub parallel_run_start: Option<NaiveDate>,
102    /// Cutover date (go-live).
103    pub cutover_date: NaiveDate,
104    /// Stabilization end date.
105    pub stabilization_end: NaiveDate,
106    /// Hypercare end date.
107    #[serde(default)]
108    pub hypercare_end: Option<NaiveDate>,
109}
110
111impl Default for MigrationPhases {
112    fn default() -> Self {
113        Self {
114            preparation_start: Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
115            data_migration_start: Some(NaiveDate::from_ymd_opt(2024, 6, 1).unwrap()),
116            parallel_run_start: Some(NaiveDate::from_ymd_opt(2024, 8, 1).unwrap()),
117            cutover_date: NaiveDate::from_ymd_opt(2024, 9, 1).unwrap(),
118            stabilization_end: NaiveDate::from_ymd_opt(2024, 11, 30).unwrap(),
119            hypercare_end: Some(NaiveDate::from_ymd_opt(2024, 12, 31).unwrap()),
120        }
121    }
122}
123
124impl MigrationPhases {
125    /// Calculate total migration duration in months.
126    pub fn total_duration_months(&self) -> u32 {
127        let start = self.preparation_start.unwrap_or(self.cutover_date);
128        let end = self.hypercare_end.unwrap_or(self.stabilization_end);
129        let days = (end - start).num_days();
130        (days / 30) as u32
131    }
132
133    /// Get the current migration phase for a given date.
134    pub fn phase_at(&self, date: NaiveDate) -> MigrationPhase {
135        if let Some(prep) = self.preparation_start {
136            if date < prep {
137                return MigrationPhase::PreMigration;
138            }
139        }
140
141        if let Some(data_mig) = self.data_migration_start {
142            if date < data_mig {
143                return MigrationPhase::Preparation;
144            }
145        }
146
147        if let Some(parallel) = self.parallel_run_start {
148            if date < parallel {
149                return MigrationPhase::DataMigration;
150            }
151            if date < self.cutover_date {
152                return MigrationPhase::ParallelRun;
153            }
154        }
155
156        if date < self.cutover_date {
157            return MigrationPhase::DataMigration;
158        }
159
160        if date < self.stabilization_end {
161            return MigrationPhase::Stabilization;
162        }
163
164        if let Some(hypercare) = self.hypercare_end {
165            if date < hypercare {
166                return MigrationPhase::Hypercare;
167            }
168        }
169
170        MigrationPhase::Complete
171    }
172}
173
174/// Migration phase stage.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
176#[serde(rename_all = "snake_case")]
177pub enum MigrationPhase {
178    /// Before migration starts.
179    PreMigration,
180    /// Preparation and planning.
181    Preparation,
182    /// Data migration in progress.
183    DataMigration,
184    /// Both systems running in parallel.
185    ParallelRun,
186    /// Post-cutover stabilization.
187    Stabilization,
188    /// Hypercare period.
189    Hypercare,
190    /// Migration complete.
191    Complete,
192}
193
194impl MigrationPhase {
195    /// Get the typical error rate multiplier for this phase.
196    pub fn error_rate_multiplier(&self) -> f64 {
197        match self {
198            Self::PreMigration => 1.0,
199            Self::Preparation => 1.0,
200            Self::DataMigration => 1.5,
201            Self::ParallelRun => 2.0,
202            Self::Stabilization => 1.8,
203            Self::Hypercare => 1.3,
204            Self::Complete => 1.0,
205        }
206    }
207
208    /// Get the typical processing time multiplier.
209    pub fn processing_time_multiplier(&self) -> f64 {
210        match self {
211            Self::PreMigration => 1.0,
212            Self::Preparation => 1.0,
213            Self::DataMigration => 1.2,
214            Self::ParallelRun => 1.5, // Dual entry
215            Self::Stabilization => 1.3,
216            Self::Hypercare => 1.1,
217            Self::Complete => 1.0,
218        }
219    }
220}
221
222/// Data migration strategy.
223#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
224#[serde(rename_all = "snake_case")]
225pub enum DataMigrationStrategy {
226    /// All at once migration.
227    #[default]
228    BigBang,
229    /// Phased migration by module or entity.
230    Phased,
231    /// Parallel run with gradual cutover.
232    Parallel,
233    /// Hybrid approach.
234    Hybrid,
235}
236
237impl DataMigrationStrategy {
238    /// Get the risk level associated with this strategy.
239    pub fn risk_level(&self) -> &'static str {
240        match self {
241            Self::BigBang => "high",
242            Self::Phased => "medium",
243            Self::Parallel => "low",
244            Self::Hybrid => "medium",
245        }
246    }
247}
248
249/// Migration issue configuration.
250#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct MigrationIssueConfig {
252    /// Rate of duplicate records during migration.
253    #[serde(default = "default_duplicate_rate")]
254    pub duplicate_rate: f64,
255    /// Rate of missing records.
256    #[serde(default = "default_missing_rate")]
257    pub missing_rate: f64,
258    /// Rate of format mismatch issues.
259    #[serde(default = "default_format_mismatch_rate")]
260    pub format_mismatch_rate: f64,
261    /// Rate of mapping errors.
262    #[serde(default = "default_mapping_error_rate")]
263    pub mapping_error_rate: f64,
264    /// Rate of timing/cutoff issues.
265    #[serde(default = "default_cutoff_issue_rate")]
266    pub cutoff_issue_rate: f64,
267}
268
269fn default_duplicate_rate() -> f64 {
270    0.02
271}
272
273fn default_missing_rate() -> f64 {
274    0.01
275}
276
277fn default_format_mismatch_rate() -> f64 {
278    0.03
279}
280
281fn default_mapping_error_rate() -> f64 {
282    0.02
283}
284
285fn default_cutoff_issue_rate() -> f64 {
286    0.01
287}
288
289impl Default for MigrationIssueConfig {
290    fn default() -> Self {
291        Self {
292            duplicate_rate: 0.02,
293            missing_rate: 0.01,
294            format_mismatch_rate: 0.03,
295            mapping_error_rate: 0.02,
296            cutoff_issue_rate: 0.01,
297        }
298    }
299}
300
301impl MigrationIssueConfig {
302    /// Calculate combined error rate.
303    pub fn combined_error_rate(&self) -> f64 {
304        // These aren't strictly additive, but this gives a reasonable approximation
305        (self.duplicate_rate
306            + self.missing_rate
307            + self.format_mismatch_rate
308            + self.mapping_error_rate
309            + self.cutoff_issue_rate)
310            .min(0.20) // Cap at 20%
311    }
312}
313
314/// Configuration for module implementation.
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct ModuleImplementationConfig {
317    /// Module being implemented.
318    pub module_name: String,
319    /// System the module is being added to.
320    #[serde(default)]
321    pub target_system: Option<String>,
322    /// Go-live date.
323    pub go_live_date: NaiveDate,
324    /// Number of months for rollout.
325    #[serde(default = "default_module_rollout")]
326    pub rollout_months: u32,
327    /// Implementation error rate.
328    #[serde(default = "default_implementation_error_rate")]
329    pub implementation_error_rate: f64,
330    /// Training completion rate (0.0 to 1.0).
331    #[serde(default = "default_training_rate")]
332    pub training_completion_rate: f64,
333    /// Affected business processes.
334    #[serde(default)]
335    pub affected_processes: Vec<String>,
336    /// Configuration changes.
337    #[serde(default)]
338    pub configuration_changes: Vec<ConfigurationChange>,
339}
340
341fn default_module_rollout() -> u32 {
342    4
343}
344
345fn default_implementation_error_rate() -> f64 {
346    0.04
347}
348
349fn default_training_rate() -> f64 {
350    0.85
351}
352
353impl Default for ModuleImplementationConfig {
354    fn default() -> Self {
355        Self {
356            module_name: String::new(),
357            target_system: None,
358            go_live_date: NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
359            rollout_months: 4,
360            implementation_error_rate: 0.04,
361            training_completion_rate: 0.85,
362            affected_processes: Vec::new(),
363            configuration_changes: Vec::new(),
364        }
365    }
366}
367
368/// Configuration change for module implementation.
369#[derive(Debug, Clone, Serialize, Deserialize)]
370pub struct ConfigurationChange {
371    /// Configuration item.
372    pub item: String,
373    /// Old value.
374    #[serde(default)]
375    pub old_value: Option<String>,
376    /// New value.
377    pub new_value: String,
378}
379
380/// Configuration for integration upgrade.
381#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct IntegrationUpgradeConfig {
383    /// Integration being upgraded.
384    pub integration_name: String,
385    /// Source system.
386    #[serde(default)]
387    pub source_system: Option<String>,
388    /// Target system.
389    #[serde(default)]
390    pub target_system: Option<String>,
391    /// Upgrade date.
392    pub upgrade_date: NaiveDate,
393    /// Number of months for transition.
394    #[serde(default = "default_integration_transition")]
395    pub transition_months: u32,
396    /// Error rate during transition.
397    #[serde(default = "default_integration_error_rate")]
398    pub transition_error_rate: f64,
399    /// Message format changes.
400    #[serde(default)]
401    pub format_changes: Vec<FormatChange>,
402    /// New fields added.
403    #[serde(default)]
404    pub new_fields: Vec<String>,
405    /// Fields deprecated.
406    #[serde(default)]
407    pub deprecated_fields: Vec<String>,
408}
409
410fn default_integration_transition() -> u32 {
411    2
412}
413
414fn default_integration_error_rate() -> f64 {
415    0.03
416}
417
418impl Default for IntegrationUpgradeConfig {
419    fn default() -> Self {
420        Self {
421            integration_name: String::new(),
422            source_system: None,
423            target_system: None,
424            upgrade_date: NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
425            transition_months: 2,
426            transition_error_rate: 0.03,
427            format_changes: Vec::new(),
428            new_fields: Vec::new(),
429            deprecated_fields: Vec::new(),
430        }
431    }
432}
433
434/// Format change in integration upgrade.
435#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct FormatChange {
437    /// Field affected.
438    pub field: String,
439    /// Old format.
440    pub old_format: String,
441    /// New format.
442    pub new_format: String,
443}
444
445/// A scheduled technology transition event.
446#[derive(Debug, Clone, Serialize, Deserialize)]
447pub struct TechnologyTransitionEvent {
448    /// Unique event ID.
449    pub event_id: String,
450    /// Event type and configuration.
451    pub event_type: TechnologyTransitionType,
452    /// Effective date of the event.
453    pub effective_date: NaiveDate,
454    /// Human-readable description.
455    #[serde(default)]
456    pub description: Option<String>,
457    /// Tags for categorization.
458    #[serde(default)]
459    pub tags: Vec<String>,
460}
461
462impl TechnologyTransitionEvent {
463    /// Create a new technology transition event.
464    pub fn new(event_id: impl Into<String>, event_type: TechnologyTransitionType) -> Self {
465        let effective_date = match &event_type {
466            TechnologyTransitionType::ErpMigration(c) => c.phases.cutover_date,
467            TechnologyTransitionType::ModuleImplementation(c) => c.go_live_date,
468            TechnologyTransitionType::IntegrationUpgrade(c) => c.upgrade_date,
469        };
470
471        Self {
472            event_id: event_id.into(),
473            event_type,
474            effective_date,
475            description: None,
476            tags: Vec::new(),
477        }
478    }
479
480    /// Check if the event is active at a given date.
481    pub fn is_active_at(&self, date: NaiveDate) -> bool {
482        match &self.event_type {
483            TechnologyTransitionType::ErpMigration(c) => {
484                let start = c.phases.preparation_start.unwrap_or(c.phases.cutover_date);
485                let end = c.phases.hypercare_end.unwrap_or(c.phases.stabilization_end);
486                date >= start && date <= end
487            }
488            TechnologyTransitionType::ModuleImplementation(c) => {
489                let end = c.go_live_date + chrono::Duration::days(c.rollout_months as i64 * 30);
490                date >= c.go_live_date && date <= end
491            }
492            TechnologyTransitionType::IntegrationUpgrade(c) => {
493                let end = c.upgrade_date + chrono::Duration::days(c.transition_months as i64 * 30);
494                date >= c.upgrade_date && date <= end
495            }
496        }
497    }
498
499    /// Get the progress through the event (0.0 to 1.0).
500    pub fn progress_at(&self, date: NaiveDate) -> f64 {
501        let (start, total_days) = match &self.event_type {
502            TechnologyTransitionType::ErpMigration(c) => {
503                let start = c.phases.preparation_start.unwrap_or(c.phases.cutover_date);
504                let end = c.phases.hypercare_end.unwrap_or(c.phases.stabilization_end);
505                (start, (end - start).num_days() as f64)
506            }
507            TechnologyTransitionType::ModuleImplementation(c) => {
508                (c.go_live_date, c.rollout_months as f64 * 30.0)
509            }
510            TechnologyTransitionType::IntegrationUpgrade(c) => {
511                (c.upgrade_date, c.transition_months as f64 * 30.0)
512            }
513        };
514
515        if date < start {
516            return 0.0;
517        }
518        if total_days <= 0.0 {
519            return 1.0;
520        }
521
522        let days_elapsed = (date - start).num_days() as f64;
523        (days_elapsed / total_days).min(1.0)
524    }
525
526    /// Get the current migration phase (for ERP migrations).
527    pub fn migration_phase_at(&self, date: NaiveDate) -> Option<MigrationPhase> {
528        match &self.event_type {
529            TechnologyTransitionType::ErpMigration(c) => Some(c.phases.phase_at(date)),
530            _ => None,
531        }
532    }
533}
534
535#[cfg(test)]
536mod tests {
537    use super::*;
538
539    #[test]
540    fn test_migration_phases() {
541        let phases = MigrationPhases {
542            preparation_start: Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
543            data_migration_start: Some(NaiveDate::from_ymd_opt(2024, 6, 1).unwrap()),
544            parallel_run_start: Some(NaiveDate::from_ymd_opt(2024, 8, 1).unwrap()),
545            cutover_date: NaiveDate::from_ymd_opt(2024, 9, 1).unwrap(),
546            stabilization_end: NaiveDate::from_ymd_opt(2024, 11, 30).unwrap(),
547            hypercare_end: Some(NaiveDate::from_ymd_opt(2024, 12, 31).unwrap()),
548        };
549
550        assert_eq!(
551            phases.phase_at(NaiveDate::from_ymd_opt(2023, 12, 1).unwrap()),
552            MigrationPhase::PreMigration
553        );
554        assert_eq!(
555            phases.phase_at(NaiveDate::from_ymd_opt(2024, 3, 1).unwrap()),
556            MigrationPhase::Preparation
557        );
558        assert_eq!(
559            phases.phase_at(NaiveDate::from_ymd_opt(2024, 7, 1).unwrap()),
560            MigrationPhase::DataMigration
561        );
562        assert_eq!(
563            phases.phase_at(NaiveDate::from_ymd_opt(2024, 8, 15).unwrap()),
564            MigrationPhase::ParallelRun
565        );
566        assert_eq!(
567            phases.phase_at(NaiveDate::from_ymd_opt(2024, 10, 1).unwrap()),
568            MigrationPhase::Stabilization
569        );
570        assert_eq!(
571            phases.phase_at(NaiveDate::from_ymd_opt(2024, 12, 15).unwrap()),
572            MigrationPhase::Hypercare
573        );
574        assert_eq!(
575            phases.phase_at(NaiveDate::from_ymd_opt(2025, 2, 1).unwrap()),
576            MigrationPhase::Complete
577        );
578    }
579
580    #[test]
581    fn test_migration_issue_combined_rate() {
582        let issues = MigrationIssueConfig::default();
583        let combined = issues.combined_error_rate();
584
585        // Should be reasonable combined rate
586        assert!(combined > 0.0);
587        assert!(combined <= 0.20);
588    }
589
590    #[test]
591    fn test_technology_transition_event() {
592        let config = ErpMigrationConfig::default();
593        let event = TechnologyTransitionEvent::new(
594            "ERP-001",
595            TechnologyTransitionType::ErpMigration(config.clone()),
596        );
597
598        // Before start
599        assert!(!event.is_active_at(NaiveDate::from_ymd_opt(2023, 1, 1).unwrap()));
600
601        // During migration
602        assert!(event.is_active_at(NaiveDate::from_ymd_opt(2024, 6, 1).unwrap()));
603
604        // After completion
605        assert!(!event.is_active_at(NaiveDate::from_ymd_opt(2025, 6, 1).unwrap()));
606    }
607
608    #[test]
609    fn test_data_migration_strategy_risk() {
610        assert_eq!(DataMigrationStrategy::BigBang.risk_level(), "high");
611        assert_eq!(DataMigrationStrategy::Parallel.risk_level(), "low");
612    }
613}