Skip to main content

datasynth_config/
schema.rs

1//! Configuration schema for synthetic data generation.
2
3use datasynth_core::distributions::{
4    AmountDistributionConfig, DebitCreditDistributionConfig, EvenOddDistributionConfig,
5    LineItemDistributionConfig, SeasonalityConfig,
6};
7use datasynth_core::models::{CoAComplexity, IndustrySector};
8use serde::{Deserialize, Serialize};
9use std::path::PathBuf;
10
11/// Root configuration for the synthetic data generator.
12///
13/// # camelCase alias policy
14///
15/// Every multi-word field carries `#[serde(alias = "camelCaseName")]`
16/// so SDK clients that follow JSON conventions can submit configs
17/// without round-tripping through a snake_case transformer.
18///
19/// Before v4.4.1 several fields — `documentFlows`, `accountingStandards`,
20/// `complianceRegulations`, `analyticsMetadata` — had no alias, so SDK
21/// submissions silently fell through to defaults. The symptom was
22/// "enabling the 6 feature subsections together collapses the archive
23/// from 99 files to 19". Root cause: those four fields never parsed;
24/// the orchestrator produced far less data than requested, and
25/// `output.exportFormat` similarly fell through so journal_entries
26/// landed as the default Parquet/CSV rather than JSON.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct GeneratorConfig {
29    /// Global settings
30    pub global: GlobalConfig,
31    /// Company configuration
32    pub companies: Vec<CompanyConfig>,
33    /// Chart of Accounts configuration
34    #[serde(alias = "chartOfAccounts")]
35    pub chart_of_accounts: ChartOfAccountsConfig,
36    /// Transaction generation settings
37    #[serde(default)]
38    pub transactions: TransactionConfig,
39    /// Output configuration
40    pub output: OutputConfig,
41    /// Fraud simulation settings
42    #[serde(default)]
43    pub fraud: FraudConfig,
44    /// Data quality variation settings
45    #[serde(default, alias = "dataQuality")]
46    pub data_quality: DataQualitySchemaConfig,
47    /// Internal Controls System settings
48    #[serde(default, alias = "internalControls")]
49    pub internal_controls: InternalControlsConfig,
50    /// Business process mix
51    #[serde(default, alias = "businessProcesses")]
52    pub business_processes: BusinessProcessConfig,
53    /// User persona distribution
54    #[serde(default, alias = "userPersonas")]
55    pub user_personas: UserPersonaConfig,
56    /// Template configuration for realistic data
57    #[serde(default)]
58    pub templates: TemplateConfig,
59    /// Approval workflow configuration
60    #[serde(default)]
61    pub approval: ApprovalConfig,
62    /// Department structure configuration
63    #[serde(default)]
64    pub departments: DepartmentConfig,
65    /// Master data generation settings
66    #[serde(default, alias = "masterData")]
67    pub master_data: MasterDataConfig,
68    /// Document flow generation settings
69    #[serde(default, alias = "documentFlows")]
70    pub document_flows: DocumentFlowConfig,
71    /// Intercompany transaction settings
72    #[serde(default)]
73    pub intercompany: IntercompanyConfig,
74    /// Balance and trial balance settings
75    #[serde(default)]
76    pub balance: BalanceConfig,
77    /// OCPM (Object-Centric Process Mining) settings
78    #[serde(default)]
79    pub ocpm: OcpmConfig,
80    /// Audit engagement and workpaper generation settings
81    #[serde(default)]
82    pub audit: AuditGenerationConfig,
83    /// Banking KYC/AML transaction generation settings
84    #[serde(default)]
85    pub banking: datasynth_banking::BankingConfig,
86    /// Scenario configuration for metadata and tagging (Phase 1.3)
87    #[serde(default)]
88    pub scenario: ScenarioConfig,
89    /// Temporal drift configuration for simulating distribution changes over time (Phase 2.2)
90    #[serde(default)]
91    pub temporal: TemporalDriftConfig,
92    /// Graph export configuration for accounting network export
93    #[serde(default, alias = "graphExport")]
94    pub graph_export: GraphExportConfig,
95    /// Streaming output API configuration
96    #[serde(default)]
97    pub streaming: StreamingSchemaConfig,
98    /// Rate limiting configuration
99    #[serde(default, alias = "rateLimit")]
100    pub rate_limit: RateLimitSchemaConfig,
101    /// Temporal attribute generation configuration
102    #[serde(default, alias = "temporalAttributes")]
103    pub temporal_attributes: TemporalAttributeSchemaConfig,
104    /// Relationship generation configuration
105    #[serde(default)]
106    pub relationships: RelationshipSchemaConfig,
107    /// Accounting standards framework configuration (IFRS, US GAAP)
108    #[serde(default, alias = "accountingStandards")]
109    pub accounting_standards: AccountingStandardsConfig,
110    /// Audit standards framework configuration (ISA, PCAOB)
111    #[serde(default, alias = "auditStandards")]
112    pub audit_standards: AuditStandardsConfig,
113    /// Advanced distribution configuration (mixture models, correlations, regime changes)
114    #[serde(default)]
115    pub distributions: AdvancedDistributionConfig,
116    /// Temporal patterns configuration (business days, period-end dynamics, processing lags)
117    #[serde(default, alias = "temporalPatterns")]
118    pub temporal_patterns: TemporalPatternsConfig,
119    /// Vendor network configuration (multi-tier supply chain modeling)
120    #[serde(default, alias = "vendorNetwork")]
121    pub vendor_network: VendorNetworkSchemaConfig,
122    /// Customer segmentation configuration (value segments, lifecycle stages)
123    #[serde(default, alias = "customerSegmentation")]
124    pub customer_segmentation: CustomerSegmentationSchemaConfig,
125    /// Relationship strength calculation configuration
126    #[serde(default, alias = "relationshipStrength")]
127    pub relationship_strength: RelationshipStrengthSchemaConfig,
128    /// Cross-process link configuration (P2P ↔ O2C via inventory)
129    #[serde(default, alias = "crossProcessLinks")]
130    pub cross_process_links: CrossProcessLinksSchemaConfig,
131    /// Organizational events configuration (acquisitions, divestitures, etc.)
132    #[serde(default, alias = "organizationalEvents")]
133    pub organizational_events: OrganizationalEventsSchemaConfig,
134    /// Behavioral drift configuration (vendor, customer, employee behavior)
135    #[serde(default, alias = "behavioralDrift")]
136    pub behavioral_drift: BehavioralDriftSchemaConfig,
137    /// Market drift configuration (economic cycles, commodities, price shocks)
138    #[serde(default, alias = "marketDrift")]
139    pub market_drift: MarketDriftSchemaConfig,
140    /// Drift labeling configuration for ground truth generation
141    #[serde(default, alias = "driftLabeling")]
142    pub drift_labeling: DriftLabelingSchemaConfig,
143    /// Enhanced anomaly injection configuration (multi-stage schemes, correlated injection, near-miss)
144    #[serde(default, alias = "anomalyInjection")]
145    pub anomaly_injection: EnhancedAnomalyConfig,
146    /// Industry-specific transaction and anomaly generation configuration
147    #[serde(default, alias = "industrySpecific")]
148    pub industry_specific: IndustrySpecificConfig,
149    /// Fingerprint privacy configuration for extraction/synthesis
150    #[serde(default, alias = "fingerprintPrivacy")]
151    pub fingerprint_privacy: FingerprintPrivacyConfig,
152    /// Quality gate configuration for pass/fail thresholds
153    #[serde(default, alias = "qualityGates")]
154    pub quality_gates: QualityGatesSchemaConfig,
155    /// Compliance configuration (EU AI Act, content marking)
156    #[serde(default)]
157    pub compliance: ComplianceSchemaConfig,
158    /// Webhook notification configuration
159    #[serde(default)]
160    pub webhooks: WebhookSchemaConfig,
161    /// LLM enrichment configuration (AI-augmented vendor names, descriptions, explanations)
162    #[serde(default)]
163    pub llm: LlmSchemaConfig,
164    /// Diffusion model configuration (statistical diffusion-based data enhancement)
165    #[serde(default)]
166    pub diffusion: DiffusionSchemaConfig,
167    /// Causal generation configuration (structural causal models, interventions)
168    #[serde(default)]
169    pub causal: CausalSchemaConfig,
170
171    // ===== Enterprise Process Chain Extensions =====
172    /// Source-to-Pay (S2C/S2P) configuration (sourcing, contracts, catalogs, scorecards)
173    #[serde(default, alias = "sourceToPay")]
174    pub source_to_pay: SourceToPayConfig,
175    /// Financial reporting configuration (financial statements, KPIs, budgets)
176    #[serde(default, alias = "financialReporting")]
177    pub financial_reporting: FinancialReportingConfig,
178    /// HR process configuration (payroll, time & attendance, expenses)
179    #[serde(default)]
180    pub hr: HrConfig,
181    /// Manufacturing configuration (production orders, WIP, routing)
182    #[serde(default)]
183    pub manufacturing: ManufacturingProcessConfig,
184    /// Sales quote configuration (quote-to-order pipeline)
185    #[serde(default, alias = "salesQuotes")]
186    pub sales_quotes: SalesQuoteConfig,
187    /// Tax accounting configuration (VAT/GST, sales tax, withholding, provisions, payroll tax)
188    #[serde(default)]
189    pub tax: TaxConfig,
190    /// Treasury and cash management configuration
191    #[serde(default)]
192    pub treasury: TreasuryConfig,
193    /// Project accounting configuration
194    #[serde(default, alias = "projectAccounting")]
195    pub project_accounting: ProjectAccountingConfig,
196    /// ESG / Sustainability reporting configuration
197    #[serde(default)]
198    pub esg: EsgConfig,
199    /// Country pack configuration (external packs directory, per-country overrides)
200    #[serde(default, alias = "countryPacks")]
201    pub country_packs: Option<CountryPacksSchemaConfig>,
202    /// Counterfactual simulation scenario configuration
203    #[serde(default)]
204    pub scenarios: ScenariosConfig,
205    /// Generation session configuration (period-by-period generation with balance carry-forward)
206    #[serde(default)]
207    pub session: SessionSchemaConfig,
208    /// Compliance regulations framework configuration (standards registry, jurisdictions, temporal versioning, audit templates, graph integration)
209    #[serde(default, alias = "complianceRegulations")]
210    pub compliance_regulations: ComplianceRegulationsConfig,
211    /// v3.3.0: analytics metadata phase — prior-year comparatives,
212    /// industry benchmarks, management reports, drift events. Off by
213    /// default so v3.2.1 archives are byte-identical.
214    #[serde(default, alias = "analyticsMetadata")]
215    pub analytics_metadata: AnalyticsMetadataConfig,
216}
217
218/// v3.3.0: analytics-metadata phase configuration.
219///
220/// Gates the `phase_analytics_metadata` pass that runs AFTER all
221/// JE-adding phases (including the fraud-bias sweep at Phase 20b).
222/// When enabled, the orchestrator calls `PriorYearGenerator`,
223/// `IndustryBenchmarkGenerator`, `ManagementReportGenerator`, and
224/// `DriftEventGenerator` in sequence; each sub-flag below controls
225/// whether that specific generator fires.
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct AnalyticsMetadataConfig {
228    /// Master switch for the whole analytics phase.
229    #[serde(default)]
230    pub enabled: bool,
231    /// Emit `PriorYearComparative` records derived from current
232    /// period's account balances.
233    #[serde(default = "default_true")]
234    pub prior_year: bool,
235    /// Emit `IndustryBenchmark` records for the configured industry.
236    #[serde(default = "default_true")]
237    pub industry_benchmark: bool,
238    /// Emit management-report artefacts.
239    #[serde(default = "default_true")]
240    pub management_reports: bool,
241    /// Emit `LabeledDriftEvent` records — post-generation sweep over
242    /// journal entries to label detected drift patterns.
243    #[serde(default = "default_true")]
244    pub drift_events: bool,
245}
246
247impl Default for AnalyticsMetadataConfig {
248    fn default() -> Self {
249        Self {
250            enabled: false,
251            prior_year: true,
252            industry_benchmark: true,
253            management_reports: true,
254            drift_events: true,
255        }
256    }
257}
258
259/// LLM enrichment configuration.
260///
261/// Controls AI-augmented metadata enrichment using LLM providers.
262/// When enabled, vendor names, transaction descriptions, and anomaly explanations
263/// are enriched using the configured provider (mock by default).
264#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct LlmSchemaConfig {
266    /// Whether LLM enrichment is enabled.
267    #[serde(default)]
268    pub enabled: bool,
269    /// Provider type: "mock", "openai", "anthropic", "custom".
270    #[serde(default = "default_llm_provider")]
271    pub provider: String,
272    /// Model name/ID for the provider.
273    #[serde(default = "default_llm_model_name")]
274    pub model: String,
275    /// Maximum number of vendor names to enrich per run.
276    #[serde(default = "default_llm_batch_size")]
277    pub max_vendor_enrichments: usize,
278
279    /// v4.1.1+: also enrich customer names at generate time.
280    /// Default `false` preserves v4.1.0 behaviour.
281    #[serde(default)]
282    pub enrich_customers: bool,
283
284    /// v4.1.1+: also enrich material descriptions at generate time.
285    /// Default `false`.
286    #[serde(default)]
287    pub enrich_materials: bool,
288
289    /// v4.1.1+: also enrich audit finding titles at generate time
290    /// (the finding narratives remain on their existing template path
291    /// because they're richer and locale-specific). Default `false`.
292    #[serde(default)]
293    pub enrich_findings: bool,
294
295    /// v4.1.1+: upper bound on customer enrichments per run. Matches
296    /// `max_vendor_enrichments` semantics.
297    #[serde(default = "default_llm_batch_size")]
298    pub max_customer_enrichments: usize,
299
300    /// v4.1.1+: upper bound on material enrichments per run.
301    #[serde(default = "default_llm_batch_size")]
302    pub max_material_enrichments: usize,
303
304    /// v4.1.1+: upper bound on finding enrichments per run.
305    #[serde(default = "default_llm_batch_size")]
306    pub max_finding_enrichments: usize,
307}
308
309fn default_llm_provider() -> String {
310    "mock".to_string()
311}
312
313fn default_llm_model_name() -> String {
314    "gpt-4o-mini".to_string()
315}
316
317fn default_llm_batch_size() -> usize {
318    50
319}
320
321impl Default for LlmSchemaConfig {
322    fn default() -> Self {
323        Self {
324            enabled: false,
325            provider: default_llm_provider(),
326            model: default_llm_model_name(),
327            max_vendor_enrichments: default_llm_batch_size(),
328            enrich_customers: false,
329            enrich_materials: false,
330            enrich_findings: false,
331            max_customer_enrichments: default_llm_batch_size(),
332            max_material_enrichments: default_llm_batch_size(),
333            max_finding_enrichments: default_llm_batch_size(),
334        }
335    }
336}
337
338/// Diffusion model configuration.
339///
340/// Controls statistical diffusion-based data enhancement that generates samples
341/// matching target distribution properties (means, standard deviations, correlations).
342#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct DiffusionSchemaConfig {
344    /// Whether diffusion enhancement is enabled.
345    #[serde(default)]
346    pub enabled: bool,
347    /// Number of diffusion steps (higher = better quality, slower).
348    #[serde(default = "default_diffusion_steps")]
349    pub n_steps: usize,
350    /// Noise schedule type: "linear", "cosine", "sigmoid".
351    #[serde(default = "default_diffusion_schedule")]
352    pub schedule: String,
353    /// Number of sample rows to generate for demonstration.
354    #[serde(default = "default_diffusion_sample_size")]
355    pub sample_size: usize,
356    /// Backend type: "statistical" (default), "neural", "hybrid".
357    #[serde(default = "default_diffusion_backend")]
358    pub backend: String,
359    /// Neural diffusion backend configuration (used when backend is "neural" or "hybrid").
360    #[serde(default)]
361    pub neural: NeuralDiffusionSchemaConfig,
362}
363
364fn default_diffusion_steps() -> usize {
365    100
366}
367
368fn default_diffusion_schedule() -> String {
369    "linear".to_string()
370}
371
372fn default_diffusion_sample_size() -> usize {
373    100
374}
375
376fn default_diffusion_backend() -> String {
377    "statistical".to_string()
378}
379
380impl Default for DiffusionSchemaConfig {
381    fn default() -> Self {
382        Self {
383            enabled: false,
384            n_steps: default_diffusion_steps(),
385            schedule: default_diffusion_schedule(),
386            sample_size: default_diffusion_sample_size(),
387            backend: default_diffusion_backend(),
388            neural: NeuralDiffusionSchemaConfig::default(),
389        }
390    }
391}
392
393/// Neural diffusion backend configuration.
394///
395/// Controls the `candle`-based neural score network that learns joint distributions
396/// from training data for the neural and hybrid diffusion backends.
397#[derive(Debug, Clone, Serialize, Deserialize)]
398pub struct NeuralDiffusionSchemaConfig {
399    /// Hidden layer dimensions for the score network MLP.
400    #[serde(default = "default_neural_hidden_dims")]
401    pub hidden_dims: Vec<usize>,
402    /// Dimensionality of the timestep embedding.
403    #[serde(default = "default_neural_timestep_embed_dim")]
404    pub timestep_embed_dim: usize,
405    /// Learning rate for training.
406    #[serde(default = "default_neural_learning_rate")]
407    pub learning_rate: f64,
408    /// Number of training epochs.
409    #[serde(default = "default_neural_training_epochs")]
410    pub training_epochs: usize,
411    /// Training batch size.
412    #[serde(default = "default_neural_batch_size")]
413    pub batch_size: usize,
414    /// Blend weight for hybrid mode (0.0 = all statistical, 1.0 = all neural).
415    #[serde(default = "default_neural_hybrid_weight")]
416    pub hybrid_weight: f64,
417    /// Hybrid blending strategy: "weighted_average", "column_select", "threshold".
418    #[serde(default = "default_neural_hybrid_strategy")]
419    pub hybrid_strategy: String,
420    /// Columns to apply neural generation to (empty = all numeric columns).
421    #[serde(default)]
422    pub neural_columns: Vec<String>,
423    /// v4.4.0+ Optional path to a pre-trained score-network checkpoint
424    /// (`.safetensors`). When set, the orchestrator loads the
425    /// checkpoint instead of training from the first batch — useful
426    /// for long-running production deployments where training cost
427    /// dominates per-run cost. When empty, the orchestrator trains
428    /// on the first generated JE amounts.
429    #[serde(default, skip_serializing_if = "Option::is_none")]
430    pub checkpoint_path: Option<String>,
431}
432
433fn default_neural_hidden_dims() -> Vec<usize> {
434    vec![256, 256, 128]
435}
436
437fn default_neural_timestep_embed_dim() -> usize {
438    64
439}
440
441fn default_neural_learning_rate() -> f64 {
442    0.001
443}
444
445fn default_neural_training_epochs() -> usize {
446    100
447}
448
449fn default_neural_batch_size() -> usize {
450    64
451}
452
453fn default_neural_hybrid_weight() -> f64 {
454    0.5
455}
456
457fn default_neural_hybrid_strategy() -> String {
458    "weighted_average".to_string()
459}
460
461impl Default for NeuralDiffusionSchemaConfig {
462    fn default() -> Self {
463        Self {
464            hidden_dims: default_neural_hidden_dims(),
465            timestep_embed_dim: default_neural_timestep_embed_dim(),
466            learning_rate: default_neural_learning_rate(),
467            training_epochs: default_neural_training_epochs(),
468            batch_size: default_neural_batch_size(),
469            hybrid_weight: default_neural_hybrid_weight(),
470            hybrid_strategy: default_neural_hybrid_strategy(),
471            neural_columns: Vec::new(),
472            checkpoint_path: None,
473        }
474    }
475}
476
477/// Causal generation configuration.
478///
479/// Controls structural causal model (SCM) based data generation that respects
480/// causal relationships between variables, supports do-calculus interventions,
481/// and enables counterfactual scenarios.
482#[derive(Debug, Clone, Serialize, Deserialize)]
483pub struct CausalSchemaConfig {
484    /// Whether causal generation is enabled.
485    #[serde(default)]
486    pub enabled: bool,
487    /// Built-in template to use: "fraud_detection", "revenue_cycle", or "custom".
488    #[serde(default = "default_causal_template")]
489    pub template: String,
490    /// Number of causal samples to generate.
491    #[serde(default = "default_causal_sample_size")]
492    pub sample_size: usize,
493    /// Whether to run causal validation on the output.
494    #[serde(default = "default_true")]
495    pub validate: bool,
496}
497
498fn default_causal_template() -> String {
499    "fraud_detection".to_string()
500}
501
502fn default_causal_sample_size() -> usize {
503    500
504}
505
506impl Default for CausalSchemaConfig {
507    fn default() -> Self {
508        Self {
509            enabled: false,
510            template: default_causal_template(),
511            sample_size: default_causal_sample_size(),
512            validate: true,
513        }
514    }
515}
516
517/// Graph export configuration for accounting network and ML training exports.
518///
519/// This section enables exporting generated data as graphs for:
520/// - Network reconstruction algorithms
521/// - Graph neural network training
522/// - Neo4j graph database import
523#[derive(Debug, Clone, Serialize, Deserialize)]
524pub struct GraphExportConfig {
525    /// Enable graph export.
526    #[serde(default)]
527    pub enabled: bool,
528
529    /// Graph types to generate.
530    #[serde(default = "default_graph_types")]
531    pub graph_types: Vec<GraphTypeConfig>,
532
533    /// Export formats to generate.
534    #[serde(default = "default_graph_formats")]
535    pub formats: Vec<GraphExportFormat>,
536
537    /// Train split ratio for ML datasets.
538    #[serde(default = "default_train_ratio")]
539    pub train_ratio: f64,
540
541    /// Validation split ratio for ML datasets.
542    #[serde(default = "default_val_ratio")]
543    pub validation_ratio: f64,
544
545    /// Random seed for train/val/test splits.
546    #[serde(default)]
547    pub split_seed: Option<u64>,
548
549    /// Output subdirectory for graph exports (relative to output directory).
550    #[serde(default = "default_graph_subdir")]
551    pub output_subdirectory: String,
552
553    /// Multi-layer hypergraph export settings for RustGraph integration.
554    #[serde(default)]
555    pub hypergraph: HypergraphExportSettings,
556
557    /// DGL-specific export settings.
558    #[serde(default)]
559    pub dgl: DglExportConfig,
560}
561
562fn default_graph_types() -> Vec<GraphTypeConfig> {
563    vec![GraphTypeConfig::default()]
564}
565
566fn default_graph_formats() -> Vec<GraphExportFormat> {
567    vec![GraphExportFormat::PytorchGeometric]
568}
569
570fn default_train_ratio() -> f64 {
571    0.7
572}
573
574fn default_val_ratio() -> f64 {
575    0.15
576}
577
578fn default_graph_subdir() -> String {
579    "graphs".to_string()
580}
581
582impl Default for GraphExportConfig {
583    fn default() -> Self {
584        Self {
585            enabled: false,
586            graph_types: default_graph_types(),
587            formats: default_graph_formats(),
588            train_ratio: 0.7,
589            validation_ratio: 0.15,
590            split_seed: None,
591            output_subdirectory: "graphs".to_string(),
592            hypergraph: HypergraphExportSettings::default(),
593            dgl: DglExportConfig::default(),
594        }
595    }
596}
597
598/// DGL-specific export settings.
599#[derive(Debug, Clone, Default, Serialize, Deserialize)]
600pub struct DglExportConfig {
601    /// Export as a heterogeneous graph (distinct node/edge types).
602    ///
603    /// When `true` the DGL exporter produces a `HeteroData` object with typed
604    /// node and edge stores rather than a single homogeneous graph.
605    /// Set to `true` in `graph_export.dgl.heterogeneous: true` in YAML.
606    #[serde(default)]
607    pub heterogeneous: bool,
608}
609
610// Default derived: heterogeneous = false (bool default)
611
612/// Settings for the multi-layer hypergraph export (RustGraph integration).
613///
614/// Produces a 3-layer hypergraph:
615/// - Layer 1: Governance & Controls (COSO, SOX, internal controls, organizational)
616/// - Layer 2: Process Events (P2P/O2C document flows, OCPM events)
617/// - Layer 3: Accounting Network (GL accounts, journal entries as hyperedges)
618#[derive(Debug, Clone, Serialize, Deserialize)]
619pub struct HypergraphExportSettings {
620    /// Enable hypergraph export.
621    #[serde(default)]
622    pub enabled: bool,
623
624    /// Maximum total nodes across all layers (default 50000).
625    #[serde(default = "default_hypergraph_max_nodes")]
626    pub max_nodes: usize,
627
628    /// Aggregation strategy when node budget is exceeded.
629    #[serde(default = "default_aggregation_strategy")]
630    pub aggregation_strategy: String,
631
632    /// Layer 1 (Governance & Controls) settings.
633    #[serde(default)]
634    pub governance_layer: GovernanceLayerSettings,
635
636    /// Layer 2 (Process Events) settings.
637    #[serde(default)]
638    pub process_layer: ProcessLayerSettings,
639
640    /// Layer 3 (Accounting Network) settings.
641    #[serde(default)]
642    pub accounting_layer: AccountingLayerSettings,
643
644    /// Cross-layer edge generation settings.
645    #[serde(default)]
646    pub cross_layer: CrossLayerSettings,
647
648    /// Output subdirectory for hypergraph files (relative to graph output directory).
649    #[serde(default = "default_hypergraph_subdir")]
650    pub output_subdirectory: String,
651
652    /// Output format: "native" (default) for internal field names, "unified" for RustGraph format.
653    #[serde(default = "default_hypergraph_format")]
654    pub output_format: String,
655
656    /// Optional URL for streaming unified JSONL to a RustGraph ingest endpoint.
657    #[serde(default)]
658    pub stream_target: Option<String>,
659
660    /// Batch size for streaming (number of JSONL lines per HTTP POST). Default: 1000.
661    #[serde(default = "default_stream_batch_size")]
662    pub stream_batch_size: usize,
663}
664
665fn default_hypergraph_max_nodes() -> usize {
666    50_000
667}
668
669fn default_aggregation_strategy() -> String {
670    "pool_by_counterparty".to_string()
671}
672
673fn default_hypergraph_subdir() -> String {
674    "hypergraph".to_string()
675}
676
677fn default_hypergraph_format() -> String {
678    "native".to_string()
679}
680
681fn default_stream_batch_size() -> usize {
682    1000
683}
684
685impl Default for HypergraphExportSettings {
686    fn default() -> Self {
687        Self {
688            enabled: false,
689            max_nodes: 50_000,
690            aggregation_strategy: "pool_by_counterparty".to_string(),
691            governance_layer: GovernanceLayerSettings::default(),
692            process_layer: ProcessLayerSettings::default(),
693            accounting_layer: AccountingLayerSettings::default(),
694            cross_layer: CrossLayerSettings::default(),
695            output_subdirectory: "hypergraph".to_string(),
696            output_format: "native".to_string(),
697            stream_target: None,
698            stream_batch_size: 1000,
699        }
700    }
701}
702
703/// Layer 1: Governance & Controls layer settings.
704#[derive(Debug, Clone, Serialize, Deserialize)]
705pub struct GovernanceLayerSettings {
706    /// Include COSO framework nodes (5 components + 17 principles).
707    #[serde(default = "default_true")]
708    pub include_coso: bool,
709    /// Include internal control nodes.
710    #[serde(default = "default_true")]
711    pub include_controls: bool,
712    /// Include SOX assertion nodes.
713    #[serde(default = "default_true")]
714    pub include_sox: bool,
715    /// Include vendor master data nodes.
716    #[serde(default = "default_true")]
717    pub include_vendors: bool,
718    /// Include customer master data nodes.
719    #[serde(default = "default_true")]
720    pub include_customers: bool,
721    /// Include employee/organizational nodes.
722    #[serde(default = "default_true")]
723    pub include_employees: bool,
724}
725
726impl Default for GovernanceLayerSettings {
727    fn default() -> Self {
728        Self {
729            include_coso: true,
730            include_controls: true,
731            include_sox: true,
732            include_vendors: true,
733            include_customers: true,
734            include_employees: true,
735        }
736    }
737}
738
739/// Layer 2: Process Events layer settings.
740#[derive(Debug, Clone, Serialize, Deserialize)]
741pub struct ProcessLayerSettings {
742    /// Include P2P (Procure-to-Pay) document flow nodes.
743    #[serde(default = "default_true")]
744    pub include_p2p: bool,
745    /// Include O2C (Order-to-Cash) document flow nodes.
746    #[serde(default = "default_true")]
747    pub include_o2c: bool,
748    /// Include S2C (Source-to-Contract) document flow nodes.
749    #[serde(default = "default_true")]
750    pub include_s2c: bool,
751    /// Include H2R (Hire-to-Retire) document flow nodes.
752    #[serde(default = "default_true")]
753    pub include_h2r: bool,
754    /// Include MFG (Manufacturing) document flow nodes.
755    #[serde(default = "default_true")]
756    pub include_mfg: bool,
757    /// Include BANK (Banking) document flow nodes.
758    #[serde(default = "default_true")]
759    pub include_bank: bool,
760    /// Include AUDIT document flow nodes.
761    #[serde(default = "default_true")]
762    pub include_audit: bool,
763    /// Include R2R (Record-to-Report) document flow nodes (bank recon + period close).
764    #[serde(default = "default_true")]
765    pub include_r2r: bool,
766    /// Export OCPM events as hyperedges.
767    #[serde(default = "default_true")]
768    pub events_as_hyperedges: bool,
769    /// Threshold: if a counterparty has more documents than this, aggregate into pool nodes.
770    #[serde(default = "default_docs_per_counterparty_threshold")]
771    pub docs_per_counterparty_threshold: usize,
772}
773
774fn default_docs_per_counterparty_threshold() -> usize {
775    20
776}
777
778impl Default for ProcessLayerSettings {
779    fn default() -> Self {
780        Self {
781            include_p2p: true,
782            include_o2c: true,
783            include_s2c: true,
784            include_h2r: true,
785            include_mfg: true,
786            include_bank: true,
787            include_audit: true,
788            include_r2r: true,
789            events_as_hyperedges: true,
790            docs_per_counterparty_threshold: 20,
791        }
792    }
793}
794
795/// Layer 3: Accounting Network layer settings.
796#[derive(Debug, Clone, Serialize, Deserialize)]
797pub struct AccountingLayerSettings {
798    /// Include GL account nodes.
799    #[serde(default = "default_true")]
800    pub include_accounts: bool,
801    /// Export journal entries as hyperedges (debit+credit accounts as participants).
802    #[serde(default = "default_true")]
803    pub je_as_hyperedges: bool,
804}
805
806impl Default for AccountingLayerSettings {
807    fn default() -> Self {
808        Self {
809            include_accounts: true,
810            je_as_hyperedges: true,
811        }
812    }
813}
814
815/// Cross-layer edge generation settings.
816#[derive(Debug, Clone, Serialize, Deserialize)]
817pub struct CrossLayerSettings {
818    /// Generate cross-layer edges (Control→Account, Vendor→PO, etc.).
819    #[serde(default = "default_true")]
820    pub enabled: bool,
821}
822
823impl Default for CrossLayerSettings {
824    fn default() -> Self {
825        Self { enabled: true }
826    }
827}
828
829/// Configuration for a specific graph type to export.
830#[derive(Debug, Clone, Serialize, Deserialize)]
831pub struct GraphTypeConfig {
832    /// Name identifier for this graph configuration.
833    #[serde(default = "default_graph_name")]
834    pub name: String,
835
836    /// Whether to aggregate parallel edges between the same nodes.
837    #[serde(default)]
838    pub aggregate_edges: bool,
839
840    /// Minimum edge weight to include (filters out small transactions).
841    #[serde(default)]
842    pub min_edge_weight: f64,
843
844    /// Whether to include document nodes (creates hub-and-spoke structure).
845    #[serde(default)]
846    pub include_document_nodes: bool,
847}
848
849fn default_graph_name() -> String {
850    "accounting_network".to_string()
851}
852
853impl Default for GraphTypeConfig {
854    fn default() -> Self {
855        Self {
856            name: "accounting_network".to_string(),
857            aggregate_edges: false,
858            min_edge_weight: 0.0,
859            include_document_nodes: false,
860        }
861    }
862}
863
864/// Export format for graph data.
865#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
866#[serde(rename_all = "snake_case")]
867pub enum GraphExportFormat {
868    /// PyTorch Geometric format (.npy files + metadata.json).
869    PytorchGeometric,
870    /// Neo4j format (CSV files + Cypher import scripts).
871    Neo4j,
872    /// Deep Graph Library format.
873    Dgl,
874    /// RustGraph/RustAssureTwin JSON format.
875    RustGraph,
876    /// RustGraph multi-layer hypergraph format (nodes.jsonl + edges.jsonl + hyperedges.jsonl).
877    RustGraphHypergraph,
878}
879
880/// Scenario configuration for metadata, tagging, and ML training setup.
881///
882/// This section enables tracking the purpose and characteristics of a generation run.
883#[derive(Debug, Clone, Default, Serialize, Deserialize)]
884pub struct ScenarioConfig {
885    /// Tags for categorizing and filtering datasets.
886    /// Examples: "fraud_detection", "retail", "month_end_stress", "ml_training"
887    #[serde(default)]
888    pub tags: Vec<String>,
889
890    /// Data quality profile preset.
891    /// - "clean": Minimal data quality issues (0.1% missing, 0.05% typos)
892    /// - "noisy": Moderate issues (5% missing, 2% typos, 1% duplicates)
893    /// - "legacy": Heavy issues simulating legacy system data (10% missing, 5% typos)
894    #[serde(default)]
895    pub profile: Option<String>,
896
897    /// Human-readable description of the scenario purpose.
898    #[serde(default)]
899    pub description: Option<String>,
900
901    /// Whether this run is for ML training (enables balanced labeling).
902    #[serde(default)]
903    pub ml_training: bool,
904
905    /// Target anomaly class balance for ML training.
906    /// If set, anomalies will be injected to achieve this ratio.
907    #[serde(default)]
908    pub target_anomaly_ratio: Option<f64>,
909
910    /// Custom metadata key-value pairs.
911    #[serde(default)]
912    pub metadata: std::collections::HashMap<String, String>,
913}
914
915/// Temporal drift configuration for simulating distribution changes over time.
916///
917/// This enables generation of data that shows realistic temporal evolution,
918/// useful for training drift detection models and testing temporal robustness.
919#[derive(Debug, Clone, Serialize, Deserialize)]
920pub struct TemporalDriftConfig {
921    /// Enable temporal drift simulation.
922    #[serde(default)]
923    pub enabled: bool,
924
925    /// Amount mean drift per period (e.g., 0.02 = 2% mean shift per month).
926    /// Simulates gradual inflation or business growth.
927    #[serde(default = "default_amount_drift")]
928    pub amount_mean_drift: f64,
929
930    /// Amount variance drift per period (e.g., 0.01 = 1% variance increase per month).
931    /// Simulates increasing volatility over time.
932    #[serde(default)]
933    pub amount_variance_drift: f64,
934
935    /// Anomaly rate drift per period (e.g., 0.001 = 0.1% increase per month).
936    /// Simulates increasing fraud attempts or degrading controls.
937    #[serde(default)]
938    pub anomaly_rate_drift: f64,
939
940    /// Concept drift rate - how quickly feature distributions change (0.0-1.0).
941    /// Higher values cause more rapid distribution shifts.
942    #[serde(default = "default_concept_drift")]
943    pub concept_drift_rate: f64,
944
945    /// Sudden drift events - probability of a sudden distribution shift in any period.
946    #[serde(default)]
947    pub sudden_drift_probability: f64,
948
949    /// Magnitude of sudden drift events when they occur (multiplier).
950    #[serde(default = "default_sudden_drift_magnitude")]
951    pub sudden_drift_magnitude: f64,
952
953    /// Seasonal drift - enable cyclic patterns that repeat annually.
954    #[serde(default)]
955    pub seasonal_drift: bool,
956
957    /// Drift start period (0 = from beginning). Use to simulate stable baseline before drift.
958    #[serde(default)]
959    pub drift_start_period: u32,
960
961    /// Drift type: "gradual", "sudden", "recurring", "mixed"
962    #[serde(default = "default_drift_type")]
963    pub drift_type: DriftType,
964}
965
966fn default_amount_drift() -> f64 {
967    0.02
968}
969
970fn default_concept_drift() -> f64 {
971    0.01
972}
973
974fn default_sudden_drift_magnitude() -> f64 {
975    2.0
976}
977
978fn default_drift_type() -> DriftType {
979    DriftType::Gradual
980}
981
982impl Default for TemporalDriftConfig {
983    fn default() -> Self {
984        Self {
985            enabled: false,
986            amount_mean_drift: 0.02,
987            amount_variance_drift: 0.0,
988            anomaly_rate_drift: 0.0,
989            concept_drift_rate: 0.01,
990            sudden_drift_probability: 0.0,
991            sudden_drift_magnitude: 2.0,
992            seasonal_drift: false,
993            drift_start_period: 0,
994            drift_type: DriftType::Gradual,
995        }
996    }
997}
998
999impl TemporalDriftConfig {
1000    /// Convert to core DriftConfig for use in generators.
1001    pub fn to_core_config(&self) -> datasynth_core::distributions::DriftConfig {
1002        datasynth_core::distributions::DriftConfig {
1003            enabled: self.enabled,
1004            amount_mean_drift: self.amount_mean_drift,
1005            amount_variance_drift: self.amount_variance_drift,
1006            anomaly_rate_drift: self.anomaly_rate_drift,
1007            concept_drift_rate: self.concept_drift_rate,
1008            sudden_drift_probability: self.sudden_drift_probability,
1009            sudden_drift_magnitude: self.sudden_drift_magnitude,
1010            seasonal_drift: self.seasonal_drift,
1011            drift_start_period: self.drift_start_period,
1012            drift_type: match self.drift_type {
1013                DriftType::Gradual => datasynth_core::distributions::DriftType::Gradual,
1014                DriftType::Sudden => datasynth_core::distributions::DriftType::Sudden,
1015                DriftType::Recurring => datasynth_core::distributions::DriftType::Recurring,
1016                DriftType::Mixed => datasynth_core::distributions::DriftType::Mixed,
1017            },
1018            regime_changes: Vec::new(),
1019            economic_cycle: Default::default(),
1020            parameter_drifts: Vec::new(),
1021        }
1022    }
1023}
1024
1025/// Types of temporal drift patterns.
1026#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1027#[serde(rename_all = "snake_case")]
1028pub enum DriftType {
1029    /// Gradual, continuous drift over time (like inflation).
1030    #[default]
1031    Gradual,
1032    /// Sudden, point-in-time shifts (like policy changes).
1033    Sudden,
1034    /// Recurring patterns that cycle (like seasonal variations).
1035    Recurring,
1036    /// Combination of gradual background drift with occasional sudden shifts.
1037    Mixed,
1038}
1039
1040// ============================================================================
1041// Streaming Output API Configuration (Phase 2)
1042// ============================================================================
1043
1044/// Configuration for streaming output API.
1045#[derive(Debug, Clone, Serialize, Deserialize)]
1046pub struct StreamingSchemaConfig {
1047    /// Enable streaming output.
1048    #[serde(default)]
1049    pub enabled: bool,
1050    /// Target events per second (0 = unlimited, default 0).
1051    #[serde(default)]
1052    pub events_per_second: f64,
1053    /// Token bucket burst size (default 100).
1054    #[serde(default = "default_burst_size")]
1055    pub burst_size: u32,
1056    /// Buffer size for streaming (number of items).
1057    #[serde(default = "default_buffer_size")]
1058    pub buffer_size: usize,
1059    /// Enable progress reporting.
1060    #[serde(default = "default_true")]
1061    pub enable_progress: bool,
1062    /// Progress reporting interval (number of items).
1063    #[serde(default = "default_progress_interval")]
1064    pub progress_interval: u64,
1065    /// Backpressure strategy.
1066    #[serde(default)]
1067    pub backpressure: BackpressureSchemaStrategy,
1068}
1069
1070fn default_buffer_size() -> usize {
1071    1000
1072}
1073
1074fn default_progress_interval() -> u64 {
1075    100
1076}
1077
1078impl Default for StreamingSchemaConfig {
1079    fn default() -> Self {
1080        Self {
1081            enabled: false,
1082            events_per_second: 0.0,
1083            burst_size: 100,
1084            buffer_size: 1000,
1085            enable_progress: true,
1086            progress_interval: 100,
1087            backpressure: BackpressureSchemaStrategy::Block,
1088        }
1089    }
1090}
1091
1092/// Backpressure strategy for streaming output.
1093#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1094#[serde(rename_all = "snake_case")]
1095pub enum BackpressureSchemaStrategy {
1096    /// Block until space is available in the buffer.
1097    #[default]
1098    Block,
1099    /// Drop oldest items when buffer is full.
1100    DropOldest,
1101    /// Drop newest items when buffer is full.
1102    DropNewest,
1103    /// Buffer overflow items up to a limit, then block.
1104    Buffer,
1105}
1106
1107// ============================================================================
1108// Rate Limiting Configuration (Phase 5)
1109// ============================================================================
1110
1111/// Configuration for rate limiting.
1112#[derive(Debug, Clone, Serialize, Deserialize)]
1113pub struct RateLimitSchemaConfig {
1114    /// Enable rate limiting.
1115    #[serde(default)]
1116    pub enabled: bool,
1117    /// Entities per second limit.
1118    #[serde(default = "default_entities_per_second")]
1119    pub entities_per_second: f64,
1120    /// Burst size (number of tokens in bucket).
1121    #[serde(default = "default_burst_size")]
1122    pub burst_size: u32,
1123    /// Backpressure strategy for rate limiting.
1124    #[serde(default)]
1125    pub backpressure: RateLimitBackpressureSchema,
1126}
1127
1128fn default_entities_per_second() -> f64 {
1129    1000.0
1130}
1131
1132fn default_burst_size() -> u32 {
1133    100
1134}
1135
1136impl Default for RateLimitSchemaConfig {
1137    fn default() -> Self {
1138        Self {
1139            enabled: false,
1140            entities_per_second: 1000.0,
1141            burst_size: 100,
1142            backpressure: RateLimitBackpressureSchema::Block,
1143        }
1144    }
1145}
1146
1147/// Backpressure strategy for rate limiting.
1148#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1149#[serde(rename_all = "snake_case")]
1150pub enum RateLimitBackpressureSchema {
1151    /// Block until rate allows.
1152    #[default]
1153    Block,
1154    /// Drop items that exceed rate.
1155    Drop,
1156    /// Buffer items and process when rate allows.
1157    Buffer,
1158}
1159
1160// ============================================================================
1161// Temporal Attribute Generation Configuration (Phase 3)
1162// ============================================================================
1163
1164/// Configuration for temporal attribute generation.
1165#[derive(Debug, Clone, Serialize, Deserialize)]
1166pub struct TemporalAttributeSchemaConfig {
1167    /// Enable temporal attribute generation.
1168    #[serde(default)]
1169    pub enabled: bool,
1170    /// Valid time configuration.
1171    #[serde(default)]
1172    pub valid_time: ValidTimeSchemaConfig,
1173    /// Transaction time configuration.
1174    #[serde(default)]
1175    pub transaction_time: TransactionTimeSchemaConfig,
1176    /// Generate version chains for entities.
1177    #[serde(default)]
1178    pub generate_version_chains: bool,
1179    /// Average number of versions per entity.
1180    #[serde(default = "default_avg_versions")]
1181    pub avg_versions_per_entity: f64,
1182}
1183
1184fn default_avg_versions() -> f64 {
1185    1.5
1186}
1187
1188impl Default for TemporalAttributeSchemaConfig {
1189    fn default() -> Self {
1190        Self {
1191            enabled: false,
1192            valid_time: ValidTimeSchemaConfig::default(),
1193            transaction_time: TransactionTimeSchemaConfig::default(),
1194            generate_version_chains: false,
1195            avg_versions_per_entity: 1.5,
1196        }
1197    }
1198}
1199
1200/// Configuration for valid time (business time) generation.
1201#[derive(Debug, Clone, Serialize, Deserialize)]
1202pub struct ValidTimeSchemaConfig {
1203    /// Probability that valid_to is set (entity has ended validity).
1204    #[serde(default = "default_closed_probability")]
1205    pub closed_probability: f64,
1206    /// Average validity duration in days.
1207    #[serde(default = "default_avg_validity_days")]
1208    pub avg_validity_days: u32,
1209    /// Standard deviation of validity duration in days.
1210    #[serde(default = "default_validity_stddev")]
1211    pub validity_stddev_days: u32,
1212}
1213
1214fn default_closed_probability() -> f64 {
1215    0.1
1216}
1217
1218fn default_avg_validity_days() -> u32 {
1219    365
1220}
1221
1222fn default_validity_stddev() -> u32 {
1223    90
1224}
1225
1226impl Default for ValidTimeSchemaConfig {
1227    fn default() -> Self {
1228        Self {
1229            closed_probability: 0.1,
1230            avg_validity_days: 365,
1231            validity_stddev_days: 90,
1232        }
1233    }
1234}
1235
1236/// Configuration for transaction time (system time) generation.
1237#[derive(Debug, Clone, Serialize, Deserialize)]
1238pub struct TransactionTimeSchemaConfig {
1239    /// Average recording delay in seconds (0 = immediate).
1240    #[serde(default)]
1241    pub avg_recording_delay_seconds: u32,
1242    /// Allow backdating (recording time before valid time).
1243    #[serde(default)]
1244    pub allow_backdating: bool,
1245    /// Probability of backdating if allowed.
1246    #[serde(default = "default_backdating_probability")]
1247    pub backdating_probability: f64,
1248    /// Maximum backdate days.
1249    #[serde(default = "default_max_backdate_days")]
1250    pub max_backdate_days: u32,
1251}
1252
1253fn default_backdating_probability() -> f64 {
1254    0.01
1255}
1256
1257fn default_max_backdate_days() -> u32 {
1258    30
1259}
1260
1261impl Default for TransactionTimeSchemaConfig {
1262    fn default() -> Self {
1263        Self {
1264            avg_recording_delay_seconds: 0,
1265            allow_backdating: false,
1266            backdating_probability: 0.01,
1267            max_backdate_days: 30,
1268        }
1269    }
1270}
1271
1272// ============================================================================
1273// Relationship Generation Configuration (Phase 4)
1274// ============================================================================
1275
1276/// Configuration for relationship generation.
1277#[derive(Debug, Clone, Serialize, Deserialize)]
1278pub struct RelationshipSchemaConfig {
1279    /// Relationship type definitions.
1280    #[serde(default)]
1281    pub relationship_types: Vec<RelationshipTypeSchemaConfig>,
1282    /// Allow orphan entities (entities with no relationships).
1283    #[serde(default = "default_true")]
1284    pub allow_orphans: bool,
1285    /// Probability of creating an orphan entity.
1286    #[serde(default = "default_orphan_probability")]
1287    pub orphan_probability: f64,
1288    /// Allow circular relationships.
1289    #[serde(default)]
1290    pub allow_circular: bool,
1291    /// Maximum depth for circular relationship detection.
1292    #[serde(default = "default_max_circular_depth")]
1293    pub max_circular_depth: u32,
1294}
1295
1296fn default_orphan_probability() -> f64 {
1297    0.01
1298}
1299
1300fn default_max_circular_depth() -> u32 {
1301    3
1302}
1303
1304impl Default for RelationshipSchemaConfig {
1305    fn default() -> Self {
1306        Self {
1307            relationship_types: Vec::new(),
1308            allow_orphans: true,
1309            orphan_probability: 0.01,
1310            allow_circular: false,
1311            max_circular_depth: 3,
1312        }
1313    }
1314}
1315
1316/// Configuration for a specific relationship type.
1317#[derive(Debug, Clone, Serialize, Deserialize)]
1318pub struct RelationshipTypeSchemaConfig {
1319    /// Name of the relationship type (e.g., "debits", "credits", "created").
1320    pub name: String,
1321    /// Source entity type (e.g., "journal_entry").
1322    pub source_type: String,
1323    /// Target entity type (e.g., "account").
1324    pub target_type: String,
1325    /// Cardinality rule for this relationship.
1326    #[serde(default)]
1327    pub cardinality: CardinalitySchemaRule,
1328    /// Weight for this relationship in random selection.
1329    #[serde(default = "default_relationship_weight")]
1330    pub weight: f64,
1331    /// Whether this relationship is required.
1332    #[serde(default)]
1333    pub required: bool,
1334    /// Whether this relationship is directed.
1335    #[serde(default = "default_true")]
1336    pub directed: bool,
1337}
1338
1339fn default_relationship_weight() -> f64 {
1340    1.0
1341}
1342
1343impl Default for RelationshipTypeSchemaConfig {
1344    fn default() -> Self {
1345        Self {
1346            name: String::new(),
1347            source_type: String::new(),
1348            target_type: String::new(),
1349            cardinality: CardinalitySchemaRule::default(),
1350            weight: 1.0,
1351            required: false,
1352            directed: true,
1353        }
1354    }
1355}
1356
1357/// Cardinality rule for relationships in schema config.
1358#[derive(Debug, Clone, Serialize, Deserialize)]
1359#[serde(rename_all = "snake_case")]
1360pub enum CardinalitySchemaRule {
1361    /// One source to one target.
1362    OneToOne,
1363    /// One source to many targets.
1364    OneToMany {
1365        /// Minimum number of targets.
1366        min: u32,
1367        /// Maximum number of targets.
1368        max: u32,
1369    },
1370    /// Many sources to one target.
1371    ManyToOne {
1372        /// Minimum number of sources.
1373        min: u32,
1374        /// Maximum number of sources.
1375        max: u32,
1376    },
1377    /// Many sources to many targets.
1378    ManyToMany {
1379        /// Minimum targets per source.
1380        min_per_source: u32,
1381        /// Maximum targets per source.
1382        max_per_source: u32,
1383    },
1384}
1385
1386impl Default for CardinalitySchemaRule {
1387    fn default() -> Self {
1388        Self::OneToMany { min: 1, max: 5 }
1389    }
1390}
1391
1392/// Global configuration settings.
1393#[derive(Debug, Clone, Serialize, Deserialize)]
1394pub struct GlobalConfig {
1395    /// Random seed for reproducibility
1396    pub seed: Option<u64>,
1397    /// Industry sector
1398    pub industry: IndustrySector,
1399    /// Simulation start date (YYYY-MM-DD)
1400    #[serde(alias = "startDate")]
1401    pub start_date: String,
1402    /// Simulation period in months
1403    #[serde(alias = "periodMonths")]
1404    pub period_months: u32,
1405    /// Base currency for group reporting
1406    #[serde(default = "default_currency", alias = "groupCurrency")]
1407    pub group_currency: String,
1408    /// Presentation currency for consolidated financial statements (ISO 4217).
1409    /// If not set, defaults to `group_currency`.
1410    #[serde(default, alias = "presentationCurrency")]
1411    pub presentation_currency: Option<String>,
1412    /// Enable parallel generation
1413    #[serde(default = "default_true")]
1414    pub parallel: bool,
1415    /// Number of worker threads (0 = auto-detect)
1416    #[serde(default, alias = "workerThreads")]
1417    pub worker_threads: usize,
1418    /// Memory limit in MB (0 = unlimited)
1419    #[serde(default, alias = "memoryLimitMb")]
1420    pub memory_limit_mb: usize,
1421    /// Fiscal year length in months (defaults to 12 if not set).
1422    /// Used by session-based generation to split the total period into fiscal years.
1423    #[serde(default, alias = "fiscalYearMonths")]
1424    pub fiscal_year_months: Option<u32>,
1425}
1426
1427fn default_currency() -> String {
1428    "USD".to_string()
1429}
1430fn default_true() -> bool {
1431    true
1432}
1433
1434/// Configuration for generation session behavior.
1435///
1436/// When enabled, the generation pipeline splits the total period into fiscal years
1437/// and generates data period-by-period, carrying forward balance state.
1438#[derive(Debug, Clone, Serialize, Deserialize)]
1439pub struct SessionSchemaConfig {
1440    /// Whether session-based (period-by-period) generation is enabled.
1441    #[serde(default)]
1442    pub enabled: bool,
1443    /// Optional path for saving/loading session checkpoint files.
1444    #[serde(default)]
1445    pub checkpoint_path: Option<String>,
1446    /// Whether to write output files per fiscal period (e.g., `period_01/`).
1447    #[serde(default = "default_true")]
1448    pub per_period_output: bool,
1449    /// Whether to also produce a single consolidated output across all periods.
1450    #[serde(default = "default_true")]
1451    pub consolidated_output: bool,
1452}
1453
1454impl Default for SessionSchemaConfig {
1455    fn default() -> Self {
1456        Self {
1457            enabled: false,
1458            checkpoint_path: None,
1459            per_period_output: true,
1460            consolidated_output: true,
1461        }
1462    }
1463}
1464
1465/// Company code configuration.
1466#[derive(Debug, Clone, Serialize, Deserialize)]
1467pub struct CompanyConfig {
1468    /// Company code identifier
1469    pub code: String,
1470    /// Company name
1471    pub name: String,
1472    /// Local currency (ISO 4217)
1473    pub currency: String,
1474    /// Functional currency for IAS 21 translation (ISO 4217).
1475    /// If not set, defaults to the `currency` field (i.e. local == functional).
1476    #[serde(default, alias = "functionalCurrency")]
1477    pub functional_currency: Option<String>,
1478    /// Country code (ISO 3166-1 alpha-2)
1479    pub country: String,
1480    /// Fiscal year variant
1481    #[serde(default = "default_fiscal_variant", alias = "fiscalYearVariant")]
1482    pub fiscal_year_variant: String,
1483    /// Transaction volume per year
1484    #[serde(alias = "annualTransactionVolume")]
1485    pub annual_transaction_volume: TransactionVolume,
1486    /// Company-specific transaction weight
1487    #[serde(default = "default_weight", alias = "volumeWeight")]
1488    pub volume_weight: f64,
1489}
1490
1491fn default_fiscal_variant() -> String {
1492    "K4".to_string()
1493}
1494fn default_weight() -> f64 {
1495    1.0
1496}
1497
1498/// Transaction volume presets.
1499#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
1500#[serde(rename_all = "snake_case")]
1501pub enum TransactionVolume {
1502    /// 10,000 transactions per year
1503    TenK,
1504    /// 100,000 transactions per year
1505    HundredK,
1506    /// 1,000,000 transactions per year
1507    OneM,
1508    /// 10,000,000 transactions per year
1509    TenM,
1510    /// 100,000,000 transactions per year
1511    HundredM,
1512    /// Custom count
1513    Custom(u64),
1514}
1515
1516impl TransactionVolume {
1517    /// Get the transaction count.
1518    pub fn count(&self) -> u64 {
1519        match self {
1520            Self::TenK => 10_000,
1521            Self::HundredK => 100_000,
1522            Self::OneM => 1_000_000,
1523            Self::TenM => 10_000_000,
1524            Self::HundredM => 100_000_000,
1525            Self::Custom(n) => *n,
1526        }
1527    }
1528}
1529
1530/// Chart of Accounts configuration.
1531#[derive(Debug, Clone, Serialize, Deserialize)]
1532pub struct ChartOfAccountsConfig {
1533    /// CoA complexity level
1534    pub complexity: CoAComplexity,
1535    /// Use industry-specific accounts
1536    #[serde(default = "default_true")]
1537    pub industry_specific: bool,
1538    /// Custom account definitions file
1539    pub custom_accounts: Option<PathBuf>,
1540    /// Minimum hierarchy depth
1541    #[serde(default = "default_min_depth")]
1542    pub min_hierarchy_depth: u8,
1543    /// Maximum hierarchy depth
1544    #[serde(default = "default_max_depth")]
1545    pub max_hierarchy_depth: u8,
1546}
1547
1548fn default_min_depth() -> u8 {
1549    2
1550}
1551fn default_max_depth() -> u8 {
1552    5
1553}
1554
1555impl Default for ChartOfAccountsConfig {
1556    fn default() -> Self {
1557        Self {
1558            complexity: CoAComplexity::Small,
1559            industry_specific: true,
1560            custom_accounts: None,
1561            min_hierarchy_depth: default_min_depth(),
1562            max_hierarchy_depth: default_max_depth(),
1563        }
1564    }
1565}
1566
1567/// Transaction generation configuration.
1568#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1569pub struct TransactionConfig {
1570    /// Line item distribution
1571    #[serde(default)]
1572    pub line_item_distribution: LineItemDistributionConfig,
1573    /// Debit/credit balance distribution
1574    #[serde(default)]
1575    pub debit_credit_distribution: DebitCreditDistributionConfig,
1576    /// Even/odd line count distribution
1577    #[serde(default)]
1578    pub even_odd_distribution: EvenOddDistributionConfig,
1579    /// Transaction source distribution
1580    #[serde(default)]
1581    pub source_distribution: SourceDistribution,
1582    /// Seasonality configuration
1583    #[serde(default)]
1584    pub seasonality: SeasonalityConfig,
1585    /// Amount distribution
1586    #[serde(default)]
1587    pub amounts: AmountDistributionConfig,
1588    /// Benford's Law compliance configuration
1589    #[serde(default)]
1590    pub benford: BenfordConfig,
1591}
1592
1593/// Benford's Law compliance configuration.
1594#[derive(Debug, Clone, Serialize, Deserialize)]
1595pub struct BenfordConfig {
1596    /// Enable Benford's Law compliance for amount generation
1597    #[serde(default = "default_true")]
1598    pub enabled: bool,
1599    /// Tolerance for deviation from ideal Benford distribution (0.0-1.0)
1600    #[serde(default = "default_benford_tolerance")]
1601    pub tolerance: f64,
1602    /// Transaction sources exempt from Benford's Law (fixed amounts)
1603    #[serde(default)]
1604    pub exempt_sources: Vec<BenfordExemption>,
1605}
1606
1607fn default_benford_tolerance() -> f64 {
1608    0.05
1609}
1610
1611impl Default for BenfordConfig {
1612    fn default() -> Self {
1613        Self {
1614            enabled: true,
1615            tolerance: default_benford_tolerance(),
1616            exempt_sources: vec![BenfordExemption::Recurring, BenfordExemption::Payroll],
1617        }
1618    }
1619}
1620
1621/// Types of transactions exempt from Benford's Law.
1622#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1623#[serde(rename_all = "snake_case")]
1624pub enum BenfordExemption {
1625    /// Recurring fixed amounts (rent, subscriptions)
1626    Recurring,
1627    /// Payroll (standardized salaries)
1628    Payroll,
1629    /// Fixed fees and charges
1630    FixedFees,
1631    /// Round number purchases (often legitimate)
1632    RoundAmounts,
1633}
1634
1635/// Distribution of transaction sources.
1636#[derive(Debug, Clone, Serialize, Deserialize)]
1637pub struct SourceDistribution {
1638    /// Manual entries percentage
1639    pub manual: f64,
1640    /// Automated system entries
1641    pub automated: f64,
1642    /// Recurring entries
1643    pub recurring: f64,
1644    /// Adjustment entries
1645    pub adjustment: f64,
1646}
1647
1648impl Default for SourceDistribution {
1649    fn default() -> Self {
1650        Self {
1651            manual: 0.20,
1652            automated: 0.70,
1653            recurring: 0.07,
1654            adjustment: 0.03,
1655        }
1656    }
1657}
1658
1659/// Output configuration.
1660#[derive(Debug, Clone, Serialize, Deserialize)]
1661pub struct OutputConfig {
1662    /// Output mode
1663    #[serde(default)]
1664    pub mode: OutputMode,
1665    /// Output directory
1666    #[serde(alias = "outputDirectory")]
1667    pub output_directory: PathBuf,
1668    /// File formats to generate. Accepts both `formats: [json, csv]`
1669    /// (canonical YAML) and `exportFormat: "json"` / `exportFormats:
1670    /// ["json", "csv"]` (SDK-style camelCase). The single-string
1671    /// `exportFormat` form is deserialised via `one_or_many_formats`
1672    /// so SDK clients submitting `exportFormat: "json"` hit the right
1673    /// code path instead of silently falling through to the Parquet
1674    /// default — the bug the SDK team flagged in v4.4.0.
1675    #[serde(
1676        default = "default_formats",
1677        alias = "exportFormats",
1678        alias = "exportFormat",
1679        deserialize_with = "one_or_many_formats"
1680    )]
1681    pub formats: Vec<FileFormat>,
1682    /// Compression settings
1683    #[serde(default)]
1684    pub compression: CompressionConfig,
1685    /// Batch size for writes
1686    #[serde(default = "default_batch_size", alias = "batchSize")]
1687    pub batch_size: usize,
1688    /// Include ACDOCA format
1689    #[serde(default = "default_true", alias = "includeAcdoca")]
1690    pub include_acdoca: bool,
1691    /// Include BSEG format
1692    #[serde(default, alias = "includeBseg")]
1693    pub include_bseg: bool,
1694    /// Partition by fiscal period
1695    #[serde(default = "default_true", alias = "partitionByPeriod")]
1696    pub partition_by_period: bool,
1697    /// Partition by company code
1698    #[serde(default, alias = "partitionByCompany")]
1699    pub partition_by_company: bool,
1700    /// Numeric serialization mode for JSON output.
1701    /// "string" (default): decimals as `"1729237.30"` — lossless precision.
1702    /// "native": decimals as `1729237.30` — friendlier for pandas/analytics.
1703    #[serde(default, alias = "numericMode")]
1704    pub numeric_mode: NumericMode,
1705    /// JSON export layout for journal entries and document flows.
1706    /// "nested" (default): `{"header": {...}, "lines": [...]}` — natural ERP structure.
1707    /// "flat": header fields repeated on every line — friendlier for analytics/ML.
1708    ///
1709    /// Accepts both `export_layout` (canonical / YAML) and `exportLayout`
1710    /// (camelCase / SDK JSON) so SDKs that follow camelCase conventions
1711    /// hit the flat path rather than silently getting the Nested default.
1712    /// Before v3.1.1 the missing camelCase alias meant SDK requests with
1713    /// `exportLayout: "flat"` were silently ignored, which SDK operators
1714    /// reported as "flat hangs generation" (the job completed with Nested
1715    /// layout, but manifests didn't match the expected flat shape).
1716    #[serde(default, alias = "exportLayout")]
1717    pub export_layout: ExportLayout,
1718    /// SAP / HANA export settings (only read when the CLI
1719    /// `--export-format sap` flag is passed). Empty by default so
1720    /// existing configs don't change behaviour; dialect defaults to
1721    /// `classic` for backward compatibility.
1722    #[serde(default, alias = "sapExport")]
1723    pub sap: SapExportSettings,
1724    /// SAF-T (Standard Audit File for Tax) export settings. Read when
1725    /// the CLI `--export-format saft` flag is passed. Defaults to
1726    /// Portugal (`pt`) because the PT variant is the most mature and
1727    /// cross-jurisdiction compatible. Override with
1728    /// `jurisdiction: pl|ro|no|lu` for the other supported countries.
1729    #[serde(default, alias = "saftExport")]
1730    pub saft: SaftExportSettings,
1731}
1732
1733/// Configuration for the SAP export writers (BKPF / BSEG / ACDOCA and
1734/// master-data tables).
1735///
1736/// Mirror of `datasynth_output::SapExportConfig` in YAML form — the CLI
1737/// translates this into the runtime struct before invoking the exporter,
1738/// replacing the v3.x hardcoded `SapExportConfig::default()`.
1739#[derive(Debug, Clone, Serialize, Deserialize)]
1740pub struct SapExportSettings {
1741    /// SAP client / MANDT column value on every table.
1742    #[serde(default = "default_sap_client")]
1743    pub client: String,
1744    /// Leading ledger for ACDOCA rows (0L for S/4HANA default).
1745    #[serde(default = "default_sap_ledger")]
1746    pub ledger: String,
1747    /// Source system identifier — written to ACDOCA.AWSYS so downstream
1748    /// consumers can distinguish synthetic rows from production ones.
1749    #[serde(default = "default_sap_source_system")]
1750    pub source_system: String,
1751    /// Local currency (WAERS / RWCUR).
1752    #[serde(default = "default_sap_currency")]
1753    pub local_currency: String,
1754    /// Optional group / consolidation currency (triggers the HSL / RHCUR columns).
1755    #[serde(default, skip_serializing_if = "Option::is_none")]
1756    pub group_currency: Option<String>,
1757    /// Which SAP tables to export. Empty = default set (bkpf, bseg, acdoca).
1758    #[serde(default)]
1759    pub tables: Vec<String>,
1760    /// Include ZSIM_* extension columns on ACDOCA rows.
1761    #[serde(default = "default_true")]
1762    pub include_extension_fields: bool,
1763    /// Export dialect — `classic` (R/3 / BODS) or `hana` (S/4HANA CDS).
1764    #[serde(default)]
1765    pub dialect: SapDialectSetting,
1766    /// Legacy flag, retained for backward compatibility. Has no effect
1767    /// when `dialect = hana`.
1768    #[serde(default = "default_true")]
1769    pub use_sap_date_format: bool,
1770}
1771
1772impl Default for SapExportSettings {
1773    fn default() -> Self {
1774        Self {
1775            client: default_sap_client(),
1776            ledger: default_sap_ledger(),
1777            source_system: default_sap_source_system(),
1778            local_currency: default_sap_currency(),
1779            group_currency: None,
1780            tables: Vec::new(),
1781            include_extension_fields: true,
1782            dialect: SapDialectSetting::default(),
1783            use_sap_date_format: true,
1784        }
1785    }
1786}
1787
1788fn default_sap_client() -> String {
1789    "100".to_string()
1790}
1791fn default_sap_ledger() -> String {
1792    "0L".to_string()
1793}
1794fn default_sap_source_system() -> String {
1795    "SYNTH".to_string()
1796}
1797fn default_sap_currency() -> String {
1798    "USD".to_string()
1799}
1800
1801/// SAP export dialect (wire form — `datasynth_output::SapDialect` is the
1802/// runtime form).
1803#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
1804#[serde(rename_all = "snake_case")]
1805pub enum SapDialectSetting {
1806    /// Legacy R/3 / BODS-compatible CSV (default).
1807    #[default]
1808    Classic,
1809    /// S/4HANA CDS dialect (semicolon + UTF-8 BOM + decimal comma + ISO dates).
1810    Hana,
1811}
1812
1813/// SAF-T export settings (v4.3.1).
1814#[derive(Debug, Clone, Serialize, Deserialize)]
1815pub struct SaftExportSettings {
1816    /// ISO-ish two-letter code: `pt` / `pl` / `ro` / `no` / `lu`.
1817    /// Defaults to `pt` (Portugal, most mature variant).
1818    #[serde(default = "default_saft_jurisdiction")]
1819    pub jurisdiction: String,
1820    /// Company tax registration number / VAT ID / TIN used in the
1821    /// `Header.TaxRegistrationNumber` element. Falls back to
1822    /// `"Desconhecido"` (Portuguese for "unknown") when empty.
1823    #[serde(default)]
1824    pub company_tax_id: String,
1825    /// Optional override for the company name used in the Header.
1826    /// When empty, the first configured company's `name` is used.
1827    #[serde(default)]
1828    pub company_name: String,
1829}
1830
1831impl Default for SaftExportSettings {
1832    fn default() -> Self {
1833        Self {
1834            jurisdiction: default_saft_jurisdiction(),
1835            company_tax_id: String::new(),
1836            company_name: String::new(),
1837        }
1838    }
1839}
1840
1841fn default_saft_jurisdiction() -> String {
1842    "pt".to_string()
1843}
1844
1845fn default_formats() -> Vec<FileFormat> {
1846    vec![FileFormat::Parquet]
1847}
1848fn default_batch_size() -> usize {
1849    100_000
1850}
1851
1852/// Custom deserializer for `formats` that accepts either a single
1853/// `FileFormat` (e.g. `"json"` for SDK `exportFormat: "json"`) or a
1854/// vector (e.g. `["json", "csv"]`). Without this shim an SDK config
1855/// with `exportFormat: "json"` would fail to parse (serde expects a
1856/// sequence for a `Vec` field) and silently fall through to defaults.
1857fn one_or_many_formats<'de, D>(deserializer: D) -> Result<Vec<FileFormat>, D::Error>
1858where
1859    D: serde::Deserializer<'de>,
1860{
1861    #[derive(Deserialize)]
1862    #[serde(untagged)]
1863    enum OneOrMany {
1864        One(FileFormat),
1865        Many(Vec<FileFormat>),
1866    }
1867    match OneOrMany::deserialize(deserializer)? {
1868        OneOrMany::One(f) => Ok(vec![f]),
1869        OneOrMany::Many(v) => Ok(v),
1870    }
1871}
1872
1873impl Default for OutputConfig {
1874    fn default() -> Self {
1875        Self {
1876            mode: OutputMode::FlatFile,
1877            output_directory: PathBuf::from("./output"),
1878            formats: default_formats(),
1879            compression: CompressionConfig::default(),
1880            batch_size: default_batch_size(),
1881            include_acdoca: true,
1882            include_bseg: false,
1883            partition_by_period: true,
1884            partition_by_company: false,
1885            numeric_mode: NumericMode::default(),
1886            export_layout: ExportLayout::default(),
1887            sap: SapExportSettings::default(),
1888            saft: SaftExportSettings::default(),
1889        }
1890    }
1891}
1892
1893/// Numeric serialization mode for JSON decimal fields.
1894#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
1895#[serde(rename_all = "snake_case")]
1896pub enum NumericMode {
1897    /// Decimals as JSON strings (e.g. `"1729237.30"`). Preserves full precision.
1898    #[default]
1899    String,
1900    /// Decimals as JSON numbers (e.g. `1729237.30`). Friendlier for pandas/analytics.
1901    Native,
1902}
1903
1904/// JSON export layout for nested structures (journal entries, document flows).
1905#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
1906#[serde(rename_all = "snake_case")]
1907pub enum ExportLayout {
1908    /// Nested structure: `{"header": {...}, "lines": [...]}`. Natural ERP format.
1909    #[default]
1910    Nested,
1911    /// Flat structure: header fields repeated on every line. Analytics-friendly.
1912    Flat,
1913}
1914
1915/// Output mode.
1916#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
1917#[serde(rename_all = "snake_case")]
1918pub enum OutputMode {
1919    /// Stream records as generated
1920    Streaming,
1921    /// Write to flat files
1922    #[default]
1923    FlatFile,
1924    /// Both streaming and flat file
1925    Both,
1926}
1927
1928/// Supported file formats.
1929#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1930#[serde(rename_all = "snake_case")]
1931pub enum FileFormat {
1932    Csv,
1933    Parquet,
1934    Json,
1935    JsonLines,
1936}
1937
1938/// Compression configuration.
1939#[derive(Debug, Clone, Serialize, Deserialize)]
1940pub struct CompressionConfig {
1941    /// Enable compression
1942    #[serde(default = "default_true")]
1943    pub enabled: bool,
1944    /// Compression algorithm
1945    #[serde(default)]
1946    pub algorithm: CompressionAlgorithm,
1947    /// Compression level (1-9)
1948    #[serde(default = "default_compression_level")]
1949    pub level: u8,
1950}
1951
1952fn default_compression_level() -> u8 {
1953    3
1954}
1955
1956impl Default for CompressionConfig {
1957    fn default() -> Self {
1958        Self {
1959            enabled: true,
1960            algorithm: CompressionAlgorithm::default(),
1961            level: default_compression_level(),
1962        }
1963    }
1964}
1965
1966/// Compression algorithms.
1967#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
1968#[serde(rename_all = "snake_case")]
1969pub enum CompressionAlgorithm {
1970    Gzip,
1971    #[default]
1972    Zstd,
1973    Lz4,
1974    Snappy,
1975}
1976
1977/// Fraud simulation configuration.
1978///
1979/// ## Document-level vs. line-level fraud
1980///
1981/// `fraud_rate` applies to individual journal-entry lines (line-level).
1982/// `document_fraud_rate` (optional) applies to source documents
1983/// (purchase orders, vendor invoices, customer invoices, payments), and when
1984/// `propagate_to_lines` is true, every JE derived from a fraudulent document
1985/// also gets `is_fraud = true`. This lets users express either:
1986///
1987///  * pure line-level fraud (`document_fraud_rate = None`): legacy behaviour;
1988///  * pure document-level fraud (`fraud_rate ≈ 0` and `document_fraud_rate` set):
1989///    fraud rings expressed at document granularity — realistic for PO/invoice
1990///    fraud schemes where one fraudulent document spawns multiple derived JEs;
1991///  * hybrid (both set): document-level scheme fraud plus unrelated line-level
1992///    slip-ups.
1993///
1994/// `propagate_to_document` does the inverse: when a JE is tagged as fraud by
1995/// the anomaly injector, its source document is also marked fraudulent.
1996#[derive(Debug, Clone, Serialize, Deserialize)]
1997pub struct FraudConfig {
1998    /// Enable fraud scenario generation
1999    #[serde(default)]
2000    pub enabled: bool,
2001    /// Line-level fraud rate: fraction of individual JE lines flagged as fraud (0.0 to 1.0).
2002    ///
2003    /// # Effective line-level prevalence
2004    ///
2005    /// If `document_fraud_rate = Some(d)` and `propagate_to_lines = true`,
2006    /// the observed line-level fraud prevalence is roughly:
2007    ///
2008    /// > `P(line is_fraud) ≈ fraud_rate + d × avg_lines_per_fraud_doc / total_lines`
2009    ///
2010    /// For a typical retail job (avg 3 lines per document, ~30 % of lines
2011    /// come from doc-flow-derived JEs) the combined rate lands near:
2012    ///
2013    /// > `fraud_rate + 0.3 × d`
2014    ///
2015    /// so setting `fraud_rate=0.02, document_fraud_rate=0.05, propagate_to_lines=true`
2016    /// produces ~3.5 % line-level fraud, not 2 %. To target a specific
2017    /// line-level prevalence X, choose `fraud_rate = X - 0.3 × d`.
2018    #[serde(default = "default_fraud_rate", alias = "fraudRate")]
2019    pub fraud_rate: f64,
2020    /// Document-level fraud rate: fraction of source documents (PO, vendor
2021    /// invoice, customer invoice, payment) flagged as fraud. `None` disables
2022    /// document-level injection; `Some(r)` marks ~r × document-count as fraud
2023    /// independently of the line-level rate.
2024    ///
2025    /// v4.4.2+ default: `Some(0.01)` — the SDK team reported
2026    /// `is_fraud_propagated: 0/72` regressed from `12/33` in 3.1.1 because
2027    /// the default had silently become None. A 1% document-fraud default
2028    /// restores the propagation signal (~0.3% of JE headers carry
2029    /// `is_fraud_propagated = true`) without meaningfully changing the
2030    /// line-level fraud prevalence. Set to `Some(0.0)` or `null` in your
2031    /// YAML to explicitly disable document-level injection.
2032    #[serde(default = "default_document_fraud_rate", alias = "documentFraudRate")]
2033    pub document_fraud_rate: Option<f64>,
2034    /// When true, flagging a document as fraudulent cascades `is_fraud = true`
2035    /// and `fraud_type` to every journal entry derived from that document,
2036    /// and records `fraud_source_document_id` on the JE header.
2037    /// Default: `true`.
2038    #[serde(default = "default_true", alias = "propagateToLines")]
2039    pub propagate_to_lines: bool,
2040    /// When true, tagging a JE as fraud via line-level anomaly injection also
2041    /// marks the JE's source document as fraudulent (if it can be resolved).
2042    /// Default: `true`.
2043    #[serde(default = "default_true", alias = "propagateToDocument")]
2044    pub propagate_to_document: bool,
2045    /// Fraud type distribution
2046    #[serde(default)]
2047    pub fraud_type_distribution: FraudTypeDistribution,
2048    /// Enable fraud clustering
2049    #[serde(default)]
2050    pub clustering_enabled: bool,
2051    /// Clustering factor
2052    #[serde(default = "default_clustering_factor")]
2053    pub clustering_factor: f64,
2054    /// Approval thresholds for threshold-adjacent fraud pattern
2055    #[serde(default = "default_approval_thresholds")]
2056    pub approval_thresholds: Vec<f64>,
2057}
2058
2059fn default_approval_thresholds() -> Vec<f64> {
2060    vec![1000.0, 5000.0, 10000.0, 25000.0, 50000.0, 100000.0]
2061}
2062
2063fn default_fraud_rate() -> f64 {
2064    0.005
2065}
2066fn default_document_fraud_rate() -> Option<f64> {
2067    Some(0.01)
2068}
2069fn default_clustering_factor() -> f64 {
2070    3.0
2071}
2072
2073impl Default for FraudConfig {
2074    fn default() -> Self {
2075        Self {
2076            enabled: false,
2077            fraud_rate: default_fraud_rate(),
2078            document_fraud_rate: default_document_fraud_rate(),
2079            propagate_to_lines: true,
2080            propagate_to_document: true,
2081            fraud_type_distribution: FraudTypeDistribution::default(),
2082            clustering_enabled: false,
2083            clustering_factor: default_clustering_factor(),
2084            approval_thresholds: default_approval_thresholds(),
2085        }
2086    }
2087}
2088
2089/// Distribution of fraud types.
2090#[derive(Debug, Clone, Serialize, Deserialize)]
2091pub struct FraudTypeDistribution {
2092    pub suspense_account_abuse: f64,
2093    pub fictitious_transaction: f64,
2094    pub revenue_manipulation: f64,
2095    pub expense_capitalization: f64,
2096    pub split_transaction: f64,
2097    pub timing_anomaly: f64,
2098    pub unauthorized_access: f64,
2099    pub duplicate_payment: f64,
2100}
2101
2102impl Default for FraudTypeDistribution {
2103    fn default() -> Self {
2104        Self {
2105            suspense_account_abuse: 0.25,
2106            fictitious_transaction: 0.15,
2107            revenue_manipulation: 0.10,
2108            expense_capitalization: 0.10,
2109            split_transaction: 0.15,
2110            timing_anomaly: 0.10,
2111            unauthorized_access: 0.10,
2112            duplicate_payment: 0.05,
2113        }
2114    }
2115}
2116
2117/// Internal Controls System (ICS) configuration.
2118#[derive(Debug, Clone, Serialize, Deserialize)]
2119pub struct InternalControlsConfig {
2120    /// Enable internal controls system
2121    #[serde(default)]
2122    pub enabled: bool,
2123    /// Rate at which controls result in exceptions (0.0 - 1.0)
2124    #[serde(default = "default_exception_rate")]
2125    pub exception_rate: f64,
2126    /// Rate at which SoD violations occur (0.0 - 1.0)
2127    #[serde(default = "default_sod_violation_rate")]
2128    pub sod_violation_rate: f64,
2129    /// Export control master data to separate files
2130    #[serde(default = "default_true")]
2131    pub export_control_master_data: bool,
2132    /// SOX materiality threshold for marking transactions as SOX-relevant
2133    #[serde(default = "default_sox_materiality_threshold")]
2134    pub sox_materiality_threshold: f64,
2135    /// Enable COSO 2013 framework integration
2136    #[serde(default = "default_true")]
2137    pub coso_enabled: bool,
2138    /// Include entity-level controls in generation
2139    #[serde(default)]
2140    pub include_entity_level_controls: bool,
2141    /// Target maturity level for controls
2142    /// Valid values: "ad_hoc", "repeatable", "defined", "managed", "optimized", "mixed"
2143    #[serde(default = "default_target_maturity_level")]
2144    pub target_maturity_level: String,
2145}
2146
2147fn default_exception_rate() -> f64 {
2148    0.02
2149}
2150
2151fn default_sod_violation_rate() -> f64 {
2152    0.01
2153}
2154
2155fn default_sox_materiality_threshold() -> f64 {
2156    10000.0
2157}
2158
2159fn default_target_maturity_level() -> String {
2160    "mixed".to_string()
2161}
2162
2163impl Default for InternalControlsConfig {
2164    fn default() -> Self {
2165        Self {
2166            enabled: false,
2167            exception_rate: default_exception_rate(),
2168            sod_violation_rate: default_sod_violation_rate(),
2169            export_control_master_data: true,
2170            sox_materiality_threshold: default_sox_materiality_threshold(),
2171            coso_enabled: true,
2172            include_entity_level_controls: false,
2173            target_maturity_level: default_target_maturity_level(),
2174        }
2175    }
2176}
2177
2178/// Business process configuration.
2179#[derive(Debug, Clone, Serialize, Deserialize)]
2180pub struct BusinessProcessConfig {
2181    /// Order-to-Cash weight
2182    #[serde(default = "default_o2c")]
2183    pub o2c_weight: f64,
2184    /// Procure-to-Pay weight
2185    #[serde(default = "default_p2p")]
2186    pub p2p_weight: f64,
2187    /// Record-to-Report weight
2188    #[serde(default = "default_r2r")]
2189    pub r2r_weight: f64,
2190    /// Hire-to-Retire weight
2191    #[serde(default = "default_h2r")]
2192    pub h2r_weight: f64,
2193    /// Acquire-to-Retire weight
2194    #[serde(default = "default_a2r")]
2195    pub a2r_weight: f64,
2196}
2197
2198fn default_o2c() -> f64 {
2199    0.35
2200}
2201fn default_p2p() -> f64 {
2202    0.30
2203}
2204fn default_r2r() -> f64 {
2205    0.20
2206}
2207fn default_h2r() -> f64 {
2208    0.10
2209}
2210fn default_a2r() -> f64 {
2211    0.05
2212}
2213
2214impl Default for BusinessProcessConfig {
2215    fn default() -> Self {
2216        Self {
2217            o2c_weight: default_o2c(),
2218            p2p_weight: default_p2p(),
2219            r2r_weight: default_r2r(),
2220            h2r_weight: default_h2r(),
2221            a2r_weight: default_a2r(),
2222        }
2223    }
2224}
2225
2226/// User persona configuration.
2227#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2228pub struct UserPersonaConfig {
2229    /// Distribution of user personas
2230    #[serde(default)]
2231    pub persona_distribution: PersonaDistribution,
2232    /// Users per persona type
2233    #[serde(default)]
2234    pub users_per_persona: UsersPerPersona,
2235}
2236
2237/// Distribution of user personas for transaction generation.
2238#[derive(Debug, Clone, Serialize, Deserialize)]
2239pub struct PersonaDistribution {
2240    pub junior_accountant: f64,
2241    pub senior_accountant: f64,
2242    pub controller: f64,
2243    pub manager: f64,
2244    pub automated_system: f64,
2245}
2246
2247impl Default for PersonaDistribution {
2248    fn default() -> Self {
2249        Self {
2250            junior_accountant: 0.15,
2251            senior_accountant: 0.15,
2252            controller: 0.05,
2253            manager: 0.05,
2254            automated_system: 0.60,
2255        }
2256    }
2257}
2258
2259/// Number of users per persona type.
2260#[derive(Debug, Clone, Serialize, Deserialize)]
2261pub struct UsersPerPersona {
2262    pub junior_accountant: usize,
2263    pub senior_accountant: usize,
2264    pub controller: usize,
2265    pub manager: usize,
2266    pub automated_system: usize,
2267}
2268
2269impl Default for UsersPerPersona {
2270    fn default() -> Self {
2271        Self {
2272            junior_accountant: 10,
2273            senior_accountant: 5,
2274            controller: 2,
2275            manager: 3,
2276            automated_system: 20,
2277        }
2278    }
2279}
2280
2281/// Template configuration for realistic data generation.
2282///
2283/// # User-supplied template packs (v3.2.0+)
2284///
2285/// Set `path` to a directory (or single YAML/JSON file) to override or
2286/// extend the embedded default pools for vendor names, customer names,
2287/// material/asset descriptions, audit findings, bank names, and
2288/// department names. When `path` is `None` (the default), generators
2289/// use the compiled-in pools and output is byte-identical to v3.1.2.
2290///
2291/// See `crates/datasynth-core/src/templates/loader.rs::TemplateData`
2292/// for the full YAML schema. Use `datasynth-data templates export` to
2293/// dump the defaults as a starter pack.
2294#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2295pub struct TemplateConfig {
2296    /// Name generation settings
2297    #[serde(default)]
2298    pub names: NameTemplateConfig,
2299    /// Description generation settings
2300    #[serde(default)]
2301    pub descriptions: DescriptionTemplateConfig,
2302    /// Reference number settings
2303    #[serde(default)]
2304    pub references: ReferenceTemplateConfig,
2305    /// Optional path to a user-supplied template file or directory.
2306    /// When set, entries from the file(s) augment or replace the
2307    /// embedded defaults according to `merge_strategy`.
2308    ///
2309    /// `None` (default) = use embedded pools only (byte-identical to v3.1.2).
2310    #[serde(default, alias = "templatesPath")]
2311    pub path: Option<std::path::PathBuf>,
2312    /// How file-based entries combine with embedded defaults.
2313    ///
2314    /// - `extend` (default): append file entries to embedded pools,
2315    ///   de-duplicating. Safe for incremental overlays.
2316    /// - `replace`: discard embedded pools entirely and use only file
2317    ///   entries. Requires a fully-populated template file.
2318    /// - `merge_prefer_file`: replace individual categories when present
2319    ///   in the file; keep embedded for absent categories.
2320    #[serde(default, alias = "mergeStrategy")]
2321    pub merge_strategy: TemplateMergeStrategy,
2322}
2323
2324/// Strategy for combining user-supplied template files with embedded defaults.
2325#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
2326#[serde(rename_all = "snake_case")]
2327pub enum TemplateMergeStrategy {
2328    /// Append file entries to embedded pools (default).
2329    #[default]
2330    Extend,
2331    /// Replace embedded pools entirely with file entries.
2332    Replace,
2333    /// Replace individual categories when present in file; keep embedded for absent ones.
2334    MergePreferFile,
2335}
2336
2337/// Name template configuration.
2338#[derive(Debug, Clone, Serialize, Deserialize)]
2339pub struct NameTemplateConfig {
2340    /// Distribution of name cultures
2341    #[serde(default)]
2342    pub culture_distribution: CultureDistribution,
2343    /// Email domain for generated users
2344    #[serde(default = "default_email_domain")]
2345    pub email_domain: String,
2346    /// Generate realistic display names
2347    #[serde(default = "default_true")]
2348    pub generate_realistic_names: bool,
2349}
2350
2351fn default_email_domain() -> String {
2352    "company.com".to_string()
2353}
2354
2355impl Default for NameTemplateConfig {
2356    fn default() -> Self {
2357        Self {
2358            culture_distribution: CultureDistribution::default(),
2359            email_domain: default_email_domain(),
2360            generate_realistic_names: true,
2361        }
2362    }
2363}
2364
2365/// Distribution of name cultures for generation.
2366#[derive(Debug, Clone, Serialize, Deserialize)]
2367pub struct CultureDistribution {
2368    pub western_us: f64,
2369    pub hispanic: f64,
2370    pub german: f64,
2371    pub french: f64,
2372    pub chinese: f64,
2373    pub japanese: f64,
2374    pub indian: f64,
2375}
2376
2377impl Default for CultureDistribution {
2378    fn default() -> Self {
2379        Self {
2380            western_us: 0.40,
2381            hispanic: 0.20,
2382            german: 0.10,
2383            french: 0.05,
2384            chinese: 0.10,
2385            japanese: 0.05,
2386            indian: 0.10,
2387        }
2388    }
2389}
2390
2391/// Description template configuration.
2392#[derive(Debug, Clone, Serialize, Deserialize)]
2393pub struct DescriptionTemplateConfig {
2394    /// Generate header text for journal entries
2395    #[serde(default = "default_true")]
2396    pub generate_header_text: bool,
2397    /// Generate line text for journal entry lines
2398    #[serde(default = "default_true")]
2399    pub generate_line_text: bool,
2400}
2401
2402impl Default for DescriptionTemplateConfig {
2403    fn default() -> Self {
2404        Self {
2405            generate_header_text: true,
2406            generate_line_text: true,
2407        }
2408    }
2409}
2410
2411/// Reference number template configuration.
2412#[derive(Debug, Clone, Serialize, Deserialize)]
2413pub struct ReferenceTemplateConfig {
2414    /// Generate reference numbers
2415    #[serde(default = "default_true")]
2416    pub generate_references: bool,
2417    /// Invoice prefix
2418    #[serde(default = "default_invoice_prefix")]
2419    pub invoice_prefix: String,
2420    /// Purchase order prefix
2421    #[serde(default = "default_po_prefix")]
2422    pub po_prefix: String,
2423    /// Sales order prefix
2424    #[serde(default = "default_so_prefix")]
2425    pub so_prefix: String,
2426}
2427
2428fn default_invoice_prefix() -> String {
2429    "INV".to_string()
2430}
2431fn default_po_prefix() -> String {
2432    "PO".to_string()
2433}
2434fn default_so_prefix() -> String {
2435    "SO".to_string()
2436}
2437
2438impl Default for ReferenceTemplateConfig {
2439    fn default() -> Self {
2440        Self {
2441            generate_references: true,
2442            invoice_prefix: default_invoice_prefix(),
2443            po_prefix: default_po_prefix(),
2444            so_prefix: default_so_prefix(),
2445        }
2446    }
2447}
2448
2449/// Approval workflow configuration.
2450#[derive(Debug, Clone, Serialize, Deserialize)]
2451pub struct ApprovalConfig {
2452    /// Enable approval workflow generation
2453    #[serde(default)]
2454    pub enabled: bool,
2455    /// Threshold below which transactions are auto-approved
2456    #[serde(default = "default_auto_approve_threshold")]
2457    pub auto_approve_threshold: f64,
2458    /// Rate at which approvals are rejected (0.0 to 1.0)
2459    #[serde(default = "default_rejection_rate")]
2460    pub rejection_rate: f64,
2461    /// Rate at which approvals require revision (0.0 to 1.0)
2462    #[serde(default = "default_revision_rate")]
2463    pub revision_rate: f64,
2464    /// Average delay in hours for approval processing
2465    #[serde(default = "default_approval_delay_hours")]
2466    pub average_approval_delay_hours: f64,
2467    /// Approval chain thresholds
2468    #[serde(default)]
2469    pub thresholds: Vec<ApprovalThresholdConfig>,
2470}
2471
2472fn default_auto_approve_threshold() -> f64 {
2473    1000.0
2474}
2475fn default_rejection_rate() -> f64 {
2476    0.02
2477}
2478fn default_revision_rate() -> f64 {
2479    0.05
2480}
2481fn default_approval_delay_hours() -> f64 {
2482    4.0
2483}
2484
2485impl Default for ApprovalConfig {
2486    fn default() -> Self {
2487        Self {
2488            enabled: false,
2489            auto_approve_threshold: default_auto_approve_threshold(),
2490            rejection_rate: default_rejection_rate(),
2491            revision_rate: default_revision_rate(),
2492            average_approval_delay_hours: default_approval_delay_hours(),
2493            thresholds: vec![
2494                ApprovalThresholdConfig {
2495                    amount: 1000.0,
2496                    level: 1,
2497                    roles: vec!["senior_accountant".to_string()],
2498                },
2499                ApprovalThresholdConfig {
2500                    amount: 10000.0,
2501                    level: 2,
2502                    roles: vec!["senior_accountant".to_string(), "controller".to_string()],
2503                },
2504                ApprovalThresholdConfig {
2505                    amount: 100000.0,
2506                    level: 3,
2507                    roles: vec![
2508                        "senior_accountant".to_string(),
2509                        "controller".to_string(),
2510                        "manager".to_string(),
2511                    ],
2512                },
2513                ApprovalThresholdConfig {
2514                    amount: 500000.0,
2515                    level: 4,
2516                    roles: vec![
2517                        "senior_accountant".to_string(),
2518                        "controller".to_string(),
2519                        "manager".to_string(),
2520                        "executive".to_string(),
2521                    ],
2522                },
2523            ],
2524        }
2525    }
2526}
2527
2528/// Configuration for a single approval threshold.
2529#[derive(Debug, Clone, Serialize, Deserialize)]
2530pub struct ApprovalThresholdConfig {
2531    /// Amount threshold
2532    pub amount: f64,
2533    /// Approval level required
2534    pub level: u8,
2535    /// Roles that can approve at this level
2536    pub roles: Vec<String>,
2537}
2538
2539/// Department configuration.
2540#[derive(Debug, Clone, Serialize, Deserialize)]
2541pub struct DepartmentConfig {
2542    /// Enable department assignment
2543    #[serde(default)]
2544    pub enabled: bool,
2545    /// Multiplier for department headcounts
2546    #[serde(default = "default_headcount_multiplier")]
2547    pub headcount_multiplier: f64,
2548    /// Custom department definitions (optional)
2549    #[serde(default)]
2550    pub custom_departments: Vec<CustomDepartmentConfig>,
2551}
2552
2553fn default_headcount_multiplier() -> f64 {
2554    1.0
2555}
2556
2557impl Default for DepartmentConfig {
2558    fn default() -> Self {
2559        Self {
2560            enabled: false,
2561            headcount_multiplier: default_headcount_multiplier(),
2562            custom_departments: Vec::new(),
2563        }
2564    }
2565}
2566
2567/// Custom department definition.
2568#[derive(Debug, Clone, Serialize, Deserialize)]
2569pub struct CustomDepartmentConfig {
2570    /// Department code
2571    pub code: String,
2572    /// Department name
2573    pub name: String,
2574    /// Associated cost center
2575    #[serde(default)]
2576    pub cost_center: Option<String>,
2577    /// Primary business processes
2578    #[serde(default)]
2579    pub primary_processes: Vec<String>,
2580    /// Parent department code
2581    #[serde(default)]
2582    pub parent_code: Option<String>,
2583}
2584
2585// ============================================================================
2586// Master Data Configuration
2587// ============================================================================
2588
2589/// Master data generation configuration.
2590#[derive(Debug, Clone, Default, Serialize, Deserialize)]
2591pub struct MasterDataConfig {
2592    /// Vendor master data settings
2593    #[serde(default)]
2594    pub vendors: VendorMasterConfig,
2595    /// Customer master data settings
2596    #[serde(default)]
2597    pub customers: CustomerMasterConfig,
2598    /// Material master data settings
2599    #[serde(default)]
2600    pub materials: MaterialMasterConfig,
2601    /// Fixed asset master data settings
2602    #[serde(default)]
2603    pub fixed_assets: FixedAssetMasterConfig,
2604    /// Employee master data settings
2605    #[serde(default)]
2606    pub employees: EmployeeMasterConfig,
2607    /// Cost center master data settings
2608    #[serde(default)]
2609    pub cost_centers: CostCenterMasterConfig,
2610}
2611
2612/// Vendor master data configuration.
2613#[derive(Debug, Clone, Serialize, Deserialize)]
2614pub struct VendorMasterConfig {
2615    /// Number of vendors to generate
2616    #[serde(default = "default_vendor_count")]
2617    pub count: usize,
2618    /// Percentage of vendors that are intercompany (0.0 to 1.0)
2619    #[serde(default = "default_intercompany_percent")]
2620    pub intercompany_percent: f64,
2621    /// Payment terms distribution
2622    #[serde(default)]
2623    pub payment_terms_distribution: PaymentTermsDistribution,
2624    /// Vendor behavior distribution
2625    #[serde(default)]
2626    pub behavior_distribution: VendorBehaviorDistribution,
2627    /// Generate bank account details
2628    #[serde(default = "default_true")]
2629    pub generate_bank_accounts: bool,
2630    /// Generate tax IDs
2631    #[serde(default = "default_true")]
2632    pub generate_tax_ids: bool,
2633}
2634
2635fn default_vendor_count() -> usize {
2636    500
2637}
2638
2639fn default_intercompany_percent() -> f64 {
2640    0.05
2641}
2642
2643impl Default for VendorMasterConfig {
2644    fn default() -> Self {
2645        Self {
2646            count: default_vendor_count(),
2647            intercompany_percent: default_intercompany_percent(),
2648            payment_terms_distribution: PaymentTermsDistribution::default(),
2649            behavior_distribution: VendorBehaviorDistribution::default(),
2650            generate_bank_accounts: true,
2651            generate_tax_ids: true,
2652        }
2653    }
2654}
2655
2656/// Payment terms distribution for vendors.
2657#[derive(Debug, Clone, Serialize, Deserialize)]
2658pub struct PaymentTermsDistribution {
2659    /// Net 30 days
2660    pub net_30: f64,
2661    /// Net 60 days
2662    pub net_60: f64,
2663    /// Net 90 days
2664    pub net_90: f64,
2665    /// 2% 10 Net 30 (early payment discount)
2666    pub two_ten_net_30: f64,
2667    /// Due on receipt
2668    pub due_on_receipt: f64,
2669    /// End of month
2670    pub end_of_month: f64,
2671}
2672
2673impl Default for PaymentTermsDistribution {
2674    fn default() -> Self {
2675        Self {
2676            net_30: 0.40,
2677            net_60: 0.20,
2678            net_90: 0.10,
2679            two_ten_net_30: 0.15,
2680            due_on_receipt: 0.05,
2681            end_of_month: 0.10,
2682        }
2683    }
2684}
2685
2686/// Vendor behavior distribution.
2687#[derive(Debug, Clone, Serialize, Deserialize)]
2688pub struct VendorBehaviorDistribution {
2689    /// Reliable vendors (consistent delivery, quality)
2690    pub reliable: f64,
2691    /// Sometimes late vendors
2692    pub sometimes_late: f64,
2693    /// Inconsistent quality vendors
2694    pub inconsistent_quality: f64,
2695    /// Premium vendors (high quality, premium pricing)
2696    pub premium: f64,
2697    /// Budget vendors (lower quality, lower pricing)
2698    pub budget: f64,
2699}
2700
2701impl Default for VendorBehaviorDistribution {
2702    fn default() -> Self {
2703        Self {
2704            reliable: 0.50,
2705            sometimes_late: 0.20,
2706            inconsistent_quality: 0.10,
2707            premium: 0.10,
2708            budget: 0.10,
2709        }
2710    }
2711}
2712
2713/// Customer master data configuration.
2714#[derive(Debug, Clone, Serialize, Deserialize)]
2715pub struct CustomerMasterConfig {
2716    /// Number of customers to generate
2717    #[serde(default = "default_customer_count")]
2718    pub count: usize,
2719    /// Percentage of customers that are intercompany (0.0 to 1.0)
2720    #[serde(default = "default_intercompany_percent")]
2721    pub intercompany_percent: f64,
2722    /// Credit rating distribution
2723    #[serde(default)]
2724    pub credit_rating_distribution: CreditRatingDistribution,
2725    /// Payment behavior distribution
2726    #[serde(default)]
2727    pub payment_behavior_distribution: PaymentBehaviorDistribution,
2728    /// Generate credit limits based on rating
2729    #[serde(default = "default_true")]
2730    pub generate_credit_limits: bool,
2731}
2732
2733fn default_customer_count() -> usize {
2734    2000
2735}
2736
2737impl Default for CustomerMasterConfig {
2738    fn default() -> Self {
2739        Self {
2740            count: default_customer_count(),
2741            intercompany_percent: default_intercompany_percent(),
2742            credit_rating_distribution: CreditRatingDistribution::default(),
2743            payment_behavior_distribution: PaymentBehaviorDistribution::default(),
2744            generate_credit_limits: true,
2745        }
2746    }
2747}
2748
2749/// Credit rating distribution for customers.
2750#[derive(Debug, Clone, Serialize, Deserialize)]
2751pub struct CreditRatingDistribution {
2752    /// AAA rating
2753    pub aaa: f64,
2754    /// AA rating
2755    pub aa: f64,
2756    /// A rating
2757    pub a: f64,
2758    /// BBB rating
2759    pub bbb: f64,
2760    /// BB rating
2761    pub bb: f64,
2762    /// B rating
2763    pub b: f64,
2764    /// Below B rating
2765    pub below_b: f64,
2766}
2767
2768impl Default for CreditRatingDistribution {
2769    fn default() -> Self {
2770        Self {
2771            aaa: 0.05,
2772            aa: 0.10,
2773            a: 0.20,
2774            bbb: 0.30,
2775            bb: 0.20,
2776            b: 0.10,
2777            below_b: 0.05,
2778        }
2779    }
2780}
2781
2782/// Payment behavior distribution for customers.
2783#[derive(Debug, Clone, Serialize, Deserialize)]
2784pub struct PaymentBehaviorDistribution {
2785    /// Always pays early
2786    pub early_payer: f64,
2787    /// Pays on time
2788    pub on_time: f64,
2789    /// Occasionally late
2790    pub occasional_late: f64,
2791    /// Frequently late
2792    pub frequent_late: f64,
2793    /// Takes early payment discounts
2794    pub discount_taker: f64,
2795}
2796
2797impl Default for PaymentBehaviorDistribution {
2798    fn default() -> Self {
2799        Self {
2800            early_payer: 0.10,
2801            on_time: 0.50,
2802            occasional_late: 0.25,
2803            frequent_late: 0.10,
2804            discount_taker: 0.05,
2805        }
2806    }
2807}
2808
2809/// Material master data configuration.
2810#[derive(Debug, Clone, Serialize, Deserialize)]
2811pub struct MaterialMasterConfig {
2812    /// Number of materials to generate
2813    #[serde(default = "default_material_count")]
2814    pub count: usize,
2815    /// Material type distribution
2816    #[serde(default)]
2817    pub type_distribution: MaterialTypeDistribution,
2818    /// Valuation method distribution
2819    #[serde(default)]
2820    pub valuation_distribution: ValuationMethodDistribution,
2821    /// Percentage of materials with BOM (bill of materials)
2822    #[serde(default = "default_bom_percent")]
2823    pub bom_percent: f64,
2824    /// Maximum BOM depth
2825    #[serde(default = "default_max_bom_depth")]
2826    pub max_bom_depth: u8,
2827}
2828
2829fn default_material_count() -> usize {
2830    5000
2831}
2832
2833fn default_bom_percent() -> f64 {
2834    0.20
2835}
2836
2837fn default_max_bom_depth() -> u8 {
2838    3
2839}
2840
2841impl Default for MaterialMasterConfig {
2842    fn default() -> Self {
2843        Self {
2844            count: default_material_count(),
2845            type_distribution: MaterialTypeDistribution::default(),
2846            valuation_distribution: ValuationMethodDistribution::default(),
2847            bom_percent: default_bom_percent(),
2848            max_bom_depth: default_max_bom_depth(),
2849        }
2850    }
2851}
2852
2853/// Material type distribution.
2854#[derive(Debug, Clone, Serialize, Deserialize)]
2855pub struct MaterialTypeDistribution {
2856    /// Raw materials
2857    pub raw_material: f64,
2858    /// Semi-finished goods
2859    pub semi_finished: f64,
2860    /// Finished goods
2861    pub finished_good: f64,
2862    /// Trading goods (purchased for resale)
2863    pub trading_good: f64,
2864    /// Operating supplies
2865    pub operating_supply: f64,
2866    /// Services
2867    pub service: f64,
2868}
2869
2870impl Default for MaterialTypeDistribution {
2871    fn default() -> Self {
2872        Self {
2873            raw_material: 0.30,
2874            semi_finished: 0.15,
2875            finished_good: 0.25,
2876            trading_good: 0.15,
2877            operating_supply: 0.10,
2878            service: 0.05,
2879        }
2880    }
2881}
2882
2883/// Valuation method distribution for materials.
2884#[derive(Debug, Clone, Serialize, Deserialize)]
2885pub struct ValuationMethodDistribution {
2886    /// Standard cost
2887    pub standard_cost: f64,
2888    /// Moving average
2889    pub moving_average: f64,
2890    /// FIFO (First In, First Out)
2891    pub fifo: f64,
2892    /// LIFO (Last In, First Out)
2893    pub lifo: f64,
2894}
2895
2896impl Default for ValuationMethodDistribution {
2897    fn default() -> Self {
2898        Self {
2899            standard_cost: 0.50,
2900            moving_average: 0.30,
2901            fifo: 0.15,
2902            lifo: 0.05,
2903        }
2904    }
2905}
2906
2907/// Fixed asset master data configuration.
2908#[derive(Debug, Clone, Serialize, Deserialize)]
2909pub struct FixedAssetMasterConfig {
2910    /// Number of fixed assets to generate
2911    #[serde(default = "default_asset_count")]
2912    pub count: usize,
2913    /// Asset class distribution
2914    #[serde(default)]
2915    pub class_distribution: AssetClassDistribution,
2916    /// Depreciation method distribution
2917    #[serde(default)]
2918    pub depreciation_distribution: DepreciationMethodDistribution,
2919    /// Percentage of assets that are fully depreciated
2920    #[serde(default = "default_fully_depreciated_percent")]
2921    pub fully_depreciated_percent: f64,
2922    /// Generate acquisition history
2923    #[serde(default = "default_true")]
2924    pub generate_acquisition_history: bool,
2925}
2926
2927fn default_asset_count() -> usize {
2928    800
2929}
2930
2931fn default_fully_depreciated_percent() -> f64 {
2932    0.15
2933}
2934
2935impl Default for FixedAssetMasterConfig {
2936    fn default() -> Self {
2937        Self {
2938            count: default_asset_count(),
2939            class_distribution: AssetClassDistribution::default(),
2940            depreciation_distribution: DepreciationMethodDistribution::default(),
2941            fully_depreciated_percent: default_fully_depreciated_percent(),
2942            generate_acquisition_history: true,
2943        }
2944    }
2945}
2946
2947/// Asset class distribution.
2948#[derive(Debug, Clone, Serialize, Deserialize)]
2949pub struct AssetClassDistribution {
2950    /// Buildings and structures
2951    pub buildings: f64,
2952    /// Machinery and equipment
2953    pub machinery: f64,
2954    /// Vehicles
2955    pub vehicles: f64,
2956    /// IT equipment
2957    pub it_equipment: f64,
2958    /// Furniture and fixtures
2959    pub furniture: f64,
2960    /// Land (non-depreciable)
2961    pub land: f64,
2962    /// Leasehold improvements
2963    pub leasehold: f64,
2964}
2965
2966impl Default for AssetClassDistribution {
2967    fn default() -> Self {
2968        Self {
2969            buildings: 0.15,
2970            machinery: 0.30,
2971            vehicles: 0.15,
2972            it_equipment: 0.20,
2973            furniture: 0.10,
2974            land: 0.05,
2975            leasehold: 0.05,
2976        }
2977    }
2978}
2979
2980/// Depreciation method distribution.
2981#[derive(Debug, Clone, Serialize, Deserialize)]
2982pub struct DepreciationMethodDistribution {
2983    /// Straight line
2984    pub straight_line: f64,
2985    /// Declining balance
2986    pub declining_balance: f64,
2987    /// Double declining balance
2988    pub double_declining: f64,
2989    /// Sum of years' digits
2990    pub sum_of_years: f64,
2991    /// Units of production
2992    pub units_of_production: f64,
2993}
2994
2995impl Default for DepreciationMethodDistribution {
2996    fn default() -> Self {
2997        Self {
2998            straight_line: 0.60,
2999            declining_balance: 0.20,
3000            double_declining: 0.10,
3001            sum_of_years: 0.05,
3002            units_of_production: 0.05,
3003        }
3004    }
3005}
3006
3007/// Employee master data configuration.
3008#[derive(Debug, Clone, Serialize, Deserialize)]
3009pub struct EmployeeMasterConfig {
3010    /// Number of employees to generate
3011    #[serde(default = "default_employee_count")]
3012    pub count: usize,
3013    /// Generate organizational hierarchy
3014    #[serde(default = "default_true")]
3015    pub generate_hierarchy: bool,
3016    /// Maximum hierarchy depth
3017    #[serde(default = "default_hierarchy_depth")]
3018    pub max_hierarchy_depth: u8,
3019    /// Average span of control (direct reports per manager)
3020    #[serde(default = "default_span_of_control")]
3021    pub average_span_of_control: f64,
3022    /// Approval limit distribution by job level
3023    #[serde(default)]
3024    pub approval_limits: ApprovalLimitDistribution,
3025    /// Department distribution
3026    #[serde(default)]
3027    pub department_distribution: EmployeeDepartmentDistribution,
3028}
3029
3030fn default_employee_count() -> usize {
3031    1500
3032}
3033
3034fn default_hierarchy_depth() -> u8 {
3035    6
3036}
3037
3038fn default_span_of_control() -> f64 {
3039    5.0
3040}
3041
3042impl Default for EmployeeMasterConfig {
3043    fn default() -> Self {
3044        Self {
3045            count: default_employee_count(),
3046            generate_hierarchy: true,
3047            max_hierarchy_depth: default_hierarchy_depth(),
3048            average_span_of_control: default_span_of_control(),
3049            approval_limits: ApprovalLimitDistribution::default(),
3050            department_distribution: EmployeeDepartmentDistribution::default(),
3051        }
3052    }
3053}
3054
3055/// Approval limit distribution by job level.
3056#[derive(Debug, Clone, Serialize, Deserialize)]
3057pub struct ApprovalLimitDistribution {
3058    /// Staff level approval limit
3059    #[serde(default = "default_staff_limit")]
3060    pub staff: f64,
3061    /// Senior staff approval limit
3062    #[serde(default = "default_senior_limit")]
3063    pub senior: f64,
3064    /// Manager approval limit
3065    #[serde(default = "default_manager_limit")]
3066    pub manager: f64,
3067    /// Director approval limit
3068    #[serde(default = "default_director_limit")]
3069    pub director: f64,
3070    /// VP approval limit
3071    #[serde(default = "default_vp_limit")]
3072    pub vp: f64,
3073    /// Executive approval limit
3074    #[serde(default = "default_executive_limit")]
3075    pub executive: f64,
3076}
3077
3078fn default_staff_limit() -> f64 {
3079    1000.0
3080}
3081fn default_senior_limit() -> f64 {
3082    5000.0
3083}
3084fn default_manager_limit() -> f64 {
3085    25000.0
3086}
3087fn default_director_limit() -> f64 {
3088    100000.0
3089}
3090fn default_vp_limit() -> f64 {
3091    500000.0
3092}
3093fn default_executive_limit() -> f64 {
3094    f64::INFINITY
3095}
3096
3097impl Default for ApprovalLimitDistribution {
3098    fn default() -> Self {
3099        Self {
3100            staff: default_staff_limit(),
3101            senior: default_senior_limit(),
3102            manager: default_manager_limit(),
3103            director: default_director_limit(),
3104            vp: default_vp_limit(),
3105            executive: default_executive_limit(),
3106        }
3107    }
3108}
3109
3110/// Employee distribution across departments.
3111#[derive(Debug, Clone, Serialize, Deserialize)]
3112pub struct EmployeeDepartmentDistribution {
3113    /// Finance and Accounting
3114    pub finance: f64,
3115    /// Procurement
3116    pub procurement: f64,
3117    /// Sales
3118    pub sales: f64,
3119    /// Warehouse and Logistics
3120    pub warehouse: f64,
3121    /// IT
3122    pub it: f64,
3123    /// Human Resources
3124    pub hr: f64,
3125    /// Operations
3126    pub operations: f64,
3127    /// Executive
3128    pub executive: f64,
3129}
3130
3131impl Default for EmployeeDepartmentDistribution {
3132    fn default() -> Self {
3133        Self {
3134            finance: 0.12,
3135            procurement: 0.10,
3136            sales: 0.25,
3137            warehouse: 0.15,
3138            it: 0.10,
3139            hr: 0.05,
3140            operations: 0.20,
3141            executive: 0.03,
3142        }
3143    }
3144}
3145
3146/// Cost center master data configuration.
3147#[derive(Debug, Clone, Serialize, Deserialize)]
3148pub struct CostCenterMasterConfig {
3149    /// Number of cost centers to generate
3150    #[serde(default = "default_cost_center_count")]
3151    pub count: usize,
3152    /// Generate cost center hierarchy
3153    #[serde(default = "default_true")]
3154    pub generate_hierarchy: bool,
3155    /// Maximum hierarchy depth
3156    #[serde(default = "default_cc_hierarchy_depth")]
3157    pub max_hierarchy_depth: u8,
3158}
3159
3160fn default_cost_center_count() -> usize {
3161    50
3162}
3163
3164fn default_cc_hierarchy_depth() -> u8 {
3165    3
3166}
3167
3168impl Default for CostCenterMasterConfig {
3169    fn default() -> Self {
3170        Self {
3171            count: default_cost_center_count(),
3172            generate_hierarchy: true,
3173            max_hierarchy_depth: default_cc_hierarchy_depth(),
3174        }
3175    }
3176}
3177
3178// ============================================================================
3179// Document Flow Configuration
3180// ============================================================================
3181
3182/// Document flow generation configuration.
3183#[derive(Debug, Clone, Serialize, Deserialize)]
3184pub struct DocumentFlowConfig {
3185    /// P2P (Procure-to-Pay) flow configuration
3186    #[serde(default)]
3187    pub p2p: P2PFlowConfig,
3188    /// O2C (Order-to-Cash) flow configuration
3189    #[serde(default)]
3190    pub o2c: O2CFlowConfig,
3191    /// Generate document reference chains
3192    #[serde(default = "default_true")]
3193    pub generate_document_references: bool,
3194    /// Export document flow graph
3195    #[serde(default)]
3196    pub export_flow_graph: bool,
3197}
3198
3199impl Default for DocumentFlowConfig {
3200    fn default() -> Self {
3201        Self {
3202            p2p: P2PFlowConfig::default(),
3203            o2c: O2CFlowConfig::default(),
3204            generate_document_references: true,
3205            export_flow_graph: false,
3206        }
3207    }
3208}
3209
3210/// P2P (Procure-to-Pay) flow configuration.
3211#[derive(Debug, Clone, Serialize, Deserialize)]
3212pub struct P2PFlowConfig {
3213    /// Enable P2P document flow generation
3214    #[serde(default = "default_true")]
3215    pub enabled: bool,
3216    /// Three-way match success rate (PO-GR-Invoice)
3217    #[serde(default = "default_three_way_match_rate")]
3218    pub three_way_match_rate: f64,
3219    /// Rate of partial deliveries
3220    #[serde(default = "default_partial_delivery_rate")]
3221    pub partial_delivery_rate: f64,
3222    /// Rate of price variances between PO and Invoice
3223    #[serde(default = "default_price_variance_rate")]
3224    pub price_variance_rate: f64,
3225    /// Maximum price variance percentage
3226    #[serde(default = "default_max_price_variance")]
3227    pub max_price_variance_percent: f64,
3228    /// Rate of quantity variances between PO/GR and Invoice
3229    #[serde(default = "default_quantity_variance_rate")]
3230    pub quantity_variance_rate: f64,
3231    /// Average days from PO to goods receipt
3232    #[serde(default = "default_po_to_gr_days")]
3233    pub average_po_to_gr_days: u32,
3234    /// Average days from GR to invoice
3235    #[serde(default = "default_gr_to_invoice_days")]
3236    pub average_gr_to_invoice_days: u32,
3237    /// Average days from invoice to payment
3238    #[serde(default = "default_invoice_to_payment_days")]
3239    pub average_invoice_to_payment_days: u32,
3240    /// PO line count distribution
3241    #[serde(default)]
3242    pub line_count_distribution: DocumentLineCountDistribution,
3243    /// Payment behavior configuration
3244    #[serde(default)]
3245    pub payment_behavior: P2PPaymentBehaviorConfig,
3246    /// Rate of over-deliveries (quantity received exceeds PO quantity)
3247    #[serde(default)]
3248    pub over_delivery_rate: Option<f64>,
3249    /// Rate of early payment discounts being taken
3250    #[serde(default)]
3251    pub early_payment_discount_rate: Option<f64>,
3252}
3253
3254fn default_three_way_match_rate() -> f64 {
3255    0.95
3256}
3257
3258fn default_partial_delivery_rate() -> f64 {
3259    0.15
3260}
3261
3262fn default_price_variance_rate() -> f64 {
3263    0.08
3264}
3265
3266fn default_max_price_variance() -> f64 {
3267    0.05
3268}
3269
3270fn default_quantity_variance_rate() -> f64 {
3271    0.05
3272}
3273
3274fn default_po_to_gr_days() -> u32 {
3275    14
3276}
3277
3278fn default_gr_to_invoice_days() -> u32 {
3279    5
3280}
3281
3282fn default_invoice_to_payment_days() -> u32 {
3283    30
3284}
3285
3286impl Default for P2PFlowConfig {
3287    fn default() -> Self {
3288        Self {
3289            enabled: true,
3290            three_way_match_rate: default_three_way_match_rate(),
3291            partial_delivery_rate: default_partial_delivery_rate(),
3292            price_variance_rate: default_price_variance_rate(),
3293            max_price_variance_percent: default_max_price_variance(),
3294            quantity_variance_rate: default_quantity_variance_rate(),
3295            average_po_to_gr_days: default_po_to_gr_days(),
3296            average_gr_to_invoice_days: default_gr_to_invoice_days(),
3297            average_invoice_to_payment_days: default_invoice_to_payment_days(),
3298            line_count_distribution: DocumentLineCountDistribution::default(),
3299            payment_behavior: P2PPaymentBehaviorConfig::default(),
3300            over_delivery_rate: None,
3301            early_payment_discount_rate: None,
3302        }
3303    }
3304}
3305
3306// ============================================================================
3307// P2P Payment Behavior Configuration
3308// ============================================================================
3309
3310/// P2P payment behavior configuration.
3311#[derive(Debug, Clone, Serialize, Deserialize)]
3312pub struct P2PPaymentBehaviorConfig {
3313    /// Rate of late payments (beyond due date)
3314    #[serde(default = "default_p2p_late_payment_rate")]
3315    pub late_payment_rate: f64,
3316    /// Distribution of late payment days
3317    #[serde(default)]
3318    pub late_payment_days_distribution: LatePaymentDaysDistribution,
3319    /// Rate of partial payments
3320    #[serde(default = "default_p2p_partial_payment_rate")]
3321    pub partial_payment_rate: f64,
3322    /// Rate of payment corrections (NSF, chargebacks, reversals)
3323    #[serde(default = "default_p2p_payment_correction_rate")]
3324    pub payment_correction_rate: f64,
3325    /// Average days until partial payment remainder is paid
3326    #[serde(default = "default_p2p_avg_days_until_remainder")]
3327    pub avg_days_until_remainder: u32,
3328}
3329
3330fn default_p2p_late_payment_rate() -> f64 {
3331    0.15
3332}
3333
3334fn default_p2p_partial_payment_rate() -> f64 {
3335    0.05
3336}
3337
3338fn default_p2p_payment_correction_rate() -> f64 {
3339    0.02
3340}
3341
3342fn default_p2p_avg_days_until_remainder() -> u32 {
3343    30
3344}
3345
3346impl Default for P2PPaymentBehaviorConfig {
3347    fn default() -> Self {
3348        Self {
3349            late_payment_rate: default_p2p_late_payment_rate(),
3350            late_payment_days_distribution: LatePaymentDaysDistribution::default(),
3351            partial_payment_rate: default_p2p_partial_payment_rate(),
3352            payment_correction_rate: default_p2p_payment_correction_rate(),
3353            avg_days_until_remainder: default_p2p_avg_days_until_remainder(),
3354        }
3355    }
3356}
3357
3358/// Distribution of late payment days for P2P.
3359#[derive(Debug, Clone, Serialize, Deserialize)]
3360pub struct LatePaymentDaysDistribution {
3361    /// 1-7 days late (slightly late)
3362    #[serde(default = "default_slightly_late")]
3363    pub slightly_late_1_to_7: f64,
3364    /// 8-14 days late
3365    #[serde(default = "default_late_8_14")]
3366    pub late_8_to_14: f64,
3367    /// 15-30 days late (very late)
3368    #[serde(default = "default_very_late")]
3369    pub very_late_15_to_30: f64,
3370    /// 31-60 days late (severely late)
3371    #[serde(default = "default_severely_late")]
3372    pub severely_late_31_to_60: f64,
3373    /// Over 60 days late (extremely late)
3374    #[serde(default = "default_extremely_late")]
3375    pub extremely_late_over_60: f64,
3376}
3377
3378fn default_slightly_late() -> f64 {
3379    0.50
3380}
3381
3382fn default_late_8_14() -> f64 {
3383    0.25
3384}
3385
3386fn default_very_late() -> f64 {
3387    0.15
3388}
3389
3390fn default_severely_late() -> f64 {
3391    0.07
3392}
3393
3394fn default_extremely_late() -> f64 {
3395    0.03
3396}
3397
3398impl Default for LatePaymentDaysDistribution {
3399    fn default() -> Self {
3400        Self {
3401            slightly_late_1_to_7: default_slightly_late(),
3402            late_8_to_14: default_late_8_14(),
3403            very_late_15_to_30: default_very_late(),
3404            severely_late_31_to_60: default_severely_late(),
3405            extremely_late_over_60: default_extremely_late(),
3406        }
3407    }
3408}
3409
3410/// O2C (Order-to-Cash) flow configuration.
3411#[derive(Debug, Clone, Serialize, Deserialize)]
3412pub struct O2CFlowConfig {
3413    /// Enable O2C document flow generation
3414    #[serde(default = "default_true")]
3415    pub enabled: bool,
3416    /// Credit check failure rate
3417    #[serde(default = "default_credit_check_failure_rate")]
3418    pub credit_check_failure_rate: f64,
3419    /// Rate of partial shipments
3420    #[serde(default = "default_partial_shipment_rate")]
3421    pub partial_shipment_rate: f64,
3422    /// Rate of returns
3423    #[serde(default = "default_return_rate")]
3424    pub return_rate: f64,
3425    /// Bad debt write-off rate
3426    #[serde(default = "default_bad_debt_rate")]
3427    pub bad_debt_rate: f64,
3428    /// Average days from SO to delivery
3429    #[serde(default = "default_so_to_delivery_days")]
3430    pub average_so_to_delivery_days: u32,
3431    /// Average days from delivery to invoice
3432    #[serde(default = "default_delivery_to_invoice_days")]
3433    pub average_delivery_to_invoice_days: u32,
3434    /// Average days from invoice to receipt
3435    #[serde(default = "default_invoice_to_receipt_days")]
3436    pub average_invoice_to_receipt_days: u32,
3437    /// SO line count distribution
3438    #[serde(default)]
3439    pub line_count_distribution: DocumentLineCountDistribution,
3440    /// Cash discount configuration
3441    #[serde(default)]
3442    pub cash_discount: CashDiscountConfig,
3443    /// Payment behavior configuration
3444    #[serde(default)]
3445    pub payment_behavior: O2CPaymentBehaviorConfig,
3446    /// Rate of late payments
3447    #[serde(default)]
3448    pub late_payment_rate: Option<f64>,
3449}
3450
3451fn default_credit_check_failure_rate() -> f64 {
3452    0.02
3453}
3454
3455fn default_partial_shipment_rate() -> f64 {
3456    0.10
3457}
3458
3459fn default_return_rate() -> f64 {
3460    0.03
3461}
3462
3463fn default_bad_debt_rate() -> f64 {
3464    0.01
3465}
3466
3467fn default_so_to_delivery_days() -> u32 {
3468    7
3469}
3470
3471fn default_delivery_to_invoice_days() -> u32 {
3472    1
3473}
3474
3475fn default_invoice_to_receipt_days() -> u32 {
3476    45
3477}
3478
3479impl Default for O2CFlowConfig {
3480    fn default() -> Self {
3481        Self {
3482            enabled: true,
3483            credit_check_failure_rate: default_credit_check_failure_rate(),
3484            partial_shipment_rate: default_partial_shipment_rate(),
3485            return_rate: default_return_rate(),
3486            bad_debt_rate: default_bad_debt_rate(),
3487            average_so_to_delivery_days: default_so_to_delivery_days(),
3488            average_delivery_to_invoice_days: default_delivery_to_invoice_days(),
3489            average_invoice_to_receipt_days: default_invoice_to_receipt_days(),
3490            line_count_distribution: DocumentLineCountDistribution::default(),
3491            cash_discount: CashDiscountConfig::default(),
3492            payment_behavior: O2CPaymentBehaviorConfig::default(),
3493            late_payment_rate: None,
3494        }
3495    }
3496}
3497
3498// ============================================================================
3499// O2C Payment Behavior Configuration
3500// ============================================================================
3501
3502/// O2C payment behavior configuration.
3503#[derive(Debug, Clone, Serialize, Deserialize, Default)]
3504pub struct O2CPaymentBehaviorConfig {
3505    /// Dunning (Mahnung) configuration
3506    #[serde(default)]
3507    pub dunning: DunningConfig,
3508    /// Partial payment configuration
3509    #[serde(default)]
3510    pub partial_payments: PartialPaymentConfig,
3511    /// Short payment configuration (unauthorized deductions)
3512    #[serde(default)]
3513    pub short_payments: ShortPaymentConfig,
3514    /// On-account payment configuration (unapplied payments)
3515    #[serde(default)]
3516    pub on_account_payments: OnAccountPaymentConfig,
3517    /// Payment correction configuration (NSF, chargebacks)
3518    #[serde(default)]
3519    pub payment_corrections: PaymentCorrectionConfig,
3520}
3521
3522/// Dunning (Mahnungen) configuration for AR collections.
3523#[derive(Debug, Clone, Serialize, Deserialize)]
3524pub struct DunningConfig {
3525    /// Enable dunning process
3526    #[serde(default)]
3527    pub enabled: bool,
3528    /// Days overdue for level 1 dunning (1st reminder)
3529    #[serde(default = "default_dunning_level_1_days")]
3530    pub level_1_days_overdue: u32,
3531    /// Days overdue for level 2 dunning (2nd reminder)
3532    #[serde(default = "default_dunning_level_2_days")]
3533    pub level_2_days_overdue: u32,
3534    /// Days overdue for level 3 dunning (final notice)
3535    #[serde(default = "default_dunning_level_3_days")]
3536    pub level_3_days_overdue: u32,
3537    /// Days overdue for collection handover
3538    #[serde(default = "default_collection_days")]
3539    pub collection_days_overdue: u32,
3540    /// Payment rates after each dunning level
3541    #[serde(default)]
3542    pub payment_after_dunning_rates: DunningPaymentRates,
3543    /// Rate of invoices blocked from dunning (disputes)
3544    #[serde(default = "default_dunning_block_rate")]
3545    pub dunning_block_rate: f64,
3546    /// Interest rate per year for overdue amounts
3547    #[serde(default = "default_dunning_interest_rate")]
3548    pub interest_rate_per_year: f64,
3549    /// Fixed dunning charge per letter
3550    #[serde(default = "default_dunning_charge")]
3551    pub dunning_charge: f64,
3552}
3553
3554fn default_dunning_level_1_days() -> u32 {
3555    14
3556}
3557
3558fn default_dunning_level_2_days() -> u32 {
3559    28
3560}
3561
3562fn default_dunning_level_3_days() -> u32 {
3563    42
3564}
3565
3566fn default_collection_days() -> u32 {
3567    60
3568}
3569
3570fn default_dunning_block_rate() -> f64 {
3571    0.05
3572}
3573
3574fn default_dunning_interest_rate() -> f64 {
3575    0.09
3576}
3577
3578fn default_dunning_charge() -> f64 {
3579    25.0
3580}
3581
3582impl Default for DunningConfig {
3583    fn default() -> Self {
3584        Self {
3585            enabled: false,
3586            level_1_days_overdue: default_dunning_level_1_days(),
3587            level_2_days_overdue: default_dunning_level_2_days(),
3588            level_3_days_overdue: default_dunning_level_3_days(),
3589            collection_days_overdue: default_collection_days(),
3590            payment_after_dunning_rates: DunningPaymentRates::default(),
3591            dunning_block_rate: default_dunning_block_rate(),
3592            interest_rate_per_year: default_dunning_interest_rate(),
3593            dunning_charge: default_dunning_charge(),
3594        }
3595    }
3596}
3597
3598/// Payment rates after each dunning level.
3599#[derive(Debug, Clone, Serialize, Deserialize)]
3600pub struct DunningPaymentRates {
3601    /// Rate that pays after level 1 reminder
3602    #[serde(default = "default_after_level_1")]
3603    pub after_level_1: f64,
3604    /// Rate that pays after level 2 reminder
3605    #[serde(default = "default_after_level_2")]
3606    pub after_level_2: f64,
3607    /// Rate that pays after level 3 final notice
3608    #[serde(default = "default_after_level_3")]
3609    pub after_level_3: f64,
3610    /// Rate that pays during collection
3611    #[serde(default = "default_during_collection")]
3612    pub during_collection: f64,
3613    /// Rate that never pays (becomes bad debt)
3614    #[serde(default = "default_never_pay")]
3615    pub never_pay: f64,
3616}
3617
3618fn default_after_level_1() -> f64 {
3619    0.40
3620}
3621
3622fn default_after_level_2() -> f64 {
3623    0.30
3624}
3625
3626fn default_after_level_3() -> f64 {
3627    0.15
3628}
3629
3630fn default_during_collection() -> f64 {
3631    0.05
3632}
3633
3634fn default_never_pay() -> f64 {
3635    0.10
3636}
3637
3638impl Default for DunningPaymentRates {
3639    fn default() -> Self {
3640        Self {
3641            after_level_1: default_after_level_1(),
3642            after_level_2: default_after_level_2(),
3643            after_level_3: default_after_level_3(),
3644            during_collection: default_during_collection(),
3645            never_pay: default_never_pay(),
3646        }
3647    }
3648}
3649
3650/// Partial payment configuration.
3651#[derive(Debug, Clone, Serialize, Deserialize)]
3652pub struct PartialPaymentConfig {
3653    /// Rate of invoices paid partially
3654    #[serde(default = "default_partial_payment_rate")]
3655    pub rate: f64,
3656    /// Distribution of partial payment percentages
3657    #[serde(default)]
3658    pub percentage_distribution: PartialPaymentPercentageDistribution,
3659    /// Average days until remainder is paid
3660    #[serde(default = "default_avg_days_until_remainder")]
3661    pub avg_days_until_remainder: u32,
3662}
3663
3664fn default_partial_payment_rate() -> f64 {
3665    0.08
3666}
3667
3668fn default_avg_days_until_remainder() -> u32 {
3669    30
3670}
3671
3672impl Default for PartialPaymentConfig {
3673    fn default() -> Self {
3674        Self {
3675            rate: default_partial_payment_rate(),
3676            percentage_distribution: PartialPaymentPercentageDistribution::default(),
3677            avg_days_until_remainder: default_avg_days_until_remainder(),
3678        }
3679    }
3680}
3681
3682/// Distribution of partial payment percentages.
3683#[derive(Debug, Clone, Serialize, Deserialize)]
3684pub struct PartialPaymentPercentageDistribution {
3685    /// Pay 25% of invoice
3686    #[serde(default = "default_partial_25")]
3687    pub pay_25_percent: f64,
3688    /// Pay 50% of invoice
3689    #[serde(default = "default_partial_50")]
3690    pub pay_50_percent: f64,
3691    /// Pay 75% of invoice
3692    #[serde(default = "default_partial_75")]
3693    pub pay_75_percent: f64,
3694    /// Pay random percentage
3695    #[serde(default = "default_partial_random")]
3696    pub pay_random_percent: f64,
3697}
3698
3699fn default_partial_25() -> f64 {
3700    0.15
3701}
3702
3703fn default_partial_50() -> f64 {
3704    0.50
3705}
3706
3707fn default_partial_75() -> f64 {
3708    0.25
3709}
3710
3711fn default_partial_random() -> f64 {
3712    0.10
3713}
3714
3715impl Default for PartialPaymentPercentageDistribution {
3716    fn default() -> Self {
3717        Self {
3718            pay_25_percent: default_partial_25(),
3719            pay_50_percent: default_partial_50(),
3720            pay_75_percent: default_partial_75(),
3721            pay_random_percent: default_partial_random(),
3722        }
3723    }
3724}
3725
3726/// Short payment configuration (unauthorized deductions).
3727#[derive(Debug, Clone, Serialize, Deserialize)]
3728pub struct ShortPaymentConfig {
3729    /// Rate of payments that are short
3730    #[serde(default = "default_short_payment_rate")]
3731    pub rate: f64,
3732    /// Distribution of short payment reasons
3733    #[serde(default)]
3734    pub reason_distribution: ShortPaymentReasonDistribution,
3735    /// Maximum percentage that can be short
3736    #[serde(default = "default_max_short_percent")]
3737    pub max_short_percent: f64,
3738}
3739
3740fn default_short_payment_rate() -> f64 {
3741    0.03
3742}
3743
3744fn default_max_short_percent() -> f64 {
3745    0.10
3746}
3747
3748impl Default for ShortPaymentConfig {
3749    fn default() -> Self {
3750        Self {
3751            rate: default_short_payment_rate(),
3752            reason_distribution: ShortPaymentReasonDistribution::default(),
3753            max_short_percent: default_max_short_percent(),
3754        }
3755    }
3756}
3757
3758/// Distribution of short payment reasons.
3759#[derive(Debug, Clone, Serialize, Deserialize)]
3760pub struct ShortPaymentReasonDistribution {
3761    /// Pricing dispute
3762    #[serde(default = "default_pricing_dispute")]
3763    pub pricing_dispute: f64,
3764    /// Quality issue
3765    #[serde(default = "default_quality_issue")]
3766    pub quality_issue: f64,
3767    /// Quantity discrepancy
3768    #[serde(default = "default_quantity_discrepancy")]
3769    pub quantity_discrepancy: f64,
3770    /// Unauthorized deduction
3771    #[serde(default = "default_unauthorized_deduction")]
3772    pub unauthorized_deduction: f64,
3773    /// Early payment discount taken incorrectly
3774    #[serde(default = "default_incorrect_discount")]
3775    pub incorrect_discount: f64,
3776}
3777
3778fn default_pricing_dispute() -> f64 {
3779    0.30
3780}
3781
3782fn default_quality_issue() -> f64 {
3783    0.20
3784}
3785
3786fn default_quantity_discrepancy() -> f64 {
3787    0.20
3788}
3789
3790fn default_unauthorized_deduction() -> f64 {
3791    0.15
3792}
3793
3794fn default_incorrect_discount() -> f64 {
3795    0.15
3796}
3797
3798impl Default for ShortPaymentReasonDistribution {
3799    fn default() -> Self {
3800        Self {
3801            pricing_dispute: default_pricing_dispute(),
3802            quality_issue: default_quality_issue(),
3803            quantity_discrepancy: default_quantity_discrepancy(),
3804            unauthorized_deduction: default_unauthorized_deduction(),
3805            incorrect_discount: default_incorrect_discount(),
3806        }
3807    }
3808}
3809
3810/// On-account payment configuration (unapplied payments).
3811#[derive(Debug, Clone, Serialize, Deserialize)]
3812pub struct OnAccountPaymentConfig {
3813    /// Rate of payments that are on-account (unapplied)
3814    #[serde(default = "default_on_account_rate")]
3815    pub rate: f64,
3816    /// Average days until on-account payments are applied
3817    #[serde(default = "default_avg_days_until_applied")]
3818    pub avg_days_until_applied: u32,
3819}
3820
3821fn default_on_account_rate() -> f64 {
3822    0.02
3823}
3824
3825fn default_avg_days_until_applied() -> u32 {
3826    14
3827}
3828
3829impl Default for OnAccountPaymentConfig {
3830    fn default() -> Self {
3831        Self {
3832            rate: default_on_account_rate(),
3833            avg_days_until_applied: default_avg_days_until_applied(),
3834        }
3835    }
3836}
3837
3838/// Payment correction configuration.
3839#[derive(Debug, Clone, Serialize, Deserialize)]
3840pub struct PaymentCorrectionConfig {
3841    /// Rate of payments requiring correction
3842    #[serde(default = "default_payment_correction_rate")]
3843    pub rate: f64,
3844    /// Distribution of correction types
3845    #[serde(default)]
3846    pub type_distribution: PaymentCorrectionTypeDistribution,
3847}
3848
3849fn default_payment_correction_rate() -> f64 {
3850    0.02
3851}
3852
3853impl Default for PaymentCorrectionConfig {
3854    fn default() -> Self {
3855        Self {
3856            rate: default_payment_correction_rate(),
3857            type_distribution: PaymentCorrectionTypeDistribution::default(),
3858        }
3859    }
3860}
3861
3862/// Distribution of payment correction types.
3863#[derive(Debug, Clone, Serialize, Deserialize)]
3864pub struct PaymentCorrectionTypeDistribution {
3865    /// NSF (Non-sufficient funds) / bounced check
3866    #[serde(default = "default_nsf_rate")]
3867    pub nsf: f64,
3868    /// Chargeback
3869    #[serde(default = "default_chargeback_rate")]
3870    pub chargeback: f64,
3871    /// Wrong amount applied
3872    #[serde(default = "default_wrong_amount_rate")]
3873    pub wrong_amount: f64,
3874    /// Wrong customer applied
3875    #[serde(default = "default_wrong_customer_rate")]
3876    pub wrong_customer: f64,
3877    /// Duplicate payment
3878    #[serde(default = "default_duplicate_payment_rate")]
3879    pub duplicate_payment: f64,
3880}
3881
3882fn default_nsf_rate() -> f64 {
3883    0.30
3884}
3885
3886fn default_chargeback_rate() -> f64 {
3887    0.20
3888}
3889
3890fn default_wrong_amount_rate() -> f64 {
3891    0.20
3892}
3893
3894fn default_wrong_customer_rate() -> f64 {
3895    0.15
3896}
3897
3898fn default_duplicate_payment_rate() -> f64 {
3899    0.15
3900}
3901
3902impl Default for PaymentCorrectionTypeDistribution {
3903    fn default() -> Self {
3904        Self {
3905            nsf: default_nsf_rate(),
3906            chargeback: default_chargeback_rate(),
3907            wrong_amount: default_wrong_amount_rate(),
3908            wrong_customer: default_wrong_customer_rate(),
3909            duplicate_payment: default_duplicate_payment_rate(),
3910        }
3911    }
3912}
3913
3914/// Document line count distribution.
3915#[derive(Debug, Clone, Serialize, Deserialize)]
3916pub struct DocumentLineCountDistribution {
3917    /// Minimum number of lines
3918    #[serde(default = "default_min_lines")]
3919    pub min_lines: u32,
3920    /// Maximum number of lines
3921    #[serde(default = "default_max_lines")]
3922    pub max_lines: u32,
3923    /// Most common line count (mode)
3924    #[serde(default = "default_mode_lines")]
3925    pub mode_lines: u32,
3926}
3927
3928fn default_min_lines() -> u32 {
3929    1
3930}
3931
3932fn default_max_lines() -> u32 {
3933    20
3934}
3935
3936fn default_mode_lines() -> u32 {
3937    3
3938}
3939
3940impl Default for DocumentLineCountDistribution {
3941    fn default() -> Self {
3942        Self {
3943            min_lines: default_min_lines(),
3944            max_lines: default_max_lines(),
3945            mode_lines: default_mode_lines(),
3946        }
3947    }
3948}
3949
3950/// Cash discount configuration.
3951#[derive(Debug, Clone, Serialize, Deserialize)]
3952pub struct CashDiscountConfig {
3953    /// Percentage of invoices eligible for cash discount
3954    #[serde(default = "default_discount_eligible_rate")]
3955    pub eligible_rate: f64,
3956    /// Rate at which customers take the discount
3957    #[serde(default = "default_discount_taken_rate")]
3958    pub taken_rate: f64,
3959    /// Standard discount percentage
3960    #[serde(default = "default_discount_percent")]
3961    pub discount_percent: f64,
3962    /// Days within which discount must be taken
3963    #[serde(default = "default_discount_days")]
3964    pub discount_days: u32,
3965}
3966
3967fn default_discount_eligible_rate() -> f64 {
3968    0.30
3969}
3970
3971fn default_discount_taken_rate() -> f64 {
3972    0.60
3973}
3974
3975fn default_discount_percent() -> f64 {
3976    0.02
3977}
3978
3979fn default_discount_days() -> u32 {
3980    10
3981}
3982
3983impl Default for CashDiscountConfig {
3984    fn default() -> Self {
3985        Self {
3986            eligible_rate: default_discount_eligible_rate(),
3987            taken_rate: default_discount_taken_rate(),
3988            discount_percent: default_discount_percent(),
3989            discount_days: default_discount_days(),
3990        }
3991    }
3992}
3993
3994// ============================================================================
3995// Intercompany Configuration
3996// ============================================================================
3997
3998/// Intercompany transaction configuration.
3999#[derive(Debug, Clone, Serialize, Deserialize)]
4000pub struct IntercompanyConfig {
4001    /// Enable intercompany transaction generation
4002    #[serde(default)]
4003    pub enabled: bool,
4004    /// Rate of transactions that are intercompany
4005    #[serde(default = "default_ic_transaction_rate")]
4006    pub ic_transaction_rate: f64,
4007    /// Transfer pricing method
4008    #[serde(default)]
4009    pub transfer_pricing_method: TransferPricingMethod,
4010    /// Transfer pricing markup percentage (for cost-plus)
4011    #[serde(default = "default_markup_percent")]
4012    pub markup_percent: f64,
4013    /// Generate matched IC pairs (offsetting entries)
4014    #[serde(default = "default_true")]
4015    pub generate_matched_pairs: bool,
4016    /// IC transaction type distribution
4017    #[serde(default)]
4018    pub transaction_type_distribution: ICTransactionTypeDistribution,
4019    /// Generate elimination entries for consolidation
4020    #[serde(default)]
4021    pub generate_eliminations: bool,
4022}
4023
4024fn default_ic_transaction_rate() -> f64 {
4025    0.15
4026}
4027
4028fn default_markup_percent() -> f64 {
4029    0.05
4030}
4031
4032impl Default for IntercompanyConfig {
4033    fn default() -> Self {
4034        Self {
4035            enabled: false,
4036            ic_transaction_rate: default_ic_transaction_rate(),
4037            transfer_pricing_method: TransferPricingMethod::default(),
4038            markup_percent: default_markup_percent(),
4039            generate_matched_pairs: true,
4040            transaction_type_distribution: ICTransactionTypeDistribution::default(),
4041            generate_eliminations: false,
4042        }
4043    }
4044}
4045
4046/// Transfer pricing method.
4047#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
4048#[serde(rename_all = "snake_case")]
4049pub enum TransferPricingMethod {
4050    /// Cost plus a markup
4051    #[default]
4052    CostPlus,
4053    /// Comparable uncontrolled price
4054    ComparableUncontrolled,
4055    /// Resale price method
4056    ResalePrice,
4057    /// Transactional net margin method
4058    TransactionalNetMargin,
4059    /// Profit split method
4060    ProfitSplit,
4061}
4062
4063/// IC transaction type distribution.
4064#[derive(Debug, Clone, Serialize, Deserialize)]
4065pub struct ICTransactionTypeDistribution {
4066    /// Goods sales between entities
4067    pub goods_sale: f64,
4068    /// Services provided
4069    pub service_provided: f64,
4070    /// Intercompany loans
4071    pub loan: f64,
4072    /// Dividends
4073    pub dividend: f64,
4074    /// Management fees
4075    pub management_fee: f64,
4076    /// Royalties
4077    pub royalty: f64,
4078    /// Cost sharing
4079    pub cost_sharing: f64,
4080}
4081
4082impl Default for ICTransactionTypeDistribution {
4083    fn default() -> Self {
4084        Self {
4085            goods_sale: 0.35,
4086            service_provided: 0.20,
4087            loan: 0.10,
4088            dividend: 0.05,
4089            management_fee: 0.15,
4090            royalty: 0.10,
4091            cost_sharing: 0.05,
4092        }
4093    }
4094}
4095
4096// ============================================================================
4097// Balance Configuration
4098// ============================================================================
4099
4100/// Balance and trial balance configuration.
4101#[derive(Debug, Clone, Serialize, Deserialize)]
4102pub struct BalanceConfig {
4103    /// Generate opening balances
4104    #[serde(default)]
4105    pub generate_opening_balances: bool,
4106    /// Generate trial balances
4107    #[serde(default = "default_true")]
4108    pub generate_trial_balances: bool,
4109    /// Target gross margin (for revenue/COGS coherence)
4110    #[serde(default = "default_gross_margin")]
4111    pub target_gross_margin: f64,
4112    /// Target DSO (Days Sales Outstanding)
4113    #[serde(default = "default_dso")]
4114    pub target_dso_days: u32,
4115    /// Target DPO (Days Payable Outstanding)
4116    #[serde(default = "default_dpo")]
4117    pub target_dpo_days: u32,
4118    /// Target current ratio
4119    #[serde(default = "default_current_ratio")]
4120    pub target_current_ratio: f64,
4121    /// Target debt-to-equity ratio
4122    #[serde(default = "default_debt_equity")]
4123    pub target_debt_to_equity: f64,
4124    /// Validate balance sheet equation (A = L + E)
4125    #[serde(default = "default_true")]
4126    pub validate_balance_equation: bool,
4127    /// Reconcile subledgers to GL control accounts
4128    #[serde(default = "default_true")]
4129    pub reconcile_subledgers: bool,
4130}
4131
4132fn default_gross_margin() -> f64 {
4133    0.35
4134}
4135
4136fn default_dso() -> u32 {
4137    45
4138}
4139
4140fn default_dpo() -> u32 {
4141    30
4142}
4143
4144fn default_current_ratio() -> f64 {
4145    1.5
4146}
4147
4148fn default_debt_equity() -> f64 {
4149    0.5
4150}
4151
4152impl Default for BalanceConfig {
4153    fn default() -> Self {
4154        Self {
4155            generate_opening_balances: false,
4156            generate_trial_balances: true,
4157            target_gross_margin: default_gross_margin(),
4158            target_dso_days: default_dso(),
4159            target_dpo_days: default_dpo(),
4160            target_current_ratio: default_current_ratio(),
4161            target_debt_to_equity: default_debt_equity(),
4162            validate_balance_equation: true,
4163            reconcile_subledgers: true,
4164        }
4165    }
4166}
4167
4168// ==========================================================================
4169// OCPM (Object-Centric Process Mining) Configuration
4170// ==========================================================================
4171
4172/// OCPM (Object-Centric Process Mining) configuration.
4173///
4174/// Controls generation of OCEL 2.0 compatible event logs with
4175/// many-to-many event-to-object relationships.
4176#[derive(Debug, Clone, Serialize, Deserialize)]
4177pub struct OcpmConfig {
4178    /// Enable OCPM event log generation
4179    #[serde(default)]
4180    pub enabled: bool,
4181
4182    /// Generate lifecycle events (Start/Complete pairs vs atomic events)
4183    #[serde(default = "default_true")]
4184    pub generate_lifecycle_events: bool,
4185
4186    /// Include object-to-object relationships in output
4187    #[serde(default = "default_true")]
4188    pub include_object_relationships: bool,
4189
4190    /// Compute and export process variants
4191    #[serde(default = "default_true")]
4192    pub compute_variants: bool,
4193
4194    /// Maximum variants to track (0 = unlimited)
4195    #[serde(default)]
4196    pub max_variants: usize,
4197
4198    /// P2P process configuration
4199    #[serde(default)]
4200    pub p2p_process: OcpmProcessConfig,
4201
4202    /// O2C process configuration
4203    #[serde(default)]
4204    pub o2c_process: OcpmProcessConfig,
4205
4206    /// Output format configuration
4207    #[serde(default)]
4208    pub output: OcpmOutputConfig,
4209}
4210
4211impl Default for OcpmConfig {
4212    fn default() -> Self {
4213        Self {
4214            enabled: false,
4215            generate_lifecycle_events: true,
4216            include_object_relationships: true,
4217            compute_variants: true,
4218            max_variants: 0,
4219            p2p_process: OcpmProcessConfig::default(),
4220            o2c_process: OcpmProcessConfig::default(),
4221            output: OcpmOutputConfig::default(),
4222        }
4223    }
4224}
4225
4226/// Process-specific OCPM configuration.
4227#[derive(Debug, Clone, Serialize, Deserialize)]
4228pub struct OcpmProcessConfig {
4229    /// Rework probability (0.0-1.0)
4230    #[serde(default = "default_rework_probability")]
4231    pub rework_probability: f64,
4232
4233    /// Skip step probability (0.0-1.0)
4234    #[serde(default = "default_skip_probability")]
4235    pub skip_step_probability: f64,
4236
4237    /// Out-of-order step probability (0.0-1.0)
4238    #[serde(default = "default_out_of_order_probability")]
4239    pub out_of_order_probability: f64,
4240}
4241
4242// Defaults deliberately produce variant counts and Inductive-Miner fitness
4243// in the range seen in real ERP data (dozens of variants, ~0.7–0.9 fitness).
4244// Lowering them all to 0 yields a single-variant happy-path log.
4245fn default_rework_probability() -> f64 {
4246    0.15
4247}
4248
4249fn default_skip_probability() -> f64 {
4250    0.10
4251}
4252
4253fn default_out_of_order_probability() -> f64 {
4254    0.08
4255}
4256
4257impl Default for OcpmProcessConfig {
4258    fn default() -> Self {
4259        Self {
4260            rework_probability: default_rework_probability(),
4261            skip_step_probability: default_skip_probability(),
4262            out_of_order_probability: default_out_of_order_probability(),
4263        }
4264    }
4265}
4266
4267/// OCPM output format configuration.
4268#[derive(Debug, Clone, Serialize, Deserialize)]
4269pub struct OcpmOutputConfig {
4270    /// Export OCEL 2.0 JSON format
4271    #[serde(default = "default_true")]
4272    pub ocel_json: bool,
4273
4274    /// Export OCEL 2.0 XML format
4275    #[serde(default)]
4276    pub ocel_xml: bool,
4277
4278    /// Export XES 2.0 XML format (IEEE standard for process mining tools)
4279    #[serde(default)]
4280    pub xes: bool,
4281
4282    /// Include lifecycle transitions in XES output (start/complete pairs)
4283    #[serde(default = "default_true")]
4284    pub xes_include_lifecycle: bool,
4285
4286    /// Include resource attributes in XES output
4287    #[serde(default = "default_true")]
4288    pub xes_include_resources: bool,
4289
4290    /// Export flattened CSV for each object type
4291    #[serde(default = "default_true")]
4292    pub flattened_csv: bool,
4293
4294    /// Export event-object relationship table
4295    #[serde(default = "default_true")]
4296    pub event_object_csv: bool,
4297
4298    /// Export object-object relationship table
4299    #[serde(default = "default_true")]
4300    pub object_relationship_csv: bool,
4301
4302    /// Export process variants summary
4303    #[serde(default = "default_true")]
4304    pub variants_csv: bool,
4305
4306    /// Export reference process models (canonical P2P, O2C, R2R)
4307    #[serde(default)]
4308    pub export_reference_models: bool,
4309}
4310
4311impl Default for OcpmOutputConfig {
4312    fn default() -> Self {
4313        Self {
4314            ocel_json: true,
4315            ocel_xml: false,
4316            xes: false,
4317            xes_include_lifecycle: true,
4318            xes_include_resources: true,
4319            flattened_csv: true,
4320            event_object_csv: true,
4321            object_relationship_csv: true,
4322            variants_csv: true,
4323            export_reference_models: false,
4324        }
4325    }
4326}
4327
4328/// Audit engagement and workpaper generation configuration.
4329#[derive(Debug, Clone, Serialize, Deserialize)]
4330pub struct AuditGenerationConfig {
4331    /// Enable audit engagement generation
4332    #[serde(default)]
4333    pub enabled: bool,
4334
4335    /// Gate for workpaper generation (v3.3.2+).
4336    /// When `false`, workpapers and dependent evidence are skipped
4337    /// while engagements / risk assessments / findings still generate.
4338    #[serde(default = "default_true")]
4339    pub generate_workpapers: bool,
4340
4341    /// Engagement type distribution (v3.3.2+). Drives per-engagement
4342    /// type draw via `AuditEngagementGenerator::draw_engagement_type`.
4343    #[serde(default)]
4344    pub engagement_types: AuditEngagementTypesConfig,
4345
4346    /// Workpaper configuration (v3.3.2+). `average_per_phase` maps onto
4347    /// `WorkpaperGenerator.workpapers_per_section` as a ±50% band
4348    /// around the average. Sampling / ISA / cross-reference flags are
4349    /// surfaced for downstream formatting overlays.
4350    #[serde(default)]
4351    pub workpapers: WorkpaperConfig,
4352
4353    /// Audit team configuration (v3.3.2+). `min_team_size` /
4354    /// `max_team_size` map directly onto
4355    /// `AuditEngagementGenerator.team_size_range`.
4356    /// `specialist_probability` is reserved for v3.4 (explicit
4357    /// specialist-role support).
4358    #[serde(default)]
4359    pub team: AuditTeamConfig,
4360
4361    /// Review workflow configuration (v3.3.2+).
4362    /// `average_review_delay_days` drives both
4363    /// `first_review_delay_range` and `second_review_delay_range` as
4364    /// a ±1-day band around the average. `rework_probability` and
4365    /// `require_partner_signoff` are reserved for v3.4 workflow
4366    /// modeling.
4367    #[serde(default)]
4368    pub review: ReviewWorkflowConfig,
4369
4370    /// FSM-driven audit generation configuration.
4371    #[serde(default)]
4372    pub fsm: Option<AuditFsmConfig>,
4373
4374    /// v3.3.0: IT general controls (access logs, change management
4375    /// records) emitted alongside audit engagements. Requires both
4376    /// `audit.enabled = true` and `audit.it_controls.enabled = true`
4377    /// to take effect — the latter defaults to `false` so current
4378    /// archives are byte-identical to v3.2.1.
4379    #[serde(default)]
4380    pub it_controls: ItControlsConfig,
4381}
4382
4383/// IT general controls config (v3.3.0+).
4384#[derive(Debug, Clone, Serialize, Deserialize)]
4385pub struct ItControlsConfig {
4386    /// Master switch — when `false`, no access logs or change records
4387    /// are generated.
4388    #[serde(default)]
4389    pub enabled: bool,
4390    /// Number of access-log entries per engagement (approximate — the
4391    /// generator may round or scale based on company size).
4392    #[serde(default = "default_access_log_count")]
4393    pub access_logs_per_engagement: usize,
4394    /// Number of change-management records per engagement.
4395    #[serde(default = "default_change_record_count")]
4396    pub change_records_per_engagement: usize,
4397}
4398
4399fn default_access_log_count() -> usize {
4400    500
4401}
4402fn default_change_record_count() -> usize {
4403    50
4404}
4405
4406impl Default for ItControlsConfig {
4407    fn default() -> Self {
4408        Self {
4409            enabled: false,
4410            access_logs_per_engagement: default_access_log_count(),
4411            change_records_per_engagement: default_change_record_count(),
4412        }
4413    }
4414}
4415
4416impl Default for AuditGenerationConfig {
4417    fn default() -> Self {
4418        Self {
4419            enabled: false,
4420            generate_workpapers: true,
4421            engagement_types: AuditEngagementTypesConfig::default(),
4422            workpapers: WorkpaperConfig::default(),
4423            team: AuditTeamConfig::default(),
4424            review: ReviewWorkflowConfig::default(),
4425            fsm: None,
4426            it_controls: ItControlsConfig::default(),
4427        }
4428    }
4429}
4430
4431/// FSM-driven audit generation configuration.
4432#[derive(Debug, Clone, Serialize, Deserialize)]
4433pub struct AuditFsmConfig {
4434    /// Enable FSM-driven audit generation.
4435    #[serde(default)]
4436    pub enabled: bool,
4437
4438    /// Blueprint source: "builtin:fsa", "builtin:ia", or a file path.
4439    #[serde(default = "default_audit_fsm_blueprint")]
4440    pub blueprint: String,
4441
4442    /// Overlay source: "builtin:default", "builtin:thorough", "builtin:rushed", or a file path.
4443    #[serde(default = "default_audit_fsm_overlay")]
4444    pub overlay: String,
4445
4446    /// Depth level override.
4447    #[serde(default)]
4448    pub depth: Option<String>,
4449
4450    /// Discriminator filter.
4451    #[serde(default)]
4452    pub discriminators: std::collections::HashMap<String, Vec<String>>,
4453
4454    /// Event trail output config.
4455    #[serde(default)]
4456    pub event_trail: AuditEventTrailConfig,
4457
4458    /// RNG seed override.
4459    #[serde(default)]
4460    pub seed: Option<u64>,
4461}
4462
4463impl Default for AuditFsmConfig {
4464    fn default() -> Self {
4465        Self {
4466            enabled: false,
4467            blueprint: default_audit_fsm_blueprint(),
4468            overlay: default_audit_fsm_overlay(),
4469            depth: None,
4470            discriminators: std::collections::HashMap::new(),
4471            event_trail: AuditEventTrailConfig::default(),
4472            seed: None,
4473        }
4474    }
4475}
4476
4477fn default_audit_fsm_blueprint() -> String {
4478    "builtin:fsa".to_string()
4479}
4480
4481fn default_audit_fsm_overlay() -> String {
4482    "builtin:default".to_string()
4483}
4484
4485/// Event trail output configuration for FSM-driven audit generation.
4486#[derive(Debug, Clone, Serialize, Deserialize)]
4487pub struct AuditEventTrailConfig {
4488    /// Emit a flat event log.
4489    #[serde(default = "default_true")]
4490    pub flat_log: bool,
4491    /// Project events to OCEL 2.0 format.
4492    #[serde(default)]
4493    pub ocel_projection: bool,
4494}
4495
4496impl Default for AuditEventTrailConfig {
4497    fn default() -> Self {
4498        Self {
4499            flat_log: true,
4500            ocel_projection: false,
4501        }
4502    }
4503}
4504
4505/// Engagement type distribution configuration.
4506#[derive(Debug, Clone, Serialize, Deserialize)]
4507pub struct AuditEngagementTypesConfig {
4508    /// Financial statement audit probability
4509    #[serde(default = "default_financial_audit_prob")]
4510    pub financial_statement: f64,
4511    /// SOX/ICFR audit probability
4512    #[serde(default = "default_sox_audit_prob")]
4513    pub sox_icfr: f64,
4514    /// Integrated audit probability
4515    #[serde(default = "default_integrated_audit_prob")]
4516    pub integrated: f64,
4517    /// Review engagement probability
4518    #[serde(default = "default_review_prob")]
4519    pub review: f64,
4520    /// Agreed-upon procedures probability
4521    #[serde(default = "default_aup_prob")]
4522    pub agreed_upon_procedures: f64,
4523}
4524
4525fn default_financial_audit_prob() -> f64 {
4526    0.40
4527}
4528fn default_sox_audit_prob() -> f64 {
4529    0.20
4530}
4531fn default_integrated_audit_prob() -> f64 {
4532    0.25
4533}
4534fn default_review_prob() -> f64 {
4535    0.10
4536}
4537fn default_aup_prob() -> f64 {
4538    0.05
4539}
4540
4541impl Default for AuditEngagementTypesConfig {
4542    fn default() -> Self {
4543        Self {
4544            financial_statement: default_financial_audit_prob(),
4545            sox_icfr: default_sox_audit_prob(),
4546            integrated: default_integrated_audit_prob(),
4547            review: default_review_prob(),
4548            agreed_upon_procedures: default_aup_prob(),
4549        }
4550    }
4551}
4552
4553/// Workpaper generation configuration.
4554#[derive(Debug, Clone, Serialize, Deserialize)]
4555pub struct WorkpaperConfig {
4556    /// Average workpapers per engagement phase
4557    #[serde(default = "default_workpapers_per_phase")]
4558    pub average_per_phase: usize,
4559
4560    /// Include ISA compliance references
4561    #[serde(default = "default_true")]
4562    pub include_isa_references: bool,
4563
4564    /// Generate sample details
4565    #[serde(default = "default_true")]
4566    pub include_sample_details: bool,
4567
4568    /// Include cross-references between workpapers
4569    #[serde(default = "default_true")]
4570    pub include_cross_references: bool,
4571
4572    /// Sampling configuration
4573    #[serde(default)]
4574    pub sampling: SamplingConfig,
4575}
4576
4577fn default_workpapers_per_phase() -> usize {
4578    5
4579}
4580
4581impl Default for WorkpaperConfig {
4582    fn default() -> Self {
4583        Self {
4584            average_per_phase: default_workpapers_per_phase(),
4585            include_isa_references: true,
4586            include_sample_details: true,
4587            include_cross_references: true,
4588            sampling: SamplingConfig::default(),
4589        }
4590    }
4591}
4592
4593/// Sampling method configuration.
4594#[derive(Debug, Clone, Serialize, Deserialize)]
4595pub struct SamplingConfig {
4596    /// Statistical sampling rate (0.0-1.0)
4597    #[serde(default = "default_statistical_rate")]
4598    pub statistical_rate: f64,
4599    /// Judgmental sampling rate (0.0-1.0)
4600    #[serde(default = "default_judgmental_rate")]
4601    pub judgmental_rate: f64,
4602    /// Haphazard sampling rate (0.0-1.0)
4603    #[serde(default = "default_haphazard_rate")]
4604    pub haphazard_rate: f64,
4605    /// 100% examination rate (0.0-1.0)
4606    #[serde(default = "default_complete_examination_rate")]
4607    pub complete_examination_rate: f64,
4608}
4609
4610fn default_statistical_rate() -> f64 {
4611    0.40
4612}
4613fn default_judgmental_rate() -> f64 {
4614    0.30
4615}
4616fn default_haphazard_rate() -> f64 {
4617    0.20
4618}
4619fn default_complete_examination_rate() -> f64 {
4620    0.10
4621}
4622
4623impl Default for SamplingConfig {
4624    fn default() -> Self {
4625        Self {
4626            statistical_rate: default_statistical_rate(),
4627            judgmental_rate: default_judgmental_rate(),
4628            haphazard_rate: default_haphazard_rate(),
4629            complete_examination_rate: default_complete_examination_rate(),
4630        }
4631    }
4632}
4633
4634/// Audit team configuration.
4635#[derive(Debug, Clone, Serialize, Deserialize)]
4636pub struct AuditTeamConfig {
4637    /// Minimum team size
4638    #[serde(default = "default_min_team_size")]
4639    pub min_team_size: usize,
4640    /// Maximum team size
4641    #[serde(default = "default_max_team_size")]
4642    pub max_team_size: usize,
4643    /// Probability of having a specialist on the team
4644    #[serde(default = "default_specialist_probability")]
4645    pub specialist_probability: f64,
4646}
4647
4648fn default_min_team_size() -> usize {
4649    3
4650}
4651fn default_max_team_size() -> usize {
4652    8
4653}
4654fn default_specialist_probability() -> f64 {
4655    0.30
4656}
4657
4658impl Default for AuditTeamConfig {
4659    fn default() -> Self {
4660        Self {
4661            min_team_size: default_min_team_size(),
4662            max_team_size: default_max_team_size(),
4663            specialist_probability: default_specialist_probability(),
4664        }
4665    }
4666}
4667
4668/// Review workflow configuration.
4669#[derive(Debug, Clone, Serialize, Deserialize)]
4670pub struct ReviewWorkflowConfig {
4671    /// Average days between preparer completion and first review
4672    #[serde(default = "default_review_delay_days")]
4673    pub average_review_delay_days: u32,
4674    /// Probability of review notes requiring rework
4675    #[serde(default = "default_rework_probability_review")]
4676    pub rework_probability: f64,
4677    /// Require partner sign-off for all workpapers
4678    #[serde(default = "default_true")]
4679    pub require_partner_signoff: bool,
4680}
4681
4682fn default_review_delay_days() -> u32 {
4683    2
4684}
4685fn default_rework_probability_review() -> f64 {
4686    0.15
4687}
4688
4689impl Default for ReviewWorkflowConfig {
4690    fn default() -> Self {
4691        Self {
4692            average_review_delay_days: default_review_delay_days(),
4693            rework_probability: default_rework_probability_review(),
4694            require_partner_signoff: true,
4695        }
4696    }
4697}
4698
4699// =============================================================================
4700// Data Quality Configuration
4701// =============================================================================
4702
4703/// Data quality variation settings for realistic flakiness injection.
4704#[derive(Debug, Clone, Serialize, Deserialize)]
4705pub struct DataQualitySchemaConfig {
4706    /// Enable data quality variations
4707    #[serde(default)]
4708    pub enabled: bool,
4709    /// Preset to use (overrides individual settings if set)
4710    #[serde(default)]
4711    pub preset: DataQualityPreset,
4712    /// Missing value injection settings
4713    #[serde(default)]
4714    pub missing_values: MissingValuesSchemaConfig,
4715    /// Typo injection settings
4716    #[serde(default)]
4717    pub typos: TypoSchemaConfig,
4718    /// Format variation settings
4719    #[serde(default)]
4720    pub format_variations: FormatVariationSchemaConfig,
4721    /// Duplicate injection settings
4722    #[serde(default)]
4723    pub duplicates: DuplicateSchemaConfig,
4724    /// Encoding issue settings
4725    #[serde(default)]
4726    pub encoding_issues: EncodingIssueSchemaConfig,
4727    /// Generate quality issue labels for ML training
4728    #[serde(default)]
4729    pub generate_labels: bool,
4730    /// Per-sink quality profiles (different settings for CSV vs JSON etc.)
4731    #[serde(default)]
4732    pub sink_profiles: SinkQualityProfiles,
4733}
4734
4735impl Default for DataQualitySchemaConfig {
4736    fn default() -> Self {
4737        Self {
4738            enabled: false,
4739            preset: DataQualityPreset::None,
4740            missing_values: MissingValuesSchemaConfig::default(),
4741            typos: TypoSchemaConfig::default(),
4742            format_variations: FormatVariationSchemaConfig::default(),
4743            duplicates: DuplicateSchemaConfig::default(),
4744            encoding_issues: EncodingIssueSchemaConfig::default(),
4745            generate_labels: true,
4746            sink_profiles: SinkQualityProfiles::default(),
4747        }
4748    }
4749}
4750
4751impl DataQualitySchemaConfig {
4752    /// Creates a config for a specific preset profile.
4753    pub fn with_preset(preset: DataQualityPreset) -> Self {
4754        let mut config = Self {
4755            preset,
4756            ..Default::default()
4757        };
4758        config.apply_preset();
4759        config
4760    }
4761
4762    /// Applies the preset settings to the individual configuration fields.
4763    /// Call this after deserializing if preset is not Custom or None.
4764    pub fn apply_preset(&mut self) {
4765        if !self.preset.overrides_settings() {
4766            return;
4767        }
4768
4769        self.enabled = true;
4770
4771        // Missing values
4772        self.missing_values.enabled = self.preset.missing_rate() > 0.0;
4773        self.missing_values.rate = self.preset.missing_rate();
4774
4775        // Typos
4776        self.typos.enabled = self.preset.typo_rate() > 0.0;
4777        self.typos.char_error_rate = self.preset.typo_rate();
4778
4779        // Duplicates
4780        self.duplicates.enabled = self.preset.duplicate_rate() > 0.0;
4781        self.duplicates.exact_duplicate_ratio = self.preset.duplicate_rate() * 0.4;
4782        self.duplicates.near_duplicate_ratio = self.preset.duplicate_rate() * 0.4;
4783        self.duplicates.fuzzy_duplicate_ratio = self.preset.duplicate_rate() * 0.2;
4784
4785        // Format variations
4786        self.format_variations.enabled = self.preset.format_variations_enabled();
4787
4788        // Encoding issues
4789        self.encoding_issues.enabled = self.preset.encoding_issues_enabled();
4790        self.encoding_issues.rate = self.preset.encoding_issue_rate();
4791
4792        // OCR errors for typos in legacy preset
4793        if self.preset.ocr_errors_enabled() {
4794            self.typos.type_weights.ocr_errors = 0.3;
4795        }
4796    }
4797
4798    /// Returns the effective missing value rate (considering preset).
4799    pub fn effective_missing_rate(&self) -> f64 {
4800        if self.preset.overrides_settings() {
4801            self.preset.missing_rate()
4802        } else {
4803            self.missing_values.rate
4804        }
4805    }
4806
4807    /// Returns the effective typo rate (considering preset).
4808    pub fn effective_typo_rate(&self) -> f64 {
4809        if self.preset.overrides_settings() {
4810            self.preset.typo_rate()
4811        } else {
4812            self.typos.char_error_rate
4813        }
4814    }
4815
4816    /// Returns the effective duplicate rate (considering preset).
4817    pub fn effective_duplicate_rate(&self) -> f64 {
4818        if self.preset.overrides_settings() {
4819            self.preset.duplicate_rate()
4820        } else {
4821            self.duplicates.exact_duplicate_ratio
4822                + self.duplicates.near_duplicate_ratio
4823                + self.duplicates.fuzzy_duplicate_ratio
4824        }
4825    }
4826
4827    /// Creates a clean profile config.
4828    pub fn clean() -> Self {
4829        Self::with_preset(DataQualityPreset::Clean)
4830    }
4831
4832    /// Creates a noisy profile config.
4833    pub fn noisy() -> Self {
4834        Self::with_preset(DataQualityPreset::Noisy)
4835    }
4836
4837    /// Creates a legacy profile config.
4838    pub fn legacy() -> Self {
4839        Self::with_preset(DataQualityPreset::Legacy)
4840    }
4841}
4842
4843/// Preset configurations for common data quality scenarios.
4844#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
4845#[serde(rename_all = "snake_case")]
4846pub enum DataQualityPreset {
4847    /// No data quality variations (clean data)
4848    #[default]
4849    None,
4850    /// Minimal variations (very clean data with rare issues)
4851    Minimal,
4852    /// Normal variations (realistic enterprise data quality)
4853    Normal,
4854    /// High variations (messy data for stress testing)
4855    High,
4856    /// Custom (use individual settings)
4857    Custom,
4858
4859    // ========================================
4860    // ML-Oriented Profiles (Phase 2.1)
4861    // ========================================
4862    /// Clean profile for ML training - minimal data quality issues
4863    /// Missing: 0.1%, Typos: 0.05%, Duplicates: 0%, Format: None
4864    Clean,
4865    /// Noisy profile simulating typical production data issues
4866    /// Missing: 5%, Typos: 2%, Duplicates: 1%, Format: Medium
4867    Noisy,
4868    /// Legacy profile simulating migrated/OCR'd historical data
4869    /// Missing: 10%, Typos: 5%, Duplicates: 3%, Format: Heavy + OCR
4870    Legacy,
4871}
4872
4873impl DataQualityPreset {
4874    /// Returns the missing value rate for this preset.
4875    pub fn missing_rate(&self) -> f64 {
4876        match self {
4877            DataQualityPreset::None => 0.0,
4878            DataQualityPreset::Minimal => 0.005,
4879            DataQualityPreset::Normal => 0.02,
4880            DataQualityPreset::High => 0.08,
4881            DataQualityPreset::Custom => 0.01, // Use config value
4882            DataQualityPreset::Clean => 0.001,
4883            DataQualityPreset::Noisy => 0.05,
4884            DataQualityPreset::Legacy => 0.10,
4885        }
4886    }
4887
4888    /// Returns the typo rate for this preset.
4889    pub fn typo_rate(&self) -> f64 {
4890        match self {
4891            DataQualityPreset::None => 0.0,
4892            DataQualityPreset::Minimal => 0.0005,
4893            DataQualityPreset::Normal => 0.002,
4894            DataQualityPreset::High => 0.01,
4895            DataQualityPreset::Custom => 0.001, // Use config value
4896            DataQualityPreset::Clean => 0.0005,
4897            DataQualityPreset::Noisy => 0.02,
4898            DataQualityPreset::Legacy => 0.05,
4899        }
4900    }
4901
4902    /// Returns the duplicate rate for this preset.
4903    pub fn duplicate_rate(&self) -> f64 {
4904        match self {
4905            DataQualityPreset::None => 0.0,
4906            DataQualityPreset::Minimal => 0.001,
4907            DataQualityPreset::Normal => 0.005,
4908            DataQualityPreset::High => 0.02,
4909            DataQualityPreset::Custom => 0.0, // Use config value
4910            DataQualityPreset::Clean => 0.0,
4911            DataQualityPreset::Noisy => 0.01,
4912            DataQualityPreset::Legacy => 0.03,
4913        }
4914    }
4915
4916    /// Returns whether format variations are enabled for this preset.
4917    pub fn format_variations_enabled(&self) -> bool {
4918        match self {
4919            DataQualityPreset::None | DataQualityPreset::Clean => false,
4920            DataQualityPreset::Minimal => true,
4921            DataQualityPreset::Normal => true,
4922            DataQualityPreset::High => true,
4923            DataQualityPreset::Custom => true,
4924            DataQualityPreset::Noisy => true,
4925            DataQualityPreset::Legacy => true,
4926        }
4927    }
4928
4929    /// Returns whether OCR-style errors are enabled for this preset.
4930    pub fn ocr_errors_enabled(&self) -> bool {
4931        matches!(self, DataQualityPreset::Legacy | DataQualityPreset::High)
4932    }
4933
4934    /// Returns whether encoding issues are enabled for this preset.
4935    pub fn encoding_issues_enabled(&self) -> bool {
4936        matches!(
4937            self,
4938            DataQualityPreset::Legacy | DataQualityPreset::High | DataQualityPreset::Noisy
4939        )
4940    }
4941
4942    /// Returns the encoding issue rate for this preset.
4943    pub fn encoding_issue_rate(&self) -> f64 {
4944        match self {
4945            DataQualityPreset::None | DataQualityPreset::Clean | DataQualityPreset::Minimal => 0.0,
4946            DataQualityPreset::Normal => 0.002,
4947            DataQualityPreset::High => 0.01,
4948            DataQualityPreset::Custom => 0.0,
4949            DataQualityPreset::Noisy => 0.005,
4950            DataQualityPreset::Legacy => 0.02,
4951        }
4952    }
4953
4954    /// Returns true if this preset overrides individual settings.
4955    pub fn overrides_settings(&self) -> bool {
4956        !matches!(self, DataQualityPreset::Custom | DataQualityPreset::None)
4957    }
4958
4959    /// Returns a human-readable description of this preset.
4960    pub fn description(&self) -> &'static str {
4961        match self {
4962            DataQualityPreset::None => "No data quality issues (pristine data)",
4963            DataQualityPreset::Minimal => "Very rare data quality issues",
4964            DataQualityPreset::Normal => "Realistic enterprise data quality",
4965            DataQualityPreset::High => "Messy data for stress testing",
4966            DataQualityPreset::Custom => "Custom settings from configuration",
4967            DataQualityPreset::Clean => "ML-ready clean data with minimal issues",
4968            DataQualityPreset::Noisy => "Typical production data with moderate issues",
4969            DataQualityPreset::Legacy => "Legacy/migrated data with heavy issues and OCR errors",
4970        }
4971    }
4972}
4973
4974/// Missing value injection configuration.
4975#[derive(Debug, Clone, Serialize, Deserialize)]
4976pub struct MissingValuesSchemaConfig {
4977    /// Enable missing value injection
4978    #[serde(default)]
4979    pub enabled: bool,
4980    /// Global missing rate (0.0 to 1.0)
4981    #[serde(default = "default_missing_rate")]
4982    pub rate: f64,
4983    /// Missing value strategy
4984    #[serde(default)]
4985    pub strategy: MissingValueStrategy,
4986    /// Field-specific rates (field name -> rate)
4987    #[serde(default)]
4988    pub field_rates: std::collections::HashMap<String, f64>,
4989    /// Fields that should never have missing values
4990    #[serde(default)]
4991    pub protected_fields: Vec<String>,
4992}
4993
4994fn default_missing_rate() -> f64 {
4995    0.01
4996}
4997
4998impl Default for MissingValuesSchemaConfig {
4999    fn default() -> Self {
5000        Self {
5001            enabled: false,
5002            rate: default_missing_rate(),
5003            strategy: MissingValueStrategy::Mcar,
5004            field_rates: std::collections::HashMap::new(),
5005            protected_fields: vec![
5006                "document_id".to_string(),
5007                "company_code".to_string(),
5008                "posting_date".to_string(),
5009            ],
5010        }
5011    }
5012}
5013
5014/// Missing value strategy types.
5015#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
5016#[serde(rename_all = "snake_case")]
5017pub enum MissingValueStrategy {
5018    /// Missing Completely At Random - equal probability for all values
5019    #[default]
5020    Mcar,
5021    /// Missing At Random - depends on other observed values
5022    Mar,
5023    /// Missing Not At Random - depends on the value itself
5024    Mnar,
5025    /// Systematic - entire field groups missing together
5026    Systematic,
5027}
5028
5029/// Typo injection configuration.
5030#[derive(Debug, Clone, Serialize, Deserialize)]
5031pub struct TypoSchemaConfig {
5032    /// Enable typo injection
5033    #[serde(default)]
5034    pub enabled: bool,
5035    /// Character error rate (per character, not per field)
5036    #[serde(default = "default_typo_rate")]
5037    pub char_error_rate: f64,
5038    /// Typo type weights
5039    #[serde(default)]
5040    pub type_weights: TypoTypeWeights,
5041    /// Fields that should never have typos
5042    #[serde(default)]
5043    pub protected_fields: Vec<String>,
5044}
5045
5046fn default_typo_rate() -> f64 {
5047    0.001
5048}
5049
5050impl Default for TypoSchemaConfig {
5051    fn default() -> Self {
5052        Self {
5053            enabled: false,
5054            char_error_rate: default_typo_rate(),
5055            type_weights: TypoTypeWeights::default(),
5056            protected_fields: vec![
5057                "document_id".to_string(),
5058                "gl_account".to_string(),
5059                "company_code".to_string(),
5060            ],
5061        }
5062    }
5063}
5064
5065/// Weights for different typo types.
5066#[derive(Debug, Clone, Serialize, Deserialize)]
5067pub struct TypoTypeWeights {
5068    /// Keyboard-adjacent substitution (e.g., 'a' -> 's')
5069    #[serde(default = "default_substitution_weight")]
5070    pub substitution: f64,
5071    /// Adjacent character transposition (e.g., 'ab' -> 'ba')
5072    #[serde(default = "default_transposition_weight")]
5073    pub transposition: f64,
5074    /// Character insertion
5075    #[serde(default = "default_insertion_weight")]
5076    pub insertion: f64,
5077    /// Character deletion
5078    #[serde(default = "default_deletion_weight")]
5079    pub deletion: f64,
5080    /// OCR-style errors (e.g., '0' -> 'O')
5081    #[serde(default = "default_ocr_weight")]
5082    pub ocr_errors: f64,
5083    /// Homophone substitution (e.g., 'their' -> 'there')
5084    #[serde(default = "default_homophone_weight")]
5085    pub homophones: f64,
5086}
5087
5088fn default_substitution_weight() -> f64 {
5089    0.35
5090}
5091fn default_transposition_weight() -> f64 {
5092    0.25
5093}
5094fn default_insertion_weight() -> f64 {
5095    0.10
5096}
5097fn default_deletion_weight() -> f64 {
5098    0.15
5099}
5100fn default_ocr_weight() -> f64 {
5101    0.10
5102}
5103fn default_homophone_weight() -> f64 {
5104    0.05
5105}
5106
5107impl Default for TypoTypeWeights {
5108    fn default() -> Self {
5109        Self {
5110            substitution: default_substitution_weight(),
5111            transposition: default_transposition_weight(),
5112            insertion: default_insertion_weight(),
5113            deletion: default_deletion_weight(),
5114            ocr_errors: default_ocr_weight(),
5115            homophones: default_homophone_weight(),
5116        }
5117    }
5118}
5119
5120/// Format variation configuration.
5121#[derive(Debug, Clone, Serialize, Deserialize, Default)]
5122pub struct FormatVariationSchemaConfig {
5123    /// Enable format variations
5124    #[serde(default)]
5125    pub enabled: bool,
5126    /// Date format variation settings
5127    #[serde(default)]
5128    pub dates: DateFormatVariationConfig,
5129    /// Amount format variation settings
5130    #[serde(default)]
5131    pub amounts: AmountFormatVariationConfig,
5132    /// Identifier format variation settings
5133    #[serde(default)]
5134    pub identifiers: IdentifierFormatVariationConfig,
5135}
5136
5137/// Date format variation configuration.
5138#[derive(Debug, Clone, Serialize, Deserialize)]
5139pub struct DateFormatVariationConfig {
5140    /// Enable date format variations
5141    #[serde(default)]
5142    pub enabled: bool,
5143    /// Overall variation rate
5144    #[serde(default = "default_date_variation_rate")]
5145    pub rate: f64,
5146    /// Include ISO format (2024-01-15)
5147    #[serde(default = "default_true")]
5148    pub iso_format: bool,
5149    /// Include US format (01/15/2024)
5150    #[serde(default)]
5151    pub us_format: bool,
5152    /// Include EU format (15.01.2024)
5153    #[serde(default)]
5154    pub eu_format: bool,
5155    /// Include long format (January 15, 2024)
5156    #[serde(default)]
5157    pub long_format: bool,
5158}
5159
5160fn default_date_variation_rate() -> f64 {
5161    0.05
5162}
5163
5164impl Default for DateFormatVariationConfig {
5165    fn default() -> Self {
5166        Self {
5167            enabled: false,
5168            rate: default_date_variation_rate(),
5169            iso_format: true,
5170            us_format: false,
5171            eu_format: false,
5172            long_format: false,
5173        }
5174    }
5175}
5176
5177/// Amount format variation configuration.
5178#[derive(Debug, Clone, Serialize, Deserialize)]
5179pub struct AmountFormatVariationConfig {
5180    /// Enable amount format variations
5181    #[serde(default)]
5182    pub enabled: bool,
5183    /// Overall variation rate
5184    #[serde(default = "default_amount_variation_rate")]
5185    pub rate: f64,
5186    /// Include US comma format (1,234.56)
5187    #[serde(default)]
5188    pub us_comma_format: bool,
5189    /// Include EU format (1.234,56)
5190    #[serde(default)]
5191    pub eu_format: bool,
5192    /// Include currency prefix ($1,234.56)
5193    #[serde(default)]
5194    pub currency_prefix: bool,
5195    /// Include accounting format with parentheses for negatives
5196    #[serde(default)]
5197    pub accounting_format: bool,
5198}
5199
5200fn default_amount_variation_rate() -> f64 {
5201    0.02
5202}
5203
5204impl Default for AmountFormatVariationConfig {
5205    fn default() -> Self {
5206        Self {
5207            enabled: false,
5208            rate: default_amount_variation_rate(),
5209            us_comma_format: false,
5210            eu_format: false,
5211            currency_prefix: false,
5212            accounting_format: false,
5213        }
5214    }
5215}
5216
5217/// Identifier format variation configuration.
5218#[derive(Debug, Clone, Serialize, Deserialize)]
5219pub struct IdentifierFormatVariationConfig {
5220    /// Enable identifier format variations
5221    #[serde(default)]
5222    pub enabled: bool,
5223    /// Overall variation rate
5224    #[serde(default = "default_identifier_variation_rate")]
5225    pub rate: f64,
5226    /// Case variations (uppercase, lowercase, mixed)
5227    #[serde(default)]
5228    pub case_variations: bool,
5229    /// Padding variations (leading zeros)
5230    #[serde(default)]
5231    pub padding_variations: bool,
5232    /// Separator variations (dash vs underscore)
5233    #[serde(default)]
5234    pub separator_variations: bool,
5235}
5236
5237fn default_identifier_variation_rate() -> f64 {
5238    0.02
5239}
5240
5241impl Default for IdentifierFormatVariationConfig {
5242    fn default() -> Self {
5243        Self {
5244            enabled: false,
5245            rate: default_identifier_variation_rate(),
5246            case_variations: false,
5247            padding_variations: false,
5248            separator_variations: false,
5249        }
5250    }
5251}
5252
5253/// Duplicate injection configuration.
5254#[derive(Debug, Clone, Serialize, Deserialize)]
5255pub struct DuplicateSchemaConfig {
5256    /// Enable duplicate injection
5257    #[serde(default)]
5258    pub enabled: bool,
5259    /// Overall duplicate rate
5260    #[serde(default = "default_duplicate_rate")]
5261    pub rate: f64,
5262    /// Exact duplicate proportion (out of duplicates)
5263    #[serde(default = "default_exact_duplicate_ratio")]
5264    pub exact_duplicate_ratio: f64,
5265    /// Near duplicate proportion (slight variations)
5266    #[serde(default = "default_near_duplicate_ratio")]
5267    pub near_duplicate_ratio: f64,
5268    /// Fuzzy duplicate proportion (typos in key fields)
5269    #[serde(default = "default_fuzzy_duplicate_ratio")]
5270    pub fuzzy_duplicate_ratio: f64,
5271    /// Maximum date offset for near/fuzzy duplicates (days)
5272    #[serde(default = "default_max_date_offset")]
5273    pub max_date_offset_days: u32,
5274    /// Maximum amount variance for near duplicates (fraction)
5275    #[serde(default = "default_max_amount_variance")]
5276    pub max_amount_variance: f64,
5277}
5278
5279fn default_duplicate_rate() -> f64 {
5280    0.005
5281}
5282fn default_exact_duplicate_ratio() -> f64 {
5283    0.4
5284}
5285fn default_near_duplicate_ratio() -> f64 {
5286    0.35
5287}
5288fn default_fuzzy_duplicate_ratio() -> f64 {
5289    0.25
5290}
5291fn default_max_date_offset() -> u32 {
5292    3
5293}
5294fn default_max_amount_variance() -> f64 {
5295    0.01
5296}
5297
5298impl Default for DuplicateSchemaConfig {
5299    fn default() -> Self {
5300        Self {
5301            enabled: false,
5302            rate: default_duplicate_rate(),
5303            exact_duplicate_ratio: default_exact_duplicate_ratio(),
5304            near_duplicate_ratio: default_near_duplicate_ratio(),
5305            fuzzy_duplicate_ratio: default_fuzzy_duplicate_ratio(),
5306            max_date_offset_days: default_max_date_offset(),
5307            max_amount_variance: default_max_amount_variance(),
5308        }
5309    }
5310}
5311
5312/// Encoding issue configuration.
5313#[derive(Debug, Clone, Serialize, Deserialize)]
5314pub struct EncodingIssueSchemaConfig {
5315    /// Enable encoding issue injection
5316    #[serde(default)]
5317    pub enabled: bool,
5318    /// Overall encoding issue rate
5319    #[serde(default = "default_encoding_rate")]
5320    pub rate: f64,
5321    /// Include mojibake (UTF-8/Latin-1 confusion)
5322    #[serde(default)]
5323    pub mojibake: bool,
5324    /// Include HTML entity corruption
5325    #[serde(default)]
5326    pub html_entities: bool,
5327    /// Include BOM issues
5328    #[serde(default)]
5329    pub bom_issues: bool,
5330}
5331
5332fn default_encoding_rate() -> f64 {
5333    0.001
5334}
5335
5336impl Default for EncodingIssueSchemaConfig {
5337    fn default() -> Self {
5338        Self {
5339            enabled: false,
5340            rate: default_encoding_rate(),
5341            mojibake: false,
5342            html_entities: false,
5343            bom_issues: false,
5344        }
5345    }
5346}
5347
5348/// Per-sink quality profiles for different output formats.
5349#[derive(Debug, Clone, Serialize, Deserialize, Default)]
5350pub struct SinkQualityProfiles {
5351    /// CSV-specific quality settings
5352    #[serde(default)]
5353    pub csv: Option<SinkQualityOverride>,
5354    /// JSON-specific quality settings
5355    #[serde(default)]
5356    pub json: Option<SinkQualityOverride>,
5357    /// Parquet-specific quality settings
5358    #[serde(default)]
5359    pub parquet: Option<SinkQualityOverride>,
5360}
5361
5362/// Quality setting overrides for a specific sink type.
5363#[derive(Debug, Clone, Serialize, Deserialize)]
5364pub struct SinkQualityOverride {
5365    /// Override enabled state
5366    pub enabled: Option<bool>,
5367    /// Override missing value rate
5368    pub missing_rate: Option<f64>,
5369    /// Override typo rate
5370    pub typo_rate: Option<f64>,
5371    /// Override format variation rate
5372    pub format_variation_rate: Option<f64>,
5373    /// Override duplicate rate
5374    pub duplicate_rate: Option<f64>,
5375}
5376
5377// =============================================================================
5378// Accounting Standards Configuration
5379// =============================================================================
5380
5381/// Accounting standards framework configuration for generating standards-compliant data.
5382///
5383/// Supports US GAAP, IFRS, and French GAAP (PCG) frameworks with specific standards:
5384/// - ASC 606/IFRS 15/PCG: Revenue Recognition
5385/// - ASC 842/IFRS 16/PCG: Leases
5386/// - ASC 820/IFRS 13/PCG: Fair Value Measurement
5387/// - ASC 360/IAS 36/PCG: Impairment
5388#[derive(Debug, Clone, Serialize, Deserialize, Default)]
5389pub struct AccountingStandardsConfig {
5390    /// Enable accounting standards generation
5391    #[serde(default)]
5392    pub enabled: bool,
5393
5394    /// Accounting framework to use.
5395    /// When `None`, the country pack's `accounting.framework` is used as fallback;
5396    /// if that is also absent the orchestrator defaults to US GAAP.
5397    #[serde(default, skip_serializing_if = "Option::is_none")]
5398    pub framework: Option<AccountingFrameworkConfig>,
5399
5400    /// Revenue recognition configuration (ASC 606/IFRS 15)
5401    #[serde(default)]
5402    pub revenue_recognition: RevenueRecognitionConfig,
5403
5404    /// Lease accounting configuration (ASC 842/IFRS 16)
5405    #[serde(default)]
5406    pub leases: LeaseAccountingConfig,
5407
5408    /// Fair value measurement configuration (ASC 820/IFRS 13)
5409    #[serde(default)]
5410    pub fair_value: FairValueConfig,
5411
5412    /// Impairment testing configuration (ASC 360/IAS 36)
5413    #[serde(default)]
5414    pub impairment: ImpairmentConfig,
5415
5416    /// Business combination configuration (IFRS 3 / ASC 805)
5417    #[serde(default)]
5418    pub business_combinations: BusinessCombinationsConfig,
5419
5420    /// Expected Credit Loss configuration (IFRS 9 / ASC 326)
5421    #[serde(default)]
5422    pub expected_credit_loss: EclConfig,
5423
5424    /// Generate framework differences for dual reporting
5425    #[serde(default)]
5426    pub generate_differences: bool,
5427}
5428
5429/// Accounting framework selection.
5430#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
5431#[serde(rename_all = "snake_case")]
5432pub enum AccountingFrameworkConfig {
5433    /// US Generally Accepted Accounting Principles
5434    #[default]
5435    UsGaap,
5436    /// International Financial Reporting Standards
5437    Ifrs,
5438    /// Generate data for both frameworks with reconciliation
5439    DualReporting,
5440    /// French GAAP (Plan Comptable Général – PCG)
5441    FrenchGaap,
5442    /// German GAAP (Handelsgesetzbuch – HGB, §238-263)
5443    GermanGaap,
5444}
5445
5446/// Revenue recognition configuration (ASC 606/IFRS 15).
5447#[derive(Debug, Clone, Serialize, Deserialize)]
5448pub struct RevenueRecognitionConfig {
5449    /// Enable revenue recognition generation
5450    #[serde(default)]
5451    pub enabled: bool,
5452
5453    /// Generate customer contracts
5454    #[serde(default = "default_true")]
5455    pub generate_contracts: bool,
5456
5457    /// Average number of performance obligations per contract
5458    #[serde(default = "default_avg_obligations")]
5459    pub avg_obligations_per_contract: f64,
5460
5461    /// Rate of contracts with variable consideration
5462    #[serde(default = "default_variable_consideration_rate")]
5463    pub variable_consideration_rate: f64,
5464
5465    /// Rate of over-time revenue recognition (vs point-in-time)
5466    #[serde(default = "default_over_time_rate")]
5467    pub over_time_recognition_rate: f64,
5468
5469    /// Number of contracts to generate
5470    #[serde(default = "default_contract_count")]
5471    pub contract_count: usize,
5472}
5473
5474fn default_avg_obligations() -> f64 {
5475    2.0
5476}
5477
5478fn default_variable_consideration_rate() -> f64 {
5479    0.15
5480}
5481
5482fn default_over_time_rate() -> f64 {
5483    0.30
5484}
5485
5486fn default_contract_count() -> usize {
5487    100
5488}
5489
5490impl Default for RevenueRecognitionConfig {
5491    fn default() -> Self {
5492        Self {
5493            enabled: false,
5494            generate_contracts: true,
5495            avg_obligations_per_contract: default_avg_obligations(),
5496            variable_consideration_rate: default_variable_consideration_rate(),
5497            over_time_recognition_rate: default_over_time_rate(),
5498            contract_count: default_contract_count(),
5499        }
5500    }
5501}
5502
5503/// Lease accounting configuration (ASC 842/IFRS 16).
5504#[derive(Debug, Clone, Serialize, Deserialize)]
5505pub struct LeaseAccountingConfig {
5506    /// Enable lease accounting generation
5507    #[serde(default)]
5508    pub enabled: bool,
5509
5510    /// Number of leases to generate
5511    #[serde(default = "default_lease_count")]
5512    pub lease_count: usize,
5513
5514    /// Percentage of finance leases (vs operating)
5515    #[serde(default = "default_finance_lease_pct")]
5516    pub finance_lease_percent: f64,
5517
5518    /// Average lease term in months
5519    #[serde(default = "default_avg_lease_term")]
5520    pub avg_lease_term_months: u32,
5521
5522    /// Generate amortization schedules
5523    #[serde(default = "default_true")]
5524    pub generate_amortization: bool,
5525
5526    /// Real estate lease percentage
5527    #[serde(default = "default_real_estate_pct")]
5528    pub real_estate_percent: f64,
5529}
5530
5531fn default_lease_count() -> usize {
5532    50
5533}
5534
5535fn default_finance_lease_pct() -> f64 {
5536    0.30
5537}
5538
5539fn default_avg_lease_term() -> u32 {
5540    60
5541}
5542
5543fn default_real_estate_pct() -> f64 {
5544    0.40
5545}
5546
5547impl Default for LeaseAccountingConfig {
5548    fn default() -> Self {
5549        Self {
5550            enabled: false,
5551            lease_count: default_lease_count(),
5552            finance_lease_percent: default_finance_lease_pct(),
5553            avg_lease_term_months: default_avg_lease_term(),
5554            generate_amortization: true,
5555            real_estate_percent: default_real_estate_pct(),
5556        }
5557    }
5558}
5559
5560/// Fair value measurement configuration (ASC 820/IFRS 13).
5561#[derive(Debug, Clone, Serialize, Deserialize)]
5562pub struct FairValueConfig {
5563    /// Enable fair value measurement generation
5564    #[serde(default)]
5565    pub enabled: bool,
5566
5567    /// Number of fair value measurements to generate
5568    #[serde(default = "default_fv_count")]
5569    pub measurement_count: usize,
5570
5571    /// Level 1 (quoted prices) percentage
5572    #[serde(default = "default_level1_pct")]
5573    pub level1_percent: f64,
5574
5575    /// Level 2 (observable inputs) percentage
5576    #[serde(default = "default_level2_pct")]
5577    pub level2_percent: f64,
5578
5579    /// Level 3 (unobservable inputs) percentage
5580    #[serde(default = "default_level3_pct")]
5581    pub level3_percent: f64,
5582
5583    /// Include sensitivity analysis for Level 3
5584    #[serde(default)]
5585    pub include_sensitivity_analysis: bool,
5586}
5587
5588fn default_fv_count() -> usize {
5589    25
5590}
5591
5592fn default_level1_pct() -> f64 {
5593    0.40
5594}
5595
5596fn default_level2_pct() -> f64 {
5597    0.35
5598}
5599
5600fn default_level3_pct() -> f64 {
5601    0.25
5602}
5603
5604impl Default for FairValueConfig {
5605    fn default() -> Self {
5606        Self {
5607            enabled: false,
5608            measurement_count: default_fv_count(),
5609            level1_percent: default_level1_pct(),
5610            level2_percent: default_level2_pct(),
5611            level3_percent: default_level3_pct(),
5612            include_sensitivity_analysis: false,
5613        }
5614    }
5615}
5616
5617/// Impairment testing configuration (ASC 360/IAS 36).
5618#[derive(Debug, Clone, Serialize, Deserialize)]
5619pub struct ImpairmentConfig {
5620    /// Enable impairment testing generation
5621    #[serde(default)]
5622    pub enabled: bool,
5623
5624    /// Number of impairment tests to generate
5625    #[serde(default = "default_impairment_count")]
5626    pub test_count: usize,
5627
5628    /// Rate of tests resulting in impairment
5629    #[serde(default = "default_impairment_rate")]
5630    pub impairment_rate: f64,
5631
5632    /// Generate cash flow projections
5633    #[serde(default = "default_true")]
5634    pub generate_projections: bool,
5635
5636    /// Include goodwill impairment tests
5637    #[serde(default)]
5638    pub include_goodwill: bool,
5639}
5640
5641fn default_impairment_count() -> usize {
5642    15
5643}
5644
5645fn default_impairment_rate() -> f64 {
5646    0.10
5647}
5648
5649impl Default for ImpairmentConfig {
5650    fn default() -> Self {
5651        Self {
5652            enabled: false,
5653            test_count: default_impairment_count(),
5654            impairment_rate: default_impairment_rate(),
5655            generate_projections: true,
5656            include_goodwill: false,
5657        }
5658    }
5659}
5660
5661// =============================================================================
5662// Business Combinations Configuration (IFRS 3 / ASC 805)
5663// =============================================================================
5664
5665/// Configuration for generating business combination (acquisition) data.
5666#[derive(Debug, Clone, Serialize, Deserialize)]
5667pub struct BusinessCombinationsConfig {
5668    /// Enable business combination generation
5669    #[serde(default)]
5670    pub enabled: bool,
5671
5672    /// Number of acquisitions to generate per company (1-5)
5673    #[serde(default = "default_bc_acquisition_count")]
5674    pub acquisition_count: usize,
5675}
5676
5677fn default_bc_acquisition_count() -> usize {
5678    2
5679}
5680
5681impl Default for BusinessCombinationsConfig {
5682    fn default() -> Self {
5683        Self {
5684            enabled: false,
5685            acquisition_count: default_bc_acquisition_count(),
5686        }
5687    }
5688}
5689
5690// =============================================================================
5691// ECL Configuration (IFRS 9 / ASC 326)
5692// =============================================================================
5693
5694/// Configuration for Expected Credit Loss generation.
5695#[derive(Debug, Clone, Serialize, Deserialize)]
5696pub struct EclConfig {
5697    /// Enable ECL generation.
5698    #[serde(default)]
5699    pub enabled: bool,
5700
5701    /// Weight for base economic scenario (0–1).
5702    #[serde(default = "default_ecl_base_weight")]
5703    pub base_scenario_weight: f64,
5704
5705    /// Multiplier for base scenario (typically 1.0).
5706    #[serde(default = "default_ecl_base_multiplier")]
5707    pub base_scenario_multiplier: f64,
5708
5709    /// Weight for optimistic economic scenario (0–1).
5710    #[serde(default = "default_ecl_optimistic_weight")]
5711    pub optimistic_scenario_weight: f64,
5712
5713    /// Multiplier for optimistic scenario (< 1.0 means lower losses).
5714    #[serde(default = "default_ecl_optimistic_multiplier")]
5715    pub optimistic_scenario_multiplier: f64,
5716
5717    /// Weight for pessimistic economic scenario (0–1).
5718    #[serde(default = "default_ecl_pessimistic_weight")]
5719    pub pessimistic_scenario_weight: f64,
5720
5721    /// Multiplier for pessimistic scenario (> 1.0 means higher losses).
5722    #[serde(default = "default_ecl_pessimistic_multiplier")]
5723    pub pessimistic_scenario_multiplier: f64,
5724}
5725
5726fn default_ecl_base_weight() -> f64 {
5727    0.50
5728}
5729fn default_ecl_base_multiplier() -> f64 {
5730    1.0
5731}
5732fn default_ecl_optimistic_weight() -> f64 {
5733    0.30
5734}
5735fn default_ecl_optimistic_multiplier() -> f64 {
5736    0.8
5737}
5738fn default_ecl_pessimistic_weight() -> f64 {
5739    0.20
5740}
5741fn default_ecl_pessimistic_multiplier() -> f64 {
5742    1.4
5743}
5744
5745impl Default for EclConfig {
5746    fn default() -> Self {
5747        Self {
5748            enabled: false,
5749            base_scenario_weight: default_ecl_base_weight(),
5750            base_scenario_multiplier: default_ecl_base_multiplier(),
5751            optimistic_scenario_weight: default_ecl_optimistic_weight(),
5752            optimistic_scenario_multiplier: default_ecl_optimistic_multiplier(),
5753            pessimistic_scenario_weight: default_ecl_pessimistic_weight(),
5754            pessimistic_scenario_multiplier: default_ecl_pessimistic_multiplier(),
5755        }
5756    }
5757}
5758
5759// =============================================================================
5760// Audit Standards Configuration
5761// =============================================================================
5762
5763/// Audit standards framework configuration for generating standards-compliant audit data.
5764///
5765/// Supports ISA (International Standards on Auditing) and PCAOB standards:
5766/// - ISA 200-720: Complete coverage of audit standards
5767/// - ISA 520: Analytical Procedures
5768/// - ISA 505: External Confirmations
5769/// - ISA 700/705/706/701: Audit Reports
5770/// - PCAOB AS 2201: ICFR Auditing
5771#[derive(Debug, Clone, Serialize, Deserialize, Default)]
5772pub struct AuditStandardsConfig {
5773    /// Enable audit standards generation
5774    #[serde(default)]
5775    pub enabled: bool,
5776
5777    /// ISA compliance configuration
5778    #[serde(default)]
5779    pub isa_compliance: IsaComplianceConfig,
5780
5781    /// Analytical procedures configuration (ISA 520)
5782    #[serde(default)]
5783    pub analytical_procedures: AnalyticalProceduresConfig,
5784
5785    /// External confirmations configuration (ISA 505)
5786    #[serde(default)]
5787    pub confirmations: ConfirmationsConfig,
5788
5789    /// Audit opinion configuration (ISA 700/705/706/701)
5790    #[serde(default)]
5791    pub opinion: AuditOpinionConfig,
5792
5793    /// Generate complete audit trail with traceability
5794    #[serde(default)]
5795    pub generate_audit_trail: bool,
5796
5797    /// SOX 302/404 compliance configuration
5798    #[serde(default)]
5799    pub sox: SoxComplianceConfig,
5800
5801    /// PCAOB-specific configuration
5802    #[serde(default)]
5803    pub pcaob: PcaobConfig,
5804}
5805
5806/// ISA compliance level configuration.
5807#[derive(Debug, Clone, Serialize, Deserialize)]
5808pub struct IsaComplianceConfig {
5809    /// Enable ISA compliance tracking
5810    #[serde(default)]
5811    pub enabled: bool,
5812
5813    /// Compliance level: "basic", "standard", "comprehensive"
5814    #[serde(default = "default_compliance_level")]
5815    pub compliance_level: String,
5816
5817    /// Generate ISA requirement mappings
5818    #[serde(default = "default_true")]
5819    pub generate_isa_mappings: bool,
5820
5821    /// Generate ISA coverage summary
5822    #[serde(default = "default_true")]
5823    pub generate_coverage_summary: bool,
5824
5825    /// Include PCAOB standard mappings (for dual framework)
5826    #[serde(default)]
5827    pub include_pcaob: bool,
5828
5829    /// Framework to use: "isa", "pcaob", "dual"
5830    #[serde(default = "default_audit_framework")]
5831    pub framework: String,
5832}
5833
5834fn default_compliance_level() -> String {
5835    "standard".to_string()
5836}
5837
5838fn default_audit_framework() -> String {
5839    "isa".to_string()
5840}
5841
5842impl Default for IsaComplianceConfig {
5843    fn default() -> Self {
5844        Self {
5845            enabled: false,
5846            compliance_level: default_compliance_level(),
5847            generate_isa_mappings: true,
5848            generate_coverage_summary: true,
5849            include_pcaob: false,
5850            framework: default_audit_framework(),
5851        }
5852    }
5853}
5854
5855/// Analytical procedures configuration (ISA 520).
5856#[derive(Debug, Clone, Serialize, Deserialize)]
5857pub struct AnalyticalProceduresConfig {
5858    /// Enable analytical procedures generation
5859    #[serde(default)]
5860    pub enabled: bool,
5861
5862    /// Number of procedures per account/area
5863    #[serde(default = "default_procedures_per_account")]
5864    pub procedures_per_account: usize,
5865
5866    /// Probability of variance exceeding threshold
5867    #[serde(default = "default_variance_probability")]
5868    pub variance_probability: f64,
5869
5870    /// Include variance investigations
5871    #[serde(default = "default_true")]
5872    pub generate_investigations: bool,
5873
5874    /// Include financial ratio analysis
5875    #[serde(default = "default_true")]
5876    pub include_ratio_analysis: bool,
5877}
5878
5879fn default_procedures_per_account() -> usize {
5880    3
5881}
5882
5883fn default_variance_probability() -> f64 {
5884    0.20
5885}
5886
5887impl Default for AnalyticalProceduresConfig {
5888    fn default() -> Self {
5889        Self {
5890            enabled: false,
5891            procedures_per_account: default_procedures_per_account(),
5892            variance_probability: default_variance_probability(),
5893            generate_investigations: true,
5894            include_ratio_analysis: true,
5895        }
5896    }
5897}
5898
5899/// External confirmations configuration (ISA 505).
5900#[derive(Debug, Clone, Serialize, Deserialize)]
5901pub struct ConfirmationsConfig {
5902    /// Enable confirmation generation
5903    #[serde(default)]
5904    pub enabled: bool,
5905
5906    /// Number of confirmations to generate
5907    #[serde(default = "default_confirmation_count")]
5908    pub confirmation_count: usize,
5909
5910    /// Positive response rate
5911    #[serde(default = "default_positive_response_rate")]
5912    pub positive_response_rate: f64,
5913
5914    /// Exception rate (responses with differences)
5915    #[serde(default = "default_exception_rate_confirm")]
5916    pub exception_rate: f64,
5917
5918    /// Non-response rate
5919    #[serde(default = "default_non_response_rate")]
5920    pub non_response_rate: f64,
5921
5922    /// Generate alternative procedures for non-responses
5923    #[serde(default = "default_true")]
5924    pub generate_alternative_procedures: bool,
5925}
5926
5927fn default_confirmation_count() -> usize {
5928    50
5929}
5930
5931fn default_positive_response_rate() -> f64 {
5932    0.85
5933}
5934
5935fn default_exception_rate_confirm() -> f64 {
5936    0.10
5937}
5938
5939fn default_non_response_rate() -> f64 {
5940    0.05
5941}
5942
5943impl Default for ConfirmationsConfig {
5944    fn default() -> Self {
5945        Self {
5946            enabled: false,
5947            confirmation_count: default_confirmation_count(),
5948            positive_response_rate: default_positive_response_rate(),
5949            exception_rate: default_exception_rate_confirm(),
5950            non_response_rate: default_non_response_rate(),
5951            generate_alternative_procedures: true,
5952        }
5953    }
5954}
5955
5956/// Audit opinion configuration (ISA 700/705/706/701).
5957#[derive(Debug, Clone, Serialize, Deserialize)]
5958pub struct AuditOpinionConfig {
5959    /// Enable audit opinion generation
5960    #[serde(default)]
5961    pub enabled: bool,
5962
5963    /// Generate Key Audit Matters (KAM) / Critical Audit Matters (CAM)
5964    #[serde(default = "default_true")]
5965    pub generate_kam: bool,
5966
5967    /// Average number of KAMs/CAMs per opinion
5968    #[serde(default = "default_kam_count")]
5969    pub average_kam_count: usize,
5970
5971    /// Rate of modified opinions
5972    #[serde(default = "default_modified_opinion_rate")]
5973    pub modified_opinion_rate: f64,
5974
5975    /// Include emphasis of matter paragraphs
5976    #[serde(default)]
5977    pub include_emphasis_of_matter: bool,
5978
5979    /// Include going concern conclusions
5980    #[serde(default = "default_true")]
5981    pub include_going_concern: bool,
5982}
5983
5984fn default_kam_count() -> usize {
5985    3
5986}
5987
5988fn default_modified_opinion_rate() -> f64 {
5989    0.05
5990}
5991
5992impl Default for AuditOpinionConfig {
5993    fn default() -> Self {
5994        Self {
5995            enabled: false,
5996            generate_kam: true,
5997            average_kam_count: default_kam_count(),
5998            modified_opinion_rate: default_modified_opinion_rate(),
5999            include_emphasis_of_matter: false,
6000            include_going_concern: true,
6001        }
6002    }
6003}
6004
6005/// SOX compliance configuration (Sections 302/404).
6006#[derive(Debug, Clone, Serialize, Deserialize)]
6007pub struct SoxComplianceConfig {
6008    /// Enable SOX compliance generation
6009    #[serde(default)]
6010    pub enabled: bool,
6011
6012    /// Generate Section 302 CEO/CFO certifications
6013    #[serde(default = "default_true")]
6014    pub generate_302_certifications: bool,
6015
6016    /// Generate Section 404 ICFR assessments
6017    #[serde(default = "default_true")]
6018    pub generate_404_assessments: bool,
6019
6020    /// Materiality threshold for SOX testing
6021    #[serde(default = "default_sox_materiality_threshold")]
6022    pub materiality_threshold: f64,
6023
6024    /// Rate of material weaknesses
6025    #[serde(default = "default_material_weakness_rate")]
6026    pub material_weakness_rate: f64,
6027
6028    /// Rate of significant deficiencies
6029    #[serde(default = "default_significant_deficiency_rate")]
6030    pub significant_deficiency_rate: f64,
6031}
6032
6033fn default_material_weakness_rate() -> f64 {
6034    0.02
6035}
6036
6037fn default_significant_deficiency_rate() -> f64 {
6038    0.08
6039}
6040
6041impl Default for SoxComplianceConfig {
6042    fn default() -> Self {
6043        Self {
6044            enabled: false,
6045            generate_302_certifications: true,
6046            generate_404_assessments: true,
6047            materiality_threshold: default_sox_materiality_threshold(),
6048            material_weakness_rate: default_material_weakness_rate(),
6049            significant_deficiency_rate: default_significant_deficiency_rate(),
6050        }
6051    }
6052}
6053
6054/// PCAOB-specific configuration.
6055#[derive(Debug, Clone, Serialize, Deserialize)]
6056pub struct PcaobConfig {
6057    /// Enable PCAOB-specific elements
6058    #[serde(default)]
6059    pub enabled: bool,
6060
6061    /// Treat as PCAOB audit (vs ISA-only)
6062    #[serde(default)]
6063    pub is_pcaob_audit: bool,
6064
6065    /// Generate Critical Audit Matters (CAM)
6066    #[serde(default = "default_true")]
6067    pub generate_cam: bool,
6068
6069    /// Include ICFR opinion (for integrated audits)
6070    #[serde(default)]
6071    pub include_icfr_opinion: bool,
6072
6073    /// Generate PCAOB-ISA standard mappings
6074    #[serde(default)]
6075    pub generate_standard_mappings: bool,
6076}
6077
6078impl Default for PcaobConfig {
6079    fn default() -> Self {
6080        Self {
6081            enabled: false,
6082            is_pcaob_audit: false,
6083            generate_cam: true,
6084            include_icfr_opinion: false,
6085            generate_standard_mappings: false,
6086        }
6087    }
6088}
6089
6090// =============================================================================
6091// Advanced Distribution Configuration
6092// =============================================================================
6093
6094/// Advanced distribution configuration for realistic data generation.
6095///
6096/// This section enables sophisticated distribution models including:
6097/// - Mixture models (multi-modal distributions)
6098/// - Cross-field correlations
6099/// - Conditional distributions
6100/// - Regime changes and economic cycles
6101/// - Statistical validation
6102#[derive(Debug, Clone, Serialize, Deserialize, Default)]
6103pub struct AdvancedDistributionConfig {
6104    /// Enable advanced distribution features.
6105    #[serde(default)]
6106    pub enabled: bool,
6107
6108    /// Mixture model configuration for amounts.
6109    #[serde(default)]
6110    pub amounts: MixtureDistributionSchemaConfig,
6111
6112    /// Cross-field correlation configuration.
6113    #[serde(default)]
6114    pub correlations: CorrelationSchemaConfig,
6115
6116    /// Conditional distribution configurations.
6117    #[serde(default)]
6118    pub conditional: Vec<ConditionalDistributionSchemaConfig>,
6119
6120    /// Regime change configuration.
6121    #[serde(default)]
6122    pub regime_changes: RegimeChangeSchemaConfig,
6123
6124    /// Industry-specific distribution profile.
6125    #[serde(default)]
6126    pub industry_profile: Option<IndustryProfileType>,
6127
6128    /// Statistical validation configuration.
6129    #[serde(default)]
6130    pub validation: StatisticalValidationSchemaConfig,
6131
6132    /// v3.4.4+ — Pareto heavy-tailed distribution for monetary amounts.
6133    /// When set and `enabled`, overrides `amounts` mixture model for the
6134    /// non-fraud amount-sampling path (fraud patterns remain orthogonal).
6135    /// Useful for capex, strategic contracts, and any domain where a small
6136    /// number of very large values dominates the tail.
6137    #[serde(default)]
6138    pub pareto: Option<ParetoSchemaConfig>,
6139}
6140
6141/// Schema-level Pareto distribution configuration (v3.4.4+).
6142///
6143/// Thin wrapper around `datasynth_core::distributions::ParetoConfig` that
6144/// adds an `enabled` gate and serde-friendly field names.
6145#[derive(Debug, Clone, Serialize, Deserialize)]
6146pub struct ParetoSchemaConfig {
6147    /// Enable Pareto sampling. When true, replaces the `amounts` mixture
6148    /// model for the non-fraud amount-sampling path.
6149    #[serde(default)]
6150    pub enabled: bool,
6151
6152    /// Shape parameter (tail heaviness). Lower values → heavier tail.
6153    /// Typical range: 1.5-3.0. Default: 2.0.
6154    #[serde(default = "default_pareto_alpha")]
6155    pub alpha: f64,
6156
6157    /// Scale / minimum value. All samples are >= x_min.
6158    /// Typical: 1000 (for capex) to 100,000 (for large contracts). Default: 100.
6159    #[serde(default = "default_pareto_x_min")]
6160    pub x_min: f64,
6161
6162    /// Optional upper clamp. `None` = unbounded (recommended for realistic
6163    /// heavy tails).
6164    #[serde(default)]
6165    pub max_value: Option<f64>,
6166
6167    /// Decimal places for rounding. Default: 2.
6168    #[serde(default = "default_pareto_decimal_places")]
6169    pub decimal_places: u8,
6170}
6171
6172fn default_pareto_alpha() -> f64 {
6173    2.0
6174}
6175
6176fn default_pareto_x_min() -> f64 {
6177    100.0
6178}
6179
6180fn default_pareto_decimal_places() -> u8 {
6181    2
6182}
6183
6184impl Default for ParetoSchemaConfig {
6185    fn default() -> Self {
6186        Self {
6187            enabled: false,
6188            alpha: default_pareto_alpha(),
6189            x_min: default_pareto_x_min(),
6190            max_value: None,
6191            decimal_places: default_pareto_decimal_places(),
6192        }
6193    }
6194}
6195
6196impl ParetoSchemaConfig {
6197    /// Convert this schema config into a `datasynth_core::distributions::ParetoConfig`.
6198    pub fn to_core_config(&self) -> datasynth_core::distributions::ParetoConfig {
6199        datasynth_core::distributions::ParetoConfig {
6200            alpha: self.alpha,
6201            x_min: self.x_min,
6202            max_value: self.max_value,
6203            decimal_places: self.decimal_places,
6204        }
6205    }
6206}
6207
6208/// Industry profile types for pre-configured distribution settings.
6209#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6210#[serde(rename_all = "snake_case")]
6211pub enum IndustryProfileType {
6212    /// Retail industry profile (POS sales, inventory, seasonal)
6213    Retail,
6214    /// Manufacturing industry profile (raw materials, maintenance, capital)
6215    Manufacturing,
6216    /// Financial services profile (wire transfers, ACH, fee income)
6217    FinancialServices,
6218    /// Healthcare profile (claims, procedures, supplies)
6219    Healthcare,
6220    /// Technology profile (subscriptions, services, R&D)
6221    Technology,
6222}
6223
6224/// Mixture model distribution configuration.
6225#[derive(Debug, Clone, Serialize, Deserialize)]
6226pub struct MixtureDistributionSchemaConfig {
6227    /// Enable mixture model for amount generation.
6228    #[serde(default)]
6229    pub enabled: bool,
6230
6231    /// Distribution type: "gaussian" or "lognormal".
6232    #[serde(default = "default_mixture_type")]
6233    pub distribution_type: MixtureDistributionType,
6234
6235    /// Mixture components with weights.
6236    #[serde(default)]
6237    pub components: Vec<MixtureComponentConfig>,
6238
6239    /// Minimum value constraint.
6240    #[serde(default = "default_min_amount")]
6241    pub min_value: f64,
6242
6243    /// Maximum value constraint (optional).
6244    #[serde(default)]
6245    pub max_value: Option<f64>,
6246
6247    /// Decimal places for rounding.
6248    #[serde(default = "default_decimal_places")]
6249    pub decimal_places: u8,
6250}
6251
6252fn default_mixture_type() -> MixtureDistributionType {
6253    MixtureDistributionType::LogNormal
6254}
6255
6256fn default_min_amount() -> f64 {
6257    0.01
6258}
6259
6260fn default_decimal_places() -> u8 {
6261    2
6262}
6263
6264impl Default for MixtureDistributionSchemaConfig {
6265    fn default() -> Self {
6266        Self {
6267            enabled: false,
6268            distribution_type: MixtureDistributionType::LogNormal,
6269            components: Vec::new(),
6270            min_value: 0.01,
6271            max_value: None,
6272            decimal_places: 2,
6273        }
6274    }
6275}
6276
6277impl MixtureDistributionSchemaConfig {
6278    /// Convert this schema-level config into a [`LogNormalMixtureConfig`]
6279    /// suitable for `LogNormalMixtureSampler::new`. Returns `None` if there
6280    /// are no components (schema default is an empty list, which cannot
6281    /// drive a sampler).
6282    ///
6283    /// Callers should gate this with `self.enabled` before invoking.
6284    pub fn to_log_normal_config(
6285        &self,
6286    ) -> Option<datasynth_core::distributions::LogNormalMixtureConfig> {
6287        if self.components.is_empty() {
6288            return None;
6289        }
6290        Some(datasynth_core::distributions::LogNormalMixtureConfig {
6291            components: self
6292                .components
6293                .iter()
6294                .map(|c| match &c.label {
6295                    Some(lbl) => datasynth_core::distributions::LogNormalComponent::with_label(
6296                        c.weight,
6297                        c.mu,
6298                        c.sigma,
6299                        lbl.clone(),
6300                    ),
6301                    None => datasynth_core::distributions::LogNormalComponent::new(
6302                        c.weight, c.mu, c.sigma,
6303                    ),
6304                })
6305                .collect(),
6306            min_value: self.min_value,
6307            max_value: self.max_value,
6308            decimal_places: self.decimal_places,
6309        })
6310    }
6311
6312    /// Convert this schema-level config into a [`GaussianMixtureConfig`].
6313    /// Returns `None` if there are no components.
6314    pub fn to_gaussian_config(
6315        &self,
6316    ) -> Option<datasynth_core::distributions::GaussianMixtureConfig> {
6317        if self.components.is_empty() {
6318            return None;
6319        }
6320        Some(datasynth_core::distributions::GaussianMixtureConfig {
6321            components: self
6322                .components
6323                .iter()
6324                .map(|c| {
6325                    datasynth_core::distributions::GaussianComponent::new(c.weight, c.mu, c.sigma)
6326                })
6327                .collect(),
6328            allow_negative: true,
6329            min_value: Some(self.min_value),
6330            max_value: self.max_value,
6331        })
6332    }
6333}
6334
6335/// Mixture distribution type.
6336#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
6337#[serde(rename_all = "snake_case")]
6338pub enum MixtureDistributionType {
6339    /// Gaussian (normal) mixture
6340    Gaussian,
6341    /// Log-normal mixture (for positive amounts)
6342    #[default]
6343    LogNormal,
6344}
6345
6346/// Configuration for a single mixture component.
6347#[derive(Debug, Clone, Serialize, Deserialize)]
6348pub struct MixtureComponentConfig {
6349    /// Weight of this component (must sum to 1.0 across all components).
6350    pub weight: f64,
6351
6352    /// Location parameter (mean for Gaussian, mu for log-normal).
6353    pub mu: f64,
6354
6355    /// Scale parameter (std dev for Gaussian, sigma for log-normal).
6356    pub sigma: f64,
6357
6358    /// Optional label for this component (e.g., "routine", "significant", "major").
6359    #[serde(default)]
6360    pub label: Option<String>,
6361}
6362
6363/// Cross-field correlation configuration.
6364#[derive(Debug, Clone, Serialize, Deserialize)]
6365pub struct CorrelationSchemaConfig {
6366    /// Enable correlation modeling.
6367    #[serde(default)]
6368    pub enabled: bool,
6369
6370    /// Copula type for dependency modeling.
6371    #[serde(default)]
6372    pub copula_type: CopulaSchemaType,
6373
6374    /// Field definitions for correlation.
6375    #[serde(default)]
6376    pub fields: Vec<CorrelatedFieldConfig>,
6377
6378    /// Correlation matrix (upper triangular, row-major).
6379    /// For n fields, this should have n*(n-1)/2 values.
6380    #[serde(default)]
6381    pub matrix: Vec<f64>,
6382
6383    /// Expected correlations for validation.
6384    #[serde(default)]
6385    pub expected_correlations: Vec<ExpectedCorrelationConfig>,
6386}
6387
6388impl Default for CorrelationSchemaConfig {
6389    fn default() -> Self {
6390        Self {
6391            enabled: false,
6392            copula_type: CopulaSchemaType::Gaussian,
6393            fields: Vec::new(),
6394            matrix: Vec::new(),
6395            expected_correlations: Vec::new(),
6396        }
6397    }
6398}
6399
6400impl CorrelationSchemaConfig {
6401    /// v3.5.4+: extract the correlation for a specific field pair from
6402    /// either the upper-triangular flat matrix (n*(n-1)/2 values) or a
6403    /// full symmetric n×n matrix (n*n values). Returns `None` when the
6404    /// named fields aren't both present or the matrix shape doesn't
6405    /// match.
6406    pub fn correlation_between(&self, field_a: &str, field_b: &str) -> Option<f64> {
6407        let idx_a = self.fields.iter().position(|f| f.name == field_a)?;
6408        let idx_b = self.fields.iter().position(|f| f.name == field_b)?;
6409        if idx_a == idx_b {
6410            return Some(1.0);
6411        }
6412        let (i, j) = if idx_a < idx_b {
6413            (idx_a, idx_b)
6414        } else {
6415            (idx_b, idx_a)
6416        };
6417        let n = self.fields.len();
6418        // Full n×n symmetric matrix?
6419        if self.matrix.len() == n * n {
6420            return self.matrix.get(idx_a * n + idx_b).copied();
6421        }
6422        // Upper triangular flat (row-major, excluding diagonal)?
6423        let expected_tri = n * (n - 1) / 2;
6424        if self.matrix.len() == expected_tri {
6425            // Row i, col j where j > i: flat index is
6426            //   sum_{k=0..i}((n-1-k)) + (j - i - 1)
6427            // = i*(n-1) - i*(i-1)/2 + (j - i - 1)
6428            let flat = i * (n - 1) - i * (i.saturating_sub(1)) / 2 + (j - i - 1);
6429            return self.matrix.get(flat).copied();
6430        }
6431        None
6432    }
6433
6434    /// Convert this schema config to a core `CopulaConfig` when the
6435    /// declared field pair `(field_a, field_b)` has a valid correlation
6436    /// entry. Returns `None` when disabled, fields missing, or matrix
6437    /// malformed.
6438    pub fn to_core_config_for_pair(
6439        &self,
6440        field_a: &str,
6441        field_b: &str,
6442    ) -> Option<datasynth_core::distributions::CopulaConfig> {
6443        if !self.enabled {
6444            return None;
6445        }
6446        let rho = self.correlation_between(field_a, field_b)?;
6447        use datasynth_core::distributions::{CopulaConfig, CopulaType};
6448        let copula_type = match self.copula_type {
6449            CopulaSchemaType::Gaussian => CopulaType::Gaussian,
6450            CopulaSchemaType::Clayton => CopulaType::Clayton,
6451            CopulaSchemaType::Gumbel => CopulaType::Gumbel,
6452            CopulaSchemaType::Frank => CopulaType::Frank,
6453            CopulaSchemaType::StudentT => CopulaType::StudentT,
6454        };
6455        // Gaussian / StudentT interpret theta as correlation; others
6456        // as a shape parameter. Minimal v3.5.4 only wires Gaussian in
6457        // the runtime, but the converter is general so follow-ups can
6458        // light up the other copulas.
6459        let theta = rho.clamp(-0.999, 0.999);
6460        Some(CopulaConfig {
6461            copula_type,
6462            theta,
6463            degrees_of_freedom: 4.0,
6464        })
6465    }
6466}
6467
6468/// Copula type for dependency modeling.
6469#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
6470#[serde(rename_all = "snake_case")]
6471pub enum CopulaSchemaType {
6472    /// Gaussian copula (symmetric, no tail dependence)
6473    #[default]
6474    Gaussian,
6475    /// Clayton copula (lower tail dependence)
6476    Clayton,
6477    /// Gumbel copula (upper tail dependence)
6478    Gumbel,
6479    /// Frank copula (symmetric, no tail dependence)
6480    Frank,
6481    /// Student-t copula (both tail dependencies)
6482    StudentT,
6483}
6484
6485/// Configuration for a correlated field.
6486#[derive(Debug, Clone, Serialize, Deserialize)]
6487pub struct CorrelatedFieldConfig {
6488    /// Field name.
6489    pub name: String,
6490
6491    /// Marginal distribution type.
6492    #[serde(default)]
6493    pub distribution: MarginalDistributionConfig,
6494}
6495
6496/// Marginal distribution configuration.
6497#[derive(Debug, Clone, Serialize, Deserialize)]
6498#[serde(tag = "type", rename_all = "snake_case")]
6499pub enum MarginalDistributionConfig {
6500    /// Normal distribution.
6501    Normal {
6502        /// Mean
6503        mu: f64,
6504        /// Standard deviation
6505        sigma: f64,
6506    },
6507    /// Log-normal distribution.
6508    LogNormal {
6509        /// Location parameter
6510        mu: f64,
6511        /// Scale parameter
6512        sigma: f64,
6513    },
6514    /// Uniform distribution.
6515    Uniform {
6516        /// Minimum value
6517        min: f64,
6518        /// Maximum value
6519        max: f64,
6520    },
6521    /// Discrete uniform distribution.
6522    DiscreteUniform {
6523        /// Minimum integer value
6524        min: i32,
6525        /// Maximum integer value
6526        max: i32,
6527    },
6528}
6529
6530impl Default for MarginalDistributionConfig {
6531    fn default() -> Self {
6532        Self::Normal {
6533            mu: 0.0,
6534            sigma: 1.0,
6535        }
6536    }
6537}
6538
6539/// Expected correlation for validation.
6540#[derive(Debug, Clone, Serialize, Deserialize)]
6541pub struct ExpectedCorrelationConfig {
6542    /// First field name.
6543    pub field1: String,
6544    /// Second field name.
6545    pub field2: String,
6546    /// Expected correlation coefficient.
6547    pub expected_r: f64,
6548    /// Acceptable tolerance.
6549    #[serde(default = "default_correlation_tolerance")]
6550    pub tolerance: f64,
6551}
6552
6553fn default_correlation_tolerance() -> f64 {
6554    0.10
6555}
6556
6557/// Conditional distribution configuration.
6558#[derive(Debug, Clone, Serialize, Deserialize)]
6559pub struct ConditionalDistributionSchemaConfig {
6560    /// Output field name to generate.
6561    pub output_field: String,
6562
6563    /// Input field name that conditions the distribution.
6564    pub input_field: String,
6565
6566    /// Breakpoints defining distribution changes.
6567    #[serde(default)]
6568    pub breakpoints: Vec<ConditionalBreakpointConfig>,
6569
6570    /// Default distribution when below all breakpoints.
6571    #[serde(default)]
6572    pub default_distribution: ConditionalDistributionParamsConfig,
6573
6574    /// Minimum output value constraint.
6575    #[serde(default)]
6576    pub min_value: Option<f64>,
6577
6578    /// Maximum output value constraint.
6579    #[serde(default)]
6580    pub max_value: Option<f64>,
6581
6582    /// Decimal places for output rounding.
6583    #[serde(default = "default_decimal_places")]
6584    pub decimal_places: u8,
6585}
6586
6587/// Breakpoint for conditional distribution.
6588#[derive(Debug, Clone, Serialize, Deserialize)]
6589pub struct ConditionalBreakpointConfig {
6590    /// Input value threshold.
6591    pub threshold: f64,
6592
6593    /// Distribution to use when input >= threshold.
6594    pub distribution: ConditionalDistributionParamsConfig,
6595}
6596
6597impl ConditionalDistributionSchemaConfig {
6598    /// Convert this schema config into a core
6599    /// [`ConditionalDistributionConfig`] suitable for
6600    /// [`ConditionalSampler::new`]. v3.5.3+.
6601    pub fn to_core_config(&self) -> datasynth_core::distributions::ConditionalDistributionConfig {
6602        use datasynth_core::distributions::{
6603            Breakpoint, ConditionalDistributionConfig, ConditionalDistributionParams,
6604        };
6605
6606        let default_distribution = convert_conditional_params(&self.default_distribution);
6607        let breakpoints: Vec<Breakpoint> = self
6608            .breakpoints
6609            .iter()
6610            .map(|bp| Breakpoint {
6611                threshold: bp.threshold,
6612                distribution: convert_conditional_params(&bp.distribution),
6613            })
6614            .collect();
6615
6616        // Use a sentinel default_distribution when the schema default is
6617        // its factory default (Fixed { value: 0.0 })  and we have
6618        // breakpoints — we don't want to clobber data for values below
6619        // the first breakpoint.
6620        let final_default = if breakpoints.is_empty() {
6621            default_distribution
6622        } else {
6623            match default_distribution {
6624                ConditionalDistributionParams::Fixed { value: 0.0 } => {
6625                    // Reuse the first breakpoint's distribution as the
6626                    // default to avoid surprising zeros.
6627                    breakpoints[0].distribution.clone()
6628                }
6629                other => other,
6630            }
6631        };
6632
6633        ConditionalDistributionConfig {
6634            output_field: self.output_field.clone(),
6635            input_field: self.input_field.clone(),
6636            breakpoints,
6637            default_distribution: final_default,
6638            min_value: self.min_value,
6639            max_value: self.max_value,
6640            decimal_places: self.decimal_places,
6641        }
6642    }
6643}
6644
6645fn convert_conditional_params(
6646    p: &ConditionalDistributionParamsConfig,
6647) -> datasynth_core::distributions::ConditionalDistributionParams {
6648    use datasynth_core::distributions::ConditionalDistributionParams as Core;
6649    match p {
6650        ConditionalDistributionParamsConfig::Fixed { value } => Core::Fixed { value: *value },
6651        ConditionalDistributionParamsConfig::Normal { mu, sigma } => Core::Normal {
6652            mu: *mu,
6653            sigma: *sigma,
6654        },
6655        ConditionalDistributionParamsConfig::LogNormal { mu, sigma } => Core::LogNormal {
6656            mu: *mu,
6657            sigma: *sigma,
6658        },
6659        ConditionalDistributionParamsConfig::Uniform { min, max } => Core::Uniform {
6660            min: *min,
6661            max: *max,
6662        },
6663        ConditionalDistributionParamsConfig::Beta {
6664            alpha,
6665            beta,
6666            min,
6667            max,
6668        } => Core::Beta {
6669            alpha: *alpha,
6670            beta: *beta,
6671            min: *min,
6672            max: *max,
6673        },
6674        ConditionalDistributionParamsConfig::Discrete { values, weights } => Core::Discrete {
6675            values: values.clone(),
6676            weights: weights.clone(),
6677        },
6678    }
6679}
6680
6681/// Distribution parameters for conditional distributions.
6682#[derive(Debug, Clone, Serialize, Deserialize)]
6683#[serde(tag = "type", rename_all = "snake_case")]
6684pub enum ConditionalDistributionParamsConfig {
6685    /// Fixed value.
6686    Fixed {
6687        /// The fixed value
6688        value: f64,
6689    },
6690    /// Normal distribution.
6691    Normal {
6692        /// Mean
6693        mu: f64,
6694        /// Standard deviation
6695        sigma: f64,
6696    },
6697    /// Log-normal distribution.
6698    LogNormal {
6699        /// Location parameter
6700        mu: f64,
6701        /// Scale parameter
6702        sigma: f64,
6703    },
6704    /// Uniform distribution.
6705    Uniform {
6706        /// Minimum
6707        min: f64,
6708        /// Maximum
6709        max: f64,
6710    },
6711    /// Beta distribution (scaled).
6712    Beta {
6713        /// Alpha parameter
6714        alpha: f64,
6715        /// Beta parameter
6716        beta: f64,
6717        /// Minimum output value
6718        min: f64,
6719        /// Maximum output value
6720        max: f64,
6721    },
6722    /// Discrete values with weights.
6723    Discrete {
6724        /// Possible values
6725        values: Vec<f64>,
6726        /// Weights (should sum to 1.0)
6727        weights: Vec<f64>,
6728    },
6729}
6730
6731impl Default for ConditionalDistributionParamsConfig {
6732    fn default() -> Self {
6733        Self::Normal {
6734            mu: 0.0,
6735            sigma: 1.0,
6736        }
6737    }
6738}
6739
6740/// Regime change configuration.
6741#[derive(Debug, Clone, Serialize, Deserialize, Default)]
6742pub struct RegimeChangeSchemaConfig {
6743    /// Enable regime change modeling.
6744    #[serde(default)]
6745    pub enabled: bool,
6746
6747    /// List of regime changes.
6748    #[serde(default)]
6749    pub changes: Vec<RegimeChangeEventConfig>,
6750
6751    /// Economic cycle configuration.
6752    #[serde(default)]
6753    pub economic_cycle: Option<EconomicCycleSchemaConfig>,
6754
6755    /// Parameter drift configurations.
6756    #[serde(default)]
6757    pub parameter_drifts: Vec<ParameterDriftSchemaConfig>,
6758}
6759
6760/// A single regime change event.
6761#[derive(Debug, Clone, Serialize, Deserialize)]
6762pub struct RegimeChangeEventConfig {
6763    /// Date when the change occurs (ISO 8601 format).
6764    pub date: String,
6765
6766    /// Type of regime change.
6767    pub change_type: RegimeChangeTypeConfig,
6768
6769    /// Description of the change.
6770    #[serde(default)]
6771    pub description: Option<String>,
6772
6773    /// Effects of this regime change.
6774    #[serde(default)]
6775    pub effects: Vec<RegimeEffectConfig>,
6776}
6777
6778/// Type of regime change.
6779#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6780#[serde(rename_all = "snake_case")]
6781pub enum RegimeChangeTypeConfig {
6782    /// Acquisition - sudden volume and amount increase
6783    Acquisition,
6784    /// Divestiture - sudden volume and amount decrease
6785    Divestiture,
6786    /// Price increase - amounts increase
6787    PriceIncrease,
6788    /// Price decrease - amounts decrease
6789    PriceDecrease,
6790    /// New product launch - volume ramp-up
6791    ProductLaunch,
6792    /// Product discontinuation - volume ramp-down
6793    ProductDiscontinuation,
6794    /// Policy change - affects patterns
6795    PolicyChange,
6796    /// Competitor entry - market disruption
6797    CompetitorEntry,
6798    /// Custom effect
6799    Custom,
6800}
6801
6802/// Effect of a regime change on a specific field.
6803#[derive(Debug, Clone, Serialize, Deserialize)]
6804pub struct RegimeEffectConfig {
6805    /// Field being affected.
6806    pub field: String,
6807
6808    /// Multiplier to apply (1.0 = no change, 1.5 = 50% increase).
6809    pub multiplier: f64,
6810}
6811
6812/// Economic cycle configuration.
6813#[derive(Debug, Clone, Serialize, Deserialize)]
6814pub struct EconomicCycleSchemaConfig {
6815    /// Enable economic cycle modeling.
6816    #[serde(default)]
6817    pub enabled: bool,
6818
6819    /// Cycle period in months (e.g., 48 for 4-year business cycle).
6820    #[serde(default = "default_cycle_period")]
6821    pub period_months: u32,
6822
6823    /// Amplitude of cycle effect (0.0-1.0).
6824    #[serde(default = "default_cycle_amplitude")]
6825    pub amplitude: f64,
6826
6827    /// Phase offset in months.
6828    #[serde(default)]
6829    pub phase_offset: u32,
6830
6831    /// Recession periods (start_month, duration_months).
6832    #[serde(default)]
6833    pub recessions: Vec<RecessionPeriodConfig>,
6834}
6835
6836fn default_cycle_period() -> u32 {
6837    48
6838}
6839
6840fn default_cycle_amplitude() -> f64 {
6841    0.15
6842}
6843
6844impl Default for EconomicCycleSchemaConfig {
6845    fn default() -> Self {
6846        Self {
6847            enabled: false,
6848            period_months: 48,
6849            amplitude: 0.15,
6850            phase_offset: 0,
6851            recessions: Vec::new(),
6852        }
6853    }
6854}
6855
6856/// Recession period configuration.
6857#[derive(Debug, Clone, Serialize, Deserialize)]
6858pub struct RecessionPeriodConfig {
6859    /// Start month (0-indexed from generation start).
6860    pub start_month: u32,
6861
6862    /// Duration in months.
6863    pub duration_months: u32,
6864
6865    /// Severity (0.0-1.0, affects volume reduction).
6866    #[serde(default = "default_recession_severity")]
6867    pub severity: f64,
6868}
6869
6870impl RegimeChangeSchemaConfig {
6871    /// Populate the regime-change, economic-cycle, and parameter-drift
6872    /// slots on a `DriftConfig` from this schema config. v3.5.2+.
6873    ///
6874    /// `generation_start` must match `config.global.start_date` so that
6875    /// absolute regime-change dates can be mapped to 0-indexed periods.
6876    /// Unparseable / out-of-range dates are silently skipped to keep
6877    /// runtime robust against user typos.
6878    pub fn apply_to(
6879        &self,
6880        drift: &mut datasynth_core::distributions::DriftConfig,
6881        generation_start: chrono::NaiveDate,
6882    ) {
6883        if !self.enabled {
6884            return;
6885        }
6886
6887        // Enable drift if any regime-change feature wants it.
6888        drift.enabled = true;
6889
6890        // Regime-change events (absolute dates → period offsets).
6891        for event in &self.changes {
6892            let period = match chrono::NaiveDate::parse_from_str(&event.date, "%Y-%m-%d") {
6893                Ok(d) => {
6894                    let days = (d - generation_start).num_days();
6895                    if days < 0 {
6896                        continue;
6897                    }
6898                    // Approximate month by dividing by 30.4 so we don't
6899                    // need chrono::Months arithmetic.
6900                    (days as f64 / 30.4).round() as u32
6901                }
6902                Err(_) => continue,
6903            };
6904            let change_type = convert_regime_change_type(event.change_type);
6905            let core_effects = event
6906                .effects
6907                .iter()
6908                .map(|e| datasynth_core::distributions::RegimeEffect {
6909                    field: e.field.clone(),
6910                    multiplier: e.multiplier,
6911                })
6912                .collect();
6913            drift
6914                .regime_changes
6915                .push(datasynth_core::distributions::RegimeChange {
6916                    period,
6917                    change_type,
6918                    description: event.description.clone(),
6919                    effects: core_effects,
6920                    transition_periods: 0,
6921                });
6922        }
6923
6924        // Economic cycle.
6925        if let Some(ec) = &self.economic_cycle {
6926            if ec.enabled {
6927                let recession_periods: Vec<u32> = ec
6928                    .recessions
6929                    .iter()
6930                    .flat_map(|r| r.start_month..r.start_month + r.duration_months)
6931                    .collect();
6932                // Use the most-severe recession as the severity driver;
6933                // fall back to default when none declared.
6934                let severity = ec
6935                    .recessions
6936                    .iter()
6937                    .map(|r| 1.0 - r.severity)
6938                    .fold(0.75f64, f64::min);
6939                drift.economic_cycle = datasynth_core::distributions::EconomicCycleConfig {
6940                    enabled: true,
6941                    cycle_length: ec.period_months,
6942                    amplitude: ec.amplitude,
6943                    phase_offset: ec.phase_offset,
6944                    recession_periods,
6945                    recession_severity: severity,
6946                };
6947                drift.drift_type = datasynth_core::distributions::DriftType::Mixed;
6948            }
6949        }
6950
6951        // Parameter drifts.
6952        for pd in &self.parameter_drifts {
6953            let drift_type = match pd.drift_type {
6954                ParameterDriftTypeConfig::Linear => {
6955                    datasynth_core::distributions::ParameterDriftType::Linear
6956                }
6957                ParameterDriftTypeConfig::Exponential => {
6958                    datasynth_core::distributions::ParameterDriftType::Exponential
6959                }
6960                ParameterDriftTypeConfig::Logistic => {
6961                    datasynth_core::distributions::ParameterDriftType::Logistic
6962                }
6963                ParameterDriftTypeConfig::Step => {
6964                    datasynth_core::distributions::ParameterDriftType::Step
6965                }
6966            };
6967            drift
6968                .parameter_drifts
6969                .push(datasynth_core::distributions::ParameterDrift {
6970                    parameter: pd.parameter.clone(),
6971                    drift_type,
6972                    initial_value: pd.start_value,
6973                    target_or_rate: pd.end_value,
6974                    start_period: pd.start_period,
6975                    end_period: pd.end_period,
6976                    steepness: 1.0,
6977                });
6978        }
6979    }
6980}
6981
6982fn convert_regime_change_type(
6983    t: RegimeChangeTypeConfig,
6984) -> datasynth_core::distributions::RegimeChangeType {
6985    use datasynth_core::distributions::RegimeChangeType as Core;
6986    match t {
6987        RegimeChangeTypeConfig::Acquisition => Core::Acquisition,
6988        RegimeChangeTypeConfig::Divestiture => Core::Divestiture,
6989        RegimeChangeTypeConfig::PriceIncrease => Core::PriceIncrease,
6990        RegimeChangeTypeConfig::PriceDecrease => Core::PriceDecrease,
6991        RegimeChangeTypeConfig::ProductLaunch => Core::ProductLaunch,
6992        RegimeChangeTypeConfig::ProductDiscontinuation => Core::ProductDiscontinuation,
6993        RegimeChangeTypeConfig::PolicyChange => Core::PolicyChange,
6994        RegimeChangeTypeConfig::CompetitorEntry => Core::CompetitorEntry,
6995        RegimeChangeTypeConfig::Custom => Core::Custom,
6996    }
6997}
6998
6999fn default_recession_severity() -> f64 {
7000    0.20
7001}
7002
7003/// Parameter drift configuration.
7004#[derive(Debug, Clone, Serialize, Deserialize)]
7005pub struct ParameterDriftSchemaConfig {
7006    /// Parameter being drifted.
7007    pub parameter: String,
7008
7009    /// Drift type.
7010    pub drift_type: ParameterDriftTypeConfig,
7011
7012    /// Start value.
7013    pub start_value: f64,
7014
7015    /// End value.
7016    pub end_value: f64,
7017
7018    /// Start period (month, 0-indexed).
7019    #[serde(default)]
7020    pub start_period: u32,
7021
7022    /// End period (month, optional - defaults to end of generation).
7023    #[serde(default)]
7024    pub end_period: Option<u32>,
7025}
7026
7027/// Parameter drift type.
7028#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
7029#[serde(rename_all = "snake_case")]
7030pub enum ParameterDriftTypeConfig {
7031    /// Linear interpolation
7032    #[default]
7033    Linear,
7034    /// Exponential growth/decay
7035    Exponential,
7036    /// S-curve (logistic)
7037    Logistic,
7038    /// Step function
7039    Step,
7040}
7041
7042/// Statistical validation configuration.
7043#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7044pub struct StatisticalValidationSchemaConfig {
7045    /// Enable statistical validation.
7046    #[serde(default)]
7047    pub enabled: bool,
7048
7049    /// Statistical tests to run.
7050    #[serde(default)]
7051    pub tests: Vec<StatisticalTestConfig>,
7052
7053    /// Validation reporting configuration.
7054    #[serde(default)]
7055    pub reporting: ValidationReportingConfig,
7056}
7057
7058/// Statistical test configuration.
7059#[derive(Debug, Clone, Serialize, Deserialize)]
7060#[serde(tag = "type", rename_all = "snake_case")]
7061pub enum StatisticalTestConfig {
7062    /// Benford's Law first digit test.
7063    BenfordFirstDigit {
7064        /// Threshold MAD for failure.
7065        #[serde(default = "default_benford_threshold")]
7066        threshold_mad: f64,
7067        /// Warning MAD threshold.
7068        #[serde(default = "default_benford_warning")]
7069        warning_mad: f64,
7070    },
7071    /// Distribution fit test.
7072    DistributionFit {
7073        /// Target distribution to test.
7074        target: TargetDistributionConfig,
7075        /// K-S test significance level.
7076        #[serde(default = "default_ks_significance")]
7077        ks_significance: f64,
7078        /// Test method (ks, anderson_darling, chi_squared).
7079        #[serde(default)]
7080        method: DistributionFitMethod,
7081    },
7082    /// Correlation check.
7083    CorrelationCheck {
7084        /// Expected correlations to validate.
7085        expected_correlations: Vec<ExpectedCorrelationConfig>,
7086    },
7087    /// Chi-squared test.
7088    ChiSquared {
7089        /// Number of bins.
7090        #[serde(default = "default_chi_squared_bins")]
7091        bins: usize,
7092        /// Significance level.
7093        #[serde(default = "default_chi_squared_significance")]
7094        significance: f64,
7095    },
7096    /// Anderson-Darling test.
7097    AndersonDarling {
7098        /// Target distribution.
7099        target: TargetDistributionConfig,
7100        /// Significance level.
7101        #[serde(default = "default_ad_significance")]
7102        significance: f64,
7103    },
7104}
7105
7106fn default_benford_threshold() -> f64 {
7107    0.015
7108}
7109
7110fn default_benford_warning() -> f64 {
7111    0.010
7112}
7113
7114fn default_ks_significance() -> f64 {
7115    0.05
7116}
7117
7118fn default_chi_squared_bins() -> usize {
7119    10
7120}
7121
7122fn default_chi_squared_significance() -> f64 {
7123    0.05
7124}
7125
7126fn default_ad_significance() -> f64 {
7127    0.05
7128}
7129
7130/// Target distribution for fit tests.
7131#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
7132#[serde(rename_all = "snake_case")]
7133pub enum TargetDistributionConfig {
7134    /// Normal distribution
7135    Normal,
7136    /// Log-normal distribution
7137    #[default]
7138    LogNormal,
7139    /// Exponential distribution
7140    Exponential,
7141    /// Uniform distribution
7142    Uniform,
7143}
7144
7145/// Distribution fit test method.
7146#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
7147#[serde(rename_all = "snake_case")]
7148pub enum DistributionFitMethod {
7149    /// Kolmogorov-Smirnov test
7150    #[default]
7151    KolmogorovSmirnov,
7152    /// Anderson-Darling test
7153    AndersonDarling,
7154    /// Chi-squared test
7155    ChiSquared,
7156}
7157
7158/// Validation reporting configuration.
7159#[derive(Debug, Clone, Serialize, Deserialize)]
7160pub struct ValidationReportingConfig {
7161    /// Output validation report to file.
7162    #[serde(default)]
7163    pub output_report: bool,
7164
7165    /// Report format.
7166    #[serde(default)]
7167    pub format: ValidationReportFormat,
7168
7169    /// Fail generation if validation fails.
7170    #[serde(default)]
7171    pub fail_on_error: bool,
7172
7173    /// Include detailed statistics in report.
7174    #[serde(default = "default_true")]
7175    pub include_details: bool,
7176}
7177
7178impl Default for ValidationReportingConfig {
7179    fn default() -> Self {
7180        Self {
7181            output_report: false,
7182            format: ValidationReportFormat::Json,
7183            fail_on_error: false,
7184            include_details: true,
7185        }
7186    }
7187}
7188
7189/// Validation report format.
7190#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
7191#[serde(rename_all = "snake_case")]
7192pub enum ValidationReportFormat {
7193    /// JSON format
7194    #[default]
7195    Json,
7196    /// YAML format
7197    Yaml,
7198    /// HTML report
7199    Html,
7200}
7201
7202// =============================================================================
7203// Temporal Patterns Configuration
7204// =============================================================================
7205
7206/// Temporal patterns configuration for business days, period-end dynamics, and processing lags.
7207///
7208/// This section enables sophisticated temporal modeling including:
7209/// - Business day calculations and settlement dates
7210/// - Regional holiday calendars
7211/// - Period-end decay curves (non-flat volume spikes)
7212/// - Processing lag modeling (event-to-posting delays)
7213#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7214pub struct TemporalPatternsConfig {
7215    /// Enable temporal patterns features.
7216    #[serde(default)]
7217    pub enabled: bool,
7218
7219    /// Business day calculation configuration.
7220    #[serde(default)]
7221    pub business_days: BusinessDaySchemaConfig,
7222
7223    /// Regional calendar configuration.
7224    #[serde(default)]
7225    pub calendars: CalendarSchemaConfig,
7226
7227    /// Period-end dynamics configuration.
7228    #[serde(default)]
7229    pub period_end: PeriodEndSchemaConfig,
7230
7231    /// Processing lag configuration.
7232    #[serde(default)]
7233    pub processing_lags: ProcessingLagSchemaConfig,
7234
7235    /// Fiscal calendar configuration (custom year start, 4-4-5, 13-period).
7236    #[serde(default)]
7237    pub fiscal_calendar: FiscalCalendarSchemaConfig,
7238
7239    /// Intra-day patterns configuration (morning spike, lunch dip, EOD rush).
7240    #[serde(default)]
7241    pub intraday: IntraDaySchemaConfig,
7242
7243    /// Timezone handling configuration.
7244    #[serde(default)]
7245    pub timezones: TimezoneSchemaConfig,
7246}
7247
7248/// Business day calculation configuration.
7249#[derive(Debug, Clone, Serialize, Deserialize)]
7250pub struct BusinessDaySchemaConfig {
7251    /// Enable business day calculations.
7252    #[serde(default = "default_true")]
7253    pub enabled: bool,
7254
7255    /// Half-day policy: "full_day", "half_day", "non_business_day".
7256    #[serde(default = "default_half_day_policy")]
7257    pub half_day_policy: String,
7258
7259    /// Settlement rules configuration.
7260    #[serde(default)]
7261    pub settlement_rules: SettlementRulesSchemaConfig,
7262
7263    /// Month-end convention: "modified_following", "preceding", "following", "end_of_month".
7264    #[serde(default = "default_month_end_convention")]
7265    pub month_end_convention: String,
7266
7267    /// Weekend days (e.g., ["saturday", "sunday"] or ["friday", "saturday"] for Middle East).
7268    #[serde(default)]
7269    pub weekend_days: Option<Vec<String>>,
7270}
7271
7272fn default_half_day_policy() -> String {
7273    "half_day".to_string()
7274}
7275
7276fn default_month_end_convention() -> String {
7277    "modified_following".to_string()
7278}
7279
7280impl Default for BusinessDaySchemaConfig {
7281    fn default() -> Self {
7282        Self {
7283            enabled: true,
7284            half_day_policy: "half_day".to_string(),
7285            settlement_rules: SettlementRulesSchemaConfig::default(),
7286            month_end_convention: "modified_following".to_string(),
7287            weekend_days: None,
7288        }
7289    }
7290}
7291
7292/// Settlement rules configuration.
7293#[derive(Debug, Clone, Serialize, Deserialize)]
7294pub struct SettlementRulesSchemaConfig {
7295    /// Equity settlement days (T+N).
7296    #[serde(default = "default_settlement_2")]
7297    pub equity_days: i32,
7298
7299    /// Government bonds settlement days.
7300    #[serde(default = "default_settlement_1")]
7301    pub government_bonds_days: i32,
7302
7303    /// FX spot settlement days.
7304    #[serde(default = "default_settlement_2")]
7305    pub fx_spot_days: i32,
7306
7307    /// Corporate bonds settlement days.
7308    #[serde(default = "default_settlement_2")]
7309    pub corporate_bonds_days: i32,
7310
7311    /// Wire transfer cutoff time (HH:MM format).
7312    #[serde(default = "default_wire_cutoff")]
7313    pub wire_cutoff_time: String,
7314
7315    /// International wire settlement days.
7316    #[serde(default = "default_settlement_1")]
7317    pub wire_international_days: i32,
7318
7319    /// ACH settlement days.
7320    #[serde(default = "default_settlement_1")]
7321    pub ach_days: i32,
7322}
7323
7324fn default_settlement_1() -> i32 {
7325    1
7326}
7327
7328fn default_settlement_2() -> i32 {
7329    2
7330}
7331
7332fn default_wire_cutoff() -> String {
7333    "14:00".to_string()
7334}
7335
7336impl Default for SettlementRulesSchemaConfig {
7337    fn default() -> Self {
7338        Self {
7339            equity_days: 2,
7340            government_bonds_days: 1,
7341            fx_spot_days: 2,
7342            corporate_bonds_days: 2,
7343            wire_cutoff_time: "14:00".to_string(),
7344            wire_international_days: 1,
7345            ach_days: 1,
7346        }
7347    }
7348}
7349
7350/// Regional calendar configuration.
7351#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7352pub struct CalendarSchemaConfig {
7353    /// List of regions to include (e.g., ["US", "DE", "BR", "SG", "KR"]).
7354    #[serde(default)]
7355    pub regions: Vec<String>,
7356
7357    /// Custom holidays (in addition to regional calendars).
7358    #[serde(default)]
7359    pub custom_holidays: Vec<CustomHolidaySchemaConfig>,
7360}
7361
7362/// Custom holiday configuration.
7363#[derive(Debug, Clone, Serialize, Deserialize)]
7364pub struct CustomHolidaySchemaConfig {
7365    /// Holiday name.
7366    pub name: String,
7367    /// Month (1-12).
7368    pub month: u8,
7369    /// Day of month.
7370    pub day: u8,
7371    /// Activity multiplier (0.0-1.0, default 0.05).
7372    #[serde(default = "default_holiday_multiplier")]
7373    pub activity_multiplier: f64,
7374}
7375
7376fn default_holiday_multiplier() -> f64 {
7377    0.05
7378}
7379
7380/// Period-end dynamics configuration.
7381#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7382pub struct PeriodEndSchemaConfig {
7383    /// Model type: "flat", "exponential", "extended_crunch", "daily_profile".
7384    #[serde(default)]
7385    pub model: Option<String>,
7386
7387    /// Month-end configuration.
7388    #[serde(default)]
7389    pub month_end: Option<PeriodEndModelSchemaConfig>,
7390
7391    /// Quarter-end configuration.
7392    #[serde(default)]
7393    pub quarter_end: Option<PeriodEndModelSchemaConfig>,
7394
7395    /// Year-end configuration.
7396    #[serde(default)]
7397    pub year_end: Option<PeriodEndModelSchemaConfig>,
7398}
7399
7400/// Period-end model configuration.
7401#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7402pub struct PeriodEndModelSchemaConfig {
7403    /// Inherit configuration from another period (e.g., "month_end").
7404    #[serde(default)]
7405    pub inherit_from: Option<String>,
7406
7407    /// Additional multiplier on top of inherited/base model.
7408    #[serde(default)]
7409    pub additional_multiplier: Option<f64>,
7410
7411    /// Days before period end to start acceleration (negative, e.g., -10).
7412    #[serde(default)]
7413    pub start_day: Option<i32>,
7414
7415    /// Base multiplier at start of acceleration.
7416    #[serde(default)]
7417    pub base_multiplier: Option<f64>,
7418
7419    /// Peak multiplier on last day.
7420    #[serde(default)]
7421    pub peak_multiplier: Option<f64>,
7422
7423    /// Decay rate for exponential model (0.1-0.5 typical).
7424    #[serde(default)]
7425    pub decay_rate: Option<f64>,
7426
7427    /// Sustained high days for crunch model.
7428    #[serde(default)]
7429    pub sustained_high_days: Option<i32>,
7430}
7431
7432/// Processing lag configuration.
7433#[derive(Debug, Clone, Serialize, Deserialize)]
7434pub struct ProcessingLagSchemaConfig {
7435    /// Enable processing lag calculations.
7436    #[serde(default = "default_true")]
7437    pub enabled: bool,
7438
7439    /// Sales order lag configuration (log-normal mu, sigma).
7440    #[serde(default)]
7441    pub sales_order_lag: Option<LagDistributionSchemaConfig>,
7442
7443    /// Purchase order lag configuration.
7444    #[serde(default)]
7445    pub purchase_order_lag: Option<LagDistributionSchemaConfig>,
7446
7447    /// Goods receipt lag configuration.
7448    #[serde(default)]
7449    pub goods_receipt_lag: Option<LagDistributionSchemaConfig>,
7450
7451    /// Invoice receipt lag configuration.
7452    #[serde(default)]
7453    pub invoice_receipt_lag: Option<LagDistributionSchemaConfig>,
7454
7455    /// Invoice issue lag configuration.
7456    #[serde(default)]
7457    pub invoice_issue_lag: Option<LagDistributionSchemaConfig>,
7458
7459    /// Payment lag configuration.
7460    #[serde(default)]
7461    pub payment_lag: Option<LagDistributionSchemaConfig>,
7462
7463    /// Journal entry lag configuration.
7464    #[serde(default)]
7465    pub journal_entry_lag: Option<LagDistributionSchemaConfig>,
7466
7467    /// Cross-day posting configuration.
7468    #[serde(default)]
7469    pub cross_day_posting: Option<CrossDayPostingSchemaConfig>,
7470}
7471
7472impl Default for ProcessingLagSchemaConfig {
7473    fn default() -> Self {
7474        Self {
7475            enabled: true,
7476            sales_order_lag: None,
7477            purchase_order_lag: None,
7478            goods_receipt_lag: None,
7479            invoice_receipt_lag: None,
7480            invoice_issue_lag: None,
7481            payment_lag: None,
7482            journal_entry_lag: None,
7483            cross_day_posting: None,
7484        }
7485    }
7486}
7487
7488/// Lag distribution configuration (log-normal parameters).
7489#[derive(Debug, Clone, Serialize, Deserialize)]
7490pub struct LagDistributionSchemaConfig {
7491    /// Log-scale mean (mu for log-normal).
7492    pub mu: f64,
7493    /// Log-scale standard deviation (sigma for log-normal).
7494    pub sigma: f64,
7495    /// Minimum lag in hours.
7496    #[serde(default)]
7497    pub min_hours: Option<f64>,
7498    /// Maximum lag in hours.
7499    #[serde(default)]
7500    pub max_hours: Option<f64>,
7501}
7502
7503/// Cross-day posting configuration.
7504#[derive(Debug, Clone, Serialize, Deserialize)]
7505pub struct CrossDayPostingSchemaConfig {
7506    /// Enable cross-day posting logic.
7507    #[serde(default = "default_true")]
7508    pub enabled: bool,
7509
7510    /// Probability of next-day posting by hour (map of hour -> probability).
7511    /// E.g., { 17: 0.7, 19: 0.9, 21: 0.99 }
7512    #[serde(default)]
7513    pub probability_by_hour: std::collections::HashMap<u8, f64>,
7514}
7515
7516impl Default for CrossDayPostingSchemaConfig {
7517    fn default() -> Self {
7518        let mut probability_by_hour = std::collections::HashMap::new();
7519        probability_by_hour.insert(17, 0.3);
7520        probability_by_hour.insert(18, 0.6);
7521        probability_by_hour.insert(19, 0.8);
7522        probability_by_hour.insert(20, 0.9);
7523        probability_by_hour.insert(21, 0.95);
7524        probability_by_hour.insert(22, 0.99);
7525
7526        Self {
7527            enabled: true,
7528            probability_by_hour,
7529        }
7530    }
7531}
7532
7533// =============================================================================
7534// Fiscal Calendar Configuration (P2)
7535// =============================================================================
7536
7537/// Fiscal calendar configuration.
7538///
7539/// Supports calendar year, custom year start, 4-4-5 retail calendar,
7540/// and 13-period calendars.
7541#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7542pub struct FiscalCalendarSchemaConfig {
7543    /// Enable non-standard fiscal calendar.
7544    #[serde(default)]
7545    pub enabled: bool,
7546
7547    /// Fiscal calendar type: "calendar_year", "custom", "four_four_five", "thirteen_period".
7548    #[serde(default = "default_fiscal_calendar_type")]
7549    pub calendar_type: String,
7550
7551    /// Month the fiscal year starts (1-12). Used for custom year start.
7552    #[serde(default)]
7553    pub year_start_month: Option<u8>,
7554
7555    /// Day the fiscal year starts (1-31). Used for custom year start.
7556    #[serde(default)]
7557    pub year_start_day: Option<u8>,
7558
7559    /// 4-4-5 calendar configuration (if calendar_type is "four_four_five").
7560    #[serde(default)]
7561    pub four_four_five: Option<FourFourFiveSchemaConfig>,
7562}
7563
7564fn default_fiscal_calendar_type() -> String {
7565    "calendar_year".to_string()
7566}
7567
7568/// 4-4-5 retail calendar configuration.
7569#[derive(Debug, Clone, Serialize, Deserialize)]
7570pub struct FourFourFiveSchemaConfig {
7571    /// Week pattern: "four_four_five", "four_five_four", "five_four_four".
7572    #[serde(default = "default_week_pattern")]
7573    pub pattern: String,
7574
7575    /// Anchor type: "first_sunday", "last_saturday", "nearest_saturday".
7576    #[serde(default = "default_anchor_type")]
7577    pub anchor_type: String,
7578
7579    /// Anchor month (1-12).
7580    #[serde(default = "default_anchor_month")]
7581    pub anchor_month: u8,
7582
7583    /// Where to place leap week: "q4_period3" or "q1_period1".
7584    #[serde(default = "default_leap_week_placement")]
7585    pub leap_week_placement: String,
7586}
7587
7588fn default_week_pattern() -> String {
7589    "four_four_five".to_string()
7590}
7591
7592fn default_anchor_type() -> String {
7593    "last_saturday".to_string()
7594}
7595
7596fn default_anchor_month() -> u8 {
7597    1 // January
7598}
7599
7600fn default_leap_week_placement() -> String {
7601    "q4_period3".to_string()
7602}
7603
7604impl Default for FourFourFiveSchemaConfig {
7605    fn default() -> Self {
7606        Self {
7607            pattern: "four_four_five".to_string(),
7608            anchor_type: "last_saturday".to_string(),
7609            anchor_month: 1,
7610            leap_week_placement: "q4_period3".to_string(),
7611        }
7612    }
7613}
7614
7615// =============================================================================
7616// Intra-Day Patterns Configuration (P2)
7617// =============================================================================
7618
7619/// Intra-day patterns configuration.
7620///
7621/// Defines time-of-day segments with different activity multipliers
7622/// for realistic modeling of morning spikes, lunch dips, and end-of-day rushes.
7623#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7624pub struct IntraDaySchemaConfig {
7625    /// Enable intra-day patterns.
7626    #[serde(default)]
7627    pub enabled: bool,
7628
7629    /// Custom intra-day segments.
7630    #[serde(default)]
7631    pub segments: Vec<IntraDaySegmentSchemaConfig>,
7632}
7633
7634/// Intra-day segment configuration.
7635#[derive(Debug, Clone, Serialize, Deserialize)]
7636pub struct IntraDaySegmentSchemaConfig {
7637    /// Name of the segment (e.g., "morning_spike", "lunch_dip").
7638    pub name: String,
7639
7640    /// Start time (HH:MM format).
7641    pub start: String,
7642
7643    /// End time (HH:MM format).
7644    pub end: String,
7645
7646    /// Activity multiplier (1.0 = normal).
7647    #[serde(default = "default_multiplier")]
7648    pub multiplier: f64,
7649
7650    /// Posting type: "human", "system", "both".
7651    #[serde(default = "default_posting_type")]
7652    pub posting_type: String,
7653}
7654
7655fn default_multiplier() -> f64 {
7656    1.0
7657}
7658
7659fn default_posting_type() -> String {
7660    "both".to_string()
7661}
7662
7663// =============================================================================
7664// Timezone Configuration
7665// =============================================================================
7666
7667/// Timezone handling configuration for multi-region entities.
7668#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7669pub struct TimezoneSchemaConfig {
7670    /// Enable timezone handling.
7671    #[serde(default)]
7672    pub enabled: bool,
7673
7674    /// Default timezone (IANA format, e.g., "America/New_York").
7675    #[serde(default = "default_timezone")]
7676    pub default_timezone: String,
7677
7678    /// Consolidation timezone for group reporting (IANA format).
7679    #[serde(default = "default_consolidation_timezone")]
7680    pub consolidation_timezone: String,
7681
7682    /// Entity-to-timezone mappings.
7683    /// Supports patterns like "EU_*" -> "Europe/London".
7684    #[serde(default)]
7685    pub entity_mappings: Vec<EntityTimezoneMapping>,
7686}
7687
7688fn default_timezone() -> String {
7689    "America/New_York".to_string()
7690}
7691
7692fn default_consolidation_timezone() -> String {
7693    "UTC".to_string()
7694}
7695
7696/// Mapping from entity pattern to timezone.
7697#[derive(Debug, Clone, Serialize, Deserialize)]
7698pub struct EntityTimezoneMapping {
7699    /// Entity code pattern (e.g., "EU_*", "*_APAC", "1000").
7700    pub pattern: String,
7701
7702    /// Timezone (IANA format, e.g., "Europe/London").
7703    pub timezone: String,
7704}
7705
7706// =============================================================================
7707// Vendor Network Configuration
7708// =============================================================================
7709
7710/// Configuration for multi-tier vendor network generation.
7711#[derive(Debug, Clone, Serialize, Deserialize)]
7712pub struct VendorNetworkSchemaConfig {
7713    /// Enable vendor network generation.
7714    #[serde(default)]
7715    pub enabled: bool,
7716
7717    /// Maximum depth of supply chain tiers (1-3).
7718    #[serde(default = "default_vendor_tier_depth")]
7719    pub depth: u8,
7720
7721    /// Tier 1 vendor count configuration.
7722    #[serde(default)]
7723    pub tier1: TierCountSchemaConfig,
7724
7725    /// Tier 2 vendors per Tier 1 parent.
7726    #[serde(default)]
7727    pub tier2_per_parent: TierCountSchemaConfig,
7728
7729    /// Tier 3 vendors per Tier 2 parent.
7730    #[serde(default)]
7731    pub tier3_per_parent: TierCountSchemaConfig,
7732
7733    /// Vendor cluster distribution.
7734    #[serde(default)]
7735    pub clusters: VendorClusterSchemaConfig,
7736
7737    /// Concentration limits.
7738    #[serde(default)]
7739    pub dependencies: DependencySchemaConfig,
7740}
7741
7742fn default_vendor_tier_depth() -> u8 {
7743    3
7744}
7745
7746impl Default for VendorNetworkSchemaConfig {
7747    fn default() -> Self {
7748        Self {
7749            enabled: false,
7750            depth: 3,
7751            tier1: TierCountSchemaConfig { min: 50, max: 100 },
7752            tier2_per_parent: TierCountSchemaConfig { min: 4, max: 10 },
7753            tier3_per_parent: TierCountSchemaConfig { min: 2, max: 5 },
7754            clusters: VendorClusterSchemaConfig::default(),
7755            dependencies: DependencySchemaConfig::default(),
7756        }
7757    }
7758}
7759
7760/// Tier count configuration.
7761#[derive(Debug, Clone, Serialize, Deserialize)]
7762pub struct TierCountSchemaConfig {
7763    /// Minimum count.
7764    #[serde(default = "default_tier_min")]
7765    pub min: usize,
7766
7767    /// Maximum count.
7768    #[serde(default = "default_tier_max")]
7769    pub max: usize,
7770}
7771
7772fn default_tier_min() -> usize {
7773    5
7774}
7775
7776fn default_tier_max() -> usize {
7777    20
7778}
7779
7780impl Default for TierCountSchemaConfig {
7781    fn default() -> Self {
7782        Self {
7783            min: default_tier_min(),
7784            max: default_tier_max(),
7785        }
7786    }
7787}
7788
7789/// Vendor cluster distribution configuration.
7790#[derive(Debug, Clone, Serialize, Deserialize)]
7791pub struct VendorClusterSchemaConfig {
7792    /// Reliable strategic vendors percentage (default: 0.20).
7793    #[serde(default = "default_reliable_strategic")]
7794    pub reliable_strategic: f64,
7795
7796    /// Standard operational vendors percentage (default: 0.50).
7797    #[serde(default = "default_standard_operational")]
7798    pub standard_operational: f64,
7799
7800    /// Transactional vendors percentage (default: 0.25).
7801    #[serde(default = "default_transactional")]
7802    pub transactional: f64,
7803
7804    /// Problematic vendors percentage (default: 0.05).
7805    #[serde(default = "default_problematic")]
7806    pub problematic: f64,
7807}
7808
7809fn default_reliable_strategic() -> f64 {
7810    0.20
7811}
7812
7813fn default_standard_operational() -> f64 {
7814    0.50
7815}
7816
7817fn default_transactional() -> f64 {
7818    0.25
7819}
7820
7821fn default_problematic() -> f64 {
7822    0.05
7823}
7824
7825impl Default for VendorClusterSchemaConfig {
7826    fn default() -> Self {
7827        Self {
7828            reliable_strategic: 0.20,
7829            standard_operational: 0.50,
7830            transactional: 0.25,
7831            problematic: 0.05,
7832        }
7833    }
7834}
7835
7836/// Dependency and concentration limits configuration.
7837#[derive(Debug, Clone, Serialize, Deserialize)]
7838pub struct DependencySchemaConfig {
7839    /// Maximum concentration for a single vendor (default: 0.15).
7840    #[serde(default = "default_max_single_vendor")]
7841    pub max_single_vendor_concentration: f64,
7842
7843    /// Maximum concentration for top 5 vendors (default: 0.45).
7844    #[serde(default = "default_max_top5")]
7845    pub top_5_concentration: f64,
7846
7847    /// Percentage of single-source vendors (default: 0.05).
7848    #[serde(default = "default_single_source_percent")]
7849    pub single_source_percent: f64,
7850}
7851
7852fn default_max_single_vendor() -> f64 {
7853    0.15
7854}
7855
7856fn default_max_top5() -> f64 {
7857    0.45
7858}
7859
7860fn default_single_source_percent() -> f64 {
7861    0.05
7862}
7863
7864impl Default for DependencySchemaConfig {
7865    fn default() -> Self {
7866        Self {
7867            max_single_vendor_concentration: 0.15,
7868            top_5_concentration: 0.45,
7869            single_source_percent: 0.05,
7870        }
7871    }
7872}
7873
7874// =============================================================================
7875// Customer Segmentation Configuration
7876// =============================================================================
7877
7878/// Configuration for customer segmentation generation.
7879#[derive(Debug, Clone, Default, Serialize, Deserialize)]
7880pub struct CustomerSegmentationSchemaConfig {
7881    /// Enable customer segmentation generation.
7882    #[serde(default)]
7883    pub enabled: bool,
7884
7885    /// Value segment distribution.
7886    #[serde(default)]
7887    pub value_segments: ValueSegmentsSchemaConfig,
7888
7889    /// Lifecycle stage configuration.
7890    #[serde(default)]
7891    pub lifecycle: LifecycleSchemaConfig,
7892
7893    /// Network (referrals, hierarchies) configuration.
7894    #[serde(default)]
7895    pub networks: CustomerNetworksSchemaConfig,
7896}
7897
7898/// Customer value segments distribution configuration.
7899#[derive(Debug, Clone, Serialize, Deserialize)]
7900pub struct ValueSegmentsSchemaConfig {
7901    /// Enterprise segment configuration.
7902    #[serde(default)]
7903    pub enterprise: SegmentDetailSchemaConfig,
7904
7905    /// Mid-market segment configuration.
7906    #[serde(default)]
7907    pub mid_market: SegmentDetailSchemaConfig,
7908
7909    /// SMB segment configuration.
7910    #[serde(default)]
7911    pub smb: SegmentDetailSchemaConfig,
7912
7913    /// Consumer segment configuration.
7914    #[serde(default)]
7915    pub consumer: SegmentDetailSchemaConfig,
7916}
7917
7918impl Default for ValueSegmentsSchemaConfig {
7919    fn default() -> Self {
7920        Self {
7921            enterprise: SegmentDetailSchemaConfig {
7922                revenue_share: 0.40,
7923                customer_share: 0.05,
7924                avg_order_value_range: "50000+".to_string(),
7925            },
7926            mid_market: SegmentDetailSchemaConfig {
7927                revenue_share: 0.35,
7928                customer_share: 0.20,
7929                avg_order_value_range: "5000-50000".to_string(),
7930            },
7931            smb: SegmentDetailSchemaConfig {
7932                revenue_share: 0.20,
7933                customer_share: 0.50,
7934                avg_order_value_range: "500-5000".to_string(),
7935            },
7936            consumer: SegmentDetailSchemaConfig {
7937                revenue_share: 0.05,
7938                customer_share: 0.25,
7939                avg_order_value_range: "50-500".to_string(),
7940            },
7941        }
7942    }
7943}
7944
7945/// Individual segment detail configuration.
7946#[derive(Debug, Clone, Serialize, Deserialize)]
7947pub struct SegmentDetailSchemaConfig {
7948    /// Revenue share for this segment.
7949    #[serde(default)]
7950    pub revenue_share: f64,
7951
7952    /// Customer share for this segment.
7953    #[serde(default)]
7954    pub customer_share: f64,
7955
7956    /// Average order value range (e.g., "5000-50000" or "50000+").
7957    #[serde(default)]
7958    pub avg_order_value_range: String,
7959}
7960
7961impl Default for SegmentDetailSchemaConfig {
7962    fn default() -> Self {
7963        Self {
7964            revenue_share: 0.25,
7965            customer_share: 0.25,
7966            avg_order_value_range: "1000-10000".to_string(),
7967        }
7968    }
7969}
7970
7971/// Customer lifecycle stage configuration.
7972#[derive(Debug, Clone, Serialize, Deserialize)]
7973pub struct LifecycleSchemaConfig {
7974    /// Prospect stage rate.
7975    #[serde(default)]
7976    pub prospect_rate: f64,
7977
7978    /// New customer stage rate.
7979    #[serde(default = "default_new_rate")]
7980    pub new_rate: f64,
7981
7982    /// Growth stage rate.
7983    #[serde(default = "default_growth_rate")]
7984    pub growth_rate: f64,
7985
7986    /// Mature stage rate.
7987    #[serde(default = "default_mature_rate")]
7988    pub mature_rate: f64,
7989
7990    /// At-risk stage rate.
7991    #[serde(default = "default_at_risk_rate")]
7992    pub at_risk_rate: f64,
7993
7994    /// Churned stage rate.
7995    #[serde(default = "default_churned_rate")]
7996    pub churned_rate: f64,
7997
7998    /// Won-back stage rate (churned customers reacquired).
7999    #[serde(default)]
8000    pub won_back_rate: f64,
8001}
8002
8003fn default_new_rate() -> f64 {
8004    0.10
8005}
8006
8007fn default_growth_rate() -> f64 {
8008    0.15
8009}
8010
8011fn default_mature_rate() -> f64 {
8012    0.60
8013}
8014
8015fn default_at_risk_rate() -> f64 {
8016    0.10
8017}
8018
8019fn default_churned_rate() -> f64 {
8020    0.05
8021}
8022
8023impl Default for LifecycleSchemaConfig {
8024    fn default() -> Self {
8025        Self {
8026            prospect_rate: 0.0,
8027            new_rate: 0.10,
8028            growth_rate: 0.15,
8029            mature_rate: 0.60,
8030            at_risk_rate: 0.10,
8031            churned_rate: 0.05,
8032            won_back_rate: 0.0,
8033        }
8034    }
8035}
8036
8037/// Customer networks configuration (referrals, hierarchies).
8038#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8039pub struct CustomerNetworksSchemaConfig {
8040    /// Referral network configuration.
8041    #[serde(default)]
8042    pub referrals: ReferralSchemaConfig,
8043
8044    /// Corporate hierarchy configuration.
8045    #[serde(default)]
8046    pub corporate_hierarchies: HierarchySchemaConfig,
8047}
8048
8049/// Referral network configuration.
8050#[derive(Debug, Clone, Serialize, Deserialize)]
8051pub struct ReferralSchemaConfig {
8052    /// Enable referral generation.
8053    #[serde(default = "default_true")]
8054    pub enabled: bool,
8055
8056    /// Rate of customers acquired via referral.
8057    #[serde(default = "default_referral_rate")]
8058    pub referral_rate: f64,
8059}
8060
8061fn default_referral_rate() -> f64 {
8062    0.15
8063}
8064
8065impl Default for ReferralSchemaConfig {
8066    fn default() -> Self {
8067        Self {
8068            enabled: true,
8069            referral_rate: 0.15,
8070        }
8071    }
8072}
8073
8074/// Corporate hierarchy configuration.
8075#[derive(Debug, Clone, Serialize, Deserialize)]
8076pub struct HierarchySchemaConfig {
8077    /// Enable corporate hierarchy generation.
8078    #[serde(default = "default_true")]
8079    pub enabled: bool,
8080
8081    /// Rate of customers in hierarchies.
8082    #[serde(default = "default_hierarchy_rate")]
8083    pub probability: f64,
8084}
8085
8086fn default_hierarchy_rate() -> f64 {
8087    0.30
8088}
8089
8090impl Default for HierarchySchemaConfig {
8091    fn default() -> Self {
8092        Self {
8093            enabled: true,
8094            probability: 0.30,
8095        }
8096    }
8097}
8098
8099// =============================================================================
8100// Relationship Strength Configuration
8101// =============================================================================
8102
8103/// Configuration for relationship strength calculation.
8104#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8105pub struct RelationshipStrengthSchemaConfig {
8106    /// Enable relationship strength calculation.
8107    #[serde(default)]
8108    pub enabled: bool,
8109
8110    /// Calculation weights.
8111    #[serde(default)]
8112    pub calculation: StrengthCalculationSchemaConfig,
8113
8114    /// Strength thresholds for classification.
8115    #[serde(default)]
8116    pub thresholds: StrengthThresholdsSchemaConfig,
8117}
8118
8119/// Strength calculation weights configuration.
8120#[derive(Debug, Clone, Serialize, Deserialize)]
8121pub struct StrengthCalculationSchemaConfig {
8122    /// Weight for transaction volume (default: 0.30).
8123    #[serde(default = "default_volume_weight")]
8124    pub transaction_volume_weight: f64,
8125
8126    /// Weight for transaction count (default: 0.25).
8127    #[serde(default = "default_count_weight")]
8128    pub transaction_count_weight: f64,
8129
8130    /// Weight for relationship duration (default: 0.20).
8131    #[serde(default = "default_duration_weight")]
8132    pub relationship_duration_weight: f64,
8133
8134    /// Weight for recency (default: 0.15).
8135    #[serde(default = "default_recency_weight")]
8136    pub recency_weight: f64,
8137
8138    /// Weight for mutual connections (default: 0.10).
8139    #[serde(default = "default_mutual_weight")]
8140    pub mutual_connections_weight: f64,
8141
8142    /// Recency half-life in days (default: 90).
8143    #[serde(default = "default_recency_half_life")]
8144    pub recency_half_life_days: u32,
8145}
8146
8147fn default_volume_weight() -> f64 {
8148    0.30
8149}
8150
8151fn default_count_weight() -> f64 {
8152    0.25
8153}
8154
8155fn default_duration_weight() -> f64 {
8156    0.20
8157}
8158
8159fn default_recency_weight() -> f64 {
8160    0.15
8161}
8162
8163fn default_mutual_weight() -> f64 {
8164    0.10
8165}
8166
8167fn default_recency_half_life() -> u32 {
8168    90
8169}
8170
8171impl Default for StrengthCalculationSchemaConfig {
8172    fn default() -> Self {
8173        Self {
8174            transaction_volume_weight: 0.30,
8175            transaction_count_weight: 0.25,
8176            relationship_duration_weight: 0.20,
8177            recency_weight: 0.15,
8178            mutual_connections_weight: 0.10,
8179            recency_half_life_days: 90,
8180        }
8181    }
8182}
8183
8184/// Strength thresholds for relationship classification.
8185#[derive(Debug, Clone, Serialize, Deserialize)]
8186pub struct StrengthThresholdsSchemaConfig {
8187    /// Threshold for strong relationships (default: 0.7).
8188    #[serde(default = "default_strong_threshold")]
8189    pub strong: f64,
8190
8191    /// Threshold for moderate relationships (default: 0.4).
8192    #[serde(default = "default_moderate_threshold")]
8193    pub moderate: f64,
8194
8195    /// Threshold for weak relationships (default: 0.1).
8196    #[serde(default = "default_weak_threshold")]
8197    pub weak: f64,
8198}
8199
8200fn default_strong_threshold() -> f64 {
8201    0.7
8202}
8203
8204fn default_moderate_threshold() -> f64 {
8205    0.4
8206}
8207
8208fn default_weak_threshold() -> f64 {
8209    0.1
8210}
8211
8212impl Default for StrengthThresholdsSchemaConfig {
8213    fn default() -> Self {
8214        Self {
8215            strong: 0.7,
8216            moderate: 0.4,
8217            weak: 0.1,
8218        }
8219    }
8220}
8221
8222// =============================================================================
8223// Cross-Process Links Configuration
8224// =============================================================================
8225
8226/// Configuration for cross-process linkages.
8227#[derive(Debug, Clone, Serialize, Deserialize)]
8228pub struct CrossProcessLinksSchemaConfig {
8229    /// Enable cross-process link generation.
8230    #[serde(default)]
8231    pub enabled: bool,
8232
8233    /// Enable inventory links between P2P and O2C.
8234    #[serde(default = "default_true")]
8235    pub inventory_p2p_o2c: bool,
8236
8237    /// Enable payment to bank reconciliation links.
8238    #[serde(default = "default_true")]
8239    pub payment_bank_reconciliation: bool,
8240
8241    /// Enable intercompany bilateral matching.
8242    #[serde(default = "default_true")]
8243    pub intercompany_bilateral: bool,
8244
8245    /// Percentage of GR/Deliveries to link via inventory (0.0 - 1.0).
8246    #[serde(default = "default_inventory_link_rate")]
8247    pub inventory_link_rate: f64,
8248}
8249
8250fn default_inventory_link_rate() -> f64 {
8251    0.30
8252}
8253
8254impl Default for CrossProcessLinksSchemaConfig {
8255    fn default() -> Self {
8256        Self {
8257            enabled: false,
8258            inventory_p2p_o2c: true,
8259            payment_bank_reconciliation: true,
8260            intercompany_bilateral: true,
8261            inventory_link_rate: 0.30,
8262        }
8263    }
8264}
8265
8266// =============================================================================
8267// Organizational Events Configuration
8268// =============================================================================
8269
8270/// Configuration for organizational events (acquisitions, divestitures, etc.).
8271#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8272pub struct OrganizationalEventsSchemaConfig {
8273    /// Enable organizational events.
8274    #[serde(default)]
8275    pub enabled: bool,
8276
8277    /// Effect blending mode (multiplicative, additive, maximum, minimum).
8278    #[serde(default)]
8279    pub effect_blending: EffectBlendingModeConfig,
8280
8281    /// Organizational events (acquisitions, divestitures, reorganizations, etc.).
8282    #[serde(default)]
8283    pub events: Vec<OrganizationalEventSchemaConfig>,
8284
8285    /// Process evolution events.
8286    #[serde(default)]
8287    pub process_evolution: Vec<ProcessEvolutionSchemaConfig>,
8288
8289    /// Technology transition events.
8290    #[serde(default)]
8291    pub technology_transitions: Vec<TechnologyTransitionSchemaConfig>,
8292}
8293
8294/// Effect blending mode for combining multiple event effects.
8295#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
8296#[serde(rename_all = "snake_case")]
8297pub enum EffectBlendingModeConfig {
8298    /// Multiply effects together.
8299    #[default]
8300    Multiplicative,
8301    /// Add effects together.
8302    Additive,
8303    /// Take the maximum effect.
8304    Maximum,
8305    /// Take the minimum effect.
8306    Minimum,
8307}
8308
8309/// Configuration for a single organizational event.
8310#[derive(Debug, Clone, Serialize, Deserialize)]
8311pub struct OrganizationalEventSchemaConfig {
8312    /// Event ID.
8313    pub id: String,
8314
8315    /// Event type and configuration.
8316    pub event_type: OrganizationalEventTypeSchemaConfig,
8317
8318    /// Effective date.
8319    pub effective_date: String,
8320
8321    /// Transition duration in months.
8322    #[serde(default = "default_org_transition_months")]
8323    pub transition_months: u32,
8324
8325    /// Description.
8326    #[serde(default)]
8327    pub description: Option<String>,
8328}
8329
8330fn default_org_transition_months() -> u32 {
8331    6
8332}
8333
8334/// Organizational event type configuration.
8335#[derive(Debug, Clone, Serialize, Deserialize)]
8336#[serde(tag = "type", rename_all = "snake_case")]
8337pub enum OrganizationalEventTypeSchemaConfig {
8338    /// Acquisition event.
8339    Acquisition {
8340        /// Acquired entity code.
8341        acquired_entity: String,
8342        /// Volume increase multiplier.
8343        #[serde(default = "default_acquisition_volume")]
8344        volume_increase: f64,
8345        /// Integration error rate.
8346        #[serde(default = "default_acquisition_error")]
8347        integration_error_rate: f64,
8348        /// Parallel posting days.
8349        #[serde(default = "default_parallel_days")]
8350        parallel_posting_days: u32,
8351    },
8352    /// Divestiture event.
8353    Divestiture {
8354        /// Divested entity code.
8355        divested_entity: String,
8356        /// Volume reduction factor.
8357        #[serde(default = "default_divestiture_volume")]
8358        volume_reduction: f64,
8359        /// Remove entity from generation.
8360        #[serde(default = "default_true_val")]
8361        remove_entity: bool,
8362    },
8363    /// Reorganization event.
8364    Reorganization {
8365        /// Cost center remapping.
8366        #[serde(default)]
8367        cost_center_remapping: std::collections::HashMap<String, String>,
8368        /// Transition error rate.
8369        #[serde(default = "default_reorg_error")]
8370        transition_error_rate: f64,
8371    },
8372    /// Leadership change event.
8373    LeadershipChange {
8374        /// Role that changed.
8375        role: String,
8376        /// Policy changes.
8377        #[serde(default)]
8378        policy_changes: Vec<String>,
8379    },
8380    /// Workforce reduction event.
8381    WorkforceReduction {
8382        /// Reduction percentage.
8383        #[serde(default = "default_workforce_reduction")]
8384        reduction_percent: f64,
8385        /// Error rate increase.
8386        #[serde(default = "default_workforce_error")]
8387        error_rate_increase: f64,
8388    },
8389    /// Merger event.
8390    Merger {
8391        /// Merged entity code.
8392        merged_entity: String,
8393        /// Volume increase multiplier.
8394        #[serde(default = "default_merger_volume")]
8395        volume_increase: f64,
8396    },
8397}
8398
8399fn default_acquisition_volume() -> f64 {
8400    1.35
8401}
8402
8403fn default_acquisition_error() -> f64 {
8404    0.05
8405}
8406
8407fn default_parallel_days() -> u32 {
8408    30
8409}
8410
8411fn default_divestiture_volume() -> f64 {
8412    0.70
8413}
8414
8415fn default_true_val() -> bool {
8416    true
8417}
8418
8419fn default_reorg_error() -> f64 {
8420    0.04
8421}
8422
8423fn default_workforce_reduction() -> f64 {
8424    0.10
8425}
8426
8427fn default_workforce_error() -> f64 {
8428    0.05
8429}
8430
8431fn default_merger_volume() -> f64 {
8432    1.80
8433}
8434
8435/// Configuration for a process evolution event.
8436#[derive(Debug, Clone, Serialize, Deserialize)]
8437pub struct ProcessEvolutionSchemaConfig {
8438    /// Event ID.
8439    pub id: String,
8440
8441    /// Event type.
8442    pub event_type: ProcessEvolutionTypeSchemaConfig,
8443
8444    /// Effective date.
8445    pub effective_date: String,
8446
8447    /// Description.
8448    #[serde(default)]
8449    pub description: Option<String>,
8450}
8451
8452/// Process evolution type configuration.
8453#[derive(Debug, Clone, Serialize, Deserialize)]
8454#[serde(tag = "type", rename_all = "snake_case")]
8455pub enum ProcessEvolutionTypeSchemaConfig {
8456    /// Process automation.
8457    ProcessAutomation {
8458        /// Process name.
8459        process_name: String,
8460        /// Manual rate before.
8461        #[serde(default = "default_manual_before")]
8462        manual_rate_before: f64,
8463        /// Manual rate after.
8464        #[serde(default = "default_manual_after")]
8465        manual_rate_after: f64,
8466    },
8467    /// Approval workflow change.
8468    ApprovalWorkflowChange {
8469        /// Description.
8470        description: String,
8471    },
8472    /// Control enhancement.
8473    ControlEnhancement {
8474        /// Control ID.
8475        control_id: String,
8476        /// Error reduction.
8477        #[serde(default = "default_error_reduction")]
8478        error_reduction: f64,
8479    },
8480}
8481
8482fn default_manual_before() -> f64 {
8483    0.80
8484}
8485
8486fn default_manual_after() -> f64 {
8487    0.15
8488}
8489
8490fn default_error_reduction() -> f64 {
8491    0.02
8492}
8493
8494/// Configuration for a technology transition event.
8495#[derive(Debug, Clone, Serialize, Deserialize)]
8496pub struct TechnologyTransitionSchemaConfig {
8497    /// Event ID.
8498    pub id: String,
8499
8500    /// Event type.
8501    pub event_type: TechnologyTransitionTypeSchemaConfig,
8502
8503    /// Description.
8504    #[serde(default)]
8505    pub description: Option<String>,
8506}
8507
8508/// Technology transition type configuration.
8509#[derive(Debug, Clone, Serialize, Deserialize)]
8510#[serde(tag = "type", rename_all = "snake_case")]
8511pub enum TechnologyTransitionTypeSchemaConfig {
8512    /// ERP migration.
8513    ErpMigration {
8514        /// Source system.
8515        source_system: String,
8516        /// Target system.
8517        target_system: String,
8518        /// Cutover date.
8519        cutover_date: String,
8520        /// Stabilization end date.
8521        stabilization_end: String,
8522        /// Duplicate rate during migration.
8523        #[serde(default = "default_erp_duplicate_rate")]
8524        duplicate_rate: f64,
8525        /// Format mismatch rate.
8526        #[serde(default = "default_format_mismatch")]
8527        format_mismatch_rate: f64,
8528    },
8529    /// Module implementation.
8530    ModuleImplementation {
8531        /// Module name.
8532        module_name: String,
8533        /// Go-live date.
8534        go_live_date: String,
8535    },
8536}
8537
8538fn default_erp_duplicate_rate() -> f64 {
8539    0.02
8540}
8541
8542fn default_format_mismatch() -> f64 {
8543    0.03
8544}
8545
8546// =============================================================================
8547// Behavioral Drift Configuration
8548// =============================================================================
8549
8550/// Configuration for behavioral drift (vendor, customer, employee behavior).
8551///
8552/// **Deprecated (v4.1.2):** this schema section is currently
8553/// validated-but-inert — no runtime code consumes its fields. Users
8554/// who want behavioral drift-style effects should reach for
8555/// `distributions.regime_changes` (v3.5.2+), which drives the
8556/// `DriftController` via the parameter-drift path. The schema type
8557/// remains for backward-compatible YAML loading; it will be removed
8558/// in a future major version once `regime_changes` gains per-entity
8559/// (vendor / customer / employee) targeting.
8560#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8561pub struct BehavioralDriftSchemaConfig {
8562    /// Enable behavioral drift.
8563    #[serde(default)]
8564    pub enabled: bool,
8565
8566    /// Vendor behavior drift.
8567    #[serde(default)]
8568    pub vendor_behavior: VendorBehaviorSchemaConfig,
8569
8570    /// Customer behavior drift.
8571    #[serde(default)]
8572    pub customer_behavior: CustomerBehaviorSchemaConfig,
8573
8574    /// Employee behavior drift.
8575    #[serde(default)]
8576    pub employee_behavior: EmployeeBehaviorSchemaConfig,
8577
8578    /// Collective behavior drift.
8579    #[serde(default)]
8580    pub collective: CollectiveBehaviorSchemaConfig,
8581}
8582
8583/// Vendor behavior drift configuration.
8584#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8585pub struct VendorBehaviorSchemaConfig {
8586    /// Payment terms drift.
8587    #[serde(default)]
8588    pub payment_terms_drift: PaymentTermsDriftSchemaConfig,
8589
8590    /// Quality drift.
8591    #[serde(default)]
8592    pub quality_drift: QualityDriftSchemaConfig,
8593}
8594
8595/// Payment terms drift configuration.
8596#[derive(Debug, Clone, Serialize, Deserialize)]
8597pub struct PaymentTermsDriftSchemaConfig {
8598    /// Extension rate per year (days).
8599    #[serde(default = "default_extension_rate")]
8600    pub extension_rate_per_year: f64,
8601
8602    /// Economic sensitivity.
8603    #[serde(default = "default_economic_sensitivity")]
8604    pub economic_sensitivity: f64,
8605}
8606
8607fn default_extension_rate() -> f64 {
8608    2.5
8609}
8610
8611fn default_economic_sensitivity() -> f64 {
8612    1.0
8613}
8614
8615impl Default for PaymentTermsDriftSchemaConfig {
8616    fn default() -> Self {
8617        Self {
8618            extension_rate_per_year: 2.5,
8619            economic_sensitivity: 1.0,
8620        }
8621    }
8622}
8623
8624/// Quality drift configuration.
8625#[derive(Debug, Clone, Serialize, Deserialize)]
8626pub struct QualityDriftSchemaConfig {
8627    /// New vendor improvement rate (per year).
8628    #[serde(default = "default_improvement_rate")]
8629    pub new_vendor_improvement_rate: f64,
8630
8631    /// Complacency decline rate (per year after first year).
8632    #[serde(default = "default_decline_rate")]
8633    pub complacency_decline_rate: f64,
8634}
8635
8636fn default_improvement_rate() -> f64 {
8637    0.02
8638}
8639
8640fn default_decline_rate() -> f64 {
8641    0.01
8642}
8643
8644impl Default for QualityDriftSchemaConfig {
8645    fn default() -> Self {
8646        Self {
8647            new_vendor_improvement_rate: 0.02,
8648            complacency_decline_rate: 0.01,
8649        }
8650    }
8651}
8652
8653/// Customer behavior drift configuration.
8654#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8655pub struct CustomerBehaviorSchemaConfig {
8656    /// Payment drift.
8657    #[serde(default)]
8658    pub payment_drift: CustomerPaymentDriftSchemaConfig,
8659
8660    /// Order drift.
8661    #[serde(default)]
8662    pub order_drift: OrderDriftSchemaConfig,
8663}
8664
8665/// Customer payment drift configuration.
8666#[derive(Debug, Clone, Serialize, Deserialize)]
8667pub struct CustomerPaymentDriftSchemaConfig {
8668    /// Days extension during downturn (min, max).
8669    #[serde(default = "default_downturn_extension")]
8670    pub downturn_days_extension: (u32, u32),
8671
8672    /// Bad debt increase during downturn.
8673    #[serde(default = "default_bad_debt_increase")]
8674    pub downturn_bad_debt_increase: f64,
8675}
8676
8677fn default_downturn_extension() -> (u32, u32) {
8678    (5, 15)
8679}
8680
8681fn default_bad_debt_increase() -> f64 {
8682    0.02
8683}
8684
8685impl Default for CustomerPaymentDriftSchemaConfig {
8686    fn default() -> Self {
8687        Self {
8688            downturn_days_extension: (5, 15),
8689            downturn_bad_debt_increase: 0.02,
8690        }
8691    }
8692}
8693
8694/// Order drift configuration.
8695#[derive(Debug, Clone, Serialize, Deserialize)]
8696pub struct OrderDriftSchemaConfig {
8697    /// Digital shift rate (per year).
8698    #[serde(default = "default_digital_shift")]
8699    pub digital_shift_rate: f64,
8700}
8701
8702fn default_digital_shift() -> f64 {
8703    0.05
8704}
8705
8706impl Default for OrderDriftSchemaConfig {
8707    fn default() -> Self {
8708        Self {
8709            digital_shift_rate: 0.05,
8710        }
8711    }
8712}
8713
8714/// Employee behavior drift configuration.
8715#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8716pub struct EmployeeBehaviorSchemaConfig {
8717    /// Approval drift.
8718    #[serde(default)]
8719    pub approval_drift: ApprovalDriftSchemaConfig,
8720
8721    /// Error drift.
8722    #[serde(default)]
8723    pub error_drift: ErrorDriftSchemaConfig,
8724}
8725
8726/// Approval drift configuration.
8727#[derive(Debug, Clone, Serialize, Deserialize)]
8728pub struct ApprovalDriftSchemaConfig {
8729    /// EOM intensity increase per year.
8730    #[serde(default = "default_eom_intensity")]
8731    pub eom_intensity_increase_per_year: f64,
8732
8733    /// Rubber stamp volume threshold.
8734    #[serde(default = "default_rubber_stamp")]
8735    pub rubber_stamp_volume_threshold: u32,
8736}
8737
8738fn default_eom_intensity() -> f64 {
8739    0.05
8740}
8741
8742fn default_rubber_stamp() -> u32 {
8743    50
8744}
8745
8746impl Default for ApprovalDriftSchemaConfig {
8747    fn default() -> Self {
8748        Self {
8749            eom_intensity_increase_per_year: 0.05,
8750            rubber_stamp_volume_threshold: 50,
8751        }
8752    }
8753}
8754
8755/// Error drift configuration.
8756#[derive(Debug, Clone, Serialize, Deserialize)]
8757pub struct ErrorDriftSchemaConfig {
8758    /// New employee error rate.
8759    #[serde(default = "default_new_error")]
8760    pub new_employee_error_rate: f64,
8761
8762    /// Learning curve months.
8763    #[serde(default = "default_learning_months")]
8764    pub learning_curve_months: u32,
8765}
8766
8767fn default_new_error() -> f64 {
8768    0.08
8769}
8770
8771fn default_learning_months() -> u32 {
8772    6
8773}
8774
8775impl Default for ErrorDriftSchemaConfig {
8776    fn default() -> Self {
8777        Self {
8778            new_employee_error_rate: 0.08,
8779            learning_curve_months: 6,
8780        }
8781    }
8782}
8783
8784/// Collective behavior drift configuration.
8785#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8786pub struct CollectiveBehaviorSchemaConfig {
8787    /// Automation adoption configuration.
8788    #[serde(default)]
8789    pub automation_adoption: AutomationAdoptionSchemaConfig,
8790}
8791
8792/// Automation adoption configuration.
8793#[derive(Debug, Clone, Serialize, Deserialize)]
8794pub struct AutomationAdoptionSchemaConfig {
8795    /// Enable S-curve adoption model.
8796    #[serde(default)]
8797    pub s_curve_enabled: bool,
8798
8799    /// Adoption midpoint in months.
8800    #[serde(default = "default_midpoint")]
8801    pub adoption_midpoint_months: u32,
8802
8803    /// Steepness of adoption curve.
8804    #[serde(default = "default_steepness")]
8805    pub steepness: f64,
8806}
8807
8808fn default_midpoint() -> u32 {
8809    24
8810}
8811
8812fn default_steepness() -> f64 {
8813    0.15
8814}
8815
8816impl Default for AutomationAdoptionSchemaConfig {
8817    fn default() -> Self {
8818        Self {
8819            s_curve_enabled: false,
8820            adoption_midpoint_months: 24,
8821            steepness: 0.15,
8822        }
8823    }
8824}
8825
8826// =============================================================================
8827// Market Drift Configuration
8828// =============================================================================
8829
8830/// Configuration for market drift (economic cycles, commodities, price shocks).
8831///
8832/// **Deprecated (v4.1.2):** validated-but-inert. Use
8833/// `distributions.regime_changes.economic_cycle` +
8834/// `distributions.regime_changes.parameter_drifts` for the
8835/// equivalent runtime behaviour (shipped in v3.5.2). The schema
8836/// type remains for backward-compatible YAML loading; will be
8837/// removed in v5.0.
8838#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8839pub struct MarketDriftSchemaConfig {
8840    /// Enable market drift.
8841    #[serde(default)]
8842    pub enabled: bool,
8843
8844    /// Economic cycle configuration.
8845    #[serde(default)]
8846    pub economic_cycle: MarketEconomicCycleSchemaConfig,
8847
8848    /// Industry-specific cycles.
8849    #[serde(default)]
8850    pub industry_cycles: std::collections::HashMap<String, IndustryCycleSchemaConfig>,
8851
8852    /// Commodity drift configuration.
8853    #[serde(default)]
8854    pub commodities: CommoditiesSchemaConfig,
8855}
8856
8857/// Market economic cycle configuration.
8858#[derive(Debug, Clone, Serialize, Deserialize)]
8859pub struct MarketEconomicCycleSchemaConfig {
8860    /// Enable economic cycle.
8861    #[serde(default)]
8862    pub enabled: bool,
8863
8864    /// Cycle type.
8865    #[serde(default)]
8866    pub cycle_type: CycleTypeSchemaConfig,
8867
8868    /// Cycle period in months.
8869    #[serde(default = "default_market_cycle_period")]
8870    pub period_months: u32,
8871
8872    /// Amplitude.
8873    #[serde(default = "default_market_amplitude")]
8874    pub amplitude: f64,
8875
8876    /// Recession configuration.
8877    #[serde(default)]
8878    pub recession: RecessionSchemaConfig,
8879}
8880
8881fn default_market_cycle_period() -> u32 {
8882    48
8883}
8884
8885fn default_market_amplitude() -> f64 {
8886    0.15
8887}
8888
8889impl Default for MarketEconomicCycleSchemaConfig {
8890    fn default() -> Self {
8891        Self {
8892            enabled: false,
8893            cycle_type: CycleTypeSchemaConfig::Sinusoidal,
8894            period_months: 48,
8895            amplitude: 0.15,
8896            recession: RecessionSchemaConfig::default(),
8897        }
8898    }
8899}
8900
8901/// Cycle type configuration.
8902#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
8903#[serde(rename_all = "snake_case")]
8904pub enum CycleTypeSchemaConfig {
8905    /// Sinusoidal cycle.
8906    #[default]
8907    Sinusoidal,
8908    /// Asymmetric cycle.
8909    Asymmetric,
8910    /// Mean-reverting cycle.
8911    MeanReverting,
8912}
8913
8914/// Recession configuration.
8915#[derive(Debug, Clone, Serialize, Deserialize)]
8916pub struct RecessionSchemaConfig {
8917    /// Enable recession simulation.
8918    #[serde(default)]
8919    pub enabled: bool,
8920
8921    /// Probability per year.
8922    #[serde(default = "default_recession_prob")]
8923    pub probability_per_year: f64,
8924
8925    /// Severity.
8926    #[serde(default)]
8927    pub severity: RecessionSeveritySchemaConfig,
8928
8929    /// Specific recession periods.
8930    #[serde(default)]
8931    pub recession_periods: Vec<RecessionPeriodSchemaConfig>,
8932}
8933
8934fn default_recession_prob() -> f64 {
8935    0.10
8936}
8937
8938impl Default for RecessionSchemaConfig {
8939    fn default() -> Self {
8940        Self {
8941            enabled: false,
8942            probability_per_year: 0.10,
8943            severity: RecessionSeveritySchemaConfig::Moderate,
8944            recession_periods: Vec::new(),
8945        }
8946    }
8947}
8948
8949/// Recession severity configuration.
8950#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
8951#[serde(rename_all = "snake_case")]
8952pub enum RecessionSeveritySchemaConfig {
8953    /// Mild recession.
8954    Mild,
8955    /// Moderate recession.
8956    #[default]
8957    Moderate,
8958    /// Severe recession.
8959    Severe,
8960}
8961
8962/// Recession period configuration.
8963#[derive(Debug, Clone, Serialize, Deserialize)]
8964pub struct RecessionPeriodSchemaConfig {
8965    /// Start month.
8966    pub start_month: u32,
8967    /// Duration in months.
8968    pub duration_months: u32,
8969}
8970
8971/// Industry cycle configuration.
8972#[derive(Debug, Clone, Serialize, Deserialize)]
8973pub struct IndustryCycleSchemaConfig {
8974    /// Period in months.
8975    #[serde(default = "default_industry_period")]
8976    pub period_months: u32,
8977
8978    /// Amplitude.
8979    #[serde(default = "default_industry_amp")]
8980    pub amplitude: f64,
8981}
8982
8983fn default_industry_period() -> u32 {
8984    36
8985}
8986
8987fn default_industry_amp() -> f64 {
8988    0.20
8989}
8990
8991/// Commodities drift configuration.
8992#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8993pub struct CommoditiesSchemaConfig {
8994    /// Enable commodity drift.
8995    #[serde(default)]
8996    pub enabled: bool,
8997
8998    /// Commodity items.
8999    #[serde(default)]
9000    pub items: Vec<CommodityItemSchemaConfig>,
9001}
9002
9003/// Commodity item configuration.
9004#[derive(Debug, Clone, Serialize, Deserialize)]
9005pub struct CommodityItemSchemaConfig {
9006    /// Commodity name.
9007    pub name: String,
9008
9009    /// Volatility.
9010    #[serde(default = "default_volatility")]
9011    pub volatility: f64,
9012
9013    /// COGS pass-through.
9014    #[serde(default)]
9015    pub cogs_pass_through: f64,
9016
9017    /// Overhead pass-through.
9018    #[serde(default)]
9019    pub overhead_pass_through: f64,
9020}
9021
9022fn default_volatility() -> f64 {
9023    0.20
9024}
9025
9026// =============================================================================
9027// Drift Labeling Configuration
9028// =============================================================================
9029
9030/// Configuration for drift ground truth labeling.
9031///
9032/// **Deprecated (v4.1.2):** validated-but-inert. The v3.3.0
9033/// analytics-metadata phase (`DriftEventGenerator` +
9034/// `AnalyticsMetadataSnapshot.drift_events`) produces drift labels
9035/// at runtime — configure it via `analytics_metadata.drift_events`
9036/// instead. The schema type remains for backward-compatible YAML
9037/// loading; will be removed in v5.0.
9038#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9039pub struct DriftLabelingSchemaConfig {
9040    /// Enable drift labeling.
9041    #[serde(default)]
9042    pub enabled: bool,
9043
9044    /// Statistical drift labeling.
9045    #[serde(default)]
9046    pub statistical: StatisticalDriftLabelingSchemaConfig,
9047
9048    /// Categorical drift labeling.
9049    #[serde(default)]
9050    pub categorical: CategoricalDriftLabelingSchemaConfig,
9051
9052    /// Temporal drift labeling.
9053    #[serde(default)]
9054    pub temporal: TemporalDriftLabelingSchemaConfig,
9055
9056    /// Regulatory calendar preset.
9057    #[serde(default)]
9058    pub regulatory_calendar_preset: Option<String>,
9059}
9060
9061/// Statistical drift labeling configuration.
9062#[derive(Debug, Clone, Serialize, Deserialize)]
9063pub struct StatisticalDriftLabelingSchemaConfig {
9064    /// Enable statistical drift labeling.
9065    #[serde(default = "default_true_val")]
9066    pub enabled: bool,
9067
9068    /// Minimum magnitude threshold.
9069    #[serde(default = "default_min_magnitude")]
9070    pub min_magnitude_threshold: f64,
9071}
9072
9073fn default_min_magnitude() -> f64 {
9074    0.05
9075}
9076
9077impl Default for StatisticalDriftLabelingSchemaConfig {
9078    fn default() -> Self {
9079        Self {
9080            enabled: true,
9081            min_magnitude_threshold: 0.05,
9082        }
9083    }
9084}
9085
9086/// Categorical drift labeling configuration.
9087#[derive(Debug, Clone, Serialize, Deserialize)]
9088pub struct CategoricalDriftLabelingSchemaConfig {
9089    /// Enable categorical drift labeling.
9090    #[serde(default = "default_true_val")]
9091    pub enabled: bool,
9092}
9093
9094impl Default for CategoricalDriftLabelingSchemaConfig {
9095    fn default() -> Self {
9096        Self { enabled: true }
9097    }
9098}
9099
9100/// Temporal drift labeling configuration.
9101#[derive(Debug, Clone, Serialize, Deserialize)]
9102pub struct TemporalDriftLabelingSchemaConfig {
9103    /// Enable temporal drift labeling.
9104    #[serde(default = "default_true_val")]
9105    pub enabled: bool,
9106}
9107
9108impl Default for TemporalDriftLabelingSchemaConfig {
9109    fn default() -> Self {
9110        Self { enabled: true }
9111    }
9112}
9113
9114// =============================================================================
9115// Enhanced Anomaly Injection Configuration
9116// =============================================================================
9117
9118/// Enhanced anomaly injection configuration.
9119///
9120/// Provides comprehensive anomaly injection capabilities including:
9121/// - Multi-stage fraud schemes (embezzlement, revenue manipulation, kickbacks)
9122/// - Correlated anomaly injection (co-occurrence patterns, error cascades)
9123/// - Near-miss generation for false positive reduction
9124/// - Detection difficulty classification
9125/// - Context-aware injection based on entity behavior
9126#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9127pub struct EnhancedAnomalyConfig {
9128    /// Enable enhanced anomaly injection.
9129    #[serde(default)]
9130    pub enabled: bool,
9131
9132    /// Base anomaly rates.
9133    #[serde(default)]
9134    pub rates: AnomalyRateConfig,
9135
9136    /// Multi-stage fraud scheme configuration.
9137    #[serde(default)]
9138    pub multi_stage_schemes: MultiStageSchemeConfig,
9139
9140    /// Correlated anomaly injection configuration.
9141    #[serde(default)]
9142    pub correlated_injection: CorrelatedInjectionConfig,
9143
9144    /// Near-miss generation configuration.
9145    #[serde(default)]
9146    pub near_miss: NearMissConfig,
9147
9148    /// Detection difficulty classification configuration.
9149    #[serde(default)]
9150    pub difficulty_classification: DifficultyClassificationConfig,
9151
9152    /// Context-aware injection configuration.
9153    #[serde(default)]
9154    pub context_aware: ContextAwareConfig,
9155
9156    /// Enhanced labeling configuration.
9157    #[serde(default)]
9158    pub labeling: EnhancedLabelingConfig,
9159}
9160
9161/// Base anomaly rate configuration.
9162#[derive(Debug, Clone, Serialize, Deserialize)]
9163pub struct AnomalyRateConfig {
9164    /// Total anomaly rate (0.0 to 1.0).
9165    #[serde(default = "default_total_anomaly_rate")]
9166    pub total_rate: f64,
9167
9168    /// Fraud anomaly rate.
9169    #[serde(default = "default_fraud_anomaly_rate")]
9170    pub fraud_rate: f64,
9171
9172    /// Error anomaly rate.
9173    #[serde(default = "default_error_anomaly_rate")]
9174    pub error_rate: f64,
9175
9176    /// Process issue rate.
9177    #[serde(default = "default_process_anomaly_rate")]
9178    pub process_rate: f64,
9179}
9180
9181fn default_total_anomaly_rate() -> f64 {
9182    0.03
9183}
9184fn default_fraud_anomaly_rate() -> f64 {
9185    0.01
9186}
9187fn default_error_anomaly_rate() -> f64 {
9188    0.015
9189}
9190fn default_process_anomaly_rate() -> f64 {
9191    0.005
9192}
9193
9194impl Default for AnomalyRateConfig {
9195    fn default() -> Self {
9196        Self {
9197            total_rate: default_total_anomaly_rate(),
9198            fraud_rate: default_fraud_anomaly_rate(),
9199            error_rate: default_error_anomaly_rate(),
9200            process_rate: default_process_anomaly_rate(),
9201        }
9202    }
9203}
9204
9205/// Multi-stage fraud scheme configuration.
9206#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9207pub struct MultiStageSchemeConfig {
9208    /// Enable multi-stage fraud schemes.
9209    #[serde(default)]
9210    pub enabled: bool,
9211
9212    /// Embezzlement scheme configuration.
9213    #[serde(default)]
9214    pub embezzlement: EmbezzlementSchemeConfig,
9215
9216    /// Revenue manipulation scheme configuration.
9217    #[serde(default)]
9218    pub revenue_manipulation: RevenueManipulationSchemeConfig,
9219
9220    /// Vendor kickback scheme configuration.
9221    #[serde(default)]
9222    pub kickback: KickbackSchemeConfig,
9223}
9224
9225/// Embezzlement scheme configuration.
9226#[derive(Debug, Clone, Serialize, Deserialize)]
9227pub struct EmbezzlementSchemeConfig {
9228    /// Probability of starting an embezzlement scheme per perpetrator per year.
9229    #[serde(default = "default_embezzlement_probability")]
9230    pub probability: f64,
9231
9232    /// Testing stage configuration.
9233    #[serde(default)]
9234    pub testing_stage: SchemeStageConfig,
9235
9236    /// Escalation stage configuration.
9237    #[serde(default)]
9238    pub escalation_stage: SchemeStageConfig,
9239
9240    /// Acceleration stage configuration.
9241    #[serde(default)]
9242    pub acceleration_stage: SchemeStageConfig,
9243
9244    /// Desperation stage configuration.
9245    #[serde(default)]
9246    pub desperation_stage: SchemeStageConfig,
9247}
9248
9249fn default_embezzlement_probability() -> f64 {
9250    0.02
9251}
9252
9253impl Default for EmbezzlementSchemeConfig {
9254    fn default() -> Self {
9255        Self {
9256            probability: default_embezzlement_probability(),
9257            testing_stage: SchemeStageConfig {
9258                duration_months: 2,
9259                amount_min: 100.0,
9260                amount_max: 500.0,
9261                transaction_count_min: 2,
9262                transaction_count_max: 5,
9263                difficulty: "hard".to_string(),
9264            },
9265            escalation_stage: SchemeStageConfig {
9266                duration_months: 6,
9267                amount_min: 500.0,
9268                amount_max: 2000.0,
9269                transaction_count_min: 3,
9270                transaction_count_max: 8,
9271                difficulty: "moderate".to_string(),
9272            },
9273            acceleration_stage: SchemeStageConfig {
9274                duration_months: 3,
9275                amount_min: 2000.0,
9276                amount_max: 10000.0,
9277                transaction_count_min: 5,
9278                transaction_count_max: 12,
9279                difficulty: "easy".to_string(),
9280            },
9281            desperation_stage: SchemeStageConfig {
9282                duration_months: 1,
9283                amount_min: 10000.0,
9284                amount_max: 50000.0,
9285                transaction_count_min: 3,
9286                transaction_count_max: 6,
9287                difficulty: "trivial".to_string(),
9288            },
9289        }
9290    }
9291}
9292
9293/// Revenue manipulation scheme configuration.
9294#[derive(Debug, Clone, Serialize, Deserialize)]
9295pub struct RevenueManipulationSchemeConfig {
9296    /// Probability of starting a revenue manipulation scheme per period.
9297    #[serde(default = "default_revenue_manipulation_probability")]
9298    pub probability: f64,
9299
9300    /// Early revenue recognition inflation target (Q4).
9301    #[serde(default = "default_early_recognition_target")]
9302    pub early_recognition_target: f64,
9303
9304    /// Expense deferral inflation target (Q1).
9305    #[serde(default = "default_expense_deferral_target")]
9306    pub expense_deferral_target: f64,
9307
9308    /// Reserve release inflation target (Q2).
9309    #[serde(default = "default_reserve_release_target")]
9310    pub reserve_release_target: f64,
9311
9312    /// Channel stuffing inflation target (Q4).
9313    #[serde(default = "default_channel_stuffing_target")]
9314    pub channel_stuffing_target: f64,
9315}
9316
9317fn default_revenue_manipulation_probability() -> f64 {
9318    0.01
9319}
9320fn default_early_recognition_target() -> f64 {
9321    0.02
9322}
9323fn default_expense_deferral_target() -> f64 {
9324    0.03
9325}
9326fn default_reserve_release_target() -> f64 {
9327    0.02
9328}
9329fn default_channel_stuffing_target() -> f64 {
9330    0.05
9331}
9332
9333impl Default for RevenueManipulationSchemeConfig {
9334    fn default() -> Self {
9335        Self {
9336            probability: default_revenue_manipulation_probability(),
9337            early_recognition_target: default_early_recognition_target(),
9338            expense_deferral_target: default_expense_deferral_target(),
9339            reserve_release_target: default_reserve_release_target(),
9340            channel_stuffing_target: default_channel_stuffing_target(),
9341        }
9342    }
9343}
9344
9345/// Vendor kickback scheme configuration.
9346#[derive(Debug, Clone, Serialize, Deserialize)]
9347pub struct KickbackSchemeConfig {
9348    /// Probability of starting a kickback scheme.
9349    #[serde(default = "default_kickback_probability")]
9350    pub probability: f64,
9351
9352    /// Minimum price inflation percentage.
9353    #[serde(default = "default_kickback_inflation_min")]
9354    pub inflation_min: f64,
9355
9356    /// Maximum price inflation percentage.
9357    #[serde(default = "default_kickback_inflation_max")]
9358    pub inflation_max: f64,
9359
9360    /// Kickback percentage (of inflation).
9361    #[serde(default = "default_kickback_percent")]
9362    pub kickback_percent: f64,
9363
9364    /// Setup duration in months.
9365    #[serde(default = "default_kickback_setup_months")]
9366    pub setup_months: u32,
9367
9368    /// Main operation duration in months.
9369    #[serde(default = "default_kickback_operation_months")]
9370    pub operation_months: u32,
9371}
9372
9373fn default_kickback_probability() -> f64 {
9374    0.01
9375}
9376fn default_kickback_inflation_min() -> f64 {
9377    0.10
9378}
9379fn default_kickback_inflation_max() -> f64 {
9380    0.25
9381}
9382fn default_kickback_percent() -> f64 {
9383    0.50
9384}
9385fn default_kickback_setup_months() -> u32 {
9386    3
9387}
9388fn default_kickback_operation_months() -> u32 {
9389    12
9390}
9391
9392impl Default for KickbackSchemeConfig {
9393    fn default() -> Self {
9394        Self {
9395            probability: default_kickback_probability(),
9396            inflation_min: default_kickback_inflation_min(),
9397            inflation_max: default_kickback_inflation_max(),
9398            kickback_percent: default_kickback_percent(),
9399            setup_months: default_kickback_setup_months(),
9400            operation_months: default_kickback_operation_months(),
9401        }
9402    }
9403}
9404
9405/// Individual scheme stage configuration.
9406#[derive(Debug, Clone, Serialize, Deserialize)]
9407pub struct SchemeStageConfig {
9408    /// Duration in months.
9409    pub duration_months: u32,
9410
9411    /// Minimum transaction amount.
9412    pub amount_min: f64,
9413
9414    /// Maximum transaction amount.
9415    pub amount_max: f64,
9416
9417    /// Minimum number of transactions.
9418    pub transaction_count_min: u32,
9419
9420    /// Maximum number of transactions.
9421    pub transaction_count_max: u32,
9422
9423    /// Detection difficulty level (trivial, easy, moderate, hard, expert).
9424    pub difficulty: String,
9425}
9426
9427impl Default for SchemeStageConfig {
9428    fn default() -> Self {
9429        Self {
9430            duration_months: 3,
9431            amount_min: 100.0,
9432            amount_max: 1000.0,
9433            transaction_count_min: 2,
9434            transaction_count_max: 10,
9435            difficulty: "moderate".to_string(),
9436        }
9437    }
9438}
9439
9440/// Correlated anomaly injection configuration.
9441#[derive(Debug, Clone, Serialize, Deserialize)]
9442pub struct CorrelatedInjectionConfig {
9443    /// Enable correlated anomaly injection.
9444    #[serde(default)]
9445    pub enabled: bool,
9446
9447    /// Enable fraud concealment co-occurrence patterns.
9448    #[serde(default = "default_true_val")]
9449    pub fraud_concealment: bool,
9450
9451    /// Enable error cascade patterns.
9452    #[serde(default = "default_true_val")]
9453    pub error_cascade: bool,
9454
9455    /// Enable temporal clustering (period-end spikes).
9456    #[serde(default = "default_true_val")]
9457    pub temporal_clustering: bool,
9458
9459    /// Temporal clustering configuration.
9460    #[serde(default)]
9461    pub temporal_clustering_config: TemporalClusteringConfig,
9462
9463    /// Co-occurrence patterns.
9464    #[serde(default)]
9465    pub co_occurrence_patterns: Vec<CoOccurrencePatternConfig>,
9466}
9467
9468impl Default for CorrelatedInjectionConfig {
9469    fn default() -> Self {
9470        Self {
9471            enabled: false,
9472            fraud_concealment: true,
9473            error_cascade: true,
9474            temporal_clustering: true,
9475            temporal_clustering_config: TemporalClusteringConfig::default(),
9476            co_occurrence_patterns: Vec::new(),
9477        }
9478    }
9479}
9480
9481/// Temporal clustering configuration.
9482#[derive(Debug, Clone, Serialize, Deserialize)]
9483pub struct TemporalClusteringConfig {
9484    /// Period-end error multiplier.
9485    #[serde(default = "default_period_end_multiplier")]
9486    pub period_end_multiplier: f64,
9487
9488    /// Number of business days before period end to apply multiplier.
9489    #[serde(default = "default_period_end_days")]
9490    pub period_end_days: u32,
9491
9492    /// Quarter-end additional multiplier.
9493    #[serde(default = "default_quarter_end_multiplier")]
9494    pub quarter_end_multiplier: f64,
9495
9496    /// Year-end additional multiplier.
9497    #[serde(default = "default_year_end_multiplier")]
9498    pub year_end_multiplier: f64,
9499}
9500
9501fn default_period_end_multiplier() -> f64 {
9502    2.5
9503}
9504fn default_period_end_days() -> u32 {
9505    5
9506}
9507fn default_quarter_end_multiplier() -> f64 {
9508    1.5
9509}
9510fn default_year_end_multiplier() -> f64 {
9511    2.0
9512}
9513
9514impl Default for TemporalClusteringConfig {
9515    fn default() -> Self {
9516        Self {
9517            period_end_multiplier: default_period_end_multiplier(),
9518            period_end_days: default_period_end_days(),
9519            quarter_end_multiplier: default_quarter_end_multiplier(),
9520            year_end_multiplier: default_year_end_multiplier(),
9521        }
9522    }
9523}
9524
9525/// Co-occurrence pattern configuration.
9526#[derive(Debug, Clone, Serialize, Deserialize)]
9527pub struct CoOccurrencePatternConfig {
9528    /// Pattern name.
9529    pub name: String,
9530
9531    /// Primary anomaly type that triggers the pattern.
9532    pub primary_type: String,
9533
9534    /// Correlated anomalies.
9535    pub correlated: Vec<CorrelatedAnomalyConfig>,
9536}
9537
9538/// Correlated anomaly configuration.
9539#[derive(Debug, Clone, Serialize, Deserialize)]
9540pub struct CorrelatedAnomalyConfig {
9541    /// Anomaly type.
9542    pub anomaly_type: String,
9543
9544    /// Probability of occurrence (0.0 to 1.0).
9545    pub probability: f64,
9546
9547    /// Minimum lag in days.
9548    pub lag_days_min: i32,
9549
9550    /// Maximum lag in days.
9551    pub lag_days_max: i32,
9552}
9553
9554/// Near-miss generation configuration.
9555#[derive(Debug, Clone, Serialize, Deserialize)]
9556pub struct NearMissConfig {
9557    /// Enable near-miss generation.
9558    #[serde(default)]
9559    pub enabled: bool,
9560
9561    /// Proportion of "anomalies" that are actually near-misses (0.0 to 1.0).
9562    #[serde(default = "default_near_miss_proportion")]
9563    pub proportion: f64,
9564
9565    /// Enable near-duplicate pattern.
9566    #[serde(default = "default_true_val")]
9567    pub near_duplicate: bool,
9568
9569    /// Near-duplicate date difference range in days.
9570    #[serde(default)]
9571    pub near_duplicate_days: NearDuplicateDaysConfig,
9572
9573    /// Enable threshold proximity pattern.
9574    #[serde(default = "default_true_val")]
9575    pub threshold_proximity: bool,
9576
9577    /// Threshold proximity range (e.g., 0.90-0.99 of threshold).
9578    #[serde(default)]
9579    pub threshold_proximity_range: ThresholdProximityRangeConfig,
9580
9581    /// Enable unusual but legitimate patterns.
9582    #[serde(default = "default_true_val")]
9583    pub unusual_legitimate: bool,
9584
9585    /// Types of unusual legitimate patterns to generate.
9586    #[serde(default = "default_unusual_legitimate_types")]
9587    pub unusual_legitimate_types: Vec<String>,
9588
9589    /// Enable corrected error patterns.
9590    #[serde(default = "default_true_val")]
9591    pub corrected_errors: bool,
9592
9593    /// Corrected error correction lag range in days.
9594    #[serde(default)]
9595    pub corrected_error_lag: CorrectedErrorLagConfig,
9596}
9597
9598fn default_near_miss_proportion() -> f64 {
9599    0.30
9600}
9601
9602fn default_unusual_legitimate_types() -> Vec<String> {
9603    vec![
9604        "year_end_bonus".to_string(),
9605        "contract_prepayment".to_string(),
9606        "insurance_claim".to_string(),
9607        "settlement_payment".to_string(),
9608    ]
9609}
9610
9611impl Default for NearMissConfig {
9612    fn default() -> Self {
9613        Self {
9614            enabled: false,
9615            proportion: default_near_miss_proportion(),
9616            near_duplicate: true,
9617            near_duplicate_days: NearDuplicateDaysConfig::default(),
9618            threshold_proximity: true,
9619            threshold_proximity_range: ThresholdProximityRangeConfig::default(),
9620            unusual_legitimate: true,
9621            unusual_legitimate_types: default_unusual_legitimate_types(),
9622            corrected_errors: true,
9623            corrected_error_lag: CorrectedErrorLagConfig::default(),
9624        }
9625    }
9626}
9627
9628/// Near-duplicate days configuration.
9629#[derive(Debug, Clone, Serialize, Deserialize)]
9630pub struct NearDuplicateDaysConfig {
9631    /// Minimum days apart.
9632    #[serde(default = "default_near_duplicate_min")]
9633    pub min: u32,
9634
9635    /// Maximum days apart.
9636    #[serde(default = "default_near_duplicate_max")]
9637    pub max: u32,
9638}
9639
9640fn default_near_duplicate_min() -> u32 {
9641    1
9642}
9643fn default_near_duplicate_max() -> u32 {
9644    3
9645}
9646
9647impl Default for NearDuplicateDaysConfig {
9648    fn default() -> Self {
9649        Self {
9650            min: default_near_duplicate_min(),
9651            max: default_near_duplicate_max(),
9652        }
9653    }
9654}
9655
9656/// Threshold proximity range configuration.
9657#[derive(Debug, Clone, Serialize, Deserialize)]
9658pub struct ThresholdProximityRangeConfig {
9659    /// Minimum proximity (e.g., 0.90 = 90% of threshold).
9660    #[serde(default = "default_threshold_proximity_min")]
9661    pub min: f64,
9662
9663    /// Maximum proximity (e.g., 0.99 = 99% of threshold).
9664    #[serde(default = "default_threshold_proximity_max")]
9665    pub max: f64,
9666}
9667
9668fn default_threshold_proximity_min() -> f64 {
9669    0.90
9670}
9671fn default_threshold_proximity_max() -> f64 {
9672    0.99
9673}
9674
9675impl Default for ThresholdProximityRangeConfig {
9676    fn default() -> Self {
9677        Self {
9678            min: default_threshold_proximity_min(),
9679            max: default_threshold_proximity_max(),
9680        }
9681    }
9682}
9683
9684/// Corrected error lag configuration.
9685#[derive(Debug, Clone, Serialize, Deserialize)]
9686pub struct CorrectedErrorLagConfig {
9687    /// Minimum correction lag in days.
9688    #[serde(default = "default_corrected_error_lag_min")]
9689    pub min: u32,
9690
9691    /// Maximum correction lag in days.
9692    #[serde(default = "default_corrected_error_lag_max")]
9693    pub max: u32,
9694}
9695
9696fn default_corrected_error_lag_min() -> u32 {
9697    1
9698}
9699fn default_corrected_error_lag_max() -> u32 {
9700    5
9701}
9702
9703impl Default for CorrectedErrorLagConfig {
9704    fn default() -> Self {
9705        Self {
9706            min: default_corrected_error_lag_min(),
9707            max: default_corrected_error_lag_max(),
9708        }
9709    }
9710}
9711
9712/// Detection difficulty classification configuration.
9713#[derive(Debug, Clone, Serialize, Deserialize)]
9714pub struct DifficultyClassificationConfig {
9715    /// Enable detection difficulty classification.
9716    #[serde(default)]
9717    pub enabled: bool,
9718
9719    /// Target distribution of difficulty levels.
9720    #[serde(default)]
9721    pub target_distribution: DifficultyDistributionConfig,
9722}
9723
9724impl Default for DifficultyClassificationConfig {
9725    fn default() -> Self {
9726        Self {
9727            enabled: true,
9728            target_distribution: DifficultyDistributionConfig::default(),
9729        }
9730    }
9731}
9732
9733/// Target distribution of detection difficulty levels.
9734#[derive(Debug, Clone, Serialize, Deserialize)]
9735pub struct DifficultyDistributionConfig {
9736    /// Proportion of trivial anomalies (expected 99% detection).
9737    #[serde(default = "default_difficulty_trivial")]
9738    pub trivial: f64,
9739
9740    /// Proportion of easy anomalies (expected 90% detection).
9741    #[serde(default = "default_difficulty_easy")]
9742    pub easy: f64,
9743
9744    /// Proportion of moderate anomalies (expected 70% detection).
9745    #[serde(default = "default_difficulty_moderate")]
9746    pub moderate: f64,
9747
9748    /// Proportion of hard anomalies (expected 40% detection).
9749    #[serde(default = "default_difficulty_hard")]
9750    pub hard: f64,
9751
9752    /// Proportion of expert anomalies (expected 15% detection).
9753    #[serde(default = "default_difficulty_expert")]
9754    pub expert: f64,
9755}
9756
9757fn default_difficulty_trivial() -> f64 {
9758    0.15
9759}
9760fn default_difficulty_easy() -> f64 {
9761    0.25
9762}
9763fn default_difficulty_moderate() -> f64 {
9764    0.30
9765}
9766fn default_difficulty_hard() -> f64 {
9767    0.20
9768}
9769fn default_difficulty_expert() -> f64 {
9770    0.10
9771}
9772
9773impl Default for DifficultyDistributionConfig {
9774    fn default() -> Self {
9775        Self {
9776            trivial: default_difficulty_trivial(),
9777            easy: default_difficulty_easy(),
9778            moderate: default_difficulty_moderate(),
9779            hard: default_difficulty_hard(),
9780            expert: default_difficulty_expert(),
9781        }
9782    }
9783}
9784
9785/// Context-aware injection configuration.
9786#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9787pub struct ContextAwareConfig {
9788    /// Enable context-aware injection.
9789    #[serde(default)]
9790    pub enabled: bool,
9791
9792    /// Vendor-specific anomaly rules.
9793    #[serde(default)]
9794    pub vendor_rules: VendorAnomalyRulesConfig,
9795
9796    /// Employee-specific anomaly rules.
9797    #[serde(default)]
9798    pub employee_rules: EmployeeAnomalyRulesConfig,
9799
9800    /// Account-specific anomaly rules.
9801    #[serde(default)]
9802    pub account_rules: AccountAnomalyRulesConfig,
9803
9804    /// Behavioral baseline configuration.
9805    #[serde(default)]
9806    pub behavioral_baseline: BehavioralBaselineConfig,
9807}
9808
9809/// Vendor-specific anomaly rules configuration.
9810#[derive(Debug, Clone, Serialize, Deserialize)]
9811pub struct VendorAnomalyRulesConfig {
9812    /// Error rate multiplier for new vendors (< threshold days).
9813    #[serde(default = "default_new_vendor_multiplier")]
9814    pub new_vendor_error_multiplier: f64,
9815
9816    /// Days threshold for "new" vendor classification.
9817    #[serde(default = "default_new_vendor_threshold")]
9818    pub new_vendor_threshold_days: u32,
9819
9820    /// Error rate multiplier for international vendors.
9821    #[serde(default = "default_international_multiplier")]
9822    pub international_error_multiplier: f64,
9823
9824    /// Strategic vendor anomaly types (may differ from general vendors).
9825    #[serde(default = "default_strategic_vendor_types")]
9826    pub strategic_vendor_anomaly_types: Vec<String>,
9827}
9828
9829fn default_new_vendor_multiplier() -> f64 {
9830    2.5
9831}
9832fn default_new_vendor_threshold() -> u32 {
9833    90
9834}
9835fn default_international_multiplier() -> f64 {
9836    1.5
9837}
9838fn default_strategic_vendor_types() -> Vec<String> {
9839    vec![
9840        "pricing_dispute".to_string(),
9841        "contract_violation".to_string(),
9842    ]
9843}
9844
9845impl Default for VendorAnomalyRulesConfig {
9846    fn default() -> Self {
9847        Self {
9848            new_vendor_error_multiplier: default_new_vendor_multiplier(),
9849            new_vendor_threshold_days: default_new_vendor_threshold(),
9850            international_error_multiplier: default_international_multiplier(),
9851            strategic_vendor_anomaly_types: default_strategic_vendor_types(),
9852        }
9853    }
9854}
9855
9856/// Employee-specific anomaly rules configuration.
9857#[derive(Debug, Clone, Serialize, Deserialize)]
9858pub struct EmployeeAnomalyRulesConfig {
9859    /// Error rate for new employees (< threshold days).
9860    #[serde(default = "default_new_employee_rate")]
9861    pub new_employee_error_rate: f64,
9862
9863    /// Days threshold for "new" employee classification.
9864    #[serde(default = "default_new_employee_threshold")]
9865    pub new_employee_threshold_days: u32,
9866
9867    /// Transaction volume threshold for fatigue errors.
9868    #[serde(default = "default_volume_fatigue_threshold")]
9869    pub volume_fatigue_threshold: u32,
9870
9871    /// Error rate multiplier when primary approver is absent.
9872    #[serde(default = "default_coverage_multiplier")]
9873    pub coverage_error_multiplier: f64,
9874}
9875
9876fn default_new_employee_rate() -> f64 {
9877    0.05
9878}
9879fn default_new_employee_threshold() -> u32 {
9880    180
9881}
9882fn default_volume_fatigue_threshold() -> u32 {
9883    50
9884}
9885fn default_coverage_multiplier() -> f64 {
9886    1.8
9887}
9888
9889impl Default for EmployeeAnomalyRulesConfig {
9890    fn default() -> Self {
9891        Self {
9892            new_employee_error_rate: default_new_employee_rate(),
9893            new_employee_threshold_days: default_new_employee_threshold(),
9894            volume_fatigue_threshold: default_volume_fatigue_threshold(),
9895            coverage_error_multiplier: default_coverage_multiplier(),
9896        }
9897    }
9898}
9899
9900/// Account-specific anomaly rules configuration.
9901#[derive(Debug, Clone, Serialize, Deserialize)]
9902pub struct AccountAnomalyRulesConfig {
9903    /// Error rate multiplier for high-risk accounts.
9904    #[serde(default = "default_high_risk_multiplier")]
9905    pub high_risk_account_multiplier: f64,
9906
9907    /// Account codes considered high-risk.
9908    #[serde(default = "default_high_risk_accounts")]
9909    pub high_risk_accounts: Vec<String>,
9910
9911    /// Error rate multiplier for suspense accounts.
9912    #[serde(default = "default_suspense_multiplier")]
9913    pub suspense_account_multiplier: f64,
9914
9915    /// Account codes considered suspense accounts.
9916    #[serde(default = "default_suspense_accounts")]
9917    pub suspense_accounts: Vec<String>,
9918
9919    /// Error rate multiplier for intercompany accounts.
9920    #[serde(default = "default_intercompany_multiplier")]
9921    pub intercompany_account_multiplier: f64,
9922}
9923
9924fn default_high_risk_multiplier() -> f64 {
9925    2.0
9926}
9927fn default_high_risk_accounts() -> Vec<String> {
9928    vec![
9929        "1100".to_string(), // AR Control
9930        "2000".to_string(), // AP Control
9931        "3000".to_string(), // Cash
9932    ]
9933}
9934fn default_suspense_multiplier() -> f64 {
9935    3.0
9936}
9937fn default_suspense_accounts() -> Vec<String> {
9938    vec!["9999".to_string(), "9998".to_string()]
9939}
9940fn default_intercompany_multiplier() -> f64 {
9941    1.5
9942}
9943
9944impl Default for AccountAnomalyRulesConfig {
9945    fn default() -> Self {
9946        Self {
9947            high_risk_account_multiplier: default_high_risk_multiplier(),
9948            high_risk_accounts: default_high_risk_accounts(),
9949            suspense_account_multiplier: default_suspense_multiplier(),
9950            suspense_accounts: default_suspense_accounts(),
9951            intercompany_account_multiplier: default_intercompany_multiplier(),
9952        }
9953    }
9954}
9955
9956/// Behavioral baseline configuration.
9957#[derive(Debug, Clone, Serialize, Deserialize)]
9958pub struct BehavioralBaselineConfig {
9959    /// Enable behavioral baseline tracking.
9960    #[serde(default)]
9961    pub enabled: bool,
9962
9963    /// Number of days to build baseline from.
9964    #[serde(default = "default_baseline_period")]
9965    pub baseline_period_days: u32,
9966
9967    /// Standard deviation threshold for amount anomalies.
9968    #[serde(default = "default_deviation_threshold")]
9969    pub deviation_threshold_std: f64,
9970
9971    /// Standard deviation threshold for frequency anomalies.
9972    #[serde(default = "default_frequency_deviation")]
9973    pub frequency_deviation_threshold: f64,
9974}
9975
9976fn default_baseline_period() -> u32 {
9977    90
9978}
9979fn default_deviation_threshold() -> f64 {
9980    3.0
9981}
9982fn default_frequency_deviation() -> f64 {
9983    2.0
9984}
9985
9986impl Default for BehavioralBaselineConfig {
9987    fn default() -> Self {
9988        Self {
9989            enabled: false,
9990            baseline_period_days: default_baseline_period(),
9991            deviation_threshold_std: default_deviation_threshold(),
9992            frequency_deviation_threshold: default_frequency_deviation(),
9993        }
9994    }
9995}
9996
9997/// Enhanced labeling configuration.
9998#[derive(Debug, Clone, Serialize, Deserialize)]
9999pub struct EnhancedLabelingConfig {
10000    /// Enable severity scoring.
10001    #[serde(default = "default_true_val")]
10002    pub severity_scoring: bool,
10003
10004    /// Enable difficulty classification.
10005    #[serde(default = "default_true_val")]
10006    pub difficulty_classification: bool,
10007
10008    /// Materiality thresholds for severity classification.
10009    #[serde(default)]
10010    pub materiality_thresholds: MaterialityThresholdsConfig,
10011}
10012
10013impl Default for EnhancedLabelingConfig {
10014    fn default() -> Self {
10015        Self {
10016            severity_scoring: true,
10017            difficulty_classification: true,
10018            materiality_thresholds: MaterialityThresholdsConfig::default(),
10019        }
10020    }
10021}
10022
10023/// Materiality thresholds configuration.
10024#[derive(Debug, Clone, Serialize, Deserialize)]
10025pub struct MaterialityThresholdsConfig {
10026    /// Threshold for trivial impact (as percentage of total).
10027    #[serde(default = "default_materiality_trivial")]
10028    pub trivial: f64,
10029
10030    /// Threshold for immaterial impact.
10031    #[serde(default = "default_materiality_immaterial")]
10032    pub immaterial: f64,
10033
10034    /// Threshold for material impact.
10035    #[serde(default = "default_materiality_material")]
10036    pub material: f64,
10037
10038    /// Threshold for highly material impact.
10039    #[serde(default = "default_materiality_highly_material")]
10040    pub highly_material: f64,
10041}
10042
10043fn default_materiality_trivial() -> f64 {
10044    0.001
10045}
10046fn default_materiality_immaterial() -> f64 {
10047    0.01
10048}
10049fn default_materiality_material() -> f64 {
10050    0.05
10051}
10052fn default_materiality_highly_material() -> f64 {
10053    0.10
10054}
10055
10056impl Default for MaterialityThresholdsConfig {
10057    fn default() -> Self {
10058        Self {
10059            trivial: default_materiality_trivial(),
10060            immaterial: default_materiality_immaterial(),
10061            material: default_materiality_material(),
10062            highly_material: default_materiality_highly_material(),
10063        }
10064    }
10065}
10066
10067// =============================================================================
10068// Industry-Specific Configuration
10069// =============================================================================
10070
10071/// Industry-specific transaction and anomaly generation configuration.
10072///
10073/// This configuration enables generation of industry-authentic:
10074/// - Transaction types with appropriate terminology
10075/// - Master data (BOM, routings, clinical codes, etc.)
10076/// - Industry-specific anomaly patterns
10077/// - Regulatory framework compliance
10078#[derive(Debug, Clone, Serialize, Deserialize, Default)]
10079pub struct IndustrySpecificConfig {
10080    /// Enable industry-specific generation.
10081    #[serde(default)]
10082    pub enabled: bool,
10083
10084    /// Manufacturing industry settings.
10085    #[serde(default)]
10086    pub manufacturing: ManufacturingConfig,
10087
10088    /// Retail industry settings.
10089    #[serde(default)]
10090    pub retail: RetailConfig,
10091
10092    /// Healthcare industry settings.
10093    #[serde(default)]
10094    pub healthcare: HealthcareConfig,
10095
10096    /// Technology industry settings.
10097    #[serde(default)]
10098    pub technology: TechnologyConfig,
10099
10100    /// Financial services industry settings.
10101    #[serde(default)]
10102    pub financial_services: FinancialServicesConfig,
10103
10104    /// Professional services industry settings.
10105    #[serde(default)]
10106    pub professional_services: ProfessionalServicesConfig,
10107}
10108
10109/// Manufacturing industry configuration.
10110#[derive(Debug, Clone, Serialize, Deserialize)]
10111pub struct ManufacturingConfig {
10112    /// Enable manufacturing-specific generation.
10113    #[serde(default)]
10114    pub enabled: bool,
10115
10116    /// Bill of Materials depth (typical: 3-7).
10117    #[serde(default = "default_bom_depth")]
10118    pub bom_depth: u32,
10119
10120    /// Whether to use just-in-time inventory.
10121    #[serde(default)]
10122    pub just_in_time: bool,
10123
10124    /// Production order types to generate.
10125    #[serde(default = "default_production_order_types")]
10126    pub production_order_types: Vec<String>,
10127
10128    /// Quality framework (ISO_9001, Six_Sigma, etc.).
10129    #[serde(default)]
10130    pub quality_framework: Option<String>,
10131
10132    /// Number of supplier tiers to model (1-3).
10133    #[serde(default = "default_supplier_tiers")]
10134    pub supplier_tiers: u32,
10135
10136    /// Standard cost update frequency.
10137    #[serde(default = "default_cost_frequency")]
10138    pub standard_cost_frequency: String,
10139
10140    /// Target yield rate (0.95-0.99 typical).
10141    #[serde(default = "default_yield_rate")]
10142    pub target_yield_rate: f64,
10143
10144    /// Scrap percentage threshold for alerts.
10145    #[serde(default = "default_scrap_threshold")]
10146    pub scrap_alert_threshold: f64,
10147
10148    /// Manufacturing anomaly injection rates.
10149    #[serde(default)]
10150    pub anomaly_rates: ManufacturingAnomalyRates,
10151
10152    /// Cost accounting configuration (WIP → FG → COGS pipeline).
10153    #[serde(default)]
10154    pub cost_accounting: ManufacturingCostAccountingConfig,
10155}
10156
10157/// Configuration for manufacturing cost accounting JE generation.
10158#[derive(Debug, Clone, Serialize, Deserialize)]
10159pub struct ManufacturingCostAccountingConfig {
10160    /// Enable multi-stage cost flow (WIP → FG → COGS) instead of flat JEs.
10161    #[serde(default = "default_true")]
10162    pub enabled: bool,
10163
10164    /// Generate standard cost variance JEs.
10165    #[serde(default = "default_true")]
10166    pub variance_accounts_enabled: bool,
10167
10168    /// Generate warranty provisions from quality inspection failures.
10169    #[serde(default = "default_true")]
10170    pub warranty_provisions_enabled: bool,
10171
10172    /// Minimum defect rate (0.0-1.0) to trigger warranty provision generation.
10173    #[serde(default = "default_warranty_defect_threshold")]
10174    pub warranty_defect_threshold: f64,
10175}
10176
10177fn default_warranty_defect_threshold() -> f64 {
10178    0.01
10179}
10180
10181impl Default for ManufacturingCostAccountingConfig {
10182    fn default() -> Self {
10183        Self {
10184            enabled: true,
10185            variance_accounts_enabled: true,
10186            warranty_provisions_enabled: true,
10187            warranty_defect_threshold: 0.01,
10188        }
10189    }
10190}
10191
10192fn default_bom_depth() -> u32 {
10193    4
10194}
10195
10196fn default_production_order_types() -> Vec<String> {
10197    vec![
10198        "standard".to_string(),
10199        "rework".to_string(),
10200        "prototype".to_string(),
10201    ]
10202}
10203
10204fn default_supplier_tiers() -> u32 {
10205    2
10206}
10207
10208fn default_cost_frequency() -> String {
10209    "quarterly".to_string()
10210}
10211
10212fn default_yield_rate() -> f64 {
10213    0.97
10214}
10215
10216fn default_scrap_threshold() -> f64 {
10217    0.03
10218}
10219
10220impl Default for ManufacturingConfig {
10221    fn default() -> Self {
10222        Self {
10223            enabled: false,
10224            bom_depth: default_bom_depth(),
10225            just_in_time: false,
10226            production_order_types: default_production_order_types(),
10227            quality_framework: Some("ISO_9001".to_string()),
10228            supplier_tiers: default_supplier_tiers(),
10229            standard_cost_frequency: default_cost_frequency(),
10230            target_yield_rate: default_yield_rate(),
10231            scrap_alert_threshold: default_scrap_threshold(),
10232            anomaly_rates: ManufacturingAnomalyRates::default(),
10233            cost_accounting: ManufacturingCostAccountingConfig::default(),
10234        }
10235    }
10236}
10237
10238/// Manufacturing anomaly injection rates.
10239#[derive(Debug, Clone, Serialize, Deserialize)]
10240pub struct ManufacturingAnomalyRates {
10241    /// Yield manipulation rate.
10242    #[serde(default = "default_mfg_yield_rate")]
10243    pub yield_manipulation: f64,
10244
10245    /// Labor misallocation rate.
10246    #[serde(default = "default_mfg_labor_rate")]
10247    pub labor_misallocation: f64,
10248
10249    /// Phantom production rate.
10250    #[serde(default = "default_mfg_phantom_rate")]
10251    pub phantom_production: f64,
10252
10253    /// Standard cost manipulation rate.
10254    #[serde(default = "default_mfg_cost_rate")]
10255    pub standard_cost_manipulation: f64,
10256
10257    /// Inventory fraud rate.
10258    #[serde(default = "default_mfg_inventory_rate")]
10259    pub inventory_fraud: f64,
10260}
10261
10262fn default_mfg_yield_rate() -> f64 {
10263    0.015
10264}
10265
10266fn default_mfg_labor_rate() -> f64 {
10267    0.02
10268}
10269
10270fn default_mfg_phantom_rate() -> f64 {
10271    0.005
10272}
10273
10274fn default_mfg_cost_rate() -> f64 {
10275    0.01
10276}
10277
10278fn default_mfg_inventory_rate() -> f64 {
10279    0.008
10280}
10281
10282impl Default for ManufacturingAnomalyRates {
10283    fn default() -> Self {
10284        Self {
10285            yield_manipulation: default_mfg_yield_rate(),
10286            labor_misallocation: default_mfg_labor_rate(),
10287            phantom_production: default_mfg_phantom_rate(),
10288            standard_cost_manipulation: default_mfg_cost_rate(),
10289            inventory_fraud: default_mfg_inventory_rate(),
10290        }
10291    }
10292}
10293
10294/// Retail industry configuration.
10295#[derive(Debug, Clone, Serialize, Deserialize)]
10296pub struct RetailConfig {
10297    /// Enable retail-specific generation.
10298    #[serde(default)]
10299    pub enabled: bool,
10300
10301    /// Store type distribution.
10302    #[serde(default)]
10303    pub store_types: RetailStoreTypeConfig,
10304
10305    /// Average daily transactions per store.
10306    #[serde(default = "default_retail_daily_txns")]
10307    pub avg_daily_transactions: u32,
10308
10309    /// Enable loss prevention tracking.
10310    #[serde(default = "default_true")]
10311    pub loss_prevention: bool,
10312
10313    /// Shrinkage rate (0.01-0.03 typical).
10314    #[serde(default = "default_shrinkage_rate")]
10315    pub shrinkage_rate: f64,
10316
10317    /// Retail anomaly injection rates.
10318    #[serde(default)]
10319    pub anomaly_rates: RetailAnomalyRates,
10320}
10321
10322fn default_retail_daily_txns() -> u32 {
10323    500
10324}
10325
10326fn default_shrinkage_rate() -> f64 {
10327    0.015
10328}
10329
10330impl Default for RetailConfig {
10331    fn default() -> Self {
10332        Self {
10333            enabled: false,
10334            store_types: RetailStoreTypeConfig::default(),
10335            avg_daily_transactions: default_retail_daily_txns(),
10336            loss_prevention: true,
10337            shrinkage_rate: default_shrinkage_rate(),
10338            anomaly_rates: RetailAnomalyRates::default(),
10339        }
10340    }
10341}
10342
10343/// Retail store type distribution.
10344#[derive(Debug, Clone, Serialize, Deserialize)]
10345pub struct RetailStoreTypeConfig {
10346    /// Percentage of flagship stores.
10347    #[serde(default = "default_flagship_pct")]
10348    pub flagship: f64,
10349
10350    /// Percentage of regional stores.
10351    #[serde(default = "default_regional_pct")]
10352    pub regional: f64,
10353
10354    /// Percentage of outlet stores.
10355    #[serde(default = "default_outlet_pct")]
10356    pub outlet: f64,
10357
10358    /// Percentage of e-commerce.
10359    #[serde(default = "default_ecommerce_pct")]
10360    pub ecommerce: f64,
10361}
10362
10363fn default_flagship_pct() -> f64 {
10364    0.10
10365}
10366
10367fn default_regional_pct() -> f64 {
10368    0.50
10369}
10370
10371fn default_outlet_pct() -> f64 {
10372    0.25
10373}
10374
10375fn default_ecommerce_pct() -> f64 {
10376    0.15
10377}
10378
10379impl Default for RetailStoreTypeConfig {
10380    fn default() -> Self {
10381        Self {
10382            flagship: default_flagship_pct(),
10383            regional: default_regional_pct(),
10384            outlet: default_outlet_pct(),
10385            ecommerce: default_ecommerce_pct(),
10386        }
10387    }
10388}
10389
10390/// Retail anomaly injection rates.
10391#[derive(Debug, Clone, Serialize, Deserialize)]
10392pub struct RetailAnomalyRates {
10393    /// Sweethearting rate.
10394    #[serde(default = "default_sweethearting_rate")]
10395    pub sweethearting: f64,
10396
10397    /// Skimming rate.
10398    #[serde(default = "default_skimming_rate")]
10399    pub skimming: f64,
10400
10401    /// Refund fraud rate.
10402    #[serde(default = "default_refund_fraud_rate")]
10403    pub refund_fraud: f64,
10404
10405    /// Void abuse rate.
10406    #[serde(default = "default_void_abuse_rate")]
10407    pub void_abuse: f64,
10408
10409    /// Gift card fraud rate.
10410    #[serde(default = "default_gift_card_rate")]
10411    pub gift_card_fraud: f64,
10412
10413    /// Vendor kickback rate.
10414    #[serde(default = "default_retail_kickback_rate")]
10415    pub vendor_kickback: f64,
10416}
10417
10418fn default_sweethearting_rate() -> f64 {
10419    0.02
10420}
10421
10422fn default_skimming_rate() -> f64 {
10423    0.005
10424}
10425
10426fn default_refund_fraud_rate() -> f64 {
10427    0.015
10428}
10429
10430fn default_void_abuse_rate() -> f64 {
10431    0.01
10432}
10433
10434fn default_gift_card_rate() -> f64 {
10435    0.008
10436}
10437
10438fn default_retail_kickback_rate() -> f64 {
10439    0.003
10440}
10441
10442impl Default for RetailAnomalyRates {
10443    fn default() -> Self {
10444        Self {
10445            sweethearting: default_sweethearting_rate(),
10446            skimming: default_skimming_rate(),
10447            refund_fraud: default_refund_fraud_rate(),
10448            void_abuse: default_void_abuse_rate(),
10449            gift_card_fraud: default_gift_card_rate(),
10450            vendor_kickback: default_retail_kickback_rate(),
10451        }
10452    }
10453}
10454
10455/// Healthcare industry configuration.
10456#[derive(Debug, Clone, Serialize, Deserialize)]
10457pub struct HealthcareConfig {
10458    /// Enable healthcare-specific generation.
10459    #[serde(default)]
10460    pub enabled: bool,
10461
10462    /// Healthcare facility type.
10463    #[serde(default = "default_facility_type")]
10464    pub facility_type: String,
10465
10466    /// Payer mix distribution.
10467    #[serde(default)]
10468    pub payer_mix: HealthcarePayerMix,
10469
10470    /// Coding systems enabled.
10471    #[serde(default)]
10472    pub coding_systems: HealthcareCodingSystems,
10473
10474    /// Healthcare compliance settings.
10475    #[serde(default)]
10476    pub compliance: HealthcareComplianceConfig,
10477
10478    /// Average daily encounters.
10479    #[serde(default = "default_daily_encounters")]
10480    pub avg_daily_encounters: u32,
10481
10482    /// Average charges per encounter.
10483    #[serde(default = "default_charges_per_encounter")]
10484    pub avg_charges_per_encounter: u32,
10485
10486    /// Denial rate (0.0-1.0).
10487    #[serde(default = "default_hc_denial_rate")]
10488    pub denial_rate: f64,
10489
10490    /// Bad debt rate (0.0-1.0).
10491    #[serde(default = "default_hc_bad_debt_rate")]
10492    pub bad_debt_rate: f64,
10493
10494    /// Charity care rate (0.0-1.0).
10495    #[serde(default = "default_hc_charity_care_rate")]
10496    pub charity_care_rate: f64,
10497
10498    /// Healthcare anomaly injection rates.
10499    #[serde(default)]
10500    pub anomaly_rates: HealthcareAnomalyRates,
10501}
10502
10503fn default_facility_type() -> String {
10504    "hospital".to_string()
10505}
10506
10507fn default_daily_encounters() -> u32 {
10508    150
10509}
10510
10511fn default_charges_per_encounter() -> u32 {
10512    8
10513}
10514
10515fn default_hc_denial_rate() -> f64 {
10516    0.05
10517}
10518
10519fn default_hc_bad_debt_rate() -> f64 {
10520    0.03
10521}
10522
10523fn default_hc_charity_care_rate() -> f64 {
10524    0.02
10525}
10526
10527impl Default for HealthcareConfig {
10528    fn default() -> Self {
10529        Self {
10530            enabled: false,
10531            facility_type: default_facility_type(),
10532            payer_mix: HealthcarePayerMix::default(),
10533            coding_systems: HealthcareCodingSystems::default(),
10534            compliance: HealthcareComplianceConfig::default(),
10535            avg_daily_encounters: default_daily_encounters(),
10536            avg_charges_per_encounter: default_charges_per_encounter(),
10537            denial_rate: default_hc_denial_rate(),
10538            bad_debt_rate: default_hc_bad_debt_rate(),
10539            charity_care_rate: default_hc_charity_care_rate(),
10540            anomaly_rates: HealthcareAnomalyRates::default(),
10541        }
10542    }
10543}
10544
10545/// Healthcare payer mix distribution.
10546#[derive(Debug, Clone, Serialize, Deserialize)]
10547pub struct HealthcarePayerMix {
10548    /// Medicare percentage.
10549    #[serde(default = "default_medicare_pct")]
10550    pub medicare: f64,
10551
10552    /// Medicaid percentage.
10553    #[serde(default = "default_medicaid_pct")]
10554    pub medicaid: f64,
10555
10556    /// Commercial insurance percentage.
10557    #[serde(default = "default_commercial_pct")]
10558    pub commercial: f64,
10559
10560    /// Self-pay percentage.
10561    #[serde(default = "default_self_pay_pct")]
10562    pub self_pay: f64,
10563}
10564
10565fn default_medicare_pct() -> f64 {
10566    0.40
10567}
10568
10569fn default_medicaid_pct() -> f64 {
10570    0.20
10571}
10572
10573fn default_commercial_pct() -> f64 {
10574    0.30
10575}
10576
10577fn default_self_pay_pct() -> f64 {
10578    0.10
10579}
10580
10581impl Default for HealthcarePayerMix {
10582    fn default() -> Self {
10583        Self {
10584            medicare: default_medicare_pct(),
10585            medicaid: default_medicaid_pct(),
10586            commercial: default_commercial_pct(),
10587            self_pay: default_self_pay_pct(),
10588        }
10589    }
10590}
10591
10592/// Healthcare coding systems configuration.
10593#[derive(Debug, Clone, Serialize, Deserialize)]
10594pub struct HealthcareCodingSystems {
10595    /// Enable ICD-10 diagnosis coding.
10596    #[serde(default = "default_true")]
10597    pub icd10: bool,
10598
10599    /// Enable CPT procedure coding.
10600    #[serde(default = "default_true")]
10601    pub cpt: bool,
10602
10603    /// Enable DRG grouping.
10604    #[serde(default = "default_true")]
10605    pub drg: bool,
10606
10607    /// Enable HCPCS Level II coding.
10608    #[serde(default = "default_true")]
10609    pub hcpcs: bool,
10610
10611    /// Enable revenue codes.
10612    #[serde(default = "default_true")]
10613    pub revenue_codes: bool,
10614}
10615
10616impl Default for HealthcareCodingSystems {
10617    fn default() -> Self {
10618        Self {
10619            icd10: true,
10620            cpt: true,
10621            drg: true,
10622            hcpcs: true,
10623            revenue_codes: true,
10624        }
10625    }
10626}
10627
10628/// Healthcare compliance configuration.
10629#[derive(Debug, Clone, Serialize, Deserialize)]
10630pub struct HealthcareComplianceConfig {
10631    /// Enable HIPAA compliance.
10632    #[serde(default = "default_true")]
10633    pub hipaa: bool,
10634
10635    /// Enable Stark Law compliance.
10636    #[serde(default = "default_true")]
10637    pub stark_law: bool,
10638
10639    /// Enable Anti-Kickback Statute compliance.
10640    #[serde(default = "default_true")]
10641    pub anti_kickback: bool,
10642
10643    /// Enable False Claims Act compliance.
10644    #[serde(default = "default_true")]
10645    pub false_claims_act: bool,
10646
10647    /// Enable EMTALA compliance (for hospitals).
10648    #[serde(default = "default_true")]
10649    pub emtala: bool,
10650}
10651
10652impl Default for HealthcareComplianceConfig {
10653    fn default() -> Self {
10654        Self {
10655            hipaa: true,
10656            stark_law: true,
10657            anti_kickback: true,
10658            false_claims_act: true,
10659            emtala: true,
10660        }
10661    }
10662}
10663
10664/// Healthcare anomaly injection rates.
10665#[derive(Debug, Clone, Serialize, Deserialize)]
10666pub struct HealthcareAnomalyRates {
10667    /// Upcoding rate.
10668    #[serde(default = "default_upcoding_rate")]
10669    pub upcoding: f64,
10670
10671    /// Unbundling rate.
10672    #[serde(default = "default_unbundling_rate")]
10673    pub unbundling: f64,
10674
10675    /// Phantom billing rate.
10676    #[serde(default = "default_phantom_billing_rate")]
10677    pub phantom_billing: f64,
10678
10679    /// Kickback rate.
10680    #[serde(default = "default_healthcare_kickback_rate")]
10681    pub kickbacks: f64,
10682
10683    /// Duplicate billing rate.
10684    #[serde(default = "default_duplicate_billing_rate")]
10685    pub duplicate_billing: f64,
10686
10687    /// Medical necessity abuse rate.
10688    #[serde(default = "default_med_necessity_rate")]
10689    pub medical_necessity_abuse: f64,
10690}
10691
10692fn default_upcoding_rate() -> f64 {
10693    0.02
10694}
10695
10696fn default_unbundling_rate() -> f64 {
10697    0.015
10698}
10699
10700fn default_phantom_billing_rate() -> f64 {
10701    0.005
10702}
10703
10704fn default_healthcare_kickback_rate() -> f64 {
10705    0.003
10706}
10707
10708fn default_duplicate_billing_rate() -> f64 {
10709    0.008
10710}
10711
10712fn default_med_necessity_rate() -> f64 {
10713    0.01
10714}
10715
10716impl Default for HealthcareAnomalyRates {
10717    fn default() -> Self {
10718        Self {
10719            upcoding: default_upcoding_rate(),
10720            unbundling: default_unbundling_rate(),
10721            phantom_billing: default_phantom_billing_rate(),
10722            kickbacks: default_healthcare_kickback_rate(),
10723            duplicate_billing: default_duplicate_billing_rate(),
10724            medical_necessity_abuse: default_med_necessity_rate(),
10725        }
10726    }
10727}
10728
10729/// Technology industry configuration.
10730#[derive(Debug, Clone, Serialize, Deserialize)]
10731pub struct TechnologyConfig {
10732    /// Enable technology-specific generation.
10733    #[serde(default)]
10734    pub enabled: bool,
10735
10736    /// Revenue model type.
10737    #[serde(default = "default_revenue_model")]
10738    pub revenue_model: String,
10739
10740    /// Subscription revenue percentage (for SaaS).
10741    #[serde(default = "default_subscription_pct")]
10742    pub subscription_revenue_pct: f64,
10743
10744    /// License revenue percentage.
10745    #[serde(default = "default_license_pct")]
10746    pub license_revenue_pct: f64,
10747
10748    /// Services revenue percentage.
10749    #[serde(default = "default_services_pct")]
10750    pub services_revenue_pct: f64,
10751
10752    /// R&D capitalization settings.
10753    #[serde(default)]
10754    pub rd_capitalization: RdCapitalizationConfig,
10755
10756    /// Technology anomaly injection rates.
10757    #[serde(default)]
10758    pub anomaly_rates: TechnologyAnomalyRates,
10759}
10760
10761fn default_revenue_model() -> String {
10762    "saas".to_string()
10763}
10764
10765fn default_subscription_pct() -> f64 {
10766    0.60
10767}
10768
10769fn default_license_pct() -> f64 {
10770    0.25
10771}
10772
10773fn default_services_pct() -> f64 {
10774    0.15
10775}
10776
10777impl Default for TechnologyConfig {
10778    fn default() -> Self {
10779        Self {
10780            enabled: false,
10781            revenue_model: default_revenue_model(),
10782            subscription_revenue_pct: default_subscription_pct(),
10783            license_revenue_pct: default_license_pct(),
10784            services_revenue_pct: default_services_pct(),
10785            rd_capitalization: RdCapitalizationConfig::default(),
10786            anomaly_rates: TechnologyAnomalyRates::default(),
10787        }
10788    }
10789}
10790
10791/// R&D capitalization configuration.
10792#[derive(Debug, Clone, Serialize, Deserialize)]
10793pub struct RdCapitalizationConfig {
10794    /// Enable R&D capitalization.
10795    #[serde(default = "default_true")]
10796    pub enabled: bool,
10797
10798    /// Capitalization rate (0.0-1.0).
10799    #[serde(default = "default_cap_rate")]
10800    pub capitalization_rate: f64,
10801
10802    /// Useful life in years.
10803    #[serde(default = "default_useful_life")]
10804    pub useful_life_years: u32,
10805}
10806
10807fn default_cap_rate() -> f64 {
10808    0.30
10809}
10810
10811fn default_useful_life() -> u32 {
10812    3
10813}
10814
10815impl Default for RdCapitalizationConfig {
10816    fn default() -> Self {
10817        Self {
10818            enabled: true,
10819            capitalization_rate: default_cap_rate(),
10820            useful_life_years: default_useful_life(),
10821        }
10822    }
10823}
10824
10825/// Technology anomaly injection rates.
10826#[derive(Debug, Clone, Serialize, Deserialize)]
10827pub struct TechnologyAnomalyRates {
10828    /// Premature revenue recognition rate.
10829    #[serde(default = "default_premature_rev_rate")]
10830    pub premature_revenue: f64,
10831
10832    /// Side letter abuse rate.
10833    #[serde(default = "default_side_letter_rate")]
10834    pub side_letter_abuse: f64,
10835
10836    /// Channel stuffing rate.
10837    #[serde(default = "default_channel_stuffing_rate")]
10838    pub channel_stuffing: f64,
10839
10840    /// Improper capitalization rate.
10841    #[serde(default = "default_improper_cap_rate")]
10842    pub improper_capitalization: f64,
10843}
10844
10845fn default_premature_rev_rate() -> f64 {
10846    0.015
10847}
10848
10849fn default_side_letter_rate() -> f64 {
10850    0.008
10851}
10852
10853fn default_channel_stuffing_rate() -> f64 {
10854    0.01
10855}
10856
10857fn default_improper_cap_rate() -> f64 {
10858    0.012
10859}
10860
10861impl Default for TechnologyAnomalyRates {
10862    fn default() -> Self {
10863        Self {
10864            premature_revenue: default_premature_rev_rate(),
10865            side_letter_abuse: default_side_letter_rate(),
10866            channel_stuffing: default_channel_stuffing_rate(),
10867            improper_capitalization: default_improper_cap_rate(),
10868        }
10869    }
10870}
10871
10872/// Financial services industry configuration.
10873#[derive(Debug, Clone, Serialize, Deserialize)]
10874pub struct FinancialServicesConfig {
10875    /// Enable financial services-specific generation.
10876    #[serde(default)]
10877    pub enabled: bool,
10878
10879    /// Financial institution type.
10880    #[serde(default = "default_fi_type")]
10881    pub institution_type: String,
10882
10883    /// Regulatory framework.
10884    #[serde(default = "default_fi_regulatory")]
10885    pub regulatory_framework: String,
10886
10887    /// Financial services anomaly injection rates.
10888    #[serde(default)]
10889    pub anomaly_rates: FinancialServicesAnomalyRates,
10890}
10891
10892fn default_fi_type() -> String {
10893    "commercial_bank".to_string()
10894}
10895
10896fn default_fi_regulatory() -> String {
10897    "us_banking".to_string()
10898}
10899
10900impl Default for FinancialServicesConfig {
10901    fn default() -> Self {
10902        Self {
10903            enabled: false,
10904            institution_type: default_fi_type(),
10905            regulatory_framework: default_fi_regulatory(),
10906            anomaly_rates: FinancialServicesAnomalyRates::default(),
10907        }
10908    }
10909}
10910
10911/// Financial services anomaly injection rates.
10912#[derive(Debug, Clone, Serialize, Deserialize)]
10913pub struct FinancialServicesAnomalyRates {
10914    /// Loan fraud rate.
10915    #[serde(default = "default_loan_fraud_rate")]
10916    pub loan_fraud: f64,
10917
10918    /// Trading fraud rate.
10919    #[serde(default = "default_trading_fraud_rate")]
10920    pub trading_fraud: f64,
10921
10922    /// Insurance fraud rate.
10923    #[serde(default = "default_insurance_fraud_rate")]
10924    pub insurance_fraud: f64,
10925
10926    /// Account manipulation rate.
10927    #[serde(default = "default_account_manip_rate")]
10928    pub account_manipulation: f64,
10929}
10930
10931fn default_loan_fraud_rate() -> f64 {
10932    0.01
10933}
10934
10935fn default_trading_fraud_rate() -> f64 {
10936    0.008
10937}
10938
10939fn default_insurance_fraud_rate() -> f64 {
10940    0.012
10941}
10942
10943fn default_account_manip_rate() -> f64 {
10944    0.005
10945}
10946
10947impl Default for FinancialServicesAnomalyRates {
10948    fn default() -> Self {
10949        Self {
10950            loan_fraud: default_loan_fraud_rate(),
10951            trading_fraud: default_trading_fraud_rate(),
10952            insurance_fraud: default_insurance_fraud_rate(),
10953            account_manipulation: default_account_manip_rate(),
10954        }
10955    }
10956}
10957
10958/// Professional services industry configuration.
10959#[derive(Debug, Clone, Serialize, Deserialize)]
10960pub struct ProfessionalServicesConfig {
10961    /// Enable professional services-specific generation.
10962    #[serde(default)]
10963    pub enabled: bool,
10964
10965    /// Firm type.
10966    #[serde(default = "default_firm_type")]
10967    pub firm_type: String,
10968
10969    /// Billing model.
10970    #[serde(default = "default_billing_model")]
10971    pub billing_model: String,
10972
10973    /// Average hourly rate.
10974    #[serde(default = "default_hourly_rate")]
10975    pub avg_hourly_rate: f64,
10976
10977    /// Trust account settings (for law firms).
10978    #[serde(default)]
10979    pub trust_accounting: TrustAccountingConfig,
10980
10981    /// Professional services anomaly injection rates.
10982    #[serde(default)]
10983    pub anomaly_rates: ProfessionalServicesAnomalyRates,
10984}
10985
10986fn default_firm_type() -> String {
10987    "consulting".to_string()
10988}
10989
10990fn default_billing_model() -> String {
10991    "time_and_materials".to_string()
10992}
10993
10994fn default_hourly_rate() -> f64 {
10995    250.0
10996}
10997
10998impl Default for ProfessionalServicesConfig {
10999    fn default() -> Self {
11000        Self {
11001            enabled: false,
11002            firm_type: default_firm_type(),
11003            billing_model: default_billing_model(),
11004            avg_hourly_rate: default_hourly_rate(),
11005            trust_accounting: TrustAccountingConfig::default(),
11006            anomaly_rates: ProfessionalServicesAnomalyRates::default(),
11007        }
11008    }
11009}
11010
11011/// Trust accounting configuration for law firms.
11012#[derive(Debug, Clone, Serialize, Deserialize)]
11013pub struct TrustAccountingConfig {
11014    /// Enable trust accounting.
11015    #[serde(default)]
11016    pub enabled: bool,
11017
11018    /// Require three-way reconciliation.
11019    #[serde(default = "default_true")]
11020    pub require_three_way_reconciliation: bool,
11021}
11022
11023impl Default for TrustAccountingConfig {
11024    fn default() -> Self {
11025        Self {
11026            enabled: false,
11027            require_three_way_reconciliation: true,
11028        }
11029    }
11030}
11031
11032/// Professional services anomaly injection rates.
11033#[derive(Debug, Clone, Serialize, Deserialize)]
11034pub struct ProfessionalServicesAnomalyRates {
11035    /// Time billing fraud rate.
11036    #[serde(default = "default_time_fraud_rate")]
11037    pub time_billing_fraud: f64,
11038
11039    /// Expense report fraud rate.
11040    #[serde(default = "default_expense_fraud_rate")]
11041    pub expense_fraud: f64,
11042
11043    /// Trust misappropriation rate.
11044    #[serde(default = "default_trust_misappropriation_rate")]
11045    pub trust_misappropriation: f64,
11046}
11047
11048fn default_time_fraud_rate() -> f64 {
11049    0.02
11050}
11051
11052fn default_expense_fraud_rate() -> f64 {
11053    0.015
11054}
11055
11056fn default_trust_misappropriation_rate() -> f64 {
11057    0.003
11058}
11059
11060impl Default for ProfessionalServicesAnomalyRates {
11061    fn default() -> Self {
11062        Self {
11063            time_billing_fraud: default_time_fraud_rate(),
11064            expense_fraud: default_expense_fraud_rate(),
11065            trust_misappropriation: default_trust_misappropriation_rate(),
11066        }
11067    }
11068}
11069
11070/// Fingerprint privacy configuration for extraction and synthesis.
11071///
11072/// Controls the privacy parameters used when extracting fingerprints
11073/// from sensitive data. Supports predefined levels or custom (epsilon, delta) tuples.
11074///
11075/// ```yaml
11076/// fingerprint_privacy:
11077///   level: custom
11078///   epsilon: 0.5
11079///   delta: 1.0e-5
11080///   k_anonymity: 10
11081///   composition_method: renyi_dp
11082/// ```
11083#[derive(Debug, Clone, Serialize, Deserialize)]
11084pub struct FingerprintPrivacyConfig {
11085    /// Privacy level preset. Use "custom" for user-specified epsilon/delta.
11086    #[serde(default)]
11087    pub level: String,
11088    /// Custom epsilon value (only used when level = "custom").
11089    #[serde(default = "default_epsilon")]
11090    pub epsilon: f64,
11091    /// Custom delta value for (epsilon, delta)-DP (only used with RDP/zCDP).
11092    #[serde(default = "default_delta")]
11093    pub delta: f64,
11094    /// K-anonymity threshold.
11095    #[serde(default = "default_k_anonymity")]
11096    pub k_anonymity: u32,
11097    /// Composition method: "naive", "advanced", "renyi_dp", "zcdp".
11098    #[serde(default)]
11099    pub composition_method: String,
11100}
11101
11102fn default_epsilon() -> f64 {
11103    1.0
11104}
11105
11106fn default_delta() -> f64 {
11107    1e-5
11108}
11109
11110fn default_k_anonymity() -> u32 {
11111    5
11112}
11113
11114impl Default for FingerprintPrivacyConfig {
11115    fn default() -> Self {
11116        Self {
11117            level: "standard".to_string(),
11118            epsilon: default_epsilon(),
11119            delta: default_delta(),
11120            k_anonymity: default_k_anonymity(),
11121            composition_method: "naive".to_string(),
11122        }
11123    }
11124}
11125
11126/// Quality gates configuration for pass/fail thresholds on generation runs.
11127///
11128/// ```yaml
11129/// quality_gates:
11130///   enabled: true
11131///   profile: strict  # strict, default, lenient, custom
11132///   fail_on_violation: true
11133///   custom_gates:
11134///     - name: benford_compliance
11135///       metric: benford_mad
11136///       threshold: 0.015
11137///       comparison: lte
11138/// ```
11139#[derive(Debug, Clone, Serialize, Deserialize)]
11140pub struct QualityGatesSchemaConfig {
11141    /// Enable quality gate evaluation.
11142    #[serde(default)]
11143    pub enabled: bool,
11144    /// Gate profile: "strict", "default", "lenient", or "custom".
11145    #[serde(default = "default_gate_profile_name")]
11146    pub profile: String,
11147    /// Whether to fail the generation on gate violations.
11148    #[serde(default)]
11149    pub fail_on_violation: bool,
11150    /// Custom gate definitions (used when profile = "custom").
11151    #[serde(default)]
11152    pub custom_gates: Vec<QualityGateEntry>,
11153}
11154
11155fn default_gate_profile_name() -> String {
11156    "default".to_string()
11157}
11158
11159impl Default for QualityGatesSchemaConfig {
11160    fn default() -> Self {
11161        Self {
11162            enabled: false,
11163            profile: default_gate_profile_name(),
11164            fail_on_violation: false,
11165            custom_gates: Vec::new(),
11166        }
11167    }
11168}
11169
11170/// A single quality gate entry in configuration.
11171#[derive(Debug, Clone, Serialize, Deserialize)]
11172pub struct QualityGateEntry {
11173    /// Gate name.
11174    pub name: String,
11175    /// Metric to check: benford_mad, balance_coherence, document_chain_integrity,
11176    /// correlation_preservation, temporal_consistency, privacy_mia_auc,
11177    /// completion_rate, duplicate_rate, referential_integrity, ic_match_rate.
11178    pub metric: String,
11179    /// Threshold value.
11180    pub threshold: f64,
11181    /// Upper threshold for "between" comparison.
11182    #[serde(default)]
11183    pub upper_threshold: Option<f64>,
11184    /// Comparison operator: "gte", "lte", "eq", "between".
11185    #[serde(default = "default_gate_comparison")]
11186    pub comparison: String,
11187}
11188
11189fn default_gate_comparison() -> String {
11190    "gte".to_string()
11191}
11192
11193/// Compliance configuration for regulatory requirements.
11194///
11195/// ```yaml
11196/// compliance:
11197///   content_marking:
11198///     enabled: true
11199///     format: embedded  # embedded, sidecar, both
11200///   article10_report: true
11201/// ```
11202#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11203pub struct ComplianceSchemaConfig {
11204    /// Synthetic content marking configuration (EU AI Act Article 50).
11205    #[serde(default)]
11206    pub content_marking: ContentMarkingSchemaConfig,
11207    /// Generate Article 10 data governance report.
11208    #[serde(default)]
11209    pub article10_report: bool,
11210    /// Certificate configuration for proving DP guarantees.
11211    #[serde(default)]
11212    pub certificates: CertificateSchemaConfig,
11213}
11214
11215/// Configuration for synthetic data certificates.
11216#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11217pub struct CertificateSchemaConfig {
11218    /// Whether certificate generation is enabled.
11219    #[serde(default)]
11220    pub enabled: bool,
11221    /// Environment variable name for the signing key.
11222    #[serde(default)]
11223    pub signing_key_env: Option<String>,
11224    /// Whether to include quality metrics in the certificate.
11225    #[serde(default)]
11226    pub include_quality_metrics: bool,
11227}
11228
11229/// Content marking configuration for synthetic data output.
11230#[derive(Debug, Clone, Serialize, Deserialize)]
11231pub struct ContentMarkingSchemaConfig {
11232    /// Whether content marking is enabled.
11233    #[serde(default = "default_true")]
11234    pub enabled: bool,
11235    /// Marking format: "embedded", "sidecar", or "both".
11236    #[serde(default = "default_marking_format")]
11237    pub format: String,
11238}
11239
11240fn default_marking_format() -> String {
11241    "embedded".to_string()
11242}
11243
11244impl Default for ContentMarkingSchemaConfig {
11245    fn default() -> Self {
11246        Self {
11247            enabled: true,
11248            format: default_marking_format(),
11249        }
11250    }
11251}
11252
11253/// Webhook notification configuration.
11254#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11255pub struct WebhookSchemaConfig {
11256    /// Whether webhooks are enabled.
11257    #[serde(default)]
11258    pub enabled: bool,
11259    /// Webhook endpoint configurations.
11260    #[serde(default)]
11261    pub endpoints: Vec<WebhookEndpointConfig>,
11262}
11263
11264/// Configuration for a single webhook endpoint.
11265#[derive(Debug, Clone, Serialize, Deserialize)]
11266pub struct WebhookEndpointConfig {
11267    /// Target URL for the webhook.
11268    pub url: String,
11269    /// Event types this endpoint subscribes to.
11270    #[serde(default)]
11271    pub events: Vec<String>,
11272    /// Optional secret for HMAC-SHA256 signature.
11273    #[serde(default)]
11274    pub secret: Option<String>,
11275    /// Maximum retry attempts (default: 3).
11276    #[serde(default = "default_webhook_retries")]
11277    pub max_retries: u32,
11278    /// Timeout in seconds (default: 10).
11279    #[serde(default = "default_webhook_timeout")]
11280    pub timeout_secs: u64,
11281}
11282
11283fn default_webhook_retries() -> u32 {
11284    3
11285}
11286fn default_webhook_timeout() -> u64 {
11287    10
11288}
11289
11290// ===== Enterprise Process Chain Config Structs =====
11291
11292// ----- Source-to-Pay (S2C/S2P) -----
11293
11294/// Source-to-Pay configuration covering the entire sourcing lifecycle.
11295#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11296pub struct SourceToPayConfig {
11297    /// Enable source-to-pay generation
11298    #[serde(default)]
11299    pub enabled: bool,
11300    /// Spend analysis configuration
11301    #[serde(default)]
11302    pub spend_analysis: SpendAnalysisConfig,
11303    /// Sourcing project configuration
11304    #[serde(default)]
11305    pub sourcing: SourcingConfig,
11306    /// Supplier qualification configuration
11307    #[serde(default)]
11308    pub qualification: QualificationConfig,
11309    /// RFx event configuration
11310    #[serde(default)]
11311    pub rfx: RfxConfig,
11312    /// Contract configuration
11313    #[serde(default)]
11314    pub contracts: ContractConfig,
11315    /// Catalog configuration
11316    #[serde(default)]
11317    pub catalog: CatalogConfig,
11318    /// Scorecard configuration
11319    #[serde(default)]
11320    pub scorecards: ScorecardConfig,
11321    /// P2P integration settings
11322    #[serde(default)]
11323    pub p2p_integration: P2PIntegrationConfig,
11324}
11325
11326/// Spend analysis configuration.
11327#[derive(Debug, Clone, Serialize, Deserialize)]
11328pub struct SpendAnalysisConfig {
11329    /// HHI threshold for triggering sourcing project
11330    #[serde(default = "default_hhi_threshold")]
11331    pub hhi_threshold: f64,
11332    /// Target spend coverage under contracts
11333    #[serde(default = "default_contract_coverage_target")]
11334    pub contract_coverage_target: f64,
11335}
11336
11337impl Default for SpendAnalysisConfig {
11338    fn default() -> Self {
11339        Self {
11340            hhi_threshold: default_hhi_threshold(),
11341            contract_coverage_target: default_contract_coverage_target(),
11342        }
11343    }
11344}
11345
11346fn default_hhi_threshold() -> f64 {
11347    2500.0
11348}
11349fn default_contract_coverage_target() -> f64 {
11350    0.80
11351}
11352
11353/// Sourcing project configuration.
11354#[derive(Debug, Clone, Serialize, Deserialize)]
11355pub struct SourcingConfig {
11356    /// Number of sourcing projects per year
11357    #[serde(default = "default_sourcing_projects_per_year")]
11358    pub projects_per_year: u32,
11359    /// Months before expiry to trigger renewal project
11360    #[serde(default = "default_renewal_horizon_months")]
11361    pub renewal_horizon_months: u32,
11362    /// Average project duration in months
11363    #[serde(default = "default_project_duration_months")]
11364    pub project_duration_months: u32,
11365}
11366
11367impl Default for SourcingConfig {
11368    fn default() -> Self {
11369        Self {
11370            projects_per_year: default_sourcing_projects_per_year(),
11371            renewal_horizon_months: default_renewal_horizon_months(),
11372            project_duration_months: default_project_duration_months(),
11373        }
11374    }
11375}
11376
11377fn default_sourcing_projects_per_year() -> u32 {
11378    10
11379}
11380fn default_renewal_horizon_months() -> u32 {
11381    3
11382}
11383fn default_project_duration_months() -> u32 {
11384    4
11385}
11386
11387/// Supplier qualification configuration.
11388#[derive(Debug, Clone, Serialize, Deserialize)]
11389pub struct QualificationConfig {
11390    /// Pass rate for qualification
11391    #[serde(default = "default_qualification_pass_rate")]
11392    pub pass_rate: f64,
11393    /// Qualification validity in days
11394    #[serde(default = "default_qualification_validity_days")]
11395    pub validity_days: u32,
11396    /// Financial stability weight
11397    #[serde(default = "default_financial_weight")]
11398    pub financial_weight: f64,
11399    /// Quality management weight
11400    #[serde(default = "default_quality_weight")]
11401    pub quality_weight: f64,
11402    /// Delivery performance weight
11403    #[serde(default = "default_delivery_weight")]
11404    pub delivery_weight: f64,
11405    /// Compliance weight
11406    #[serde(default = "default_compliance_weight")]
11407    pub compliance_weight: f64,
11408}
11409
11410impl Default for QualificationConfig {
11411    fn default() -> Self {
11412        Self {
11413            pass_rate: default_qualification_pass_rate(),
11414            validity_days: default_qualification_validity_days(),
11415            financial_weight: default_financial_weight(),
11416            quality_weight: default_quality_weight(),
11417            delivery_weight: default_delivery_weight(),
11418            compliance_weight: default_compliance_weight(),
11419        }
11420    }
11421}
11422
11423fn default_qualification_pass_rate() -> f64 {
11424    0.75
11425}
11426fn default_qualification_validity_days() -> u32 {
11427    365
11428}
11429fn default_financial_weight() -> f64 {
11430    0.25
11431}
11432fn default_quality_weight() -> f64 {
11433    0.30
11434}
11435fn default_delivery_weight() -> f64 {
11436    0.25
11437}
11438fn default_compliance_weight() -> f64 {
11439    0.20
11440}
11441
11442/// RFx event configuration.
11443#[derive(Debug, Clone, Serialize, Deserialize)]
11444pub struct RfxConfig {
11445    /// Spend threshold above which RFI is required before RFP
11446    #[serde(default = "default_rfi_threshold")]
11447    pub rfi_threshold: f64,
11448    /// Minimum vendors invited per RFx
11449    #[serde(default = "default_min_invited_vendors")]
11450    pub min_invited_vendors: u32,
11451    /// Maximum vendors invited per RFx
11452    #[serde(default = "default_max_invited_vendors")]
11453    pub max_invited_vendors: u32,
11454    /// Response rate (% of invited vendors that submit bids)
11455    #[serde(default = "default_response_rate")]
11456    pub response_rate: f64,
11457    /// Default price weight in evaluation
11458    #[serde(default = "default_price_weight")]
11459    pub default_price_weight: f64,
11460    /// Default quality weight in evaluation
11461    #[serde(default = "default_rfx_quality_weight")]
11462    pub default_quality_weight: f64,
11463    /// Default delivery weight in evaluation
11464    #[serde(default = "default_rfx_delivery_weight")]
11465    pub default_delivery_weight: f64,
11466}
11467
11468impl Default for RfxConfig {
11469    fn default() -> Self {
11470        Self {
11471            rfi_threshold: default_rfi_threshold(),
11472            min_invited_vendors: default_min_invited_vendors(),
11473            max_invited_vendors: default_max_invited_vendors(),
11474            response_rate: default_response_rate(),
11475            default_price_weight: default_price_weight(),
11476            default_quality_weight: default_rfx_quality_weight(),
11477            default_delivery_weight: default_rfx_delivery_weight(),
11478        }
11479    }
11480}
11481
11482fn default_rfi_threshold() -> f64 {
11483    100_000.0
11484}
11485fn default_min_invited_vendors() -> u32 {
11486    3
11487}
11488fn default_max_invited_vendors() -> u32 {
11489    8
11490}
11491fn default_response_rate() -> f64 {
11492    0.70
11493}
11494fn default_price_weight() -> f64 {
11495    0.40
11496}
11497fn default_rfx_quality_weight() -> f64 {
11498    0.35
11499}
11500fn default_rfx_delivery_weight() -> f64 {
11501    0.25
11502}
11503
11504/// Contract configuration.
11505#[derive(Debug, Clone, Serialize, Deserialize)]
11506pub struct ContractConfig {
11507    /// Minimum contract duration in months
11508    #[serde(default = "default_min_contract_months")]
11509    pub min_duration_months: u32,
11510    /// Maximum contract duration in months
11511    #[serde(default = "default_max_contract_months")]
11512    pub max_duration_months: u32,
11513    /// Auto-renewal rate
11514    #[serde(default = "default_auto_renewal_rate")]
11515    pub auto_renewal_rate: f64,
11516    /// Amendment rate (% of contracts with at least one amendment)
11517    #[serde(default = "default_amendment_rate")]
11518    pub amendment_rate: f64,
11519    /// Distribution of contract types
11520    #[serde(default)]
11521    pub type_distribution: ContractTypeDistribution,
11522}
11523
11524impl Default for ContractConfig {
11525    fn default() -> Self {
11526        Self {
11527            min_duration_months: default_min_contract_months(),
11528            max_duration_months: default_max_contract_months(),
11529            auto_renewal_rate: default_auto_renewal_rate(),
11530            amendment_rate: default_amendment_rate(),
11531            type_distribution: ContractTypeDistribution::default(),
11532        }
11533    }
11534}
11535
11536fn default_min_contract_months() -> u32 {
11537    12
11538}
11539fn default_max_contract_months() -> u32 {
11540    36
11541}
11542fn default_auto_renewal_rate() -> f64 {
11543    0.40
11544}
11545fn default_amendment_rate() -> f64 {
11546    0.20
11547}
11548
11549/// Distribution of contract types.
11550#[derive(Debug, Clone, Serialize, Deserialize)]
11551pub struct ContractTypeDistribution {
11552    /// Fixed price percentage
11553    #[serde(default = "default_fixed_price_pct")]
11554    pub fixed_price: f64,
11555    /// Blanket/framework percentage
11556    #[serde(default = "default_blanket_pct")]
11557    pub blanket: f64,
11558    /// Time and materials percentage
11559    #[serde(default = "default_time_materials_pct")]
11560    pub time_and_materials: f64,
11561    /// Service agreement percentage
11562    #[serde(default = "default_service_agreement_pct")]
11563    pub service_agreement: f64,
11564}
11565
11566impl Default for ContractTypeDistribution {
11567    fn default() -> Self {
11568        Self {
11569            fixed_price: default_fixed_price_pct(),
11570            blanket: default_blanket_pct(),
11571            time_and_materials: default_time_materials_pct(),
11572            service_agreement: default_service_agreement_pct(),
11573        }
11574    }
11575}
11576
11577fn default_fixed_price_pct() -> f64 {
11578    0.40
11579}
11580fn default_blanket_pct() -> f64 {
11581    0.30
11582}
11583fn default_time_materials_pct() -> f64 {
11584    0.15
11585}
11586fn default_service_agreement_pct() -> f64 {
11587    0.15
11588}
11589
11590/// Catalog configuration.
11591#[derive(Debug, Clone, Serialize, Deserialize)]
11592pub struct CatalogConfig {
11593    /// Percentage of catalog items marked as preferred
11594    #[serde(default = "default_preferred_vendor_flag_rate")]
11595    pub preferred_vendor_flag_rate: f64,
11596    /// Rate of materials with multiple sources in catalog
11597    #[serde(default = "default_multi_source_rate")]
11598    pub multi_source_rate: f64,
11599}
11600
11601impl Default for CatalogConfig {
11602    fn default() -> Self {
11603        Self {
11604            preferred_vendor_flag_rate: default_preferred_vendor_flag_rate(),
11605            multi_source_rate: default_multi_source_rate(),
11606        }
11607    }
11608}
11609
11610fn default_preferred_vendor_flag_rate() -> f64 {
11611    0.70
11612}
11613fn default_multi_source_rate() -> f64 {
11614    0.25
11615}
11616
11617/// Scorecard configuration.
11618#[derive(Debug, Clone, Serialize, Deserialize)]
11619pub struct ScorecardConfig {
11620    /// Scorecard review frequency (quarterly, monthly)
11621    #[serde(default = "default_scorecard_frequency")]
11622    pub frequency: String,
11623    /// On-time delivery weight in overall score
11624    #[serde(default = "default_otd_weight")]
11625    pub on_time_delivery_weight: f64,
11626    /// Quality weight in overall score
11627    #[serde(default = "default_quality_score_weight")]
11628    pub quality_weight: f64,
11629    /// Price competitiveness weight
11630    #[serde(default = "default_price_score_weight")]
11631    pub price_weight: f64,
11632    /// Responsiveness weight
11633    #[serde(default = "default_responsiveness_weight")]
11634    pub responsiveness_weight: f64,
11635    /// Grade A threshold (score >= this)
11636    #[serde(default = "default_grade_a_threshold")]
11637    pub grade_a_threshold: f64,
11638    /// Grade B threshold
11639    #[serde(default = "default_grade_b_threshold")]
11640    pub grade_b_threshold: f64,
11641    /// Grade C threshold
11642    #[serde(default = "default_grade_c_threshold")]
11643    pub grade_c_threshold: f64,
11644}
11645
11646impl Default for ScorecardConfig {
11647    fn default() -> Self {
11648        Self {
11649            frequency: default_scorecard_frequency(),
11650            on_time_delivery_weight: default_otd_weight(),
11651            quality_weight: default_quality_score_weight(),
11652            price_weight: default_price_score_weight(),
11653            responsiveness_weight: default_responsiveness_weight(),
11654            grade_a_threshold: default_grade_a_threshold(),
11655            grade_b_threshold: default_grade_b_threshold(),
11656            grade_c_threshold: default_grade_c_threshold(),
11657        }
11658    }
11659}
11660
11661fn default_scorecard_frequency() -> String {
11662    "quarterly".to_string()
11663}
11664fn default_otd_weight() -> f64 {
11665    0.30
11666}
11667fn default_quality_score_weight() -> f64 {
11668    0.30
11669}
11670fn default_price_score_weight() -> f64 {
11671    0.25
11672}
11673fn default_responsiveness_weight() -> f64 {
11674    0.15
11675}
11676fn default_grade_a_threshold() -> f64 {
11677    90.0
11678}
11679fn default_grade_b_threshold() -> f64 {
11680    75.0
11681}
11682fn default_grade_c_threshold() -> f64 {
11683    60.0
11684}
11685
11686/// P2P integration settings for contract enforcement.
11687#[derive(Debug, Clone, Serialize, Deserialize)]
11688pub struct P2PIntegrationConfig {
11689    /// Rate of off-contract (maverick) purchases
11690    #[serde(default = "default_off_contract_rate")]
11691    pub off_contract_rate: f64,
11692    /// Price tolerance for contract price validation
11693    #[serde(default = "default_price_tolerance")]
11694    pub price_tolerance: f64,
11695    /// Whether to enforce catalog ordering
11696    #[serde(default)]
11697    pub catalog_enforcement: bool,
11698}
11699
11700impl Default for P2PIntegrationConfig {
11701    fn default() -> Self {
11702        Self {
11703            off_contract_rate: default_off_contract_rate(),
11704            price_tolerance: default_price_tolerance(),
11705            catalog_enforcement: false,
11706        }
11707    }
11708}
11709
11710fn default_off_contract_rate() -> f64 {
11711    0.15
11712}
11713fn default_price_tolerance() -> f64 {
11714    0.02
11715}
11716
11717// ----- Financial Reporting -----
11718
11719/// Financial reporting configuration.
11720#[derive(Debug, Clone, Serialize, Deserialize)]
11721pub struct FinancialReportingConfig {
11722    /// Enable financial reporting generation
11723    #[serde(default)]
11724    pub enabled: bool,
11725    /// Generate balance sheet
11726    #[serde(default = "default_true")]
11727    pub generate_balance_sheet: bool,
11728    /// Generate income statement
11729    #[serde(default = "default_true")]
11730    pub generate_income_statement: bool,
11731    /// Generate cash flow statement
11732    #[serde(default = "default_true")]
11733    pub generate_cash_flow: bool,
11734    /// Generate changes in equity statement
11735    #[serde(default = "default_true")]
11736    pub generate_changes_in_equity: bool,
11737    /// Number of comparative periods
11738    #[serde(default = "default_comparative_periods")]
11739    pub comparative_periods: u32,
11740    /// Management KPIs configuration
11741    #[serde(default)]
11742    pub management_kpis: ManagementKpisConfig,
11743    /// Budget configuration
11744    #[serde(default)]
11745    pub budgets: BudgetConfig,
11746}
11747
11748impl Default for FinancialReportingConfig {
11749    fn default() -> Self {
11750        Self {
11751            enabled: false,
11752            generate_balance_sheet: true,
11753            generate_income_statement: true,
11754            generate_cash_flow: true,
11755            generate_changes_in_equity: true,
11756            comparative_periods: default_comparative_periods(),
11757            management_kpis: ManagementKpisConfig::default(),
11758            budgets: BudgetConfig::default(),
11759        }
11760    }
11761}
11762
11763fn default_comparative_periods() -> u32 {
11764    1
11765}
11766
11767/// Management KPIs configuration.
11768#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11769pub struct ManagementKpisConfig {
11770    /// Enable KPI generation
11771    #[serde(default)]
11772    pub enabled: bool,
11773    /// KPI calculation frequency (monthly, quarterly)
11774    #[serde(default = "default_kpi_frequency")]
11775    pub frequency: String,
11776}
11777
11778fn default_kpi_frequency() -> String {
11779    "monthly".to_string()
11780}
11781
11782/// Budget configuration.
11783#[derive(Debug, Clone, Serialize, Deserialize)]
11784pub struct BudgetConfig {
11785    /// Enable budget generation
11786    #[serde(default)]
11787    pub enabled: bool,
11788    /// Expected revenue growth rate for budgeting
11789    #[serde(default = "default_revenue_growth_rate")]
11790    pub revenue_growth_rate: f64,
11791    /// Expected expense inflation rate
11792    #[serde(default = "default_expense_inflation_rate")]
11793    pub expense_inflation_rate: f64,
11794    /// Random noise to add to budget vs actual
11795    #[serde(default = "default_variance_noise")]
11796    pub variance_noise: f64,
11797}
11798
11799impl Default for BudgetConfig {
11800    fn default() -> Self {
11801        Self {
11802            enabled: false,
11803            revenue_growth_rate: default_revenue_growth_rate(),
11804            expense_inflation_rate: default_expense_inflation_rate(),
11805            variance_noise: default_variance_noise(),
11806        }
11807    }
11808}
11809
11810fn default_revenue_growth_rate() -> f64 {
11811    0.05
11812}
11813fn default_expense_inflation_rate() -> f64 {
11814    0.03
11815}
11816fn default_variance_noise() -> f64 {
11817    0.10
11818}
11819
11820// ----- HR Configuration -----
11821
11822/// HR (Hire-to-Retire) process configuration.
11823#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11824pub struct HrConfig {
11825    /// Enable HR generation
11826    #[serde(default)]
11827    pub enabled: bool,
11828    /// Payroll configuration
11829    #[serde(default)]
11830    pub payroll: PayrollConfig,
11831    /// Time and attendance configuration
11832    #[serde(default)]
11833    pub time_attendance: TimeAttendanceConfig,
11834    /// Expense management configuration
11835    #[serde(default)]
11836    pub expenses: ExpenseConfig,
11837}
11838
11839/// Payroll configuration.
11840#[derive(Debug, Clone, Serialize, Deserialize)]
11841pub struct PayrollConfig {
11842    /// Enable payroll generation
11843    #[serde(default = "default_true")]
11844    pub enabled: bool,
11845    /// Pay frequency (monthly, biweekly, weekly)
11846    #[serde(default = "default_pay_frequency")]
11847    pub pay_frequency: String,
11848    /// Salary ranges by job level
11849    #[serde(default)]
11850    pub salary_ranges: PayrollSalaryRanges,
11851    /// Effective tax rates
11852    #[serde(default)]
11853    pub tax_rates: PayrollTaxRates,
11854    /// Benefits enrollment rate
11855    #[serde(default = "default_benefits_enrollment_rate")]
11856    pub benefits_enrollment_rate: f64,
11857    /// Retirement plan participation rate
11858    #[serde(default = "default_retirement_participation_rate")]
11859    pub retirement_participation_rate: f64,
11860}
11861
11862impl Default for PayrollConfig {
11863    fn default() -> Self {
11864        Self {
11865            enabled: true,
11866            pay_frequency: default_pay_frequency(),
11867            salary_ranges: PayrollSalaryRanges::default(),
11868            tax_rates: PayrollTaxRates::default(),
11869            benefits_enrollment_rate: default_benefits_enrollment_rate(),
11870            retirement_participation_rate: default_retirement_participation_rate(),
11871        }
11872    }
11873}
11874
11875fn default_pay_frequency() -> String {
11876    "monthly".to_string()
11877}
11878fn default_benefits_enrollment_rate() -> f64 {
11879    0.60
11880}
11881fn default_retirement_participation_rate() -> f64 {
11882    0.45
11883}
11884
11885/// Salary ranges by job level.
11886#[derive(Debug, Clone, Serialize, Deserialize)]
11887pub struct PayrollSalaryRanges {
11888    /// Staff level min/max
11889    #[serde(default = "default_staff_min")]
11890    pub staff_min: f64,
11891    #[serde(default = "default_staff_max")]
11892    pub staff_max: f64,
11893    /// Manager level min/max
11894    #[serde(default = "default_manager_min")]
11895    pub manager_min: f64,
11896    #[serde(default = "default_manager_max")]
11897    pub manager_max: f64,
11898    /// Director level min/max
11899    #[serde(default = "default_director_min")]
11900    pub director_min: f64,
11901    #[serde(default = "default_director_max")]
11902    pub director_max: f64,
11903    /// Executive level min/max
11904    #[serde(default = "default_executive_min")]
11905    pub executive_min: f64,
11906    #[serde(default = "default_executive_max")]
11907    pub executive_max: f64,
11908}
11909
11910impl Default for PayrollSalaryRanges {
11911    fn default() -> Self {
11912        Self {
11913            staff_min: default_staff_min(),
11914            staff_max: default_staff_max(),
11915            manager_min: default_manager_min(),
11916            manager_max: default_manager_max(),
11917            director_min: default_director_min(),
11918            director_max: default_director_max(),
11919            executive_min: default_executive_min(),
11920            executive_max: default_executive_max(),
11921        }
11922    }
11923}
11924
11925fn default_staff_min() -> f64 {
11926    50_000.0
11927}
11928fn default_staff_max() -> f64 {
11929    70_000.0
11930}
11931fn default_manager_min() -> f64 {
11932    80_000.0
11933}
11934fn default_manager_max() -> f64 {
11935    120_000.0
11936}
11937fn default_director_min() -> f64 {
11938    120_000.0
11939}
11940fn default_director_max() -> f64 {
11941    180_000.0
11942}
11943fn default_executive_min() -> f64 {
11944    180_000.0
11945}
11946fn default_executive_max() -> f64 {
11947    350_000.0
11948}
11949
11950/// Effective tax rates for payroll.
11951#[derive(Debug, Clone, Serialize, Deserialize)]
11952pub struct PayrollTaxRates {
11953    /// Federal effective tax rate
11954    #[serde(default = "default_federal_rate")]
11955    pub federal_effective: f64,
11956    /// State effective tax rate
11957    #[serde(default = "default_state_rate")]
11958    pub state_effective: f64,
11959    /// FICA/social security rate
11960    #[serde(default = "default_fica_rate")]
11961    pub fica: f64,
11962}
11963
11964impl Default for PayrollTaxRates {
11965    fn default() -> Self {
11966        Self {
11967            federal_effective: default_federal_rate(),
11968            state_effective: default_state_rate(),
11969            fica: default_fica_rate(),
11970        }
11971    }
11972}
11973
11974fn default_federal_rate() -> f64 {
11975    0.22
11976}
11977fn default_state_rate() -> f64 {
11978    0.05
11979}
11980fn default_fica_rate() -> f64 {
11981    0.0765
11982}
11983
11984/// Time and attendance configuration.
11985#[derive(Debug, Clone, Serialize, Deserialize)]
11986pub struct TimeAttendanceConfig {
11987    /// Enable time tracking
11988    #[serde(default = "default_true")]
11989    pub enabled: bool,
11990    /// Overtime rate (% of employees with overtime in a period)
11991    #[serde(default = "default_overtime_rate")]
11992    pub overtime_rate: f64,
11993}
11994
11995impl Default for TimeAttendanceConfig {
11996    fn default() -> Self {
11997        Self {
11998            enabled: true,
11999            overtime_rate: default_overtime_rate(),
12000        }
12001    }
12002}
12003
12004fn default_overtime_rate() -> f64 {
12005    0.10
12006}
12007
12008/// Expense management configuration.
12009#[derive(Debug, Clone, Serialize, Deserialize)]
12010pub struct ExpenseConfig {
12011    /// Enable expense report generation
12012    #[serde(default = "default_true")]
12013    pub enabled: bool,
12014    /// Rate of employees submitting expenses per month
12015    #[serde(default = "default_expense_submission_rate")]
12016    pub submission_rate: f64,
12017    /// Rate of policy violations
12018    #[serde(default = "default_policy_violation_rate")]
12019    pub policy_violation_rate: f64,
12020}
12021
12022impl Default for ExpenseConfig {
12023    fn default() -> Self {
12024        Self {
12025            enabled: true,
12026            submission_rate: default_expense_submission_rate(),
12027            policy_violation_rate: default_policy_violation_rate(),
12028        }
12029    }
12030}
12031
12032fn default_expense_submission_rate() -> f64 {
12033    0.30
12034}
12035fn default_policy_violation_rate() -> f64 {
12036    0.08
12037}
12038
12039// ----- Manufacturing Configuration -----
12040
12041/// Manufacturing process configuration (production orders, WIP, routing).
12042#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12043pub struct ManufacturingProcessConfig {
12044    /// Enable manufacturing generation
12045    #[serde(default)]
12046    pub enabled: bool,
12047    /// Production order configuration
12048    #[serde(default)]
12049    pub production_orders: ProductionOrderConfig,
12050    /// Costing configuration
12051    #[serde(default)]
12052    pub costing: ManufacturingCostingConfig,
12053    /// Routing configuration
12054    #[serde(default)]
12055    pub routing: RoutingConfig,
12056}
12057
12058/// Production order configuration.
12059#[derive(Debug, Clone, Serialize, Deserialize)]
12060pub struct ProductionOrderConfig {
12061    /// Orders per month
12062    #[serde(default = "default_prod_orders_per_month")]
12063    pub orders_per_month: u32,
12064    /// Average batch size
12065    #[serde(default = "default_prod_avg_batch_size")]
12066    pub avg_batch_size: u32,
12067    /// Yield rate
12068    #[serde(default = "default_prod_yield_rate")]
12069    pub yield_rate: f64,
12070    /// Make-to-order rate (vs make-to-stock)
12071    #[serde(default = "default_prod_make_to_order_rate")]
12072    pub make_to_order_rate: f64,
12073    /// Rework rate
12074    #[serde(default = "default_prod_rework_rate")]
12075    pub rework_rate: f64,
12076}
12077
12078impl Default for ProductionOrderConfig {
12079    fn default() -> Self {
12080        Self {
12081            orders_per_month: default_prod_orders_per_month(),
12082            avg_batch_size: default_prod_avg_batch_size(),
12083            yield_rate: default_prod_yield_rate(),
12084            make_to_order_rate: default_prod_make_to_order_rate(),
12085            rework_rate: default_prod_rework_rate(),
12086        }
12087    }
12088}
12089
12090fn default_prod_orders_per_month() -> u32 {
12091    50
12092}
12093fn default_prod_avg_batch_size() -> u32 {
12094    100
12095}
12096fn default_prod_yield_rate() -> f64 {
12097    0.97
12098}
12099fn default_prod_make_to_order_rate() -> f64 {
12100    0.20
12101}
12102fn default_prod_rework_rate() -> f64 {
12103    0.03
12104}
12105
12106/// Manufacturing costing configuration.
12107#[derive(Debug, Clone, Serialize, Deserialize)]
12108pub struct ManufacturingCostingConfig {
12109    /// Labor rate per hour
12110    #[serde(default = "default_labor_rate")]
12111    pub labor_rate_per_hour: f64,
12112    /// Overhead application rate (multiplier on direct labor)
12113    #[serde(default = "default_overhead_rate")]
12114    pub overhead_rate: f64,
12115    /// Standard cost update frequency
12116    #[serde(default = "default_cost_update_frequency")]
12117    pub standard_cost_update_frequency: String,
12118}
12119
12120impl Default for ManufacturingCostingConfig {
12121    fn default() -> Self {
12122        Self {
12123            labor_rate_per_hour: default_labor_rate(),
12124            overhead_rate: default_overhead_rate(),
12125            standard_cost_update_frequency: default_cost_update_frequency(),
12126        }
12127    }
12128}
12129
12130fn default_labor_rate() -> f64 {
12131    35.0
12132}
12133fn default_overhead_rate() -> f64 {
12134    1.50
12135}
12136fn default_cost_update_frequency() -> String {
12137    "quarterly".to_string()
12138}
12139
12140/// Routing configuration for production operations.
12141#[derive(Debug, Clone, Serialize, Deserialize)]
12142pub struct RoutingConfig {
12143    /// Average number of operations per routing
12144    #[serde(default = "default_avg_operations")]
12145    pub avg_operations: u32,
12146    /// Average setup time in hours
12147    #[serde(default = "default_setup_time")]
12148    pub setup_time_hours: f64,
12149    /// Run time variation coefficient
12150    #[serde(default = "default_run_time_variation")]
12151    pub run_time_variation: f64,
12152}
12153
12154impl Default for RoutingConfig {
12155    fn default() -> Self {
12156        Self {
12157            avg_operations: default_avg_operations(),
12158            setup_time_hours: default_setup_time(),
12159            run_time_variation: default_run_time_variation(),
12160        }
12161    }
12162}
12163
12164fn default_avg_operations() -> u32 {
12165    4
12166}
12167fn default_setup_time() -> f64 {
12168    1.5
12169}
12170fn default_run_time_variation() -> f64 {
12171    0.15
12172}
12173
12174// ----- Sales Quote Configuration -----
12175
12176/// Sales quote (quote-to-order) pipeline configuration.
12177#[derive(Debug, Clone, Serialize, Deserialize)]
12178pub struct SalesQuoteConfig {
12179    /// Enable sales quote generation
12180    #[serde(default)]
12181    pub enabled: bool,
12182    /// Quotes per month
12183    #[serde(default = "default_quotes_per_month")]
12184    pub quotes_per_month: u32,
12185    /// Win rate (fraction of quotes that convert to orders)
12186    #[serde(default = "default_quote_win_rate")]
12187    pub win_rate: f64,
12188    /// Average quote validity in days
12189    #[serde(default = "default_quote_validity_days")]
12190    pub validity_days: u32,
12191}
12192
12193impl Default for SalesQuoteConfig {
12194    fn default() -> Self {
12195        Self {
12196            enabled: false,
12197            quotes_per_month: default_quotes_per_month(),
12198            win_rate: default_quote_win_rate(),
12199            validity_days: default_quote_validity_days(),
12200        }
12201    }
12202}
12203
12204fn default_quotes_per_month() -> u32 {
12205    30
12206}
12207fn default_quote_win_rate() -> f64 {
12208    0.35
12209}
12210fn default_quote_validity_days() -> u32 {
12211    30
12212}
12213
12214// =============================================================================
12215// Tax Accounting Configuration
12216// =============================================================================
12217
12218/// Tax accounting configuration.
12219///
12220/// Controls generation of tax-related data including VAT/GST, sales tax,
12221/// withholding tax, tax provisions, and payroll tax across multiple jurisdictions.
12222#[derive(Debug, Clone, Serialize, Deserialize)]
12223pub struct TaxConfig {
12224    /// Whether tax generation is enabled.
12225    #[serde(default)]
12226    pub enabled: bool,
12227    /// Tax jurisdiction configuration.
12228    #[serde(default)]
12229    pub jurisdictions: TaxJurisdictionConfig,
12230    /// VAT/GST configuration.
12231    #[serde(default)]
12232    pub vat_gst: VatGstConfig,
12233    /// Sales tax configuration.
12234    #[serde(default)]
12235    pub sales_tax: SalesTaxConfig,
12236    /// Withholding tax configuration.
12237    #[serde(default)]
12238    pub withholding: WithholdingTaxSchemaConfig,
12239    /// Tax provision configuration.
12240    #[serde(default)]
12241    pub provisions: TaxProvisionSchemaConfig,
12242    /// Payroll tax configuration.
12243    #[serde(default)]
12244    pub payroll_tax: PayrollTaxSchemaConfig,
12245    /// Anomaly injection rate for tax data (0.0 to 1.0).
12246    #[serde(default = "default_tax_anomaly_rate")]
12247    pub anomaly_rate: f64,
12248}
12249
12250fn default_tax_anomaly_rate() -> f64 {
12251    0.03
12252}
12253
12254impl Default for TaxConfig {
12255    fn default() -> Self {
12256        Self {
12257            enabled: false,
12258            jurisdictions: TaxJurisdictionConfig::default(),
12259            vat_gst: VatGstConfig::default(),
12260            sales_tax: SalesTaxConfig::default(),
12261            withholding: WithholdingTaxSchemaConfig::default(),
12262            provisions: TaxProvisionSchemaConfig::default(),
12263            payroll_tax: PayrollTaxSchemaConfig::default(),
12264            anomaly_rate: default_tax_anomaly_rate(),
12265        }
12266    }
12267}
12268
12269/// Tax jurisdiction configuration.
12270///
12271/// Specifies which countries and subnational jurisdictions to include
12272/// when generating tax data.
12273#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12274pub struct TaxJurisdictionConfig {
12275    /// List of country codes to include (e.g., ["US", "DE", "GB"]).
12276    #[serde(default)]
12277    pub countries: Vec<String>,
12278    /// Whether to include subnational jurisdictions (e.g., US states, Canadian provinces).
12279    #[serde(default)]
12280    pub include_subnational: bool,
12281}
12282
12283/// VAT/GST configuration.
12284///
12285/// Controls generation of Value Added Tax / Goods and Services Tax data,
12286/// including standard and reduced rates, exempt categories, and reverse charge.
12287#[derive(Debug, Clone, Serialize, Deserialize)]
12288pub struct VatGstConfig {
12289    /// Whether VAT/GST generation is enabled.
12290    #[serde(default)]
12291    pub enabled: bool,
12292    /// Standard VAT/GST rates by country code (e.g., {"DE": 0.19, "GB": 0.20}).
12293    #[serde(default)]
12294    pub standard_rates: std::collections::HashMap<String, f64>,
12295    /// Reduced VAT/GST rates by country code (e.g., {"DE": 0.07, "GB": 0.05}).
12296    #[serde(default)]
12297    pub reduced_rates: std::collections::HashMap<String, f64>,
12298    /// Categories exempt from VAT/GST (e.g., ["financial_services", "healthcare"]).
12299    #[serde(default)]
12300    pub exempt_categories: Vec<String>,
12301    /// Whether to apply reverse charge mechanism for cross-border B2B transactions.
12302    #[serde(default = "default_true")]
12303    pub reverse_charge: bool,
12304}
12305
12306impl Default for VatGstConfig {
12307    fn default() -> Self {
12308        Self {
12309            enabled: false,
12310            standard_rates: std::collections::HashMap::new(),
12311            reduced_rates: std::collections::HashMap::new(),
12312            exempt_categories: Vec::new(),
12313            reverse_charge: true,
12314        }
12315    }
12316}
12317
12318/// Sales tax configuration.
12319///
12320/// Controls generation of US-style sales tax data including nexus determination.
12321#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12322pub struct SalesTaxConfig {
12323    /// Whether sales tax generation is enabled.
12324    #[serde(default)]
12325    pub enabled: bool,
12326    /// US states where the company has nexus (e.g., ["CA", "NY", "TX"]).
12327    #[serde(default)]
12328    pub nexus_states: Vec<String>,
12329}
12330
12331/// Withholding tax configuration.
12332///
12333/// Controls generation of withholding tax data for cross-border payments,
12334/// including treaty network and rate overrides.
12335#[derive(Debug, Clone, Serialize, Deserialize)]
12336pub struct WithholdingTaxSchemaConfig {
12337    /// Whether withholding tax generation is enabled.
12338    #[serde(default)]
12339    pub enabled: bool,
12340    /// Whether to simulate a treaty network with reduced rates.
12341    #[serde(default = "default_true")]
12342    pub treaty_network: bool,
12343    /// Default withholding tax rate for non-treaty countries (0.0 to 1.0).
12344    #[serde(default = "default_withholding_rate")]
12345    pub default_rate: f64,
12346    /// Reduced withholding tax rate for treaty countries (0.0 to 1.0).
12347    #[serde(default = "default_treaty_reduced_rate")]
12348    pub treaty_reduced_rate: f64,
12349}
12350
12351fn default_withholding_rate() -> f64 {
12352    0.30
12353}
12354
12355fn default_treaty_reduced_rate() -> f64 {
12356    0.15
12357}
12358
12359impl Default for WithholdingTaxSchemaConfig {
12360    fn default() -> Self {
12361        Self {
12362            enabled: false,
12363            treaty_network: true,
12364            default_rate: default_withholding_rate(),
12365            treaty_reduced_rate: default_treaty_reduced_rate(),
12366        }
12367    }
12368}
12369
12370/// Tax provision configuration.
12371///
12372/// Controls generation of tax provision data including statutory rates
12373/// and uncertain tax positions (ASC 740 / IAS 12).
12374#[derive(Debug, Clone, Serialize, Deserialize)]
12375pub struct TaxProvisionSchemaConfig {
12376    /// Whether tax provision generation is enabled.
12377    /// Defaults to true when tax is enabled, as provisions are typically required.
12378    #[serde(default = "default_true")]
12379    pub enabled: bool,
12380    /// Statutory corporate tax rate (0.0 to 1.0).
12381    #[serde(default = "default_statutory_rate")]
12382    pub statutory_rate: f64,
12383    /// Whether to generate uncertain tax positions (FIN 48 / IFRIC 23).
12384    #[serde(default = "default_true")]
12385    pub uncertain_positions: bool,
12386}
12387
12388fn default_statutory_rate() -> f64 {
12389    0.21
12390}
12391
12392impl Default for TaxProvisionSchemaConfig {
12393    fn default() -> Self {
12394        Self {
12395            enabled: true,
12396            statutory_rate: default_statutory_rate(),
12397            uncertain_positions: true,
12398        }
12399    }
12400}
12401
12402/// Payroll tax configuration.
12403///
12404/// Controls generation of payroll tax data (employer/employee contributions,
12405/// social security, Medicare, etc.).
12406#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12407pub struct PayrollTaxSchemaConfig {
12408    /// Whether payroll tax generation is enabled.
12409    #[serde(default)]
12410    pub enabled: bool,
12411}
12412
12413// ---------------------------------------------------------------------------
12414// Treasury & Cash Management Configuration
12415// ---------------------------------------------------------------------------
12416
12417/// Treasury and cash management configuration.
12418///
12419/// Controls generation of cash positions, forecasts, pooling, hedging
12420/// instruments (ASC 815 / IFRS 9), debt instruments with covenants,
12421/// bank guarantees, and intercompany netting runs.
12422#[derive(Debug, Clone, Serialize, Deserialize)]
12423pub struct TreasuryConfig {
12424    /// Whether treasury generation is enabled.
12425    #[serde(default)]
12426    pub enabled: bool,
12427    /// Cash positioning configuration.
12428    #[serde(default)]
12429    pub cash_positioning: CashPositioningConfig,
12430    /// Cash forecasting configuration.
12431    #[serde(default)]
12432    pub cash_forecasting: CashForecastingConfig,
12433    /// Cash pooling configuration.
12434    #[serde(default)]
12435    pub cash_pooling: CashPoolingConfig,
12436    /// Hedging configuration (FX forwards, IR swaps, etc.).
12437    #[serde(default)]
12438    pub hedging: HedgingSchemaConfig,
12439    /// Debt instrument and covenant configuration.
12440    #[serde(default)]
12441    pub debt: DebtSchemaConfig,
12442    /// Intercompany netting configuration.
12443    #[serde(default)]
12444    pub netting: NettingSchemaConfig,
12445    /// Bank guarantee / letter of credit configuration.
12446    #[serde(default)]
12447    pub bank_guarantees: BankGuaranteeSchemaConfig,
12448    /// Anomaly injection rate for treasury data (0.0 to 1.0).
12449    #[serde(default = "default_treasury_anomaly_rate")]
12450    pub anomaly_rate: f64,
12451}
12452
12453fn default_treasury_anomaly_rate() -> f64 {
12454    0.02
12455}
12456
12457impl Default for TreasuryConfig {
12458    fn default() -> Self {
12459        Self {
12460            enabled: false,
12461            cash_positioning: CashPositioningConfig::default(),
12462            cash_forecasting: CashForecastingConfig::default(),
12463            cash_pooling: CashPoolingConfig::default(),
12464            hedging: HedgingSchemaConfig::default(),
12465            debt: DebtSchemaConfig::default(),
12466            netting: NettingSchemaConfig::default(),
12467            bank_guarantees: BankGuaranteeSchemaConfig::default(),
12468            anomaly_rate: default_treasury_anomaly_rate(),
12469        }
12470    }
12471}
12472
12473/// Cash positioning configuration.
12474///
12475/// Controls daily cash position generation per entity/bank account.
12476#[derive(Debug, Clone, Serialize, Deserialize)]
12477pub struct CashPositioningConfig {
12478    /// Whether cash positioning is enabled.
12479    #[serde(default = "default_true")]
12480    pub enabled: bool,
12481    /// Position generation frequency.
12482    #[serde(default = "default_cash_frequency")]
12483    pub frequency: String,
12484    /// Minimum cash balance policy threshold.
12485    #[serde(default = "default_minimum_balance_policy")]
12486    pub minimum_balance_policy: f64,
12487}
12488
12489fn default_cash_frequency() -> String {
12490    "daily".to_string()
12491}
12492
12493fn default_minimum_balance_policy() -> f64 {
12494    100_000.0
12495}
12496
12497impl Default for CashPositioningConfig {
12498    fn default() -> Self {
12499        Self {
12500            enabled: true,
12501            frequency: default_cash_frequency(),
12502            minimum_balance_policy: default_minimum_balance_policy(),
12503        }
12504    }
12505}
12506
12507/// Cash forecasting configuration.
12508///
12509/// Controls forward-looking cash forecast generation with probability-weighted items.
12510#[derive(Debug, Clone, Serialize, Deserialize)]
12511pub struct CashForecastingConfig {
12512    /// Whether cash forecasting is enabled.
12513    #[serde(default = "default_true")]
12514    pub enabled: bool,
12515    /// Number of days to forecast into the future.
12516    #[serde(default = "default_horizon_days")]
12517    pub horizon_days: u32,
12518    /// AR collection probability curve type ("aging" or "flat").
12519    #[serde(default = "default_ar_probability_curve")]
12520    pub ar_collection_probability_curve: String,
12521    /// Confidence interval for the forecast (0.0 to 1.0).
12522    #[serde(default = "default_confidence_interval")]
12523    pub confidence_interval: f64,
12524}
12525
12526fn default_horizon_days() -> u32 {
12527    90
12528}
12529
12530fn default_ar_probability_curve() -> String {
12531    "aging".to_string()
12532}
12533
12534fn default_confidence_interval() -> f64 {
12535    0.90
12536}
12537
12538impl Default for CashForecastingConfig {
12539    fn default() -> Self {
12540        Self {
12541            enabled: true,
12542            horizon_days: default_horizon_days(),
12543            ar_collection_probability_curve: default_ar_probability_curve(),
12544            confidence_interval: default_confidence_interval(),
12545        }
12546    }
12547}
12548
12549/// Cash pooling configuration.
12550///
12551/// Controls cash pool structure generation (physical, notional, zero-balancing).
12552#[derive(Debug, Clone, Serialize, Deserialize)]
12553pub struct CashPoolingConfig {
12554    /// Whether cash pooling is enabled.
12555    #[serde(default)]
12556    pub enabled: bool,
12557    /// Pool type: "physical_pooling", "notional_pooling", or "zero_balancing".
12558    #[serde(default = "default_pool_type")]
12559    pub pool_type: String,
12560    /// Time of day when sweeps occur (HH:MM format).
12561    #[serde(default = "default_sweep_time")]
12562    pub sweep_time: String,
12563}
12564
12565fn default_pool_type() -> String {
12566    "zero_balancing".to_string()
12567}
12568
12569fn default_sweep_time() -> String {
12570    "16:00".to_string()
12571}
12572
12573impl Default for CashPoolingConfig {
12574    fn default() -> Self {
12575        Self {
12576            enabled: false,
12577            pool_type: default_pool_type(),
12578            sweep_time: default_sweep_time(),
12579        }
12580    }
12581}
12582
12583/// Hedging configuration.
12584///
12585/// Controls generation of hedging instruments and hedge relationship designations
12586/// under ASC 815 / IFRS 9.
12587#[derive(Debug, Clone, Serialize, Deserialize)]
12588pub struct HedgingSchemaConfig {
12589    /// Whether hedging generation is enabled.
12590    #[serde(default)]
12591    pub enabled: bool,
12592    /// Target hedge ratio (0.0 to 1.0). Proportion of FX exposure to hedge.
12593    #[serde(default = "default_hedge_ratio")]
12594    pub hedge_ratio: f64,
12595    /// Types of instruments to generate (e.g., ["fx_forward", "interest_rate_swap"]).
12596    #[serde(default = "default_hedge_instruments")]
12597    pub instruments: Vec<String>,
12598    /// Whether to designate formal hedge accounting relationships.
12599    #[serde(default = "default_true")]
12600    pub hedge_accounting: bool,
12601    /// Effectiveness testing method: "dollar_offset", "regression", or "critical_terms".
12602    #[serde(default = "default_effectiveness_method")]
12603    pub effectiveness_method: String,
12604}
12605
12606fn default_hedge_ratio() -> f64 {
12607    0.75
12608}
12609
12610fn default_hedge_instruments() -> Vec<String> {
12611    vec!["fx_forward".to_string(), "interest_rate_swap".to_string()]
12612}
12613
12614fn default_effectiveness_method() -> String {
12615    "regression".to_string()
12616}
12617
12618impl Default for HedgingSchemaConfig {
12619    fn default() -> Self {
12620        Self {
12621            enabled: false,
12622            hedge_ratio: default_hedge_ratio(),
12623            instruments: default_hedge_instruments(),
12624            hedge_accounting: true,
12625            effectiveness_method: default_effectiveness_method(),
12626        }
12627    }
12628}
12629
12630/// Debt instrument configuration.
12631///
12632/// Controls generation of debt instruments (term loans, revolving credit, bonds)
12633/// with amortization schedules and financial covenants.
12634#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12635pub struct DebtSchemaConfig {
12636    /// Whether debt instrument generation is enabled.
12637    #[serde(default)]
12638    pub enabled: bool,
12639    /// Debt instrument definitions.
12640    #[serde(default)]
12641    pub instruments: Vec<DebtInstrumentDef>,
12642    /// Covenant definitions.
12643    #[serde(default)]
12644    pub covenants: Vec<CovenantDef>,
12645}
12646
12647/// Definition of a debt instrument in configuration.
12648#[derive(Debug, Clone, Serialize, Deserialize)]
12649pub struct DebtInstrumentDef {
12650    /// Instrument type: "term_loan", "revolving_credit", "bond", "commercial_paper", "bridge_loan".
12651    #[serde(rename = "type")]
12652    pub instrument_type: String,
12653    /// Principal amount (for term loans, bonds).
12654    #[serde(default)]
12655    pub principal: Option<f64>,
12656    /// Interest rate (annual, as decimal fraction).
12657    #[serde(default)]
12658    pub rate: Option<f64>,
12659    /// Maturity in months.
12660    #[serde(default)]
12661    pub maturity_months: Option<u32>,
12662    /// Facility limit (for revolving credit).
12663    #[serde(default)]
12664    pub facility: Option<f64>,
12665}
12666
12667/// Definition of a debt covenant in configuration.
12668#[derive(Debug, Clone, Serialize, Deserialize)]
12669pub struct CovenantDef {
12670    /// Covenant type: "debt_to_equity", "interest_coverage", "current_ratio",
12671    /// "net_worth", "debt_to_ebitda", "fixed_charge_coverage".
12672    #[serde(rename = "type")]
12673    pub covenant_type: String,
12674    /// Covenant threshold value.
12675    pub threshold: f64,
12676}
12677
12678/// Intercompany netting configuration.
12679///
12680/// Controls generation of multilateral netting runs.
12681#[derive(Debug, Clone, Serialize, Deserialize)]
12682pub struct NettingSchemaConfig {
12683    /// Whether netting generation is enabled.
12684    #[serde(default)]
12685    pub enabled: bool,
12686    /// Netting cycle: "daily", "weekly", or "monthly".
12687    #[serde(default = "default_netting_cycle")]
12688    pub cycle: String,
12689}
12690
12691fn default_netting_cycle() -> String {
12692    "monthly".to_string()
12693}
12694
12695impl Default for NettingSchemaConfig {
12696    fn default() -> Self {
12697        Self {
12698            enabled: false,
12699            cycle: default_netting_cycle(),
12700        }
12701    }
12702}
12703
12704/// Bank guarantee and letter of credit configuration.
12705///
12706/// Controls generation of bank guarantees, standby LCs, and performance bonds.
12707#[derive(Debug, Clone, Serialize, Deserialize)]
12708pub struct BankGuaranteeSchemaConfig {
12709    /// Whether bank guarantee generation is enabled.
12710    #[serde(default)]
12711    pub enabled: bool,
12712    /// Number of guarantees to generate.
12713    #[serde(default = "default_guarantee_count")]
12714    pub count: u32,
12715}
12716
12717fn default_guarantee_count() -> u32 {
12718    5
12719}
12720
12721impl Default for BankGuaranteeSchemaConfig {
12722    fn default() -> Self {
12723        Self {
12724            enabled: false,
12725            count: default_guarantee_count(),
12726        }
12727    }
12728}
12729
12730// ===========================================================================
12731// Project Accounting Configuration
12732// ===========================================================================
12733
12734/// Project accounting configuration.
12735///
12736/// Controls generation of project cost lines, revenue recognition,
12737/// milestones, change orders, retainage, and earned value metrics.
12738#[derive(Debug, Clone, Serialize, Deserialize)]
12739pub struct ProjectAccountingConfig {
12740    /// Whether project accounting is enabled.
12741    #[serde(default)]
12742    pub enabled: bool,
12743    /// Number of projects to generate.
12744    #[serde(default = "default_project_count")]
12745    pub project_count: u32,
12746    /// Distribution of project types (capital, internal, customer, r_and_d, maintenance, technology).
12747    #[serde(default)]
12748    pub project_types: ProjectTypeDistribution,
12749    /// WBS structure configuration.
12750    #[serde(default)]
12751    pub wbs: WbsSchemaConfig,
12752    /// Cost allocation rates (what % of source documents get project-tagged).
12753    #[serde(default)]
12754    pub cost_allocation: CostAllocationConfig,
12755    /// Revenue recognition configuration for project accounting.
12756    #[serde(default)]
12757    pub revenue_recognition: ProjectRevenueRecognitionConfig,
12758    /// Milestone configuration.
12759    #[serde(default)]
12760    pub milestones: MilestoneSchemaConfig,
12761    /// Change order configuration.
12762    #[serde(default)]
12763    pub change_orders: ChangeOrderSchemaConfig,
12764    /// Retainage configuration.
12765    #[serde(default)]
12766    pub retainage: RetainageSchemaConfig,
12767    /// Earned value management configuration.
12768    #[serde(default)]
12769    pub earned_value: EarnedValueSchemaConfig,
12770    /// Anomaly injection rate for project accounting data (0.0 to 1.0).
12771    #[serde(default = "default_project_anomaly_rate")]
12772    pub anomaly_rate: f64,
12773}
12774
12775fn default_project_count() -> u32 {
12776    10
12777}
12778
12779fn default_project_anomaly_rate() -> f64 {
12780    0.03
12781}
12782
12783impl Default for ProjectAccountingConfig {
12784    fn default() -> Self {
12785        Self {
12786            enabled: false,
12787            project_count: default_project_count(),
12788            project_types: ProjectTypeDistribution::default(),
12789            wbs: WbsSchemaConfig::default(),
12790            cost_allocation: CostAllocationConfig::default(),
12791            revenue_recognition: ProjectRevenueRecognitionConfig::default(),
12792            milestones: MilestoneSchemaConfig::default(),
12793            change_orders: ChangeOrderSchemaConfig::default(),
12794            retainage: RetainageSchemaConfig::default(),
12795            earned_value: EarnedValueSchemaConfig::default(),
12796            anomaly_rate: default_project_anomaly_rate(),
12797        }
12798    }
12799}
12800
12801/// Distribution of project types by weight.
12802#[derive(Debug, Clone, Serialize, Deserialize)]
12803pub struct ProjectTypeDistribution {
12804    /// Weight for capital projects (default 0.25).
12805    #[serde(default = "default_capital_weight")]
12806    pub capital: f64,
12807    /// Weight for internal projects (default 0.20).
12808    #[serde(default = "default_internal_weight")]
12809    pub internal: f64,
12810    /// Weight for customer projects (default 0.30).
12811    #[serde(default = "default_customer_weight")]
12812    pub customer: f64,
12813    /// Weight for R&D projects (default 0.10).
12814    #[serde(default = "default_rnd_weight")]
12815    pub r_and_d: f64,
12816    /// Weight for maintenance projects (default 0.10).
12817    #[serde(default = "default_maintenance_weight")]
12818    pub maintenance: f64,
12819    /// Weight for technology projects (default 0.05).
12820    #[serde(default = "default_technology_weight")]
12821    pub technology: f64,
12822}
12823
12824fn default_capital_weight() -> f64 {
12825    0.25
12826}
12827fn default_internal_weight() -> f64 {
12828    0.20
12829}
12830fn default_customer_weight() -> f64 {
12831    0.30
12832}
12833fn default_rnd_weight() -> f64 {
12834    0.10
12835}
12836fn default_maintenance_weight() -> f64 {
12837    0.10
12838}
12839fn default_technology_weight() -> f64 {
12840    0.05
12841}
12842
12843impl Default for ProjectTypeDistribution {
12844    fn default() -> Self {
12845        Self {
12846            capital: default_capital_weight(),
12847            internal: default_internal_weight(),
12848            customer: default_customer_weight(),
12849            r_and_d: default_rnd_weight(),
12850            maintenance: default_maintenance_weight(),
12851            technology: default_technology_weight(),
12852        }
12853    }
12854}
12855
12856/// WBS structure configuration.
12857#[derive(Debug, Clone, Serialize, Deserialize)]
12858pub struct WbsSchemaConfig {
12859    /// Maximum depth of WBS hierarchy (default 3).
12860    #[serde(default = "default_wbs_max_depth")]
12861    pub max_depth: u32,
12862    /// Minimum elements per level-1 WBS (default 2).
12863    #[serde(default = "default_wbs_min_elements")]
12864    pub min_elements_per_level: u32,
12865    /// Maximum elements per level-1 WBS (default 6).
12866    #[serde(default = "default_wbs_max_elements")]
12867    pub max_elements_per_level: u32,
12868}
12869
12870fn default_wbs_max_depth() -> u32 {
12871    3
12872}
12873fn default_wbs_min_elements() -> u32 {
12874    2
12875}
12876fn default_wbs_max_elements() -> u32 {
12877    6
12878}
12879
12880impl Default for WbsSchemaConfig {
12881    fn default() -> Self {
12882        Self {
12883            max_depth: default_wbs_max_depth(),
12884            min_elements_per_level: default_wbs_min_elements(),
12885            max_elements_per_level: default_wbs_max_elements(),
12886        }
12887    }
12888}
12889
12890/// Cost allocation rates — what fraction of each document type gets linked to a project.
12891#[derive(Debug, Clone, Serialize, Deserialize)]
12892pub struct CostAllocationConfig {
12893    /// Fraction of time entries assigned to projects (0.0 to 1.0).
12894    #[serde(default = "default_time_entry_rate")]
12895    pub time_entry_project_rate: f64,
12896    /// Fraction of expense reports assigned to projects (0.0 to 1.0).
12897    #[serde(default = "default_expense_rate")]
12898    pub expense_project_rate: f64,
12899    /// Fraction of purchase orders assigned to projects (0.0 to 1.0).
12900    #[serde(default = "default_po_rate")]
12901    pub purchase_order_project_rate: f64,
12902    /// Fraction of vendor invoices assigned to projects (0.0 to 1.0).
12903    #[serde(default = "default_vi_rate")]
12904    pub vendor_invoice_project_rate: f64,
12905}
12906
12907fn default_time_entry_rate() -> f64 {
12908    0.60
12909}
12910fn default_expense_rate() -> f64 {
12911    0.30
12912}
12913fn default_po_rate() -> f64 {
12914    0.40
12915}
12916fn default_vi_rate() -> f64 {
12917    0.35
12918}
12919
12920impl Default for CostAllocationConfig {
12921    fn default() -> Self {
12922        Self {
12923            time_entry_project_rate: default_time_entry_rate(),
12924            expense_project_rate: default_expense_rate(),
12925            purchase_order_project_rate: default_po_rate(),
12926            vendor_invoice_project_rate: default_vi_rate(),
12927        }
12928    }
12929}
12930
12931/// Revenue recognition configuration for project accounting.
12932#[derive(Debug, Clone, Serialize, Deserialize)]
12933pub struct ProjectRevenueRecognitionConfig {
12934    /// Whether revenue recognition is enabled for customer projects.
12935    #[serde(default = "default_true")]
12936    pub enabled: bool,
12937    /// Default method: "percentage_of_completion", "completed_contract", "milestone_based".
12938    #[serde(default = "default_revenue_method")]
12939    pub method: String,
12940    /// Default completion measure: "cost_to_cost", "labor_hours", "physical_completion".
12941    #[serde(default = "default_completion_measure")]
12942    pub completion_measure: String,
12943    /// Average contract value for customer projects.
12944    #[serde(default = "default_avg_contract_value")]
12945    pub avg_contract_value: f64,
12946}
12947
12948fn default_revenue_method() -> String {
12949    "percentage_of_completion".to_string()
12950}
12951fn default_completion_measure() -> String {
12952    "cost_to_cost".to_string()
12953}
12954fn default_avg_contract_value() -> f64 {
12955    500_000.0
12956}
12957
12958impl Default for ProjectRevenueRecognitionConfig {
12959    fn default() -> Self {
12960        Self {
12961            enabled: true,
12962            method: default_revenue_method(),
12963            completion_measure: default_completion_measure(),
12964            avg_contract_value: default_avg_contract_value(),
12965        }
12966    }
12967}
12968
12969/// Milestone configuration.
12970#[derive(Debug, Clone, Serialize, Deserialize)]
12971pub struct MilestoneSchemaConfig {
12972    /// Whether milestone generation is enabled.
12973    #[serde(default = "default_true")]
12974    pub enabled: bool,
12975    /// Average number of milestones per project.
12976    #[serde(default = "default_milestones_per_project")]
12977    pub avg_per_project: u32,
12978    /// Fraction of milestones that are payment milestones (0.0 to 1.0).
12979    #[serde(default = "default_payment_milestone_rate")]
12980    pub payment_milestone_rate: f64,
12981}
12982
12983fn default_milestones_per_project() -> u32 {
12984    4
12985}
12986fn default_payment_milestone_rate() -> f64 {
12987    0.50
12988}
12989
12990impl Default for MilestoneSchemaConfig {
12991    fn default() -> Self {
12992        Self {
12993            enabled: true,
12994            avg_per_project: default_milestones_per_project(),
12995            payment_milestone_rate: default_payment_milestone_rate(),
12996        }
12997    }
12998}
12999
13000/// Change order configuration.
13001#[derive(Debug, Clone, Serialize, Deserialize)]
13002pub struct ChangeOrderSchemaConfig {
13003    /// Whether change order generation is enabled.
13004    #[serde(default = "default_true")]
13005    pub enabled: bool,
13006    /// Probability that a project will have at least one change order (0.0 to 1.0).
13007    #[serde(default = "default_change_order_probability")]
13008    pub probability: f64,
13009    /// Maximum change orders per project.
13010    #[serde(default = "default_max_change_orders")]
13011    pub max_per_project: u32,
13012    /// Approval rate for change orders (0.0 to 1.0).
13013    #[serde(default = "default_change_order_approval_rate")]
13014    pub approval_rate: f64,
13015}
13016
13017fn default_change_order_probability() -> f64 {
13018    0.40
13019}
13020fn default_max_change_orders() -> u32 {
13021    3
13022}
13023fn default_change_order_approval_rate() -> f64 {
13024    0.75
13025}
13026
13027impl Default for ChangeOrderSchemaConfig {
13028    fn default() -> Self {
13029        Self {
13030            enabled: true,
13031            probability: default_change_order_probability(),
13032            max_per_project: default_max_change_orders(),
13033            approval_rate: default_change_order_approval_rate(),
13034        }
13035    }
13036}
13037
13038/// Retainage configuration.
13039#[derive(Debug, Clone, Serialize, Deserialize)]
13040pub struct RetainageSchemaConfig {
13041    /// Whether retainage is enabled.
13042    #[serde(default)]
13043    pub enabled: bool,
13044    /// Default retainage percentage (0.0 to 1.0, e.g., 0.10 for 10%).
13045    #[serde(default = "default_retainage_pct")]
13046    pub default_percentage: f64,
13047}
13048
13049fn default_retainage_pct() -> f64 {
13050    0.10
13051}
13052
13053impl Default for RetainageSchemaConfig {
13054    fn default() -> Self {
13055        Self {
13056            enabled: false,
13057            default_percentage: default_retainage_pct(),
13058        }
13059    }
13060}
13061
13062/// Earned value management (EVM) configuration.
13063#[derive(Debug, Clone, Serialize, Deserialize)]
13064pub struct EarnedValueSchemaConfig {
13065    /// Whether EVM metrics are generated.
13066    #[serde(default = "default_true")]
13067    pub enabled: bool,
13068    /// Measurement frequency: "weekly", "biweekly", "monthly".
13069    #[serde(default = "default_evm_frequency")]
13070    pub frequency: String,
13071}
13072
13073fn default_evm_frequency() -> String {
13074    "monthly".to_string()
13075}
13076
13077impl Default for EarnedValueSchemaConfig {
13078    fn default() -> Self {
13079        Self {
13080            enabled: true,
13081            frequency: default_evm_frequency(),
13082        }
13083    }
13084}
13085
13086// =============================================================================
13087// ESG / Sustainability Configuration
13088// =============================================================================
13089
13090/// Top-level ESG / sustainability reporting configuration.
13091#[derive(Debug, Clone, Serialize, Deserialize)]
13092pub struct EsgConfig {
13093    /// Whether ESG generation is enabled.
13094    #[serde(default)]
13095    pub enabled: bool,
13096    /// Environmental metrics (emissions, energy, water, waste).
13097    #[serde(default)]
13098    pub environmental: EnvironmentalConfig,
13099    /// Social metrics (diversity, pay equity, safety).
13100    #[serde(default)]
13101    pub social: SocialConfig,
13102    /// Governance metrics (board composition, ethics, compliance).
13103    #[serde(default)]
13104    pub governance: GovernanceSchemaConfig,
13105    /// Supply-chain ESG assessment settings.
13106    #[serde(default)]
13107    pub supply_chain_esg: SupplyChainEsgConfig,
13108    /// ESG reporting / disclosure framework settings.
13109    #[serde(default)]
13110    pub reporting: EsgReportingConfig,
13111    /// Climate scenario analysis settings.
13112    #[serde(default)]
13113    pub climate_scenarios: ClimateScenarioConfig,
13114    /// Anomaly injection rate for ESG data (0.0 to 1.0).
13115    #[serde(default = "default_esg_anomaly_rate")]
13116    pub anomaly_rate: f64,
13117}
13118
13119fn default_esg_anomaly_rate() -> f64 {
13120    0.02
13121}
13122
13123impl Default for EsgConfig {
13124    fn default() -> Self {
13125        Self {
13126            enabled: false,
13127            environmental: EnvironmentalConfig::default(),
13128            social: SocialConfig::default(),
13129            governance: GovernanceSchemaConfig::default(),
13130            supply_chain_esg: SupplyChainEsgConfig::default(),
13131            reporting: EsgReportingConfig::default(),
13132            climate_scenarios: ClimateScenarioConfig::default(),
13133            anomaly_rate: default_esg_anomaly_rate(),
13134        }
13135    }
13136}
13137
13138/// Country pack configuration.
13139///
13140/// Controls where to load additional country packs and per-country overrides.
13141/// When omitted, only the built-in packs (_default, US, DE, GB) are used.
13142#[derive(Debug, Clone, Serialize, Deserialize, Default)]
13143pub struct CountryPacksSchemaConfig {
13144    /// Optional directory containing additional `*.json` country packs.
13145    #[serde(default)]
13146    pub external_dir: Option<PathBuf>,
13147    /// Per-country overrides applied after loading.
13148    /// Keys are ISO 3166-1 alpha-2 codes; values are partial JSON objects
13149    /// that are deep-merged on top of the loaded pack.
13150    #[serde(default)]
13151    pub overrides: std::collections::HashMap<String, serde_json::Value>,
13152}
13153
13154/// Environmental metrics configuration.
13155#[derive(Debug, Clone, Serialize, Deserialize)]
13156pub struct EnvironmentalConfig {
13157    /// Whether environmental metrics are generated.
13158    #[serde(default = "default_true")]
13159    pub enabled: bool,
13160    /// Scope 1 (direct) emission generation settings.
13161    #[serde(default)]
13162    pub scope1: EmissionScopeConfig,
13163    /// Scope 2 (purchased energy) emission generation settings.
13164    #[serde(default)]
13165    pub scope2: EmissionScopeConfig,
13166    /// Scope 3 (value chain) emission generation settings.
13167    #[serde(default)]
13168    pub scope3: Scope3Config,
13169    /// Energy consumption tracking settings.
13170    #[serde(default)]
13171    pub energy: EnergySchemaConfig,
13172    /// Water usage tracking settings.
13173    #[serde(default)]
13174    pub water: WaterSchemaConfig,
13175    /// Waste management tracking settings.
13176    #[serde(default)]
13177    pub waste: WasteSchemaConfig,
13178}
13179
13180impl Default for EnvironmentalConfig {
13181    fn default() -> Self {
13182        Self {
13183            enabled: true,
13184            scope1: EmissionScopeConfig::default(),
13185            scope2: EmissionScopeConfig::default(),
13186            scope3: Scope3Config::default(),
13187            energy: EnergySchemaConfig::default(),
13188            water: WaterSchemaConfig::default(),
13189            waste: WasteSchemaConfig::default(),
13190        }
13191    }
13192}
13193
13194/// Configuration for a single emission scope (Scope 1 or 2).
13195#[derive(Debug, Clone, Serialize, Deserialize)]
13196pub struct EmissionScopeConfig {
13197    /// Whether this scope is enabled.
13198    #[serde(default = "default_true")]
13199    pub enabled: bool,
13200    /// Emission factor region (e.g., "US", "EU", "global").
13201    #[serde(default = "default_emission_region")]
13202    pub factor_region: String,
13203}
13204
13205fn default_emission_region() -> String {
13206    "US".to_string()
13207}
13208
13209impl Default for EmissionScopeConfig {
13210    fn default() -> Self {
13211        Self {
13212            enabled: true,
13213            factor_region: default_emission_region(),
13214        }
13215    }
13216}
13217
13218/// Scope 3 (value chain) emission configuration.
13219#[derive(Debug, Clone, Serialize, Deserialize)]
13220pub struct Scope3Config {
13221    /// Whether Scope 3 emissions are generated.
13222    #[serde(default = "default_true")]
13223    pub enabled: bool,
13224    /// Categories to include (e.g., "purchased_goods", "business_travel", "commuting").
13225    #[serde(default = "default_scope3_categories")]
13226    pub categories: Vec<String>,
13227    /// Spend-based emission intensity (kg CO2e per USD).
13228    #[serde(default = "default_spend_intensity")]
13229    pub default_spend_intensity_kg_per_usd: f64,
13230}
13231
13232fn default_scope3_categories() -> Vec<String> {
13233    vec![
13234        "purchased_goods".to_string(),
13235        "business_travel".to_string(),
13236        "employee_commuting".to_string(),
13237    ]
13238}
13239
13240fn default_spend_intensity() -> f64 {
13241    0.5
13242}
13243
13244impl Default for Scope3Config {
13245    fn default() -> Self {
13246        Self {
13247            enabled: true,
13248            categories: default_scope3_categories(),
13249            default_spend_intensity_kg_per_usd: default_spend_intensity(),
13250        }
13251    }
13252}
13253
13254/// Energy consumption configuration.
13255#[derive(Debug, Clone, Serialize, Deserialize)]
13256pub struct EnergySchemaConfig {
13257    /// Whether energy consumption tracking is enabled.
13258    #[serde(default = "default_true")]
13259    pub enabled: bool,
13260    /// Number of facilities to generate.
13261    #[serde(default = "default_facility_count")]
13262    pub facility_count: u32,
13263    /// Target percentage of energy from renewable sources (0.0 to 1.0).
13264    #[serde(default = "default_renewable_target")]
13265    pub renewable_target: f64,
13266}
13267
13268fn default_facility_count() -> u32 {
13269    5
13270}
13271
13272fn default_renewable_target() -> f64 {
13273    0.30
13274}
13275
13276impl Default for EnergySchemaConfig {
13277    fn default() -> Self {
13278        Self {
13279            enabled: true,
13280            facility_count: default_facility_count(),
13281            renewable_target: default_renewable_target(),
13282        }
13283    }
13284}
13285
13286/// Water usage configuration.
13287#[derive(Debug, Clone, Serialize, Deserialize)]
13288pub struct WaterSchemaConfig {
13289    /// Whether water usage tracking is enabled.
13290    #[serde(default = "default_true")]
13291    pub enabled: bool,
13292    /// Number of facilities with water tracking.
13293    #[serde(default = "default_water_facility_count")]
13294    pub facility_count: u32,
13295}
13296
13297fn default_water_facility_count() -> u32 {
13298    3
13299}
13300
13301impl Default for WaterSchemaConfig {
13302    fn default() -> Self {
13303        Self {
13304            enabled: true,
13305            facility_count: default_water_facility_count(),
13306        }
13307    }
13308}
13309
13310/// Waste management configuration.
13311#[derive(Debug, Clone, Serialize, Deserialize)]
13312pub struct WasteSchemaConfig {
13313    /// Whether waste tracking is enabled.
13314    #[serde(default = "default_true")]
13315    pub enabled: bool,
13316    /// Target diversion rate (0.0 to 1.0).
13317    #[serde(default = "default_diversion_target")]
13318    pub diversion_target: f64,
13319}
13320
13321fn default_diversion_target() -> f64 {
13322    0.50
13323}
13324
13325impl Default for WasteSchemaConfig {
13326    fn default() -> Self {
13327        Self {
13328            enabled: true,
13329            diversion_target: default_diversion_target(),
13330        }
13331    }
13332}
13333
13334/// Social metrics configuration.
13335#[derive(Debug, Clone, Serialize, Deserialize)]
13336pub struct SocialConfig {
13337    /// Whether social metrics are generated.
13338    #[serde(default = "default_true")]
13339    pub enabled: bool,
13340    /// Workforce diversity tracking settings.
13341    #[serde(default)]
13342    pub diversity: DiversitySchemaConfig,
13343    /// Pay equity analysis settings.
13344    #[serde(default)]
13345    pub pay_equity: PayEquitySchemaConfig,
13346    /// Safety incident and metrics settings.
13347    #[serde(default)]
13348    pub safety: SafetySchemaConfig,
13349}
13350
13351impl Default for SocialConfig {
13352    fn default() -> Self {
13353        Self {
13354            enabled: true,
13355            diversity: DiversitySchemaConfig::default(),
13356            pay_equity: PayEquitySchemaConfig::default(),
13357            safety: SafetySchemaConfig::default(),
13358        }
13359    }
13360}
13361
13362/// Workforce diversity configuration.
13363#[derive(Debug, Clone, Serialize, Deserialize)]
13364pub struct DiversitySchemaConfig {
13365    /// Whether diversity metrics are generated.
13366    #[serde(default = "default_true")]
13367    pub enabled: bool,
13368    /// Dimensions to track (e.g., "gender", "ethnicity", "age_group").
13369    #[serde(default = "default_diversity_dimensions")]
13370    pub dimensions: Vec<String>,
13371}
13372
13373fn default_diversity_dimensions() -> Vec<String> {
13374    vec![
13375        "gender".to_string(),
13376        "ethnicity".to_string(),
13377        "age_group".to_string(),
13378    ]
13379}
13380
13381impl Default for DiversitySchemaConfig {
13382    fn default() -> Self {
13383        Self {
13384            enabled: true,
13385            dimensions: default_diversity_dimensions(),
13386        }
13387    }
13388}
13389
13390/// Pay equity analysis configuration.
13391#[derive(Debug, Clone, Serialize, Deserialize)]
13392pub struct PayEquitySchemaConfig {
13393    /// Whether pay equity analysis is generated.
13394    #[serde(default = "default_true")]
13395    pub enabled: bool,
13396    /// Target pay gap threshold for flagging (e.g., 0.05 = 5% gap).
13397    #[serde(default = "default_pay_gap_threshold")]
13398    pub gap_threshold: f64,
13399}
13400
13401fn default_pay_gap_threshold() -> f64 {
13402    0.05
13403}
13404
13405impl Default for PayEquitySchemaConfig {
13406    fn default() -> Self {
13407        Self {
13408            enabled: true,
13409            gap_threshold: default_pay_gap_threshold(),
13410        }
13411    }
13412}
13413
13414/// Safety metrics configuration.
13415#[derive(Debug, Clone, Serialize, Deserialize)]
13416pub struct SafetySchemaConfig {
13417    /// Whether safety metrics are generated.
13418    #[serde(default = "default_true")]
13419    pub enabled: bool,
13420    /// Average annual recordable incidents per 200,000 hours.
13421    #[serde(default = "default_trir_target")]
13422    pub target_trir: f64,
13423    /// Number of safety incidents to generate.
13424    #[serde(default = "default_incident_count")]
13425    pub incident_count: u32,
13426}
13427
13428fn default_trir_target() -> f64 {
13429    2.5
13430}
13431
13432fn default_incident_count() -> u32 {
13433    20
13434}
13435
13436impl Default for SafetySchemaConfig {
13437    fn default() -> Self {
13438        Self {
13439            enabled: true,
13440            target_trir: default_trir_target(),
13441            incident_count: default_incident_count(),
13442        }
13443    }
13444}
13445
13446/// Governance metrics configuration.
13447#[derive(Debug, Clone, Serialize, Deserialize)]
13448pub struct GovernanceSchemaConfig {
13449    /// Whether governance metrics are generated.
13450    #[serde(default = "default_true")]
13451    pub enabled: bool,
13452    /// Number of board members.
13453    #[serde(default = "default_board_size")]
13454    pub board_size: u32,
13455    /// Target independent director ratio (0.0 to 1.0).
13456    #[serde(default = "default_independence_target")]
13457    pub independence_target: f64,
13458}
13459
13460fn default_board_size() -> u32 {
13461    11
13462}
13463
13464fn default_independence_target() -> f64 {
13465    0.67
13466}
13467
13468impl Default for GovernanceSchemaConfig {
13469    fn default() -> Self {
13470        Self {
13471            enabled: true,
13472            board_size: default_board_size(),
13473            independence_target: default_independence_target(),
13474        }
13475    }
13476}
13477
13478/// Supply-chain ESG assessment configuration.
13479#[derive(Debug, Clone, Serialize, Deserialize)]
13480pub struct SupplyChainEsgConfig {
13481    /// Whether supply chain ESG assessments are generated.
13482    #[serde(default = "default_true")]
13483    pub enabled: bool,
13484    /// Proportion of vendors to assess (0.0 to 1.0).
13485    #[serde(default = "default_assessment_coverage")]
13486    pub assessment_coverage: f64,
13487    /// High-risk country codes for automatic flagging.
13488    #[serde(default = "default_high_risk_countries")]
13489    pub high_risk_countries: Vec<String>,
13490}
13491
13492fn default_assessment_coverage() -> f64 {
13493    0.80
13494}
13495
13496fn default_high_risk_countries() -> Vec<String> {
13497    vec!["CN".to_string(), "BD".to_string(), "MM".to_string()]
13498}
13499
13500impl Default for SupplyChainEsgConfig {
13501    fn default() -> Self {
13502        Self {
13503            enabled: true,
13504            assessment_coverage: default_assessment_coverage(),
13505            high_risk_countries: default_high_risk_countries(),
13506        }
13507    }
13508}
13509
13510/// ESG reporting / disclosure framework configuration.
13511#[derive(Debug, Clone, Serialize, Deserialize)]
13512pub struct EsgReportingConfig {
13513    /// Whether ESG disclosures are generated.
13514    #[serde(default = "default_true")]
13515    pub enabled: bool,
13516    /// Frameworks to generate disclosures for.
13517    #[serde(default = "default_esg_frameworks")]
13518    pub frameworks: Vec<String>,
13519    /// Whether materiality assessment is performed.
13520    #[serde(default = "default_true")]
13521    pub materiality_assessment: bool,
13522    /// Materiality threshold for impact dimension (0.0 to 1.0).
13523    #[serde(default = "default_materiality_threshold")]
13524    pub impact_threshold: f64,
13525    /// Materiality threshold for financial dimension (0.0 to 1.0).
13526    #[serde(default = "default_materiality_threshold")]
13527    pub financial_threshold: f64,
13528}
13529
13530fn default_esg_frameworks() -> Vec<String> {
13531    vec!["GRI".to_string(), "ESRS".to_string()]
13532}
13533
13534fn default_materiality_threshold() -> f64 {
13535    0.6
13536}
13537
13538impl Default for EsgReportingConfig {
13539    fn default() -> Self {
13540        Self {
13541            enabled: true,
13542            frameworks: default_esg_frameworks(),
13543            materiality_assessment: true,
13544            impact_threshold: default_materiality_threshold(),
13545            financial_threshold: default_materiality_threshold(),
13546        }
13547    }
13548}
13549
13550/// Climate scenario analysis configuration.
13551#[derive(Debug, Clone, Serialize, Deserialize)]
13552pub struct ClimateScenarioConfig {
13553    /// Whether climate scenario analysis is generated.
13554    #[serde(default)]
13555    pub enabled: bool,
13556    /// Scenarios to model (e.g., "net_zero_2050", "stated_policies", "current_trajectory").
13557    #[serde(default = "default_climate_scenarios")]
13558    pub scenarios: Vec<String>,
13559    /// Time horizons in years to project.
13560    #[serde(default = "default_time_horizons")]
13561    pub time_horizons: Vec<u32>,
13562}
13563
13564fn default_climate_scenarios() -> Vec<String> {
13565    vec![
13566        "net_zero_2050".to_string(),
13567        "stated_policies".to_string(),
13568        "current_trajectory".to_string(),
13569    ]
13570}
13571
13572fn default_time_horizons() -> Vec<u32> {
13573    vec![5, 10, 30]
13574}
13575
13576impl Default for ClimateScenarioConfig {
13577    fn default() -> Self {
13578        Self {
13579            enabled: false,
13580            scenarios: default_climate_scenarios(),
13581            time_horizons: default_time_horizons(),
13582        }
13583    }
13584}
13585
13586// ===== Counterfactual Simulation Scenarios =====
13587
13588/// Configuration for counterfactual simulation scenarios.
13589#[derive(Debug, Clone, Serialize, Deserialize, Default)]
13590pub struct ScenariosConfig {
13591    /// Whether scenario generation is enabled.
13592    #[serde(default)]
13593    pub enabled: bool,
13594    /// List of scenario definitions.
13595    #[serde(default)]
13596    pub scenarios: Vec<ScenarioSchemaConfig>,
13597    /// Causal model configuration.
13598    #[serde(default)]
13599    pub causal_model: CausalModelSchemaConfig,
13600    /// Default settings applied to all scenarios.
13601    #[serde(default)]
13602    pub defaults: ScenarioDefaultsConfig,
13603    /// Generate counterfactual (original, mutated) JE pairs for ML training.
13604    /// When true, the orchestrator produces paired clean/anomalous journal entries.
13605    #[serde(default)]
13606    pub generate_counterfactuals: bool,
13607}
13608
13609/// A single scenario definition in the config.
13610#[derive(Debug, Clone, Serialize, Deserialize)]
13611pub struct ScenarioSchemaConfig {
13612    /// Scenario name (must be unique).
13613    pub name: String,
13614    /// Human-readable description.
13615    #[serde(default)]
13616    pub description: String,
13617    /// Tags for categorization.
13618    #[serde(default)]
13619    pub tags: Vec<String>,
13620    /// Base scenario name (None = default config).
13621    pub base: Option<String>,
13622    /// IFRS 9-style probability weight.
13623    pub probability_weight: Option<f64>,
13624    /// List of interventions to apply.
13625    #[serde(default)]
13626    pub interventions: Vec<InterventionSchemaConfig>,
13627    /// Constraint overrides for this scenario.
13628    #[serde(default)]
13629    pub constraints: ScenarioConstraintsSchemaConfig,
13630    /// Output configuration for this scenario.
13631    #[serde(default)]
13632    pub output: ScenarioOutputSchemaConfig,
13633    /// Arbitrary metadata.
13634    #[serde(default)]
13635    pub metadata: std::collections::HashMap<String, String>,
13636}
13637
13638/// An intervention definition in the config.
13639#[derive(Debug, Clone, Serialize, Deserialize)]
13640pub struct InterventionSchemaConfig {
13641    /// Intervention type and parameters (flattened tagged enum).
13642    #[serde(flatten)]
13643    pub intervention_type: serde_json::Value,
13644    /// Timing configuration.
13645    #[serde(default)]
13646    pub timing: InterventionTimingSchemaConfig,
13647    /// Human-readable label.
13648    pub label: Option<String>,
13649    /// Priority for conflict resolution (higher wins).
13650    #[serde(default)]
13651    pub priority: u32,
13652}
13653
13654/// Timing configuration for an intervention.
13655#[derive(Debug, Clone, Serialize, Deserialize)]
13656pub struct InterventionTimingSchemaConfig {
13657    /// Month offset from start (1-indexed).
13658    #[serde(default = "default_start_month")]
13659    pub start_month: u32,
13660    /// Duration in months.
13661    pub duration_months: Option<u32>,
13662    /// Onset type: "sudden", "gradual", "oscillating", "custom".
13663    #[serde(default = "default_onset")]
13664    pub onset: String,
13665    /// Ramp period in months.
13666    pub ramp_months: Option<u32>,
13667}
13668
13669fn default_start_month() -> u32 {
13670    1
13671}
13672
13673fn default_onset() -> String {
13674    "sudden".to_string()
13675}
13676
13677impl Default for InterventionTimingSchemaConfig {
13678    fn default() -> Self {
13679        Self {
13680            start_month: 1,
13681            duration_months: None,
13682            onset: "sudden".to_string(),
13683            ramp_months: None,
13684        }
13685    }
13686}
13687
13688/// Scenario constraint overrides.
13689#[derive(Debug, Clone, Serialize, Deserialize)]
13690pub struct ScenarioConstraintsSchemaConfig {
13691    #[serde(default = "default_true")]
13692    pub preserve_accounting_identity: bool,
13693    #[serde(default = "default_true")]
13694    pub preserve_document_chains: bool,
13695    #[serde(default = "default_true")]
13696    pub preserve_period_close: bool,
13697    #[serde(default = "default_true")]
13698    pub preserve_balance_coherence: bool,
13699    #[serde(default)]
13700    pub custom: Vec<CustomConstraintSchemaConfig>,
13701}
13702
13703impl Default for ScenarioConstraintsSchemaConfig {
13704    fn default() -> Self {
13705        Self {
13706            preserve_accounting_identity: true,
13707            preserve_document_chains: true,
13708            preserve_period_close: true,
13709            preserve_balance_coherence: true,
13710            custom: Vec::new(),
13711        }
13712    }
13713}
13714
13715/// Custom constraint in config.
13716#[derive(Debug, Clone, Serialize, Deserialize)]
13717pub struct CustomConstraintSchemaConfig {
13718    pub config_path: String,
13719    pub min: Option<f64>,
13720    pub max: Option<f64>,
13721    #[serde(default)]
13722    pub description: String,
13723}
13724
13725/// Output configuration for a scenario.
13726#[derive(Debug, Clone, Serialize, Deserialize)]
13727pub struct ScenarioOutputSchemaConfig {
13728    #[serde(default = "default_true")]
13729    pub paired: bool,
13730    #[serde(default = "default_diff_formats_schema")]
13731    pub diff_formats: Vec<String>,
13732    #[serde(default)]
13733    pub diff_scope: Vec<String>,
13734}
13735
13736fn default_diff_formats_schema() -> Vec<String> {
13737    vec!["summary".to_string(), "aggregate".to_string()]
13738}
13739
13740impl Default for ScenarioOutputSchemaConfig {
13741    fn default() -> Self {
13742        Self {
13743            paired: true,
13744            diff_formats: default_diff_formats_schema(),
13745            diff_scope: Vec::new(),
13746        }
13747    }
13748}
13749
13750/// Causal model configuration.
13751#[derive(Debug, Clone, Serialize, Deserialize)]
13752pub struct CausalModelSchemaConfig {
13753    /// Preset name: "default", "minimal", or "custom".
13754    #[serde(default = "default_causal_preset")]
13755    pub preset: String,
13756    /// Custom nodes (merged with preset).
13757    #[serde(default)]
13758    pub nodes: Vec<serde_json::Value>,
13759    /// Custom edges (merged with preset).
13760    #[serde(default)]
13761    pub edges: Vec<serde_json::Value>,
13762}
13763
13764fn default_causal_preset() -> String {
13765    "default".to_string()
13766}
13767
13768impl Default for CausalModelSchemaConfig {
13769    fn default() -> Self {
13770        Self {
13771            preset: "default".to_string(),
13772            nodes: Vec::new(),
13773            edges: Vec::new(),
13774        }
13775    }
13776}
13777
13778/// Default settings applied to all scenarios.
13779#[derive(Debug, Clone, Serialize, Deserialize, Default)]
13780pub struct ScenarioDefaultsConfig {
13781    #[serde(default)]
13782    pub constraints: ScenarioConstraintsSchemaConfig,
13783    #[serde(default)]
13784    pub output: ScenarioOutputSchemaConfig,
13785}
13786
13787// =====================================================================
13788// Compliance Regulations Framework Configuration
13789// =====================================================================
13790
13791/// Top-level configuration for the compliance regulations framework.
13792///
13793/// Controls standards registry, jurisdiction profiles, temporal versioning,
13794/// audit procedure templates, compliance graph integration, and output settings.
13795///
13796/// # Example
13797///
13798/// ```yaml
13799/// compliance_regulations:
13800///   enabled: true
13801///   jurisdictions: [US, DE, GB]
13802///   reference_date: "2025-06-30"
13803///   standards_selection:
13804///     categories: [accounting, auditing, regulatory]
13805///     include: ["IFRS-16", "ASC-606"]
13806///   audit_procedures:
13807///     enabled: true
13808///     procedures_per_standard: 3
13809///   findings:
13810///     enabled: true
13811///     finding_rate: 0.05
13812///   filings:
13813///     enabled: true
13814///   graph:
13815///     enabled: true
13816///     include_compliance_nodes: true
13817///     include_compliance_edges: true
13818/// ```
13819#[derive(Debug, Clone, Default, Serialize, Deserialize)]
13820pub struct ComplianceRegulationsConfig {
13821    /// Master switch for the compliance regulations framework.
13822    #[serde(default)]
13823    pub enabled: bool,
13824    /// Jurisdictions to generate compliance data for (ISO 3166-1 alpha-2 codes).
13825    /// If empty, inferred from company countries in the config.
13826    #[serde(default)]
13827    pub jurisdictions: Vec<String>,
13828    /// Reference date for temporal standard resolution (YYYY-MM-DD).
13829    /// Defaults to the global start_date if not set.
13830    #[serde(default)]
13831    pub reference_date: Option<String>,
13832    /// Standards selection filters.
13833    #[serde(default)]
13834    pub standards_selection: StandardsSelectionConfig,
13835    /// Audit procedure generation settings.
13836    #[serde(default)]
13837    pub audit_procedures: AuditProcedureGenConfig,
13838    /// Compliance finding generation settings.
13839    #[serde(default)]
13840    pub findings: ComplianceFindingGenConfig,
13841    /// Regulatory filing generation settings.
13842    #[serde(default)]
13843    pub filings: ComplianceFilingGenConfig,
13844    /// Compliance graph integration settings.
13845    #[serde(default)]
13846    pub graph: ComplianceGraphConfig,
13847    /// Output settings for compliance-specific files.
13848    #[serde(default)]
13849    pub output: ComplianceOutputConfig,
13850    /// v3.3.0: legal-document generation (engagement letters,
13851    /// management reps, legal opinions, regulatory filings, board
13852    /// resolutions). Requires `compliance_regulations.enabled = true`
13853    /// AND `legal_documents.enabled = true` to take effect.
13854    #[serde(default)]
13855    pub legal_documents: LegalDocumentsConfig,
13856}
13857
13858/// Legal-document generation settings (v3.3.0+).
13859///
13860/// Wires `LegalDocumentGenerator` into the orchestrator. Generates one
13861/// batch per audit engagement when enabled.
13862#[derive(Debug, Clone, Serialize, Deserialize)]
13863pub struct LegalDocumentsConfig {
13864    /// Master switch.
13865    #[serde(default)]
13866    pub enabled: bool,
13867    /// Probability of including a legal-opinion document in an engagement.
13868    #[serde(default = "default_legal_opinion_probability")]
13869    pub legal_opinion_probability: f64,
13870}
13871
13872fn default_legal_opinion_probability() -> f64 {
13873    0.40
13874}
13875
13876impl Default for LegalDocumentsConfig {
13877    fn default() -> Self {
13878        Self {
13879            enabled: false,
13880            legal_opinion_probability: default_legal_opinion_probability(),
13881        }
13882    }
13883}
13884
13885/// Filters which standards are included in the generation.
13886#[derive(Debug, Clone, Default, Serialize, Deserialize)]
13887pub struct StandardsSelectionConfig {
13888    /// Standard categories to include (accounting, auditing, regulatory, tax, esg).
13889    /// Empty = all categories.
13890    #[serde(default)]
13891    pub categories: Vec<String>,
13892    /// Explicit standard IDs to include (e.g., ["IFRS-16", "ASC-606"]).
13893    /// When non-empty, only these standards (plus mandatory ones for selected jurisdictions) are used.
13894    #[serde(default)]
13895    pub include: Vec<String>,
13896    /// Standard IDs to exclude.
13897    #[serde(default)]
13898    pub exclude: Vec<String>,
13899    /// Include superseded standards in the output (for historical analysis).
13900    #[serde(default)]
13901    pub include_superseded: bool,
13902}
13903
13904/// Configuration for audit procedure template generation.
13905#[derive(Debug, Clone, Serialize, Deserialize)]
13906pub struct AuditProcedureGenConfig {
13907    /// Whether audit procedure generation is enabled.
13908    #[serde(default)]
13909    pub enabled: bool,
13910    /// Number of procedures to generate per applicable standard.
13911    #[serde(default = "default_procedures_per_standard")]
13912    pub procedures_per_standard: usize,
13913    /// Sampling methodology: "statistical", "non_statistical", "mixed".
13914    #[serde(default = "default_sampling_method")]
13915    pub sampling_method: String,
13916    /// Confidence level for statistical sampling (0.0-1.0).
13917    #[serde(default = "default_confidence_level")]
13918    pub confidence_level: f64,
13919    /// Tolerable misstatement rate for sampling (0.0-1.0).
13920    #[serde(default = "default_tolerable_misstatement")]
13921    pub tolerable_misstatement: f64,
13922}
13923
13924fn default_procedures_per_standard() -> usize {
13925    3
13926}
13927
13928fn default_sampling_method() -> String {
13929    "statistical".to_string()
13930}
13931
13932fn default_confidence_level() -> f64 {
13933    0.95
13934}
13935
13936fn default_tolerable_misstatement() -> f64 {
13937    0.05
13938}
13939
13940impl Default for AuditProcedureGenConfig {
13941    fn default() -> Self {
13942        Self {
13943            enabled: false,
13944            procedures_per_standard: default_procedures_per_standard(),
13945            sampling_method: default_sampling_method(),
13946            confidence_level: default_confidence_level(),
13947            tolerable_misstatement: default_tolerable_misstatement(),
13948        }
13949    }
13950}
13951
13952/// Configuration for compliance finding generation.
13953#[derive(Debug, Clone, Serialize, Deserialize)]
13954pub struct ComplianceFindingGenConfig {
13955    /// Whether finding generation is enabled.
13956    #[serde(default)]
13957    pub enabled: bool,
13958    /// Rate of findings per audit procedure (0.0-1.0).
13959    #[serde(default = "default_finding_rate")]
13960    pub finding_rate: f64,
13961    /// Rate of material weakness findings among all findings (0.0-1.0).
13962    #[serde(default = "default_cr_material_weakness_rate")]
13963    pub material_weakness_rate: f64,
13964    /// Rate of significant deficiency findings among all findings (0.0-1.0).
13965    #[serde(default = "default_cr_significant_deficiency_rate")]
13966    pub significant_deficiency_rate: f64,
13967    /// Whether to generate remediation plans for findings.
13968    #[serde(default = "default_true")]
13969    pub generate_remediation: bool,
13970}
13971
13972fn default_finding_rate() -> f64 {
13973    0.05
13974}
13975
13976fn default_cr_material_weakness_rate() -> f64 {
13977    0.02
13978}
13979
13980fn default_cr_significant_deficiency_rate() -> f64 {
13981    0.08
13982}
13983
13984impl Default for ComplianceFindingGenConfig {
13985    fn default() -> Self {
13986        Self {
13987            enabled: false,
13988            finding_rate: default_finding_rate(),
13989            material_weakness_rate: default_cr_material_weakness_rate(),
13990            significant_deficiency_rate: default_cr_significant_deficiency_rate(),
13991            generate_remediation: true,
13992        }
13993    }
13994}
13995
13996/// Configuration for regulatory filing generation.
13997#[derive(Debug, Clone, Serialize, Deserialize)]
13998pub struct ComplianceFilingGenConfig {
13999    /// Whether filing generation is enabled.
14000    #[serde(default)]
14001    pub enabled: bool,
14002    /// Filing types to include (e.g., ["10-K", "10-Q", "Jahresabschluss"]).
14003    /// Empty = all applicable filings for the selected jurisdictions.
14004    #[serde(default)]
14005    pub filing_types: Vec<String>,
14006    /// Generate filing status progression (draft → filed → accepted).
14007    #[serde(default = "default_true")]
14008    pub generate_status_progression: bool,
14009}
14010
14011impl Default for ComplianceFilingGenConfig {
14012    fn default() -> Self {
14013        Self {
14014            enabled: false,
14015            filing_types: Vec::new(),
14016            generate_status_progression: true,
14017        }
14018    }
14019}
14020
14021/// Configuration for compliance graph integration.
14022#[derive(Debug, Clone, Serialize, Deserialize)]
14023pub struct ComplianceGraphConfig {
14024    /// Whether compliance graph integration is enabled.
14025    #[serde(default)]
14026    pub enabled: bool,
14027    /// Include compliance nodes (Standard, Regulation, Jurisdiction, etc.).
14028    #[serde(default = "default_true")]
14029    pub include_compliance_nodes: bool,
14030    /// Include compliance edges (MapsToStandard, TestsControl, etc.).
14031    #[serde(default = "default_true")]
14032    pub include_compliance_edges: bool,
14033    /// Include cross-reference edges between standards.
14034    #[serde(default = "default_true")]
14035    pub include_cross_references: bool,
14036    /// Include temporal supersession edges.
14037    #[serde(default)]
14038    pub include_supersession_edges: bool,
14039    /// Include edges linking standards to the GL account types they govern.
14040    #[serde(default = "default_true")]
14041    pub include_account_links: bool,
14042    /// Include edges linking standards to the internal controls that implement them.
14043    #[serde(default = "default_true")]
14044    pub include_control_links: bool,
14045    /// Include edges linking filings and jurisdictions to the originating company.
14046    #[serde(default = "default_true")]
14047    pub include_company_links: bool,
14048}
14049
14050impl Default for ComplianceGraphConfig {
14051    fn default() -> Self {
14052        Self {
14053            enabled: false,
14054            include_compliance_nodes: true,
14055            include_compliance_edges: true,
14056            include_cross_references: true,
14057            include_supersession_edges: false,
14058            include_account_links: true,
14059            include_control_links: true,
14060            include_company_links: true,
14061        }
14062    }
14063}
14064
14065/// Output settings for compliance-specific data files.
14066#[derive(Debug, Clone, Serialize, Deserialize)]
14067pub struct ComplianceOutputConfig {
14068    /// Export the standards registry catalog.
14069    #[serde(default = "default_true")]
14070    pub export_registry: bool,
14071    /// Export jurisdiction profiles.
14072    #[serde(default = "default_true")]
14073    pub export_jurisdictions: bool,
14074    /// Export cross-reference map.
14075    #[serde(default = "default_true")]
14076    pub export_cross_references: bool,
14077    /// Export temporal version history.
14078    #[serde(default)]
14079    pub export_version_history: bool,
14080}
14081
14082impl Default for ComplianceOutputConfig {
14083    fn default() -> Self {
14084        Self {
14085            export_registry: true,
14086            export_jurisdictions: true,
14087            export_cross_references: true,
14088            export_version_history: false,
14089        }
14090    }
14091}
14092
14093#[cfg(test)]
14094#[allow(clippy::unwrap_used)]
14095mod tests {
14096    use super::*;
14097    use crate::presets::demo_preset;
14098
14099    // ==========================================================================
14100    // Serialization/Deserialization Tests
14101    // ==========================================================================
14102
14103    #[test]
14104    fn test_config_yaml_roundtrip() {
14105        let config = demo_preset();
14106        let yaml = serde_yaml::to_string(&config).expect("Failed to serialize to YAML");
14107        let deserialized: GeneratorConfig =
14108            serde_yaml::from_str(&yaml).expect("Failed to deserialize from YAML");
14109
14110        assert_eq!(
14111            config.global.period_months,
14112            deserialized.global.period_months
14113        );
14114        assert_eq!(config.global.industry, deserialized.global.industry);
14115        assert_eq!(config.companies.len(), deserialized.companies.len());
14116        assert_eq!(config.companies[0].code, deserialized.companies[0].code);
14117    }
14118
14119    #[test]
14120    fn test_config_json_roundtrip() {
14121        // Create a config without infinity values (JSON can't serialize f64::INFINITY)
14122        let mut config = demo_preset();
14123        // Replace infinity with a large but finite value for JSON compatibility
14124        config.master_data.employees.approval_limits.executive = 1e12;
14125
14126        let json = serde_json::to_string(&config).expect("Failed to serialize to JSON");
14127        let deserialized: GeneratorConfig =
14128            serde_json::from_str(&json).expect("Failed to deserialize from JSON");
14129
14130        assert_eq!(
14131            config.global.period_months,
14132            deserialized.global.period_months
14133        );
14134        assert_eq!(config.global.industry, deserialized.global.industry);
14135        assert_eq!(config.companies.len(), deserialized.companies.len());
14136    }
14137
14138    #[test]
14139    fn test_transaction_volume_serialization() {
14140        // Test various transaction volumes serialize correctly
14141        let volumes = vec![
14142            (TransactionVolume::TenK, "ten_k"),
14143            (TransactionVolume::HundredK, "hundred_k"),
14144            (TransactionVolume::OneM, "one_m"),
14145            (TransactionVolume::TenM, "ten_m"),
14146            (TransactionVolume::HundredM, "hundred_m"),
14147        ];
14148
14149        for (volume, expected_key) in volumes {
14150            let json = serde_json::to_string(&volume).expect("Failed to serialize");
14151            assert!(
14152                json.contains(expected_key),
14153                "Expected {} in JSON: {}",
14154                expected_key,
14155                json
14156            );
14157        }
14158    }
14159
14160    #[test]
14161    fn test_transaction_volume_custom_serialization() {
14162        let volume = TransactionVolume::Custom(12345);
14163        let json = serde_json::to_string(&volume).expect("Failed to serialize");
14164        let deserialized: TransactionVolume =
14165            serde_json::from_str(&json).expect("Failed to deserialize");
14166        assert_eq!(deserialized.count(), 12345);
14167    }
14168
14169    #[test]
14170    fn test_output_mode_serialization() {
14171        let modes = vec![
14172            OutputMode::Streaming,
14173            OutputMode::FlatFile,
14174            OutputMode::Both,
14175        ];
14176
14177        for mode in modes {
14178            let json = serde_json::to_string(&mode).expect("Failed to serialize");
14179            let deserialized: OutputMode =
14180                serde_json::from_str(&json).expect("Failed to deserialize");
14181            assert!(format!("{:?}", mode) == format!("{:?}", deserialized));
14182        }
14183    }
14184
14185    #[test]
14186    fn test_file_format_serialization() {
14187        let formats = vec![
14188            FileFormat::Csv,
14189            FileFormat::Parquet,
14190            FileFormat::Json,
14191            FileFormat::JsonLines,
14192        ];
14193
14194        for format in formats {
14195            let json = serde_json::to_string(&format).expect("Failed to serialize");
14196            let deserialized: FileFormat =
14197                serde_json::from_str(&json).expect("Failed to deserialize");
14198            assert!(format!("{:?}", format) == format!("{:?}", deserialized));
14199        }
14200    }
14201
14202    #[test]
14203    fn test_compression_algorithm_serialization() {
14204        let algos = vec![
14205            CompressionAlgorithm::Gzip,
14206            CompressionAlgorithm::Zstd,
14207            CompressionAlgorithm::Lz4,
14208            CompressionAlgorithm::Snappy,
14209        ];
14210
14211        for algo in algos {
14212            let json = serde_json::to_string(&algo).expect("Failed to serialize");
14213            let deserialized: CompressionAlgorithm =
14214                serde_json::from_str(&json).expect("Failed to deserialize");
14215            assert!(format!("{:?}", algo) == format!("{:?}", deserialized));
14216        }
14217    }
14218
14219    #[test]
14220    fn test_transfer_pricing_method_serialization() {
14221        let methods = vec![
14222            TransferPricingMethod::CostPlus,
14223            TransferPricingMethod::ComparableUncontrolled,
14224            TransferPricingMethod::ResalePrice,
14225            TransferPricingMethod::TransactionalNetMargin,
14226            TransferPricingMethod::ProfitSplit,
14227        ];
14228
14229        for method in methods {
14230            let json = serde_json::to_string(&method).expect("Failed to serialize");
14231            let deserialized: TransferPricingMethod =
14232                serde_json::from_str(&json).expect("Failed to deserialize");
14233            assert!(format!("{:?}", method) == format!("{:?}", deserialized));
14234        }
14235    }
14236
14237    #[test]
14238    fn test_benford_exemption_serialization() {
14239        let exemptions = vec![
14240            BenfordExemption::Recurring,
14241            BenfordExemption::Payroll,
14242            BenfordExemption::FixedFees,
14243            BenfordExemption::RoundAmounts,
14244        ];
14245
14246        for exemption in exemptions {
14247            let json = serde_json::to_string(&exemption).expect("Failed to serialize");
14248            let deserialized: BenfordExemption =
14249                serde_json::from_str(&json).expect("Failed to deserialize");
14250            assert!(format!("{:?}", exemption) == format!("{:?}", deserialized));
14251        }
14252    }
14253
14254    // ==========================================================================
14255    // Default Value Tests
14256    // ==========================================================================
14257
14258    #[test]
14259    fn test_global_config_defaults() {
14260        let yaml = r#"
14261            industry: manufacturing
14262            start_date: "2024-01-01"
14263            period_months: 6
14264        "#;
14265        let config: GlobalConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14266        assert_eq!(config.group_currency, "USD");
14267        assert!(config.parallel);
14268        assert_eq!(config.worker_threads, 0);
14269        assert_eq!(config.memory_limit_mb, 0);
14270    }
14271
14272    #[test]
14273    fn test_fraud_config_defaults() {
14274        let config = FraudConfig::default();
14275        assert!(!config.enabled);
14276        assert_eq!(config.fraud_rate, 0.005);
14277        assert!(!config.clustering_enabled);
14278    }
14279
14280    #[test]
14281    fn test_internal_controls_config_defaults() {
14282        let config = InternalControlsConfig::default();
14283        assert!(!config.enabled);
14284        assert_eq!(config.exception_rate, 0.02);
14285        assert_eq!(config.sod_violation_rate, 0.01);
14286        assert!(config.export_control_master_data);
14287        assert_eq!(config.sox_materiality_threshold, 10000.0);
14288        // COSO fields
14289        assert!(config.coso_enabled);
14290        assert!(!config.include_entity_level_controls);
14291        assert_eq!(config.target_maturity_level, "mixed");
14292    }
14293
14294    #[test]
14295    fn test_output_config_defaults() {
14296        let config = OutputConfig::default();
14297        assert!(matches!(config.mode, OutputMode::FlatFile));
14298        assert_eq!(config.formats, vec![FileFormat::Parquet]);
14299        assert!(config.compression.enabled);
14300        assert!(matches!(
14301            config.compression.algorithm,
14302            CompressionAlgorithm::Zstd
14303        ));
14304        assert!(config.include_acdoca);
14305        assert!(!config.include_bseg);
14306        assert!(config.partition_by_period);
14307        assert!(!config.partition_by_company);
14308    }
14309
14310    #[test]
14311    fn test_approval_config_defaults() {
14312        let config = ApprovalConfig::default();
14313        assert!(!config.enabled);
14314        assert_eq!(config.auto_approve_threshold, 1000.0);
14315        assert_eq!(config.rejection_rate, 0.02);
14316        assert_eq!(config.revision_rate, 0.05);
14317        assert_eq!(config.average_approval_delay_hours, 4.0);
14318        assert_eq!(config.thresholds.len(), 4);
14319    }
14320
14321    #[test]
14322    fn test_p2p_flow_config_defaults() {
14323        let config = P2PFlowConfig::default();
14324        assert!(config.enabled);
14325        assert_eq!(config.three_way_match_rate, 0.95);
14326        assert_eq!(config.partial_delivery_rate, 0.15);
14327        assert_eq!(config.average_po_to_gr_days, 14);
14328    }
14329
14330    #[test]
14331    fn test_o2c_flow_config_defaults() {
14332        let config = O2CFlowConfig::default();
14333        assert!(config.enabled);
14334        assert_eq!(config.credit_check_failure_rate, 0.02);
14335        assert_eq!(config.return_rate, 0.03);
14336        assert_eq!(config.bad_debt_rate, 0.01);
14337    }
14338
14339    #[test]
14340    fn test_balance_config_defaults() {
14341        let config = BalanceConfig::default();
14342        assert!(!config.generate_opening_balances);
14343        assert!(config.generate_trial_balances);
14344        assert_eq!(config.target_gross_margin, 0.35);
14345        assert!(config.validate_balance_equation);
14346        assert!(config.reconcile_subledgers);
14347    }
14348
14349    // ==========================================================================
14350    // Partial Config Deserialization Tests
14351    // ==========================================================================
14352
14353    #[test]
14354    fn test_partial_config_with_defaults() {
14355        // Minimal config that should use all defaults
14356        let yaml = r#"
14357            global:
14358              industry: manufacturing
14359              start_date: "2024-01-01"
14360              period_months: 3
14361            companies:
14362              - code: "TEST"
14363                name: "Test Company"
14364                currency: "USD"
14365                country: "US"
14366                annual_transaction_volume: ten_k
14367            chart_of_accounts:
14368              complexity: small
14369            output:
14370              output_directory: "./output"
14371        "#;
14372
14373        let config: GeneratorConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14374        assert_eq!(config.global.period_months, 3);
14375        assert_eq!(config.companies.len(), 1);
14376        assert!(!config.fraud.enabled); // Default
14377        assert!(!config.internal_controls.enabled); // Default
14378    }
14379
14380    #[test]
14381    fn test_config_with_fraud_enabled() {
14382        let yaml = r#"
14383            global:
14384              industry: retail
14385              start_date: "2024-01-01"
14386              period_months: 12
14387            companies:
14388              - code: "RETAIL"
14389                name: "Retail Co"
14390                currency: "USD"
14391                country: "US"
14392                annual_transaction_volume: hundred_k
14393            chart_of_accounts:
14394              complexity: medium
14395            output:
14396              output_directory: "./output"
14397            fraud:
14398              enabled: true
14399              fraud_rate: 0.05
14400              clustering_enabled: true
14401        "#;
14402
14403        let config: GeneratorConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14404        assert!(config.fraud.enabled);
14405        assert_eq!(config.fraud.fraud_rate, 0.05);
14406        assert!(config.fraud.clustering_enabled);
14407    }
14408
14409    #[test]
14410    fn test_config_with_multiple_companies() {
14411        let yaml = r#"
14412            global:
14413              industry: manufacturing
14414              start_date: "2024-01-01"
14415              period_months: 6
14416            companies:
14417              - code: "HQ"
14418                name: "Headquarters"
14419                currency: "USD"
14420                country: "US"
14421                annual_transaction_volume: hundred_k
14422                volume_weight: 1.0
14423              - code: "EU"
14424                name: "European Subsidiary"
14425                currency: "EUR"
14426                country: "DE"
14427                annual_transaction_volume: hundred_k
14428                volume_weight: 0.5
14429              - code: "APAC"
14430                name: "Asia Pacific"
14431                currency: "JPY"
14432                country: "JP"
14433                annual_transaction_volume: ten_k
14434                volume_weight: 0.3
14435            chart_of_accounts:
14436              complexity: large
14437            output:
14438              output_directory: "./output"
14439        "#;
14440
14441        let config: GeneratorConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14442        assert_eq!(config.companies.len(), 3);
14443        assert_eq!(config.companies[0].code, "HQ");
14444        assert_eq!(config.companies[1].currency, "EUR");
14445        assert_eq!(config.companies[2].volume_weight, 0.3);
14446    }
14447
14448    #[test]
14449    fn test_intercompany_config() {
14450        let yaml = r#"
14451            enabled: true
14452            ic_transaction_rate: 0.20
14453            transfer_pricing_method: cost_plus
14454            markup_percent: 0.08
14455            generate_matched_pairs: true
14456            generate_eliminations: true
14457        "#;
14458
14459        let config: IntercompanyConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14460        assert!(config.enabled);
14461        assert_eq!(config.ic_transaction_rate, 0.20);
14462        assert!(matches!(
14463            config.transfer_pricing_method,
14464            TransferPricingMethod::CostPlus
14465        ));
14466        assert_eq!(config.markup_percent, 0.08);
14467        assert!(config.generate_eliminations);
14468    }
14469
14470    // ==========================================================================
14471    // Company Config Tests
14472    // ==========================================================================
14473
14474    #[test]
14475    fn test_company_config_defaults() {
14476        let yaml = r#"
14477            code: "TEST"
14478            name: "Test Company"
14479            currency: "USD"
14480            country: "US"
14481            annual_transaction_volume: ten_k
14482        "#;
14483
14484        let config: CompanyConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14485        assert_eq!(config.fiscal_year_variant, "K4"); // Default
14486        assert_eq!(config.volume_weight, 1.0); // Default
14487    }
14488
14489    // ==========================================================================
14490    // Chart of Accounts Config Tests
14491    // ==========================================================================
14492
14493    #[test]
14494    fn test_coa_config_defaults() {
14495        let yaml = r#"
14496            complexity: medium
14497        "#;
14498
14499        let config: ChartOfAccountsConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14500        assert!(config.industry_specific); // Default true
14501        assert!(config.custom_accounts.is_none());
14502        assert_eq!(config.min_hierarchy_depth, 2); // Default
14503        assert_eq!(config.max_hierarchy_depth, 5); // Default
14504    }
14505
14506    // ==========================================================================
14507    // Accounting Standards Config Tests
14508    // ==========================================================================
14509
14510    #[test]
14511    fn test_accounting_standards_config_defaults() {
14512        let config = AccountingStandardsConfig::default();
14513        assert!(!config.enabled);
14514        assert!(config.framework.is_none());
14515        assert!(!config.revenue_recognition.enabled);
14516        assert!(!config.leases.enabled);
14517        assert!(!config.fair_value.enabled);
14518        assert!(!config.impairment.enabled);
14519        assert!(!config.generate_differences);
14520    }
14521
14522    #[test]
14523    fn test_accounting_standards_config_yaml() {
14524        let yaml = r#"
14525            enabled: true
14526            framework: ifrs
14527            revenue_recognition:
14528              enabled: true
14529              generate_contracts: true
14530              avg_obligations_per_contract: 2.5
14531              variable_consideration_rate: 0.20
14532              over_time_recognition_rate: 0.35
14533              contract_count: 150
14534            leases:
14535              enabled: true
14536              lease_count: 75
14537              finance_lease_percent: 0.25
14538              avg_lease_term_months: 48
14539            generate_differences: true
14540        "#;
14541
14542        let config: AccountingStandardsConfig =
14543            serde_yaml::from_str(yaml).expect("Failed to parse");
14544        assert!(config.enabled);
14545        assert!(matches!(
14546            config.framework,
14547            Some(AccountingFrameworkConfig::Ifrs)
14548        ));
14549        assert!(config.revenue_recognition.enabled);
14550        assert_eq!(config.revenue_recognition.contract_count, 150);
14551        assert_eq!(config.revenue_recognition.avg_obligations_per_contract, 2.5);
14552        assert!(config.leases.enabled);
14553        assert_eq!(config.leases.lease_count, 75);
14554        assert_eq!(config.leases.finance_lease_percent, 0.25);
14555        assert!(config.generate_differences);
14556    }
14557
14558    #[test]
14559    fn test_accounting_framework_serialization() {
14560        let frameworks = [
14561            AccountingFrameworkConfig::UsGaap,
14562            AccountingFrameworkConfig::Ifrs,
14563            AccountingFrameworkConfig::DualReporting,
14564            AccountingFrameworkConfig::FrenchGaap,
14565            AccountingFrameworkConfig::GermanGaap,
14566        ];
14567
14568        for framework in frameworks {
14569            let json = serde_json::to_string(&framework).expect("Failed to serialize");
14570            let deserialized: AccountingFrameworkConfig =
14571                serde_json::from_str(&json).expect("Failed to deserialize");
14572            assert!(format!("{:?}", framework) == format!("{:?}", deserialized));
14573        }
14574    }
14575
14576    #[test]
14577    fn test_revenue_recognition_config_defaults() {
14578        let config = RevenueRecognitionConfig::default();
14579        assert!(!config.enabled);
14580        assert!(config.generate_contracts);
14581        assert_eq!(config.avg_obligations_per_contract, 2.0);
14582        assert_eq!(config.variable_consideration_rate, 0.15);
14583        assert_eq!(config.over_time_recognition_rate, 0.30);
14584        assert_eq!(config.contract_count, 100);
14585    }
14586
14587    #[test]
14588    fn test_lease_accounting_config_defaults() {
14589        let config = LeaseAccountingConfig::default();
14590        assert!(!config.enabled);
14591        assert_eq!(config.lease_count, 50);
14592        assert_eq!(config.finance_lease_percent, 0.30);
14593        assert_eq!(config.avg_lease_term_months, 60);
14594        assert!(config.generate_amortization);
14595        assert_eq!(config.real_estate_percent, 0.40);
14596    }
14597
14598    #[test]
14599    fn test_fair_value_config_defaults() {
14600        let config = FairValueConfig::default();
14601        assert!(!config.enabled);
14602        assert_eq!(config.measurement_count, 25);
14603        assert_eq!(config.level1_percent, 0.40);
14604        assert_eq!(config.level2_percent, 0.35);
14605        assert_eq!(config.level3_percent, 0.25);
14606        assert!(!config.include_sensitivity_analysis);
14607    }
14608
14609    #[test]
14610    fn test_impairment_config_defaults() {
14611        let config = ImpairmentConfig::default();
14612        assert!(!config.enabled);
14613        assert_eq!(config.test_count, 15);
14614        assert_eq!(config.impairment_rate, 0.10);
14615        assert!(config.generate_projections);
14616        assert!(!config.include_goodwill);
14617    }
14618
14619    // ==========================================================================
14620    // Audit Standards Config Tests
14621    // ==========================================================================
14622
14623    #[test]
14624    fn test_audit_standards_config_defaults() {
14625        let config = AuditStandardsConfig::default();
14626        assert!(!config.enabled);
14627        assert!(!config.isa_compliance.enabled);
14628        assert!(!config.analytical_procedures.enabled);
14629        assert!(!config.confirmations.enabled);
14630        assert!(!config.opinion.enabled);
14631        assert!(!config.generate_audit_trail);
14632        assert!(!config.sox.enabled);
14633        assert!(!config.pcaob.enabled);
14634    }
14635
14636    #[test]
14637    fn test_audit_standards_config_yaml() {
14638        let yaml = r#"
14639            enabled: true
14640            isa_compliance:
14641              enabled: true
14642              compliance_level: comprehensive
14643              generate_isa_mappings: true
14644              include_pcaob: true
14645              framework: dual
14646            analytical_procedures:
14647              enabled: true
14648              procedures_per_account: 5
14649              variance_probability: 0.25
14650            confirmations:
14651              enabled: true
14652              confirmation_count: 75
14653              positive_response_rate: 0.90
14654              exception_rate: 0.08
14655            opinion:
14656              enabled: true
14657              generate_kam: true
14658              average_kam_count: 4
14659            sox:
14660              enabled: true
14661              generate_302_certifications: true
14662              generate_404_assessments: true
14663              material_weakness_rate: 0.03
14664            pcaob:
14665              enabled: true
14666              is_pcaob_audit: true
14667              include_icfr_opinion: true
14668            generate_audit_trail: true
14669        "#;
14670
14671        let config: AuditStandardsConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14672        assert!(config.enabled);
14673        assert!(config.isa_compliance.enabled);
14674        assert_eq!(config.isa_compliance.compliance_level, "comprehensive");
14675        assert!(config.isa_compliance.include_pcaob);
14676        assert_eq!(config.isa_compliance.framework, "dual");
14677        assert!(config.analytical_procedures.enabled);
14678        assert_eq!(config.analytical_procedures.procedures_per_account, 5);
14679        assert!(config.confirmations.enabled);
14680        assert_eq!(config.confirmations.confirmation_count, 75);
14681        assert!(config.opinion.enabled);
14682        assert_eq!(config.opinion.average_kam_count, 4);
14683        assert!(config.sox.enabled);
14684        assert!(config.sox.generate_302_certifications);
14685        assert_eq!(config.sox.material_weakness_rate, 0.03);
14686        assert!(config.pcaob.enabled);
14687        assert!(config.pcaob.is_pcaob_audit);
14688        assert!(config.pcaob.include_icfr_opinion);
14689        assert!(config.generate_audit_trail);
14690    }
14691
14692    #[test]
14693    fn test_isa_compliance_config_defaults() {
14694        let config = IsaComplianceConfig::default();
14695        assert!(!config.enabled);
14696        assert_eq!(config.compliance_level, "standard");
14697        assert!(config.generate_isa_mappings);
14698        assert!(config.generate_coverage_summary);
14699        assert!(!config.include_pcaob);
14700        assert_eq!(config.framework, "isa");
14701    }
14702
14703    #[test]
14704    fn test_sox_compliance_config_defaults() {
14705        let config = SoxComplianceConfig::default();
14706        assert!(!config.enabled);
14707        assert!(config.generate_302_certifications);
14708        assert!(config.generate_404_assessments);
14709        assert_eq!(config.materiality_threshold, 10000.0);
14710        assert_eq!(config.material_weakness_rate, 0.02);
14711        assert_eq!(config.significant_deficiency_rate, 0.08);
14712    }
14713
14714    #[test]
14715    fn test_pcaob_config_defaults() {
14716        let config = PcaobConfig::default();
14717        assert!(!config.enabled);
14718        assert!(!config.is_pcaob_audit);
14719        assert!(config.generate_cam);
14720        assert!(!config.include_icfr_opinion);
14721        assert!(!config.generate_standard_mappings);
14722    }
14723
14724    #[test]
14725    fn test_config_with_standards_enabled() {
14726        let yaml = r#"
14727            global:
14728              industry: financial_services
14729              start_date: "2024-01-01"
14730              period_months: 12
14731            companies:
14732              - code: "BANK"
14733                name: "Test Bank"
14734                currency: "USD"
14735                country: "US"
14736                annual_transaction_volume: hundred_k
14737            chart_of_accounts:
14738              complexity: large
14739            output:
14740              output_directory: "./output"
14741            accounting_standards:
14742              enabled: true
14743              framework: us_gaap
14744              revenue_recognition:
14745                enabled: true
14746              leases:
14747                enabled: true
14748            audit_standards:
14749              enabled: true
14750              isa_compliance:
14751                enabled: true
14752              sox:
14753                enabled: true
14754        "#;
14755
14756        let config: GeneratorConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14757        assert!(config.accounting_standards.enabled);
14758        assert!(matches!(
14759            config.accounting_standards.framework,
14760            Some(AccountingFrameworkConfig::UsGaap)
14761        ));
14762        assert!(config.accounting_standards.revenue_recognition.enabled);
14763        assert!(config.accounting_standards.leases.enabled);
14764        assert!(config.audit_standards.enabled);
14765        assert!(config.audit_standards.isa_compliance.enabled);
14766        assert!(config.audit_standards.sox.enabled);
14767    }
14768
14769    // ==========================================================================
14770    // Industry-Specific Config Tests
14771    // ==========================================================================
14772
14773    #[test]
14774    fn test_industry_specific_config_defaults() {
14775        let config = IndustrySpecificConfig::default();
14776        assert!(!config.enabled);
14777        assert!(!config.manufacturing.enabled);
14778        assert!(!config.retail.enabled);
14779        assert!(!config.healthcare.enabled);
14780        assert!(!config.technology.enabled);
14781        assert!(!config.financial_services.enabled);
14782        assert!(!config.professional_services.enabled);
14783    }
14784
14785    #[test]
14786    fn test_manufacturing_config_defaults() {
14787        let config = ManufacturingConfig::default();
14788        assert!(!config.enabled);
14789        assert_eq!(config.bom_depth, 4);
14790        assert!(!config.just_in_time);
14791        assert_eq!(config.supplier_tiers, 2);
14792        assert_eq!(config.target_yield_rate, 0.97);
14793        assert_eq!(config.scrap_alert_threshold, 0.03);
14794    }
14795
14796    #[test]
14797    fn test_retail_config_defaults() {
14798        let config = RetailConfig::default();
14799        assert!(!config.enabled);
14800        assert_eq!(config.avg_daily_transactions, 500);
14801        assert!(config.loss_prevention);
14802        assert_eq!(config.shrinkage_rate, 0.015);
14803    }
14804
14805    #[test]
14806    fn test_healthcare_config_defaults() {
14807        let config = HealthcareConfig::default();
14808        assert!(!config.enabled);
14809        assert_eq!(config.facility_type, "hospital");
14810        assert_eq!(config.avg_daily_encounters, 150);
14811        assert!(config.compliance.hipaa);
14812        assert!(config.compliance.stark_law);
14813        assert!(config.coding_systems.icd10);
14814        assert!(config.coding_systems.cpt);
14815    }
14816
14817    #[test]
14818    fn test_technology_config_defaults() {
14819        let config = TechnologyConfig::default();
14820        assert!(!config.enabled);
14821        assert_eq!(config.revenue_model, "saas");
14822        assert_eq!(config.subscription_revenue_pct, 0.60);
14823        assert!(config.rd_capitalization.enabled);
14824    }
14825
14826    #[test]
14827    fn test_config_with_industry_specific() {
14828        let yaml = r#"
14829            global:
14830              industry: healthcare
14831              start_date: "2024-01-01"
14832              period_months: 12
14833            companies:
14834              - code: "HOSP"
14835                name: "Test Hospital"
14836                currency: "USD"
14837                country: "US"
14838                annual_transaction_volume: hundred_k
14839            chart_of_accounts:
14840              complexity: medium
14841            output:
14842              output_directory: "./output"
14843            industry_specific:
14844              enabled: true
14845              healthcare:
14846                enabled: true
14847                facility_type: hospital
14848                payer_mix:
14849                  medicare: 0.45
14850                  medicaid: 0.15
14851                  commercial: 0.35
14852                  self_pay: 0.05
14853                coding_systems:
14854                  icd10: true
14855                  cpt: true
14856                  drg: true
14857                compliance:
14858                  hipaa: true
14859                  stark_law: true
14860                anomaly_rates:
14861                  upcoding: 0.03
14862                  unbundling: 0.02
14863        "#;
14864
14865        let config: GeneratorConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14866        assert!(config.industry_specific.enabled);
14867        assert!(config.industry_specific.healthcare.enabled);
14868        assert_eq!(
14869            config.industry_specific.healthcare.facility_type,
14870            "hospital"
14871        );
14872        assert_eq!(config.industry_specific.healthcare.payer_mix.medicare, 0.45);
14873        assert_eq!(config.industry_specific.healthcare.payer_mix.self_pay, 0.05);
14874        assert!(config.industry_specific.healthcare.coding_systems.icd10);
14875        assert!(config.industry_specific.healthcare.compliance.hipaa);
14876        assert_eq!(
14877            config.industry_specific.healthcare.anomaly_rates.upcoding,
14878            0.03
14879        );
14880    }
14881
14882    #[test]
14883    fn test_config_with_manufacturing_specific() {
14884        let yaml = r#"
14885            global:
14886              industry: manufacturing
14887              start_date: "2024-01-01"
14888              period_months: 12
14889            companies:
14890              - code: "MFG"
14891                name: "Test Manufacturing"
14892                currency: "USD"
14893                country: "US"
14894                annual_transaction_volume: hundred_k
14895            chart_of_accounts:
14896              complexity: medium
14897            output:
14898              output_directory: "./output"
14899            industry_specific:
14900              enabled: true
14901              manufacturing:
14902                enabled: true
14903                bom_depth: 5
14904                just_in_time: true
14905                supplier_tiers: 3
14906                target_yield_rate: 0.98
14907                anomaly_rates:
14908                  yield_manipulation: 0.02
14909                  phantom_production: 0.01
14910        "#;
14911
14912        let config: GeneratorConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14913        assert!(config.industry_specific.enabled);
14914        assert!(config.industry_specific.manufacturing.enabled);
14915        assert_eq!(config.industry_specific.manufacturing.bom_depth, 5);
14916        assert!(config.industry_specific.manufacturing.just_in_time);
14917        assert_eq!(config.industry_specific.manufacturing.supplier_tiers, 3);
14918        assert_eq!(
14919            config.industry_specific.manufacturing.target_yield_rate,
14920            0.98
14921        );
14922        assert_eq!(
14923            config
14924                .industry_specific
14925                .manufacturing
14926                .anomaly_rates
14927                .yield_manipulation,
14928            0.02
14929        );
14930    }
14931
14932    // ==========================================================================
14933    // Tax Configuration Tests
14934    // ==========================================================================
14935
14936    #[test]
14937    fn test_tax_config_defaults() {
14938        let tax = TaxConfig::default();
14939        assert!(!tax.enabled);
14940        assert!(tax.jurisdictions.countries.is_empty());
14941        assert!(!tax.jurisdictions.include_subnational);
14942        assert!(!tax.vat_gst.enabled);
14943        assert!(tax.vat_gst.standard_rates.is_empty());
14944        assert!(tax.vat_gst.reduced_rates.is_empty());
14945        assert!(tax.vat_gst.exempt_categories.is_empty());
14946        assert!(tax.vat_gst.reverse_charge);
14947        assert!(!tax.sales_tax.enabled);
14948        assert!(tax.sales_tax.nexus_states.is_empty());
14949        assert!(!tax.withholding.enabled);
14950        assert!(tax.withholding.treaty_network);
14951        assert_eq!(tax.withholding.default_rate, 0.30);
14952        assert_eq!(tax.withholding.treaty_reduced_rate, 0.15);
14953        assert!(tax.provisions.enabled);
14954        assert_eq!(tax.provisions.statutory_rate, 0.21);
14955        assert!(tax.provisions.uncertain_positions);
14956        assert!(!tax.payroll_tax.enabled);
14957        assert_eq!(tax.anomaly_rate, 0.03);
14958    }
14959
14960    #[test]
14961    fn test_tax_config_from_yaml() {
14962        let yaml = r#"
14963            global:
14964              seed: 42
14965              start_date: "2024-01-01"
14966              period_months: 12
14967              industry: retail
14968            companies:
14969              - code: C001
14970                name: Test Corp
14971                currency: USD
14972                country: US
14973                annual_transaction_volume: ten_k
14974            chart_of_accounts:
14975              complexity: small
14976            output:
14977              output_directory: ./output
14978            tax:
14979              enabled: true
14980              anomaly_rate: 0.05
14981              jurisdictions:
14982                countries: ["US", "DE", "GB"]
14983                include_subnational: true
14984              vat_gst:
14985                enabled: true
14986                standard_rates:
14987                  DE: 0.19
14988                  GB: 0.20
14989                reduced_rates:
14990                  DE: 0.07
14991                  GB: 0.05
14992                exempt_categories:
14993                  - financial_services
14994                  - healthcare
14995                reverse_charge: false
14996              sales_tax:
14997                enabled: true
14998                nexus_states: ["CA", "NY", "TX"]
14999              withholding:
15000                enabled: true
15001                treaty_network: false
15002                default_rate: 0.25
15003                treaty_reduced_rate: 0.10
15004              provisions:
15005                enabled: false
15006                statutory_rate: 0.28
15007                uncertain_positions: false
15008              payroll_tax:
15009                enabled: true
15010        "#;
15011
15012        let config: GeneratorConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
15013        assert!(config.tax.enabled);
15014        assert_eq!(config.tax.anomaly_rate, 0.05);
15015
15016        // Jurisdictions
15017        assert_eq!(config.tax.jurisdictions.countries.len(), 3);
15018        assert!(config
15019            .tax
15020            .jurisdictions
15021            .countries
15022            .contains(&"DE".to_string()));
15023        assert!(config.tax.jurisdictions.include_subnational);
15024
15025        // VAT/GST
15026        assert!(config.tax.vat_gst.enabled);
15027        assert_eq!(config.tax.vat_gst.standard_rates.get("DE"), Some(&0.19));
15028        assert_eq!(config.tax.vat_gst.standard_rates.get("GB"), Some(&0.20));
15029        assert_eq!(config.tax.vat_gst.reduced_rates.get("DE"), Some(&0.07));
15030        assert_eq!(config.tax.vat_gst.exempt_categories.len(), 2);
15031        assert!(!config.tax.vat_gst.reverse_charge);
15032
15033        // Sales tax
15034        assert!(config.tax.sales_tax.enabled);
15035        assert_eq!(config.tax.sales_tax.nexus_states.len(), 3);
15036        assert!(config
15037            .tax
15038            .sales_tax
15039            .nexus_states
15040            .contains(&"CA".to_string()));
15041
15042        // Withholding
15043        assert!(config.tax.withholding.enabled);
15044        assert!(!config.tax.withholding.treaty_network);
15045        assert_eq!(config.tax.withholding.default_rate, 0.25);
15046        assert_eq!(config.tax.withholding.treaty_reduced_rate, 0.10);
15047
15048        // Provisions
15049        assert!(!config.tax.provisions.enabled);
15050        assert_eq!(config.tax.provisions.statutory_rate, 0.28);
15051        assert!(!config.tax.provisions.uncertain_positions);
15052
15053        // Payroll tax
15054        assert!(config.tax.payroll_tax.enabled);
15055    }
15056
15057    #[test]
15058    fn test_generator_config_with_tax_default() {
15059        let yaml = r#"
15060            global:
15061              seed: 42
15062              start_date: "2024-01-01"
15063              period_months: 12
15064              industry: retail
15065            companies:
15066              - code: C001
15067                name: Test Corp
15068                currency: USD
15069                country: US
15070                annual_transaction_volume: ten_k
15071            chart_of_accounts:
15072              complexity: small
15073            output:
15074              output_directory: ./output
15075        "#;
15076
15077        let config: GeneratorConfig =
15078            serde_yaml::from_str(yaml).expect("Failed to parse config without tax section");
15079        // Tax should be present with defaults when not specified in YAML
15080        assert!(!config.tax.enabled);
15081        assert!(config.tax.jurisdictions.countries.is_empty());
15082        assert_eq!(config.tax.anomaly_rate, 0.03);
15083        assert!(config.tax.provisions.enabled); // provisions default to enabled=true
15084        assert_eq!(config.tax.provisions.statutory_rate, 0.21);
15085    }
15086
15087    // ==========================================================================
15088    // SessionSchemaConfig Tests
15089    // ==========================================================================
15090
15091    #[test]
15092    fn test_session_config_default_disabled() {
15093        let yaml = "{}";
15094        let config: SessionSchemaConfig =
15095            serde_yaml::from_str(yaml).expect("Failed to parse empty session config");
15096        assert!(!config.enabled);
15097        assert!(config.checkpoint_path.is_none());
15098        assert!(config.per_period_output);
15099        assert!(config.consolidated_output);
15100    }
15101
15102    #[test]
15103    fn test_config_backward_compatible_without_session() {
15104        let yaml = r#"
15105            global:
15106              seed: 42
15107              start_date: "2024-01-01"
15108              period_months: 12
15109              industry: retail
15110            companies:
15111              - code: C001
15112                name: Test Corp
15113                currency: USD
15114                country: US
15115                annual_transaction_volume: ten_k
15116            chart_of_accounts:
15117              complexity: small
15118            output:
15119              output_directory: ./output
15120        "#;
15121
15122        let config: GeneratorConfig =
15123            serde_yaml::from_str(yaml).expect("Failed to parse config without session");
15124        // Session should default to disabled
15125        assert!(!config.session.enabled);
15126        assert!(config.session.per_period_output);
15127        assert!(config.session.consolidated_output);
15128        // fiscal_year_months should be None
15129        assert!(config.global.fiscal_year_months.is_none());
15130    }
15131
15132    #[test]
15133    fn test_fiscal_year_months_parsed() {
15134        let yaml = r#"
15135            global:
15136              seed: 42
15137              start_date: "2024-01-01"
15138              period_months: 24
15139              industry: retail
15140              fiscal_year_months: 12
15141            companies:
15142              - code: C001
15143                name: Test Corp
15144                currency: USD
15145                country: US
15146                annual_transaction_volume: ten_k
15147            chart_of_accounts:
15148              complexity: small
15149            output:
15150              output_directory: ./output
15151            session:
15152              enabled: true
15153              checkpoint_path: /tmp/checkpoints
15154              per_period_output: true
15155              consolidated_output: false
15156        "#;
15157
15158        let config: GeneratorConfig =
15159            serde_yaml::from_str(yaml).expect("Failed to parse config with fiscal_year_months");
15160        assert_eq!(config.global.fiscal_year_months, Some(12));
15161        assert!(config.session.enabled);
15162        assert_eq!(
15163            config.session.checkpoint_path,
15164            Some("/tmp/checkpoints".to_string())
15165        );
15166        assert!(config.session.per_period_output);
15167        assert!(!config.session.consolidated_output);
15168    }
15169}