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    // v5.0.1: bumped 0.01 → 0.05 to deliver meaningful scheme-level
2068    // fraud propagation at typical line-level rates. The 1 % default
2069    // (set in v4.4.2 to restore `is_fraud_propagated > 0`) was too
2070    // conservative — at `fraud_rate = 0.08` it produced ~3.6 % observed
2071    // propagation against a 26.7 % target. The new 5 % default + the
2072    // additive formula `P(line is_fraud) ≈ fraud_rate + 0.3 × d` yields
2073    // ~9.5 % combined at fraud_rate=0.08 (closer to the spec target).
2074    // Set explicitly to `Some(0.0)` or `null` in YAML to disable, or to
2075    // a higher value (e.g. 0.20) for scheme-heavy fraud workloads.
2076    Some(0.05)
2077}
2078fn default_clustering_factor() -> f64 {
2079    3.0
2080}
2081
2082impl Default for FraudConfig {
2083    fn default() -> Self {
2084        Self {
2085            enabled: false,
2086            fraud_rate: default_fraud_rate(),
2087            document_fraud_rate: default_document_fraud_rate(),
2088            propagate_to_lines: true,
2089            propagate_to_document: true,
2090            fraud_type_distribution: FraudTypeDistribution::default(),
2091            clustering_enabled: false,
2092            clustering_factor: default_clustering_factor(),
2093            approval_thresholds: default_approval_thresholds(),
2094        }
2095    }
2096}
2097
2098/// Distribution of fraud types.
2099#[derive(Debug, Clone, Serialize, Deserialize)]
2100pub struct FraudTypeDistribution {
2101    pub suspense_account_abuse: f64,
2102    pub fictitious_transaction: f64,
2103    pub revenue_manipulation: f64,
2104    pub expense_capitalization: f64,
2105    pub split_transaction: f64,
2106    pub timing_anomaly: f64,
2107    pub unauthorized_access: f64,
2108    pub duplicate_payment: f64,
2109}
2110
2111impl Default for FraudTypeDistribution {
2112    fn default() -> Self {
2113        Self {
2114            suspense_account_abuse: 0.25,
2115            fictitious_transaction: 0.15,
2116            revenue_manipulation: 0.10,
2117            expense_capitalization: 0.10,
2118            split_transaction: 0.15,
2119            timing_anomaly: 0.10,
2120            unauthorized_access: 0.10,
2121            duplicate_payment: 0.05,
2122        }
2123    }
2124}
2125
2126/// Internal Controls System (ICS) configuration.
2127#[derive(Debug, Clone, Serialize, Deserialize)]
2128pub struct InternalControlsConfig {
2129    /// Enable internal controls system
2130    #[serde(default)]
2131    pub enabled: bool,
2132    /// Rate at which controls result in exceptions (0.0 - 1.0)
2133    #[serde(default = "default_exception_rate")]
2134    pub exception_rate: f64,
2135    /// Rate at which SoD violations occur (0.0 - 1.0)
2136    #[serde(default = "default_sod_violation_rate")]
2137    pub sod_violation_rate: f64,
2138    /// Export control master data to separate files
2139    #[serde(default = "default_true")]
2140    pub export_control_master_data: bool,
2141    /// SOX materiality threshold for marking transactions as SOX-relevant
2142    #[serde(default = "default_sox_materiality_threshold")]
2143    pub sox_materiality_threshold: f64,
2144    /// Enable COSO 2013 framework integration
2145    #[serde(default = "default_true")]
2146    pub coso_enabled: bool,
2147    /// Include entity-level controls in generation
2148    #[serde(default)]
2149    pub include_entity_level_controls: bool,
2150    /// Target maturity level for controls
2151    /// Valid values: "ad_hoc", "repeatable", "defined", "managed", "optimized", "mixed"
2152    #[serde(default = "default_target_maturity_level")]
2153    pub target_maturity_level: String,
2154}
2155
2156fn default_exception_rate() -> f64 {
2157    0.02
2158}
2159
2160fn default_sod_violation_rate() -> f64 {
2161    0.01
2162}
2163
2164fn default_sox_materiality_threshold() -> f64 {
2165    10000.0
2166}
2167
2168fn default_target_maturity_level() -> String {
2169    "mixed".to_string()
2170}
2171
2172impl Default for InternalControlsConfig {
2173    fn default() -> Self {
2174        Self {
2175            enabled: false,
2176            exception_rate: default_exception_rate(),
2177            sod_violation_rate: default_sod_violation_rate(),
2178            export_control_master_data: true,
2179            sox_materiality_threshold: default_sox_materiality_threshold(),
2180            coso_enabled: true,
2181            include_entity_level_controls: false,
2182            target_maturity_level: default_target_maturity_level(),
2183        }
2184    }
2185}
2186
2187/// Business process configuration.
2188#[derive(Debug, Clone, Serialize, Deserialize)]
2189pub struct BusinessProcessConfig {
2190    /// Order-to-Cash weight
2191    #[serde(default = "default_o2c")]
2192    pub o2c_weight: f64,
2193    /// Procure-to-Pay weight
2194    #[serde(default = "default_p2p")]
2195    pub p2p_weight: f64,
2196    /// Record-to-Report weight
2197    #[serde(default = "default_r2r")]
2198    pub r2r_weight: f64,
2199    /// Hire-to-Retire weight
2200    #[serde(default = "default_h2r")]
2201    pub h2r_weight: f64,
2202    /// Acquire-to-Retire weight
2203    #[serde(default = "default_a2r")]
2204    pub a2r_weight: f64,
2205}
2206
2207fn default_o2c() -> f64 {
2208    0.35
2209}
2210fn default_p2p() -> f64 {
2211    0.30
2212}
2213fn default_r2r() -> f64 {
2214    0.20
2215}
2216fn default_h2r() -> f64 {
2217    0.10
2218}
2219fn default_a2r() -> f64 {
2220    0.05
2221}
2222
2223impl Default for BusinessProcessConfig {
2224    fn default() -> Self {
2225        Self {
2226            o2c_weight: default_o2c(),
2227            p2p_weight: default_p2p(),
2228            r2r_weight: default_r2r(),
2229            h2r_weight: default_h2r(),
2230            a2r_weight: default_a2r(),
2231        }
2232    }
2233}
2234
2235/// User persona configuration.
2236#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2237pub struct UserPersonaConfig {
2238    /// Distribution of user personas
2239    #[serde(default)]
2240    pub persona_distribution: PersonaDistribution,
2241    /// Users per persona type
2242    #[serde(default)]
2243    pub users_per_persona: UsersPerPersona,
2244}
2245
2246/// Distribution of user personas for transaction generation.
2247#[derive(Debug, Clone, Serialize, Deserialize)]
2248pub struct PersonaDistribution {
2249    pub junior_accountant: f64,
2250    pub senior_accountant: f64,
2251    pub controller: f64,
2252    pub manager: f64,
2253    pub automated_system: f64,
2254}
2255
2256impl Default for PersonaDistribution {
2257    fn default() -> Self {
2258        Self {
2259            junior_accountant: 0.15,
2260            senior_accountant: 0.15,
2261            controller: 0.05,
2262            manager: 0.05,
2263            automated_system: 0.60,
2264        }
2265    }
2266}
2267
2268/// Number of users per persona type.
2269#[derive(Debug, Clone, Serialize, Deserialize)]
2270pub struct UsersPerPersona {
2271    pub junior_accountant: usize,
2272    pub senior_accountant: usize,
2273    pub controller: usize,
2274    pub manager: usize,
2275    pub automated_system: usize,
2276}
2277
2278impl Default for UsersPerPersona {
2279    fn default() -> Self {
2280        Self {
2281            junior_accountant: 10,
2282            senior_accountant: 5,
2283            controller: 2,
2284            manager: 3,
2285            automated_system: 20,
2286        }
2287    }
2288}
2289
2290/// Template configuration for realistic data generation.
2291///
2292/// # User-supplied template packs (v3.2.0+)
2293///
2294/// Set `path` to a directory (or single YAML/JSON file) to override or
2295/// extend the embedded default pools for vendor names, customer names,
2296/// material/asset descriptions, audit findings, bank names, and
2297/// department names. When `path` is `None` (the default), generators
2298/// use the compiled-in pools and output is byte-identical to v3.1.2.
2299///
2300/// See `crates/datasynth-core/src/templates/loader.rs::TemplateData`
2301/// for the full YAML schema. Use `datasynth-data templates export` to
2302/// dump the defaults as a starter pack.
2303#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2304pub struct TemplateConfig {
2305    /// Name generation settings
2306    #[serde(default)]
2307    pub names: NameTemplateConfig,
2308    /// Description generation settings
2309    #[serde(default)]
2310    pub descriptions: DescriptionTemplateConfig,
2311    /// Reference number settings
2312    #[serde(default)]
2313    pub references: ReferenceTemplateConfig,
2314    /// Optional path to a user-supplied template file or directory.
2315    /// When set, entries from the file(s) augment or replace the
2316    /// embedded defaults according to `merge_strategy`.
2317    ///
2318    /// `None` (default) = use embedded pools only (byte-identical to v3.1.2).
2319    #[serde(default, alias = "templatesPath")]
2320    pub path: Option<std::path::PathBuf>,
2321    /// How file-based entries combine with embedded defaults.
2322    ///
2323    /// - `extend` (default): append file entries to embedded pools,
2324    ///   de-duplicating. Safe for incremental overlays.
2325    /// - `replace`: discard embedded pools entirely and use only file
2326    ///   entries. Requires a fully-populated template file.
2327    /// - `merge_prefer_file`: replace individual categories when present
2328    ///   in the file; keep embedded for absent categories.
2329    #[serde(default, alias = "mergeStrategy")]
2330    pub merge_strategy: TemplateMergeStrategy,
2331}
2332
2333/// Strategy for combining user-supplied template files with embedded defaults.
2334#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
2335#[serde(rename_all = "snake_case")]
2336pub enum TemplateMergeStrategy {
2337    /// Append file entries to embedded pools (default).
2338    #[default]
2339    Extend,
2340    /// Replace embedded pools entirely with file entries.
2341    Replace,
2342    /// Replace individual categories when present in file; keep embedded for absent ones.
2343    MergePreferFile,
2344}
2345
2346/// Name template configuration.
2347#[derive(Debug, Clone, Serialize, Deserialize)]
2348pub struct NameTemplateConfig {
2349    /// Distribution of name cultures
2350    #[serde(default)]
2351    pub culture_distribution: CultureDistribution,
2352    /// Email domain for generated users
2353    #[serde(default = "default_email_domain")]
2354    pub email_domain: String,
2355    /// Generate realistic display names
2356    #[serde(default = "default_true")]
2357    pub generate_realistic_names: bool,
2358}
2359
2360fn default_email_domain() -> String {
2361    "company.com".to_string()
2362}
2363
2364impl Default for NameTemplateConfig {
2365    fn default() -> Self {
2366        Self {
2367            culture_distribution: CultureDistribution::default(),
2368            email_domain: default_email_domain(),
2369            generate_realistic_names: true,
2370        }
2371    }
2372}
2373
2374/// Distribution of name cultures for generation.
2375#[derive(Debug, Clone, Serialize, Deserialize)]
2376pub struct CultureDistribution {
2377    pub western_us: f64,
2378    pub hispanic: f64,
2379    pub german: f64,
2380    pub french: f64,
2381    pub chinese: f64,
2382    pub japanese: f64,
2383    pub indian: f64,
2384}
2385
2386impl Default for CultureDistribution {
2387    fn default() -> Self {
2388        Self {
2389            western_us: 0.40,
2390            hispanic: 0.20,
2391            german: 0.10,
2392            french: 0.05,
2393            chinese: 0.10,
2394            japanese: 0.05,
2395            indian: 0.10,
2396        }
2397    }
2398}
2399
2400/// Description template configuration.
2401#[derive(Debug, Clone, Serialize, Deserialize)]
2402pub struct DescriptionTemplateConfig {
2403    /// Generate header text for journal entries
2404    #[serde(default = "default_true")]
2405    pub generate_header_text: bool,
2406    /// Generate line text for journal entry lines
2407    #[serde(default = "default_true")]
2408    pub generate_line_text: bool,
2409}
2410
2411impl Default for DescriptionTemplateConfig {
2412    fn default() -> Self {
2413        Self {
2414            generate_header_text: true,
2415            generate_line_text: true,
2416        }
2417    }
2418}
2419
2420/// Reference number template configuration.
2421#[derive(Debug, Clone, Serialize, Deserialize)]
2422pub struct ReferenceTemplateConfig {
2423    /// Generate reference numbers
2424    #[serde(default = "default_true")]
2425    pub generate_references: bool,
2426    /// Invoice prefix
2427    #[serde(default = "default_invoice_prefix")]
2428    pub invoice_prefix: String,
2429    /// Purchase order prefix
2430    #[serde(default = "default_po_prefix")]
2431    pub po_prefix: String,
2432    /// Sales order prefix
2433    #[serde(default = "default_so_prefix")]
2434    pub so_prefix: String,
2435}
2436
2437fn default_invoice_prefix() -> String {
2438    "INV".to_string()
2439}
2440fn default_po_prefix() -> String {
2441    "PO".to_string()
2442}
2443fn default_so_prefix() -> String {
2444    "SO".to_string()
2445}
2446
2447impl Default for ReferenceTemplateConfig {
2448    fn default() -> Self {
2449        Self {
2450            generate_references: true,
2451            invoice_prefix: default_invoice_prefix(),
2452            po_prefix: default_po_prefix(),
2453            so_prefix: default_so_prefix(),
2454        }
2455    }
2456}
2457
2458/// Approval workflow configuration.
2459#[derive(Debug, Clone, Serialize, Deserialize)]
2460pub struct ApprovalConfig {
2461    /// Enable approval workflow generation
2462    #[serde(default)]
2463    pub enabled: bool,
2464    /// Threshold below which transactions are auto-approved
2465    #[serde(default = "default_auto_approve_threshold")]
2466    pub auto_approve_threshold: f64,
2467    /// Rate at which approvals are rejected (0.0 to 1.0)
2468    #[serde(default = "default_rejection_rate")]
2469    pub rejection_rate: f64,
2470    /// Rate at which approvals require revision (0.0 to 1.0)
2471    #[serde(default = "default_revision_rate")]
2472    pub revision_rate: f64,
2473    /// Average delay in hours for approval processing
2474    #[serde(default = "default_approval_delay_hours")]
2475    pub average_approval_delay_hours: f64,
2476    /// Approval chain thresholds
2477    #[serde(default)]
2478    pub thresholds: Vec<ApprovalThresholdConfig>,
2479}
2480
2481fn default_auto_approve_threshold() -> f64 {
2482    1000.0
2483}
2484fn default_rejection_rate() -> f64 {
2485    0.02
2486}
2487fn default_revision_rate() -> f64 {
2488    0.05
2489}
2490fn default_approval_delay_hours() -> f64 {
2491    4.0
2492}
2493
2494impl Default for ApprovalConfig {
2495    fn default() -> Self {
2496        Self {
2497            enabled: false,
2498            auto_approve_threshold: default_auto_approve_threshold(),
2499            rejection_rate: default_rejection_rate(),
2500            revision_rate: default_revision_rate(),
2501            average_approval_delay_hours: default_approval_delay_hours(),
2502            thresholds: vec![
2503                ApprovalThresholdConfig {
2504                    amount: 1000.0,
2505                    level: 1,
2506                    roles: vec!["senior_accountant".to_string()],
2507                },
2508                ApprovalThresholdConfig {
2509                    amount: 10000.0,
2510                    level: 2,
2511                    roles: vec!["senior_accountant".to_string(), "controller".to_string()],
2512                },
2513                ApprovalThresholdConfig {
2514                    amount: 100000.0,
2515                    level: 3,
2516                    roles: vec![
2517                        "senior_accountant".to_string(),
2518                        "controller".to_string(),
2519                        "manager".to_string(),
2520                    ],
2521                },
2522                ApprovalThresholdConfig {
2523                    amount: 500000.0,
2524                    level: 4,
2525                    roles: vec![
2526                        "senior_accountant".to_string(),
2527                        "controller".to_string(),
2528                        "manager".to_string(),
2529                        "executive".to_string(),
2530                    ],
2531                },
2532            ],
2533        }
2534    }
2535}
2536
2537/// Configuration for a single approval threshold.
2538#[derive(Debug, Clone, Serialize, Deserialize)]
2539pub struct ApprovalThresholdConfig {
2540    /// Amount threshold
2541    pub amount: f64,
2542    /// Approval level required
2543    pub level: u8,
2544    /// Roles that can approve at this level
2545    pub roles: Vec<String>,
2546}
2547
2548/// Department configuration.
2549#[derive(Debug, Clone, Serialize, Deserialize)]
2550pub struct DepartmentConfig {
2551    /// Enable department assignment
2552    #[serde(default)]
2553    pub enabled: bool,
2554    /// Multiplier for department headcounts
2555    #[serde(default = "default_headcount_multiplier")]
2556    pub headcount_multiplier: f64,
2557    /// Custom department definitions (optional)
2558    #[serde(default)]
2559    pub custom_departments: Vec<CustomDepartmentConfig>,
2560}
2561
2562fn default_headcount_multiplier() -> f64 {
2563    1.0
2564}
2565
2566impl Default for DepartmentConfig {
2567    fn default() -> Self {
2568        Self {
2569            enabled: false,
2570            headcount_multiplier: default_headcount_multiplier(),
2571            custom_departments: Vec::new(),
2572        }
2573    }
2574}
2575
2576/// Custom department definition.
2577#[derive(Debug, Clone, Serialize, Deserialize)]
2578pub struct CustomDepartmentConfig {
2579    /// Department code
2580    pub code: String,
2581    /// Department name
2582    pub name: String,
2583    /// Associated cost center
2584    #[serde(default)]
2585    pub cost_center: Option<String>,
2586    /// Primary business processes
2587    #[serde(default)]
2588    pub primary_processes: Vec<String>,
2589    /// Parent department code
2590    #[serde(default)]
2591    pub parent_code: Option<String>,
2592}
2593
2594// ============================================================================
2595// Master Data Configuration
2596// ============================================================================
2597
2598/// Master data generation configuration.
2599#[derive(Debug, Clone, Default, Serialize, Deserialize)]
2600pub struct MasterDataConfig {
2601    /// Vendor master data settings
2602    #[serde(default)]
2603    pub vendors: VendorMasterConfig,
2604    /// Customer master data settings
2605    #[serde(default)]
2606    pub customers: CustomerMasterConfig,
2607    /// Material master data settings
2608    #[serde(default)]
2609    pub materials: MaterialMasterConfig,
2610    /// Fixed asset master data settings
2611    #[serde(default)]
2612    pub fixed_assets: FixedAssetMasterConfig,
2613    /// Employee master data settings
2614    #[serde(default)]
2615    pub employees: EmployeeMasterConfig,
2616    /// Cost center master data settings
2617    #[serde(default)]
2618    pub cost_centers: CostCenterMasterConfig,
2619}
2620
2621/// Vendor master data configuration.
2622#[derive(Debug, Clone, Serialize, Deserialize)]
2623pub struct VendorMasterConfig {
2624    /// Number of vendors to generate
2625    #[serde(default = "default_vendor_count")]
2626    pub count: usize,
2627    /// Percentage of vendors that are intercompany (0.0 to 1.0)
2628    #[serde(default = "default_intercompany_percent")]
2629    pub intercompany_percent: f64,
2630    /// Payment terms distribution
2631    #[serde(default)]
2632    pub payment_terms_distribution: PaymentTermsDistribution,
2633    /// Vendor behavior distribution
2634    #[serde(default)]
2635    pub behavior_distribution: VendorBehaviorDistribution,
2636    /// Generate bank account details
2637    #[serde(default = "default_true")]
2638    pub generate_bank_accounts: bool,
2639    /// Generate tax IDs
2640    #[serde(default = "default_true")]
2641    pub generate_tax_ids: bool,
2642}
2643
2644fn default_vendor_count() -> usize {
2645    500
2646}
2647
2648fn default_intercompany_percent() -> f64 {
2649    0.05
2650}
2651
2652impl Default for VendorMasterConfig {
2653    fn default() -> Self {
2654        Self {
2655            count: default_vendor_count(),
2656            intercompany_percent: default_intercompany_percent(),
2657            payment_terms_distribution: PaymentTermsDistribution::default(),
2658            behavior_distribution: VendorBehaviorDistribution::default(),
2659            generate_bank_accounts: true,
2660            generate_tax_ids: true,
2661        }
2662    }
2663}
2664
2665/// Payment terms distribution for vendors.
2666#[derive(Debug, Clone, Serialize, Deserialize)]
2667pub struct PaymentTermsDistribution {
2668    /// Net 30 days
2669    pub net_30: f64,
2670    /// Net 60 days
2671    pub net_60: f64,
2672    /// Net 90 days
2673    pub net_90: f64,
2674    /// 2% 10 Net 30 (early payment discount)
2675    pub two_ten_net_30: f64,
2676    /// Due on receipt
2677    pub due_on_receipt: f64,
2678    /// End of month
2679    pub end_of_month: f64,
2680}
2681
2682impl Default for PaymentTermsDistribution {
2683    fn default() -> Self {
2684        Self {
2685            net_30: 0.40,
2686            net_60: 0.20,
2687            net_90: 0.10,
2688            two_ten_net_30: 0.15,
2689            due_on_receipt: 0.05,
2690            end_of_month: 0.10,
2691        }
2692    }
2693}
2694
2695/// Vendor behavior distribution.
2696#[derive(Debug, Clone, Serialize, Deserialize)]
2697pub struct VendorBehaviorDistribution {
2698    /// Reliable vendors (consistent delivery, quality)
2699    pub reliable: f64,
2700    /// Sometimes late vendors
2701    pub sometimes_late: f64,
2702    /// Inconsistent quality vendors
2703    pub inconsistent_quality: f64,
2704    /// Premium vendors (high quality, premium pricing)
2705    pub premium: f64,
2706    /// Budget vendors (lower quality, lower pricing)
2707    pub budget: f64,
2708}
2709
2710impl Default for VendorBehaviorDistribution {
2711    fn default() -> Self {
2712        Self {
2713            reliable: 0.50,
2714            sometimes_late: 0.20,
2715            inconsistent_quality: 0.10,
2716            premium: 0.10,
2717            budget: 0.10,
2718        }
2719    }
2720}
2721
2722/// Customer master data configuration.
2723#[derive(Debug, Clone, Serialize, Deserialize)]
2724pub struct CustomerMasterConfig {
2725    /// Number of customers to generate
2726    #[serde(default = "default_customer_count")]
2727    pub count: usize,
2728    /// Percentage of customers that are intercompany (0.0 to 1.0)
2729    #[serde(default = "default_intercompany_percent")]
2730    pub intercompany_percent: f64,
2731    /// Credit rating distribution
2732    #[serde(default)]
2733    pub credit_rating_distribution: CreditRatingDistribution,
2734    /// Payment behavior distribution
2735    #[serde(default)]
2736    pub payment_behavior_distribution: PaymentBehaviorDistribution,
2737    /// Generate credit limits based on rating
2738    #[serde(default = "default_true")]
2739    pub generate_credit_limits: bool,
2740}
2741
2742fn default_customer_count() -> usize {
2743    2000
2744}
2745
2746impl Default for CustomerMasterConfig {
2747    fn default() -> Self {
2748        Self {
2749            count: default_customer_count(),
2750            intercompany_percent: default_intercompany_percent(),
2751            credit_rating_distribution: CreditRatingDistribution::default(),
2752            payment_behavior_distribution: PaymentBehaviorDistribution::default(),
2753            generate_credit_limits: true,
2754        }
2755    }
2756}
2757
2758/// Credit rating distribution for customers.
2759#[derive(Debug, Clone, Serialize, Deserialize)]
2760pub struct CreditRatingDistribution {
2761    /// AAA rating
2762    pub aaa: f64,
2763    /// AA rating
2764    pub aa: f64,
2765    /// A rating
2766    pub a: f64,
2767    /// BBB rating
2768    pub bbb: f64,
2769    /// BB rating
2770    pub bb: f64,
2771    /// B rating
2772    pub b: f64,
2773    /// Below B rating
2774    pub below_b: f64,
2775}
2776
2777impl Default for CreditRatingDistribution {
2778    fn default() -> Self {
2779        Self {
2780            aaa: 0.05,
2781            aa: 0.10,
2782            a: 0.20,
2783            bbb: 0.30,
2784            bb: 0.20,
2785            b: 0.10,
2786            below_b: 0.05,
2787        }
2788    }
2789}
2790
2791/// Payment behavior distribution for customers.
2792#[derive(Debug, Clone, Serialize, Deserialize)]
2793pub struct PaymentBehaviorDistribution {
2794    /// Always pays early
2795    pub early_payer: f64,
2796    /// Pays on time
2797    pub on_time: f64,
2798    /// Occasionally late
2799    pub occasional_late: f64,
2800    /// Frequently late
2801    pub frequent_late: f64,
2802    /// Takes early payment discounts
2803    pub discount_taker: f64,
2804}
2805
2806impl Default for PaymentBehaviorDistribution {
2807    fn default() -> Self {
2808        Self {
2809            early_payer: 0.10,
2810            on_time: 0.50,
2811            occasional_late: 0.25,
2812            frequent_late: 0.10,
2813            discount_taker: 0.05,
2814        }
2815    }
2816}
2817
2818/// Material master data configuration.
2819#[derive(Debug, Clone, Serialize, Deserialize)]
2820pub struct MaterialMasterConfig {
2821    /// Number of materials to generate
2822    #[serde(default = "default_material_count")]
2823    pub count: usize,
2824    /// Material type distribution
2825    #[serde(default)]
2826    pub type_distribution: MaterialTypeDistribution,
2827    /// Valuation method distribution
2828    #[serde(default)]
2829    pub valuation_distribution: ValuationMethodDistribution,
2830    /// Percentage of materials with BOM (bill of materials)
2831    #[serde(default = "default_bom_percent")]
2832    pub bom_percent: f64,
2833    /// Maximum BOM depth
2834    #[serde(default = "default_max_bom_depth")]
2835    pub max_bom_depth: u8,
2836}
2837
2838fn default_material_count() -> usize {
2839    5000
2840}
2841
2842fn default_bom_percent() -> f64 {
2843    0.20
2844}
2845
2846fn default_max_bom_depth() -> u8 {
2847    3
2848}
2849
2850impl Default for MaterialMasterConfig {
2851    fn default() -> Self {
2852        Self {
2853            count: default_material_count(),
2854            type_distribution: MaterialTypeDistribution::default(),
2855            valuation_distribution: ValuationMethodDistribution::default(),
2856            bom_percent: default_bom_percent(),
2857            max_bom_depth: default_max_bom_depth(),
2858        }
2859    }
2860}
2861
2862/// Material type distribution.
2863#[derive(Debug, Clone, Serialize, Deserialize)]
2864pub struct MaterialTypeDistribution {
2865    /// Raw materials
2866    pub raw_material: f64,
2867    /// Semi-finished goods
2868    pub semi_finished: f64,
2869    /// Finished goods
2870    pub finished_good: f64,
2871    /// Trading goods (purchased for resale)
2872    pub trading_good: f64,
2873    /// Operating supplies
2874    pub operating_supply: f64,
2875    /// Services
2876    pub service: f64,
2877}
2878
2879impl Default for MaterialTypeDistribution {
2880    fn default() -> Self {
2881        Self {
2882            raw_material: 0.30,
2883            semi_finished: 0.15,
2884            finished_good: 0.25,
2885            trading_good: 0.15,
2886            operating_supply: 0.10,
2887            service: 0.05,
2888        }
2889    }
2890}
2891
2892/// Valuation method distribution for materials.
2893#[derive(Debug, Clone, Serialize, Deserialize)]
2894pub struct ValuationMethodDistribution {
2895    /// Standard cost
2896    pub standard_cost: f64,
2897    /// Moving average
2898    pub moving_average: f64,
2899    /// FIFO (First In, First Out)
2900    pub fifo: f64,
2901    /// LIFO (Last In, First Out)
2902    pub lifo: f64,
2903}
2904
2905impl Default for ValuationMethodDistribution {
2906    fn default() -> Self {
2907        Self {
2908            standard_cost: 0.50,
2909            moving_average: 0.30,
2910            fifo: 0.15,
2911            lifo: 0.05,
2912        }
2913    }
2914}
2915
2916/// Fixed asset master data configuration.
2917#[derive(Debug, Clone, Serialize, Deserialize)]
2918pub struct FixedAssetMasterConfig {
2919    /// Number of fixed assets to generate
2920    #[serde(default = "default_asset_count")]
2921    pub count: usize,
2922    /// Asset class distribution
2923    #[serde(default)]
2924    pub class_distribution: AssetClassDistribution,
2925    /// Depreciation method distribution
2926    #[serde(default)]
2927    pub depreciation_distribution: DepreciationMethodDistribution,
2928    /// Percentage of assets that are fully depreciated
2929    #[serde(default = "default_fully_depreciated_percent")]
2930    pub fully_depreciated_percent: f64,
2931    /// Generate acquisition history
2932    #[serde(default = "default_true")]
2933    pub generate_acquisition_history: bool,
2934}
2935
2936fn default_asset_count() -> usize {
2937    800
2938}
2939
2940fn default_fully_depreciated_percent() -> f64 {
2941    0.15
2942}
2943
2944impl Default for FixedAssetMasterConfig {
2945    fn default() -> Self {
2946        Self {
2947            count: default_asset_count(),
2948            class_distribution: AssetClassDistribution::default(),
2949            depreciation_distribution: DepreciationMethodDistribution::default(),
2950            fully_depreciated_percent: default_fully_depreciated_percent(),
2951            generate_acquisition_history: true,
2952        }
2953    }
2954}
2955
2956/// Asset class distribution.
2957#[derive(Debug, Clone, Serialize, Deserialize)]
2958pub struct AssetClassDistribution {
2959    /// Buildings and structures
2960    pub buildings: f64,
2961    /// Machinery and equipment
2962    pub machinery: f64,
2963    /// Vehicles
2964    pub vehicles: f64,
2965    /// IT equipment
2966    pub it_equipment: f64,
2967    /// Furniture and fixtures
2968    pub furniture: f64,
2969    /// Land (non-depreciable)
2970    pub land: f64,
2971    /// Leasehold improvements
2972    pub leasehold: f64,
2973}
2974
2975impl Default for AssetClassDistribution {
2976    fn default() -> Self {
2977        Self {
2978            buildings: 0.15,
2979            machinery: 0.30,
2980            vehicles: 0.15,
2981            it_equipment: 0.20,
2982            furniture: 0.10,
2983            land: 0.05,
2984            leasehold: 0.05,
2985        }
2986    }
2987}
2988
2989/// Depreciation method distribution.
2990#[derive(Debug, Clone, Serialize, Deserialize)]
2991pub struct DepreciationMethodDistribution {
2992    /// Straight line
2993    pub straight_line: f64,
2994    /// Declining balance
2995    pub declining_balance: f64,
2996    /// Double declining balance
2997    pub double_declining: f64,
2998    /// Sum of years' digits
2999    pub sum_of_years: f64,
3000    /// Units of production
3001    pub units_of_production: f64,
3002}
3003
3004impl Default for DepreciationMethodDistribution {
3005    fn default() -> Self {
3006        Self {
3007            straight_line: 0.60,
3008            declining_balance: 0.20,
3009            double_declining: 0.10,
3010            sum_of_years: 0.05,
3011            units_of_production: 0.05,
3012        }
3013    }
3014}
3015
3016/// Employee master data configuration.
3017#[derive(Debug, Clone, Serialize, Deserialize)]
3018pub struct EmployeeMasterConfig {
3019    /// Number of employees to generate
3020    #[serde(default = "default_employee_count")]
3021    pub count: usize,
3022    /// Generate organizational hierarchy
3023    #[serde(default = "default_true")]
3024    pub generate_hierarchy: bool,
3025    /// Maximum hierarchy depth
3026    #[serde(default = "default_hierarchy_depth")]
3027    pub max_hierarchy_depth: u8,
3028    /// Average span of control (direct reports per manager)
3029    #[serde(default = "default_span_of_control")]
3030    pub average_span_of_control: f64,
3031    /// Approval limit distribution by job level
3032    #[serde(default)]
3033    pub approval_limits: ApprovalLimitDistribution,
3034    /// Department distribution
3035    #[serde(default)]
3036    pub department_distribution: EmployeeDepartmentDistribution,
3037}
3038
3039fn default_employee_count() -> usize {
3040    1500
3041}
3042
3043fn default_hierarchy_depth() -> u8 {
3044    6
3045}
3046
3047fn default_span_of_control() -> f64 {
3048    5.0
3049}
3050
3051impl Default for EmployeeMasterConfig {
3052    fn default() -> Self {
3053        Self {
3054            count: default_employee_count(),
3055            generate_hierarchy: true,
3056            max_hierarchy_depth: default_hierarchy_depth(),
3057            average_span_of_control: default_span_of_control(),
3058            approval_limits: ApprovalLimitDistribution::default(),
3059            department_distribution: EmployeeDepartmentDistribution::default(),
3060        }
3061    }
3062}
3063
3064/// Approval limit distribution by job level.
3065#[derive(Debug, Clone, Serialize, Deserialize)]
3066pub struct ApprovalLimitDistribution {
3067    /// Staff level approval limit
3068    #[serde(default = "default_staff_limit")]
3069    pub staff: f64,
3070    /// Senior staff approval limit
3071    #[serde(default = "default_senior_limit")]
3072    pub senior: f64,
3073    /// Manager approval limit
3074    #[serde(default = "default_manager_limit")]
3075    pub manager: f64,
3076    /// Director approval limit
3077    #[serde(default = "default_director_limit")]
3078    pub director: f64,
3079    /// VP approval limit
3080    #[serde(default = "default_vp_limit")]
3081    pub vp: f64,
3082    /// Executive approval limit
3083    #[serde(default = "default_executive_limit")]
3084    pub executive: f64,
3085}
3086
3087fn default_staff_limit() -> f64 {
3088    1000.0
3089}
3090fn default_senior_limit() -> f64 {
3091    5000.0
3092}
3093fn default_manager_limit() -> f64 {
3094    25000.0
3095}
3096fn default_director_limit() -> f64 {
3097    100000.0
3098}
3099fn default_vp_limit() -> f64 {
3100    500000.0
3101}
3102fn default_executive_limit() -> f64 {
3103    f64::INFINITY
3104}
3105
3106impl Default for ApprovalLimitDistribution {
3107    fn default() -> Self {
3108        Self {
3109            staff: default_staff_limit(),
3110            senior: default_senior_limit(),
3111            manager: default_manager_limit(),
3112            director: default_director_limit(),
3113            vp: default_vp_limit(),
3114            executive: default_executive_limit(),
3115        }
3116    }
3117}
3118
3119/// Employee distribution across departments.
3120#[derive(Debug, Clone, Serialize, Deserialize)]
3121pub struct EmployeeDepartmentDistribution {
3122    /// Finance and Accounting
3123    pub finance: f64,
3124    /// Procurement
3125    pub procurement: f64,
3126    /// Sales
3127    pub sales: f64,
3128    /// Warehouse and Logistics
3129    pub warehouse: f64,
3130    /// IT
3131    pub it: f64,
3132    /// Human Resources
3133    pub hr: f64,
3134    /// Operations
3135    pub operations: f64,
3136    /// Executive
3137    pub executive: f64,
3138}
3139
3140impl Default for EmployeeDepartmentDistribution {
3141    fn default() -> Self {
3142        Self {
3143            finance: 0.12,
3144            procurement: 0.10,
3145            sales: 0.25,
3146            warehouse: 0.15,
3147            it: 0.10,
3148            hr: 0.05,
3149            operations: 0.20,
3150            executive: 0.03,
3151        }
3152    }
3153}
3154
3155/// Cost center master data configuration.
3156#[derive(Debug, Clone, Serialize, Deserialize)]
3157pub struct CostCenterMasterConfig {
3158    /// Number of cost centers to generate
3159    #[serde(default = "default_cost_center_count")]
3160    pub count: usize,
3161    /// Generate cost center hierarchy
3162    #[serde(default = "default_true")]
3163    pub generate_hierarchy: bool,
3164    /// Maximum hierarchy depth
3165    #[serde(default = "default_cc_hierarchy_depth")]
3166    pub max_hierarchy_depth: u8,
3167}
3168
3169fn default_cost_center_count() -> usize {
3170    50
3171}
3172
3173fn default_cc_hierarchy_depth() -> u8 {
3174    3
3175}
3176
3177impl Default for CostCenterMasterConfig {
3178    fn default() -> Self {
3179        Self {
3180            count: default_cost_center_count(),
3181            generate_hierarchy: true,
3182            max_hierarchy_depth: default_cc_hierarchy_depth(),
3183        }
3184    }
3185}
3186
3187// ============================================================================
3188// Document Flow Configuration
3189// ============================================================================
3190
3191/// Document flow generation configuration.
3192#[derive(Debug, Clone, Serialize, Deserialize)]
3193pub struct DocumentFlowConfig {
3194    /// P2P (Procure-to-Pay) flow configuration
3195    #[serde(default)]
3196    pub p2p: P2PFlowConfig,
3197    /// O2C (Order-to-Cash) flow configuration
3198    #[serde(default)]
3199    pub o2c: O2CFlowConfig,
3200    /// Generate document reference chains
3201    #[serde(default = "default_true")]
3202    pub generate_document_references: bool,
3203    /// Export document flow graph
3204    #[serde(default)]
3205    pub export_flow_graph: bool,
3206}
3207
3208impl Default for DocumentFlowConfig {
3209    fn default() -> Self {
3210        Self {
3211            p2p: P2PFlowConfig::default(),
3212            o2c: O2CFlowConfig::default(),
3213            generate_document_references: true,
3214            export_flow_graph: false,
3215        }
3216    }
3217}
3218
3219/// P2P (Procure-to-Pay) flow configuration.
3220#[derive(Debug, Clone, Serialize, Deserialize)]
3221pub struct P2PFlowConfig {
3222    /// Enable P2P document flow generation
3223    #[serde(default = "default_true")]
3224    pub enabled: bool,
3225    /// Three-way match success rate (PO-GR-Invoice)
3226    #[serde(default = "default_three_way_match_rate")]
3227    pub three_way_match_rate: f64,
3228    /// Rate of partial deliveries
3229    #[serde(default = "default_partial_delivery_rate")]
3230    pub partial_delivery_rate: f64,
3231    /// Rate of price variances between PO and Invoice
3232    #[serde(default = "default_price_variance_rate")]
3233    pub price_variance_rate: f64,
3234    /// Maximum price variance percentage
3235    #[serde(default = "default_max_price_variance")]
3236    pub max_price_variance_percent: f64,
3237    /// Rate of quantity variances between PO/GR and Invoice
3238    #[serde(default = "default_quantity_variance_rate")]
3239    pub quantity_variance_rate: f64,
3240    /// Average days from PO to goods receipt
3241    #[serde(default = "default_po_to_gr_days")]
3242    pub average_po_to_gr_days: u32,
3243    /// Average days from GR to invoice
3244    #[serde(default = "default_gr_to_invoice_days")]
3245    pub average_gr_to_invoice_days: u32,
3246    /// Average days from invoice to payment
3247    #[serde(default = "default_invoice_to_payment_days")]
3248    pub average_invoice_to_payment_days: u32,
3249    /// PO line count distribution
3250    #[serde(default)]
3251    pub line_count_distribution: DocumentLineCountDistribution,
3252    /// Payment behavior configuration
3253    #[serde(default)]
3254    pub payment_behavior: P2PPaymentBehaviorConfig,
3255    /// Rate of over-deliveries (quantity received exceeds PO quantity)
3256    #[serde(default)]
3257    pub over_delivery_rate: Option<f64>,
3258    /// Rate of early payment discounts being taken
3259    #[serde(default)]
3260    pub early_payment_discount_rate: Option<f64>,
3261}
3262
3263fn default_three_way_match_rate() -> f64 {
3264    0.95
3265}
3266
3267fn default_partial_delivery_rate() -> f64 {
3268    0.15
3269}
3270
3271fn default_price_variance_rate() -> f64 {
3272    0.08
3273}
3274
3275fn default_max_price_variance() -> f64 {
3276    0.05
3277}
3278
3279fn default_quantity_variance_rate() -> f64 {
3280    0.05
3281}
3282
3283fn default_po_to_gr_days() -> u32 {
3284    14
3285}
3286
3287fn default_gr_to_invoice_days() -> u32 {
3288    5
3289}
3290
3291fn default_invoice_to_payment_days() -> u32 {
3292    30
3293}
3294
3295impl Default for P2PFlowConfig {
3296    fn default() -> Self {
3297        Self {
3298            enabled: true,
3299            three_way_match_rate: default_three_way_match_rate(),
3300            partial_delivery_rate: default_partial_delivery_rate(),
3301            price_variance_rate: default_price_variance_rate(),
3302            max_price_variance_percent: default_max_price_variance(),
3303            quantity_variance_rate: default_quantity_variance_rate(),
3304            average_po_to_gr_days: default_po_to_gr_days(),
3305            average_gr_to_invoice_days: default_gr_to_invoice_days(),
3306            average_invoice_to_payment_days: default_invoice_to_payment_days(),
3307            line_count_distribution: DocumentLineCountDistribution::default(),
3308            payment_behavior: P2PPaymentBehaviorConfig::default(),
3309            over_delivery_rate: None,
3310            early_payment_discount_rate: None,
3311        }
3312    }
3313}
3314
3315// ============================================================================
3316// P2P Payment Behavior Configuration
3317// ============================================================================
3318
3319/// P2P payment behavior configuration.
3320#[derive(Debug, Clone, Serialize, Deserialize)]
3321pub struct P2PPaymentBehaviorConfig {
3322    /// Rate of late payments (beyond due date)
3323    #[serde(default = "default_p2p_late_payment_rate")]
3324    pub late_payment_rate: f64,
3325    /// Distribution of late payment days
3326    #[serde(default)]
3327    pub late_payment_days_distribution: LatePaymentDaysDistribution,
3328    /// Rate of partial payments
3329    #[serde(default = "default_p2p_partial_payment_rate")]
3330    pub partial_payment_rate: f64,
3331    /// Rate of payment corrections (NSF, chargebacks, reversals)
3332    #[serde(default = "default_p2p_payment_correction_rate")]
3333    pub payment_correction_rate: f64,
3334    /// Average days until partial payment remainder is paid
3335    #[serde(default = "default_p2p_avg_days_until_remainder")]
3336    pub avg_days_until_remainder: u32,
3337}
3338
3339fn default_p2p_late_payment_rate() -> f64 {
3340    0.15
3341}
3342
3343fn default_p2p_partial_payment_rate() -> f64 {
3344    0.05
3345}
3346
3347fn default_p2p_payment_correction_rate() -> f64 {
3348    0.02
3349}
3350
3351fn default_p2p_avg_days_until_remainder() -> u32 {
3352    30
3353}
3354
3355impl Default for P2PPaymentBehaviorConfig {
3356    fn default() -> Self {
3357        Self {
3358            late_payment_rate: default_p2p_late_payment_rate(),
3359            late_payment_days_distribution: LatePaymentDaysDistribution::default(),
3360            partial_payment_rate: default_p2p_partial_payment_rate(),
3361            payment_correction_rate: default_p2p_payment_correction_rate(),
3362            avg_days_until_remainder: default_p2p_avg_days_until_remainder(),
3363        }
3364    }
3365}
3366
3367/// Distribution of late payment days for P2P.
3368#[derive(Debug, Clone, Serialize, Deserialize)]
3369pub struct LatePaymentDaysDistribution {
3370    /// 1-7 days late (slightly late)
3371    #[serde(default = "default_slightly_late")]
3372    pub slightly_late_1_to_7: f64,
3373    /// 8-14 days late
3374    #[serde(default = "default_late_8_14")]
3375    pub late_8_to_14: f64,
3376    /// 15-30 days late (very late)
3377    #[serde(default = "default_very_late")]
3378    pub very_late_15_to_30: f64,
3379    /// 31-60 days late (severely late)
3380    #[serde(default = "default_severely_late")]
3381    pub severely_late_31_to_60: f64,
3382    /// Over 60 days late (extremely late)
3383    #[serde(default = "default_extremely_late")]
3384    pub extremely_late_over_60: f64,
3385}
3386
3387fn default_slightly_late() -> f64 {
3388    0.50
3389}
3390
3391fn default_late_8_14() -> f64 {
3392    0.25
3393}
3394
3395fn default_very_late() -> f64 {
3396    0.15
3397}
3398
3399fn default_severely_late() -> f64 {
3400    0.07
3401}
3402
3403fn default_extremely_late() -> f64 {
3404    0.03
3405}
3406
3407impl Default for LatePaymentDaysDistribution {
3408    fn default() -> Self {
3409        Self {
3410            slightly_late_1_to_7: default_slightly_late(),
3411            late_8_to_14: default_late_8_14(),
3412            very_late_15_to_30: default_very_late(),
3413            severely_late_31_to_60: default_severely_late(),
3414            extremely_late_over_60: default_extremely_late(),
3415        }
3416    }
3417}
3418
3419/// O2C (Order-to-Cash) flow configuration.
3420#[derive(Debug, Clone, Serialize, Deserialize)]
3421pub struct O2CFlowConfig {
3422    /// Enable O2C document flow generation
3423    #[serde(default = "default_true")]
3424    pub enabled: bool,
3425    /// Credit check failure rate
3426    #[serde(default = "default_credit_check_failure_rate")]
3427    pub credit_check_failure_rate: f64,
3428    /// Rate of partial shipments
3429    #[serde(default = "default_partial_shipment_rate")]
3430    pub partial_shipment_rate: f64,
3431    /// Rate of returns
3432    #[serde(default = "default_return_rate")]
3433    pub return_rate: f64,
3434    /// Bad debt write-off rate
3435    #[serde(default = "default_bad_debt_rate")]
3436    pub bad_debt_rate: f64,
3437    /// Average days from SO to delivery
3438    #[serde(default = "default_so_to_delivery_days")]
3439    pub average_so_to_delivery_days: u32,
3440    /// Average days from delivery to invoice
3441    #[serde(default = "default_delivery_to_invoice_days")]
3442    pub average_delivery_to_invoice_days: u32,
3443    /// Average days from invoice to receipt
3444    #[serde(default = "default_invoice_to_receipt_days")]
3445    pub average_invoice_to_receipt_days: u32,
3446    /// SO line count distribution
3447    #[serde(default)]
3448    pub line_count_distribution: DocumentLineCountDistribution,
3449    /// Cash discount configuration
3450    #[serde(default)]
3451    pub cash_discount: CashDiscountConfig,
3452    /// Payment behavior configuration
3453    #[serde(default)]
3454    pub payment_behavior: O2CPaymentBehaviorConfig,
3455    /// Rate of late payments
3456    #[serde(default)]
3457    pub late_payment_rate: Option<f64>,
3458}
3459
3460fn default_credit_check_failure_rate() -> f64 {
3461    0.02
3462}
3463
3464fn default_partial_shipment_rate() -> f64 {
3465    0.10
3466}
3467
3468fn default_return_rate() -> f64 {
3469    0.03
3470}
3471
3472fn default_bad_debt_rate() -> f64 {
3473    0.01
3474}
3475
3476fn default_so_to_delivery_days() -> u32 {
3477    7
3478}
3479
3480fn default_delivery_to_invoice_days() -> u32 {
3481    1
3482}
3483
3484fn default_invoice_to_receipt_days() -> u32 {
3485    45
3486}
3487
3488impl Default for O2CFlowConfig {
3489    fn default() -> Self {
3490        Self {
3491            enabled: true,
3492            credit_check_failure_rate: default_credit_check_failure_rate(),
3493            partial_shipment_rate: default_partial_shipment_rate(),
3494            return_rate: default_return_rate(),
3495            bad_debt_rate: default_bad_debt_rate(),
3496            average_so_to_delivery_days: default_so_to_delivery_days(),
3497            average_delivery_to_invoice_days: default_delivery_to_invoice_days(),
3498            average_invoice_to_receipt_days: default_invoice_to_receipt_days(),
3499            line_count_distribution: DocumentLineCountDistribution::default(),
3500            cash_discount: CashDiscountConfig::default(),
3501            payment_behavior: O2CPaymentBehaviorConfig::default(),
3502            late_payment_rate: None,
3503        }
3504    }
3505}
3506
3507// ============================================================================
3508// O2C Payment Behavior Configuration
3509// ============================================================================
3510
3511/// O2C payment behavior configuration.
3512#[derive(Debug, Clone, Serialize, Deserialize, Default)]
3513pub struct O2CPaymentBehaviorConfig {
3514    /// Dunning (Mahnung) configuration
3515    #[serde(default)]
3516    pub dunning: DunningConfig,
3517    /// Partial payment configuration
3518    #[serde(default)]
3519    pub partial_payments: PartialPaymentConfig,
3520    /// Short payment configuration (unauthorized deductions)
3521    #[serde(default)]
3522    pub short_payments: ShortPaymentConfig,
3523    /// On-account payment configuration (unapplied payments)
3524    #[serde(default)]
3525    pub on_account_payments: OnAccountPaymentConfig,
3526    /// Payment correction configuration (NSF, chargebacks)
3527    #[serde(default)]
3528    pub payment_corrections: PaymentCorrectionConfig,
3529}
3530
3531/// Dunning (Mahnungen) configuration for AR collections.
3532#[derive(Debug, Clone, Serialize, Deserialize)]
3533pub struct DunningConfig {
3534    /// Enable dunning process
3535    #[serde(default)]
3536    pub enabled: bool,
3537    /// Days overdue for level 1 dunning (1st reminder)
3538    #[serde(default = "default_dunning_level_1_days")]
3539    pub level_1_days_overdue: u32,
3540    /// Days overdue for level 2 dunning (2nd reminder)
3541    #[serde(default = "default_dunning_level_2_days")]
3542    pub level_2_days_overdue: u32,
3543    /// Days overdue for level 3 dunning (final notice)
3544    #[serde(default = "default_dunning_level_3_days")]
3545    pub level_3_days_overdue: u32,
3546    /// Days overdue for collection handover
3547    #[serde(default = "default_collection_days")]
3548    pub collection_days_overdue: u32,
3549    /// Payment rates after each dunning level
3550    #[serde(default)]
3551    pub payment_after_dunning_rates: DunningPaymentRates,
3552    /// Rate of invoices blocked from dunning (disputes)
3553    #[serde(default = "default_dunning_block_rate")]
3554    pub dunning_block_rate: f64,
3555    /// Interest rate per year for overdue amounts
3556    #[serde(default = "default_dunning_interest_rate")]
3557    pub interest_rate_per_year: f64,
3558    /// Fixed dunning charge per letter
3559    #[serde(default = "default_dunning_charge")]
3560    pub dunning_charge: f64,
3561}
3562
3563fn default_dunning_level_1_days() -> u32 {
3564    14
3565}
3566
3567fn default_dunning_level_2_days() -> u32 {
3568    28
3569}
3570
3571fn default_dunning_level_3_days() -> u32 {
3572    42
3573}
3574
3575fn default_collection_days() -> u32 {
3576    60
3577}
3578
3579fn default_dunning_block_rate() -> f64 {
3580    0.05
3581}
3582
3583fn default_dunning_interest_rate() -> f64 {
3584    0.09
3585}
3586
3587fn default_dunning_charge() -> f64 {
3588    25.0
3589}
3590
3591impl Default for DunningConfig {
3592    fn default() -> Self {
3593        Self {
3594            enabled: false,
3595            level_1_days_overdue: default_dunning_level_1_days(),
3596            level_2_days_overdue: default_dunning_level_2_days(),
3597            level_3_days_overdue: default_dunning_level_3_days(),
3598            collection_days_overdue: default_collection_days(),
3599            payment_after_dunning_rates: DunningPaymentRates::default(),
3600            dunning_block_rate: default_dunning_block_rate(),
3601            interest_rate_per_year: default_dunning_interest_rate(),
3602            dunning_charge: default_dunning_charge(),
3603        }
3604    }
3605}
3606
3607/// Payment rates after each dunning level.
3608#[derive(Debug, Clone, Serialize, Deserialize)]
3609pub struct DunningPaymentRates {
3610    /// Rate that pays after level 1 reminder
3611    #[serde(default = "default_after_level_1")]
3612    pub after_level_1: f64,
3613    /// Rate that pays after level 2 reminder
3614    #[serde(default = "default_after_level_2")]
3615    pub after_level_2: f64,
3616    /// Rate that pays after level 3 final notice
3617    #[serde(default = "default_after_level_3")]
3618    pub after_level_3: f64,
3619    /// Rate that pays during collection
3620    #[serde(default = "default_during_collection")]
3621    pub during_collection: f64,
3622    /// Rate that never pays (becomes bad debt)
3623    #[serde(default = "default_never_pay")]
3624    pub never_pay: f64,
3625}
3626
3627fn default_after_level_1() -> f64 {
3628    0.40
3629}
3630
3631fn default_after_level_2() -> f64 {
3632    0.30
3633}
3634
3635fn default_after_level_3() -> f64 {
3636    0.15
3637}
3638
3639fn default_during_collection() -> f64 {
3640    0.05
3641}
3642
3643fn default_never_pay() -> f64 {
3644    0.10
3645}
3646
3647impl Default for DunningPaymentRates {
3648    fn default() -> Self {
3649        Self {
3650            after_level_1: default_after_level_1(),
3651            after_level_2: default_after_level_2(),
3652            after_level_3: default_after_level_3(),
3653            during_collection: default_during_collection(),
3654            never_pay: default_never_pay(),
3655        }
3656    }
3657}
3658
3659/// Partial payment configuration.
3660#[derive(Debug, Clone, Serialize, Deserialize)]
3661pub struct PartialPaymentConfig {
3662    /// Rate of invoices paid partially
3663    #[serde(default = "default_partial_payment_rate")]
3664    pub rate: f64,
3665    /// Distribution of partial payment percentages
3666    #[serde(default)]
3667    pub percentage_distribution: PartialPaymentPercentageDistribution,
3668    /// Average days until remainder is paid
3669    #[serde(default = "default_avg_days_until_remainder")]
3670    pub avg_days_until_remainder: u32,
3671}
3672
3673fn default_partial_payment_rate() -> f64 {
3674    0.08
3675}
3676
3677fn default_avg_days_until_remainder() -> u32 {
3678    30
3679}
3680
3681impl Default for PartialPaymentConfig {
3682    fn default() -> Self {
3683        Self {
3684            rate: default_partial_payment_rate(),
3685            percentage_distribution: PartialPaymentPercentageDistribution::default(),
3686            avg_days_until_remainder: default_avg_days_until_remainder(),
3687        }
3688    }
3689}
3690
3691/// Distribution of partial payment percentages.
3692#[derive(Debug, Clone, Serialize, Deserialize)]
3693pub struct PartialPaymentPercentageDistribution {
3694    /// Pay 25% of invoice
3695    #[serde(default = "default_partial_25")]
3696    pub pay_25_percent: f64,
3697    /// Pay 50% of invoice
3698    #[serde(default = "default_partial_50")]
3699    pub pay_50_percent: f64,
3700    /// Pay 75% of invoice
3701    #[serde(default = "default_partial_75")]
3702    pub pay_75_percent: f64,
3703    /// Pay random percentage
3704    #[serde(default = "default_partial_random")]
3705    pub pay_random_percent: f64,
3706}
3707
3708fn default_partial_25() -> f64 {
3709    0.15
3710}
3711
3712fn default_partial_50() -> f64 {
3713    0.50
3714}
3715
3716fn default_partial_75() -> f64 {
3717    0.25
3718}
3719
3720fn default_partial_random() -> f64 {
3721    0.10
3722}
3723
3724impl Default for PartialPaymentPercentageDistribution {
3725    fn default() -> Self {
3726        Self {
3727            pay_25_percent: default_partial_25(),
3728            pay_50_percent: default_partial_50(),
3729            pay_75_percent: default_partial_75(),
3730            pay_random_percent: default_partial_random(),
3731        }
3732    }
3733}
3734
3735/// Short payment configuration (unauthorized deductions).
3736#[derive(Debug, Clone, Serialize, Deserialize)]
3737pub struct ShortPaymentConfig {
3738    /// Rate of payments that are short
3739    #[serde(default = "default_short_payment_rate")]
3740    pub rate: f64,
3741    /// Distribution of short payment reasons
3742    #[serde(default)]
3743    pub reason_distribution: ShortPaymentReasonDistribution,
3744    /// Maximum percentage that can be short
3745    #[serde(default = "default_max_short_percent")]
3746    pub max_short_percent: f64,
3747}
3748
3749fn default_short_payment_rate() -> f64 {
3750    0.03
3751}
3752
3753fn default_max_short_percent() -> f64 {
3754    0.10
3755}
3756
3757impl Default for ShortPaymentConfig {
3758    fn default() -> Self {
3759        Self {
3760            rate: default_short_payment_rate(),
3761            reason_distribution: ShortPaymentReasonDistribution::default(),
3762            max_short_percent: default_max_short_percent(),
3763        }
3764    }
3765}
3766
3767/// Distribution of short payment reasons.
3768#[derive(Debug, Clone, Serialize, Deserialize)]
3769pub struct ShortPaymentReasonDistribution {
3770    /// Pricing dispute
3771    #[serde(default = "default_pricing_dispute")]
3772    pub pricing_dispute: f64,
3773    /// Quality issue
3774    #[serde(default = "default_quality_issue")]
3775    pub quality_issue: f64,
3776    /// Quantity discrepancy
3777    #[serde(default = "default_quantity_discrepancy")]
3778    pub quantity_discrepancy: f64,
3779    /// Unauthorized deduction
3780    #[serde(default = "default_unauthorized_deduction")]
3781    pub unauthorized_deduction: f64,
3782    /// Early payment discount taken incorrectly
3783    #[serde(default = "default_incorrect_discount")]
3784    pub incorrect_discount: f64,
3785}
3786
3787fn default_pricing_dispute() -> f64 {
3788    0.30
3789}
3790
3791fn default_quality_issue() -> f64 {
3792    0.20
3793}
3794
3795fn default_quantity_discrepancy() -> f64 {
3796    0.20
3797}
3798
3799fn default_unauthorized_deduction() -> f64 {
3800    0.15
3801}
3802
3803fn default_incorrect_discount() -> f64 {
3804    0.15
3805}
3806
3807impl Default for ShortPaymentReasonDistribution {
3808    fn default() -> Self {
3809        Self {
3810            pricing_dispute: default_pricing_dispute(),
3811            quality_issue: default_quality_issue(),
3812            quantity_discrepancy: default_quantity_discrepancy(),
3813            unauthorized_deduction: default_unauthorized_deduction(),
3814            incorrect_discount: default_incorrect_discount(),
3815        }
3816    }
3817}
3818
3819/// On-account payment configuration (unapplied payments).
3820#[derive(Debug, Clone, Serialize, Deserialize)]
3821pub struct OnAccountPaymentConfig {
3822    /// Rate of payments that are on-account (unapplied)
3823    #[serde(default = "default_on_account_rate")]
3824    pub rate: f64,
3825    /// Average days until on-account payments are applied
3826    #[serde(default = "default_avg_days_until_applied")]
3827    pub avg_days_until_applied: u32,
3828}
3829
3830fn default_on_account_rate() -> f64 {
3831    0.02
3832}
3833
3834fn default_avg_days_until_applied() -> u32 {
3835    14
3836}
3837
3838impl Default for OnAccountPaymentConfig {
3839    fn default() -> Self {
3840        Self {
3841            rate: default_on_account_rate(),
3842            avg_days_until_applied: default_avg_days_until_applied(),
3843        }
3844    }
3845}
3846
3847/// Payment correction configuration.
3848#[derive(Debug, Clone, Serialize, Deserialize)]
3849pub struct PaymentCorrectionConfig {
3850    /// Rate of payments requiring correction
3851    #[serde(default = "default_payment_correction_rate")]
3852    pub rate: f64,
3853    /// Distribution of correction types
3854    #[serde(default)]
3855    pub type_distribution: PaymentCorrectionTypeDistribution,
3856}
3857
3858fn default_payment_correction_rate() -> f64 {
3859    0.02
3860}
3861
3862impl Default for PaymentCorrectionConfig {
3863    fn default() -> Self {
3864        Self {
3865            rate: default_payment_correction_rate(),
3866            type_distribution: PaymentCorrectionTypeDistribution::default(),
3867        }
3868    }
3869}
3870
3871/// Distribution of payment correction types.
3872#[derive(Debug, Clone, Serialize, Deserialize)]
3873pub struct PaymentCorrectionTypeDistribution {
3874    /// NSF (Non-sufficient funds) / bounced check
3875    #[serde(default = "default_nsf_rate")]
3876    pub nsf: f64,
3877    /// Chargeback
3878    #[serde(default = "default_chargeback_rate")]
3879    pub chargeback: f64,
3880    /// Wrong amount applied
3881    #[serde(default = "default_wrong_amount_rate")]
3882    pub wrong_amount: f64,
3883    /// Wrong customer applied
3884    #[serde(default = "default_wrong_customer_rate")]
3885    pub wrong_customer: f64,
3886    /// Duplicate payment
3887    #[serde(default = "default_duplicate_payment_rate")]
3888    pub duplicate_payment: f64,
3889}
3890
3891fn default_nsf_rate() -> f64 {
3892    0.30
3893}
3894
3895fn default_chargeback_rate() -> f64 {
3896    0.20
3897}
3898
3899fn default_wrong_amount_rate() -> f64 {
3900    0.20
3901}
3902
3903fn default_wrong_customer_rate() -> f64 {
3904    0.15
3905}
3906
3907fn default_duplicate_payment_rate() -> f64 {
3908    0.15
3909}
3910
3911impl Default for PaymentCorrectionTypeDistribution {
3912    fn default() -> Self {
3913        Self {
3914            nsf: default_nsf_rate(),
3915            chargeback: default_chargeback_rate(),
3916            wrong_amount: default_wrong_amount_rate(),
3917            wrong_customer: default_wrong_customer_rate(),
3918            duplicate_payment: default_duplicate_payment_rate(),
3919        }
3920    }
3921}
3922
3923/// Document line count distribution.
3924#[derive(Debug, Clone, Serialize, Deserialize)]
3925pub struct DocumentLineCountDistribution {
3926    /// Minimum number of lines
3927    #[serde(default = "default_min_lines")]
3928    pub min_lines: u32,
3929    /// Maximum number of lines
3930    #[serde(default = "default_max_lines")]
3931    pub max_lines: u32,
3932    /// Most common line count (mode)
3933    #[serde(default = "default_mode_lines")]
3934    pub mode_lines: u32,
3935}
3936
3937fn default_min_lines() -> u32 {
3938    1
3939}
3940
3941fn default_max_lines() -> u32 {
3942    20
3943}
3944
3945fn default_mode_lines() -> u32 {
3946    3
3947}
3948
3949impl Default for DocumentLineCountDistribution {
3950    fn default() -> Self {
3951        Self {
3952            min_lines: default_min_lines(),
3953            max_lines: default_max_lines(),
3954            mode_lines: default_mode_lines(),
3955        }
3956    }
3957}
3958
3959/// Cash discount configuration.
3960#[derive(Debug, Clone, Serialize, Deserialize)]
3961pub struct CashDiscountConfig {
3962    /// Percentage of invoices eligible for cash discount
3963    #[serde(default = "default_discount_eligible_rate")]
3964    pub eligible_rate: f64,
3965    /// Rate at which customers take the discount
3966    #[serde(default = "default_discount_taken_rate")]
3967    pub taken_rate: f64,
3968    /// Standard discount percentage
3969    #[serde(default = "default_discount_percent")]
3970    pub discount_percent: f64,
3971    /// Days within which discount must be taken
3972    #[serde(default = "default_discount_days")]
3973    pub discount_days: u32,
3974}
3975
3976fn default_discount_eligible_rate() -> f64 {
3977    0.30
3978}
3979
3980fn default_discount_taken_rate() -> f64 {
3981    0.60
3982}
3983
3984fn default_discount_percent() -> f64 {
3985    0.02
3986}
3987
3988fn default_discount_days() -> u32 {
3989    10
3990}
3991
3992impl Default for CashDiscountConfig {
3993    fn default() -> Self {
3994        Self {
3995            eligible_rate: default_discount_eligible_rate(),
3996            taken_rate: default_discount_taken_rate(),
3997            discount_percent: default_discount_percent(),
3998            discount_days: default_discount_days(),
3999        }
4000    }
4001}
4002
4003// ============================================================================
4004// Intercompany Configuration
4005// ============================================================================
4006
4007/// Intercompany transaction configuration.
4008#[derive(Debug, Clone, Serialize, Deserialize)]
4009pub struct IntercompanyConfig {
4010    /// Enable intercompany transaction generation
4011    #[serde(default)]
4012    pub enabled: bool,
4013    /// Rate of transactions that are intercompany
4014    #[serde(default = "default_ic_transaction_rate")]
4015    pub ic_transaction_rate: f64,
4016    /// Transfer pricing method
4017    #[serde(default)]
4018    pub transfer_pricing_method: TransferPricingMethod,
4019    /// Transfer pricing markup percentage (for cost-plus)
4020    #[serde(default = "default_markup_percent")]
4021    pub markup_percent: f64,
4022    /// Generate matched IC pairs (offsetting entries)
4023    #[serde(default = "default_true")]
4024    pub generate_matched_pairs: bool,
4025    /// IC transaction type distribution
4026    #[serde(default)]
4027    pub transaction_type_distribution: ICTransactionTypeDistribution,
4028    /// Generate elimination entries for consolidation
4029    #[serde(default)]
4030    pub generate_eliminations: bool,
4031}
4032
4033fn default_ic_transaction_rate() -> f64 {
4034    0.15
4035}
4036
4037fn default_markup_percent() -> f64 {
4038    0.05
4039}
4040
4041impl Default for IntercompanyConfig {
4042    fn default() -> Self {
4043        Self {
4044            enabled: false,
4045            ic_transaction_rate: default_ic_transaction_rate(),
4046            transfer_pricing_method: TransferPricingMethod::default(),
4047            markup_percent: default_markup_percent(),
4048            generate_matched_pairs: true,
4049            transaction_type_distribution: ICTransactionTypeDistribution::default(),
4050            generate_eliminations: false,
4051        }
4052    }
4053}
4054
4055/// Transfer pricing method.
4056#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
4057#[serde(rename_all = "snake_case")]
4058pub enum TransferPricingMethod {
4059    /// Cost plus a markup
4060    #[default]
4061    CostPlus,
4062    /// Comparable uncontrolled price
4063    ComparableUncontrolled,
4064    /// Resale price method
4065    ResalePrice,
4066    /// Transactional net margin method
4067    TransactionalNetMargin,
4068    /// Profit split method
4069    ProfitSplit,
4070}
4071
4072/// IC transaction type distribution.
4073#[derive(Debug, Clone, Serialize, Deserialize)]
4074pub struct ICTransactionTypeDistribution {
4075    /// Goods sales between entities
4076    pub goods_sale: f64,
4077    /// Services provided
4078    pub service_provided: f64,
4079    /// Intercompany loans
4080    pub loan: f64,
4081    /// Dividends
4082    pub dividend: f64,
4083    /// Management fees
4084    pub management_fee: f64,
4085    /// Royalties
4086    pub royalty: f64,
4087    /// Cost sharing
4088    pub cost_sharing: f64,
4089}
4090
4091impl Default for ICTransactionTypeDistribution {
4092    fn default() -> Self {
4093        Self {
4094            goods_sale: 0.35,
4095            service_provided: 0.20,
4096            loan: 0.10,
4097            dividend: 0.05,
4098            management_fee: 0.15,
4099            royalty: 0.10,
4100            cost_sharing: 0.05,
4101        }
4102    }
4103}
4104
4105// ============================================================================
4106// Balance Configuration
4107// ============================================================================
4108
4109/// Balance and trial balance configuration.
4110#[derive(Debug, Clone, Serialize, Deserialize)]
4111pub struct BalanceConfig {
4112    /// Generate opening balances
4113    #[serde(default)]
4114    pub generate_opening_balances: bool,
4115    /// Generate trial balances
4116    #[serde(default = "default_true")]
4117    pub generate_trial_balances: bool,
4118    /// Target gross margin (for revenue/COGS coherence)
4119    #[serde(default = "default_gross_margin")]
4120    pub target_gross_margin: f64,
4121    /// Target DSO (Days Sales Outstanding)
4122    #[serde(default = "default_dso")]
4123    pub target_dso_days: u32,
4124    /// Target DPO (Days Payable Outstanding)
4125    #[serde(default = "default_dpo")]
4126    pub target_dpo_days: u32,
4127    /// Target current ratio
4128    #[serde(default = "default_current_ratio")]
4129    pub target_current_ratio: f64,
4130    /// Target debt-to-equity ratio
4131    #[serde(default = "default_debt_equity")]
4132    pub target_debt_to_equity: f64,
4133    /// Validate balance sheet equation (A = L + E)
4134    #[serde(default = "default_true")]
4135    pub validate_balance_equation: bool,
4136    /// Reconcile subledgers to GL control accounts
4137    #[serde(default = "default_true")]
4138    pub reconcile_subledgers: bool,
4139}
4140
4141fn default_gross_margin() -> f64 {
4142    0.35
4143}
4144
4145fn default_dso() -> u32 {
4146    45
4147}
4148
4149fn default_dpo() -> u32 {
4150    30
4151}
4152
4153fn default_current_ratio() -> f64 {
4154    1.5
4155}
4156
4157fn default_debt_equity() -> f64 {
4158    0.5
4159}
4160
4161impl Default for BalanceConfig {
4162    fn default() -> Self {
4163        Self {
4164            generate_opening_balances: false,
4165            generate_trial_balances: true,
4166            target_gross_margin: default_gross_margin(),
4167            target_dso_days: default_dso(),
4168            target_dpo_days: default_dpo(),
4169            target_current_ratio: default_current_ratio(),
4170            target_debt_to_equity: default_debt_equity(),
4171            validate_balance_equation: true,
4172            reconcile_subledgers: true,
4173        }
4174    }
4175}
4176
4177// ==========================================================================
4178// OCPM (Object-Centric Process Mining) Configuration
4179// ==========================================================================
4180
4181/// OCPM (Object-Centric Process Mining) configuration.
4182///
4183/// Controls generation of OCEL 2.0 compatible event logs with
4184/// many-to-many event-to-object relationships.
4185#[derive(Debug, Clone, Serialize, Deserialize)]
4186pub struct OcpmConfig {
4187    /// Enable OCPM event log generation
4188    #[serde(default)]
4189    pub enabled: bool,
4190
4191    /// Generate lifecycle events (Start/Complete pairs vs atomic events)
4192    #[serde(default = "default_true")]
4193    pub generate_lifecycle_events: bool,
4194
4195    /// Include object-to-object relationships in output
4196    #[serde(default = "default_true")]
4197    pub include_object_relationships: bool,
4198
4199    /// Compute and export process variants
4200    #[serde(default = "default_true")]
4201    pub compute_variants: bool,
4202
4203    /// Maximum variants to track (0 = unlimited)
4204    #[serde(default)]
4205    pub max_variants: usize,
4206
4207    /// P2P process configuration
4208    #[serde(default)]
4209    pub p2p_process: OcpmProcessConfig,
4210
4211    /// O2C process configuration
4212    #[serde(default)]
4213    pub o2c_process: OcpmProcessConfig,
4214
4215    /// Output format configuration
4216    #[serde(default)]
4217    pub output: OcpmOutputConfig,
4218}
4219
4220impl Default for OcpmConfig {
4221    fn default() -> Self {
4222        Self {
4223            enabled: false,
4224            generate_lifecycle_events: true,
4225            include_object_relationships: true,
4226            compute_variants: true,
4227            max_variants: 0,
4228            p2p_process: OcpmProcessConfig::default(),
4229            o2c_process: OcpmProcessConfig::default(),
4230            output: OcpmOutputConfig::default(),
4231        }
4232    }
4233}
4234
4235/// Process-specific OCPM configuration.
4236#[derive(Debug, Clone, Serialize, Deserialize)]
4237pub struct OcpmProcessConfig {
4238    /// Rework probability (0.0-1.0)
4239    #[serde(default = "default_rework_probability")]
4240    pub rework_probability: f64,
4241
4242    /// Skip step probability (0.0-1.0)
4243    #[serde(default = "default_skip_probability")]
4244    pub skip_step_probability: f64,
4245
4246    /// Out-of-order step probability (0.0-1.0)
4247    #[serde(default = "default_out_of_order_probability")]
4248    pub out_of_order_probability: f64,
4249}
4250
4251// Defaults deliberately produce variant counts and Inductive-Miner fitness
4252// in the range seen in real ERP data (dozens of variants, ~0.7–0.9 fitness).
4253// Lowering them all to 0 yields a single-variant happy-path log.
4254fn default_rework_probability() -> f64 {
4255    0.15
4256}
4257
4258fn default_skip_probability() -> f64 {
4259    0.10
4260}
4261
4262fn default_out_of_order_probability() -> f64 {
4263    0.08
4264}
4265
4266impl Default for OcpmProcessConfig {
4267    fn default() -> Self {
4268        Self {
4269            rework_probability: default_rework_probability(),
4270            skip_step_probability: default_skip_probability(),
4271            out_of_order_probability: default_out_of_order_probability(),
4272        }
4273    }
4274}
4275
4276/// OCPM output format configuration.
4277#[derive(Debug, Clone, Serialize, Deserialize)]
4278pub struct OcpmOutputConfig {
4279    /// Export OCEL 2.0 JSON format
4280    #[serde(default = "default_true")]
4281    pub ocel_json: bool,
4282
4283    /// Export OCEL 2.0 XML format
4284    #[serde(default)]
4285    pub ocel_xml: bool,
4286
4287    /// Export XES 2.0 XML format (IEEE standard for process mining tools)
4288    #[serde(default)]
4289    pub xes: bool,
4290
4291    /// Include lifecycle transitions in XES output (start/complete pairs)
4292    #[serde(default = "default_true")]
4293    pub xes_include_lifecycle: bool,
4294
4295    /// Include resource attributes in XES output
4296    #[serde(default = "default_true")]
4297    pub xes_include_resources: bool,
4298
4299    /// Export flattened CSV for each object type
4300    #[serde(default = "default_true")]
4301    pub flattened_csv: bool,
4302
4303    /// Export event-object relationship table
4304    #[serde(default = "default_true")]
4305    pub event_object_csv: bool,
4306
4307    /// Export object-object relationship table
4308    #[serde(default = "default_true")]
4309    pub object_relationship_csv: bool,
4310
4311    /// Export process variants summary
4312    #[serde(default = "default_true")]
4313    pub variants_csv: bool,
4314
4315    /// Export reference process models (canonical P2P, O2C, R2R)
4316    #[serde(default)]
4317    pub export_reference_models: bool,
4318}
4319
4320impl Default for OcpmOutputConfig {
4321    fn default() -> Self {
4322        Self {
4323            ocel_json: true,
4324            ocel_xml: false,
4325            xes: false,
4326            xes_include_lifecycle: true,
4327            xes_include_resources: true,
4328            flattened_csv: true,
4329            event_object_csv: true,
4330            object_relationship_csv: true,
4331            variants_csv: true,
4332            export_reference_models: false,
4333        }
4334    }
4335}
4336
4337/// Audit engagement and workpaper generation configuration.
4338#[derive(Debug, Clone, Serialize, Deserialize)]
4339pub struct AuditGenerationConfig {
4340    /// Enable audit engagement generation
4341    #[serde(default)]
4342    pub enabled: bool,
4343
4344    /// Gate for workpaper generation (v3.3.2+).
4345    /// When `false`, workpapers and dependent evidence are skipped
4346    /// while engagements / risk assessments / findings still generate.
4347    #[serde(default = "default_true")]
4348    pub generate_workpapers: bool,
4349
4350    /// Engagement type distribution (v3.3.2+). Drives per-engagement
4351    /// type draw via `AuditEngagementGenerator::draw_engagement_type`.
4352    #[serde(default)]
4353    pub engagement_types: AuditEngagementTypesConfig,
4354
4355    /// Workpaper configuration (v3.3.2+). `average_per_phase` maps onto
4356    /// `WorkpaperGenerator.workpapers_per_section` as a ±50% band
4357    /// around the average. Sampling / ISA / cross-reference flags are
4358    /// surfaced for downstream formatting overlays.
4359    #[serde(default)]
4360    pub workpapers: WorkpaperConfig,
4361
4362    /// Audit team configuration (v3.3.2+). `min_team_size` /
4363    /// `max_team_size` map directly onto
4364    /// `AuditEngagementGenerator.team_size_range`.
4365    /// `specialist_probability` is reserved for v3.4 (explicit
4366    /// specialist-role support).
4367    #[serde(default)]
4368    pub team: AuditTeamConfig,
4369
4370    /// Review workflow configuration (v3.3.2+).
4371    /// `average_review_delay_days` drives both
4372    /// `first_review_delay_range` and `second_review_delay_range` as
4373    /// a ±1-day band around the average. `rework_probability` and
4374    /// `require_partner_signoff` are reserved for v3.4 workflow
4375    /// modeling.
4376    #[serde(default)]
4377    pub review: ReviewWorkflowConfig,
4378
4379    /// FSM-driven audit generation configuration.
4380    #[serde(default)]
4381    pub fsm: Option<AuditFsmConfig>,
4382
4383    /// v3.3.0: IT general controls (access logs, change management
4384    /// records) emitted alongside audit engagements. Requires both
4385    /// `audit.enabled = true` and `audit.it_controls.enabled = true`
4386    /// to take effect — the latter defaults to `false` so current
4387    /// archives are byte-identical to v3.2.1.
4388    #[serde(default)]
4389    pub it_controls: ItControlsConfig,
4390}
4391
4392/// IT general controls config (v3.3.0+).
4393#[derive(Debug, Clone, Serialize, Deserialize)]
4394pub struct ItControlsConfig {
4395    /// Master switch — when `false`, no access logs or change records
4396    /// are generated.
4397    #[serde(default)]
4398    pub enabled: bool,
4399    /// Number of access-log entries per engagement (approximate — the
4400    /// generator may round or scale based on company size).
4401    #[serde(default = "default_access_log_count")]
4402    pub access_logs_per_engagement: usize,
4403    /// Number of change-management records per engagement.
4404    #[serde(default = "default_change_record_count")]
4405    pub change_records_per_engagement: usize,
4406}
4407
4408fn default_access_log_count() -> usize {
4409    500
4410}
4411fn default_change_record_count() -> usize {
4412    50
4413}
4414
4415impl Default for ItControlsConfig {
4416    fn default() -> Self {
4417        Self {
4418            enabled: false,
4419            access_logs_per_engagement: default_access_log_count(),
4420            change_records_per_engagement: default_change_record_count(),
4421        }
4422    }
4423}
4424
4425impl Default for AuditGenerationConfig {
4426    fn default() -> Self {
4427        Self {
4428            enabled: false,
4429            generate_workpapers: true,
4430            engagement_types: AuditEngagementTypesConfig::default(),
4431            workpapers: WorkpaperConfig::default(),
4432            team: AuditTeamConfig::default(),
4433            review: ReviewWorkflowConfig::default(),
4434            fsm: None,
4435            it_controls: ItControlsConfig::default(),
4436        }
4437    }
4438}
4439
4440/// FSM-driven audit generation configuration.
4441#[derive(Debug, Clone, Serialize, Deserialize)]
4442pub struct AuditFsmConfig {
4443    /// Enable FSM-driven audit generation.
4444    #[serde(default)]
4445    pub enabled: bool,
4446
4447    /// Blueprint source: "builtin:fsa", "builtin:ia", or a file path.
4448    #[serde(default = "default_audit_fsm_blueprint")]
4449    pub blueprint: String,
4450
4451    /// Overlay source: "builtin:default", "builtin:thorough", "builtin:rushed", or a file path.
4452    #[serde(default = "default_audit_fsm_overlay")]
4453    pub overlay: String,
4454
4455    /// Depth level override.
4456    #[serde(default)]
4457    pub depth: Option<String>,
4458
4459    /// Discriminator filter.
4460    #[serde(default)]
4461    pub discriminators: std::collections::HashMap<String, Vec<String>>,
4462
4463    /// Event trail output config.
4464    #[serde(default)]
4465    pub event_trail: AuditEventTrailConfig,
4466
4467    /// RNG seed override.
4468    #[serde(default)]
4469    pub seed: Option<u64>,
4470}
4471
4472impl Default for AuditFsmConfig {
4473    fn default() -> Self {
4474        Self {
4475            enabled: false,
4476            blueprint: default_audit_fsm_blueprint(),
4477            overlay: default_audit_fsm_overlay(),
4478            depth: None,
4479            discriminators: std::collections::HashMap::new(),
4480            event_trail: AuditEventTrailConfig::default(),
4481            seed: None,
4482        }
4483    }
4484}
4485
4486fn default_audit_fsm_blueprint() -> String {
4487    "builtin:fsa".to_string()
4488}
4489
4490fn default_audit_fsm_overlay() -> String {
4491    "builtin:default".to_string()
4492}
4493
4494/// Event trail output configuration for FSM-driven audit generation.
4495#[derive(Debug, Clone, Serialize, Deserialize)]
4496pub struct AuditEventTrailConfig {
4497    /// Emit a flat event log.
4498    #[serde(default = "default_true")]
4499    pub flat_log: bool,
4500    /// Project events to OCEL 2.0 format.
4501    #[serde(default)]
4502    pub ocel_projection: bool,
4503}
4504
4505impl Default for AuditEventTrailConfig {
4506    fn default() -> Self {
4507        Self {
4508            flat_log: true,
4509            ocel_projection: false,
4510        }
4511    }
4512}
4513
4514/// Engagement type distribution configuration.
4515#[derive(Debug, Clone, Serialize, Deserialize)]
4516pub struct AuditEngagementTypesConfig {
4517    /// Financial statement audit probability
4518    #[serde(default = "default_financial_audit_prob")]
4519    pub financial_statement: f64,
4520    /// SOX/ICFR audit probability
4521    #[serde(default = "default_sox_audit_prob")]
4522    pub sox_icfr: f64,
4523    /// Integrated audit probability
4524    #[serde(default = "default_integrated_audit_prob")]
4525    pub integrated: f64,
4526    /// Review engagement probability
4527    #[serde(default = "default_review_prob")]
4528    pub review: f64,
4529    /// Agreed-upon procedures probability
4530    #[serde(default = "default_aup_prob")]
4531    pub agreed_upon_procedures: f64,
4532}
4533
4534fn default_financial_audit_prob() -> f64 {
4535    0.40
4536}
4537fn default_sox_audit_prob() -> f64 {
4538    0.20
4539}
4540fn default_integrated_audit_prob() -> f64 {
4541    0.25
4542}
4543fn default_review_prob() -> f64 {
4544    0.10
4545}
4546fn default_aup_prob() -> f64 {
4547    0.05
4548}
4549
4550impl Default for AuditEngagementTypesConfig {
4551    fn default() -> Self {
4552        Self {
4553            financial_statement: default_financial_audit_prob(),
4554            sox_icfr: default_sox_audit_prob(),
4555            integrated: default_integrated_audit_prob(),
4556            review: default_review_prob(),
4557            agreed_upon_procedures: default_aup_prob(),
4558        }
4559    }
4560}
4561
4562/// Workpaper generation configuration.
4563#[derive(Debug, Clone, Serialize, Deserialize)]
4564pub struct WorkpaperConfig {
4565    /// Average workpapers per engagement phase
4566    #[serde(default = "default_workpapers_per_phase")]
4567    pub average_per_phase: usize,
4568
4569    /// Include ISA compliance references
4570    #[serde(default = "default_true")]
4571    pub include_isa_references: bool,
4572
4573    /// Generate sample details
4574    #[serde(default = "default_true")]
4575    pub include_sample_details: bool,
4576
4577    /// Include cross-references between workpapers
4578    #[serde(default = "default_true")]
4579    pub include_cross_references: bool,
4580
4581    /// Sampling configuration
4582    #[serde(default)]
4583    pub sampling: SamplingConfig,
4584}
4585
4586fn default_workpapers_per_phase() -> usize {
4587    5
4588}
4589
4590impl Default for WorkpaperConfig {
4591    fn default() -> Self {
4592        Self {
4593            average_per_phase: default_workpapers_per_phase(),
4594            include_isa_references: true,
4595            include_sample_details: true,
4596            include_cross_references: true,
4597            sampling: SamplingConfig::default(),
4598        }
4599    }
4600}
4601
4602/// Sampling method configuration.
4603#[derive(Debug, Clone, Serialize, Deserialize)]
4604pub struct SamplingConfig {
4605    /// Statistical sampling rate (0.0-1.0)
4606    #[serde(default = "default_statistical_rate")]
4607    pub statistical_rate: f64,
4608    /// Judgmental sampling rate (0.0-1.0)
4609    #[serde(default = "default_judgmental_rate")]
4610    pub judgmental_rate: f64,
4611    /// Haphazard sampling rate (0.0-1.0)
4612    #[serde(default = "default_haphazard_rate")]
4613    pub haphazard_rate: f64,
4614    /// 100% examination rate (0.0-1.0)
4615    #[serde(default = "default_complete_examination_rate")]
4616    pub complete_examination_rate: f64,
4617}
4618
4619fn default_statistical_rate() -> f64 {
4620    0.40
4621}
4622fn default_judgmental_rate() -> f64 {
4623    0.30
4624}
4625fn default_haphazard_rate() -> f64 {
4626    0.20
4627}
4628fn default_complete_examination_rate() -> f64 {
4629    0.10
4630}
4631
4632impl Default for SamplingConfig {
4633    fn default() -> Self {
4634        Self {
4635            statistical_rate: default_statistical_rate(),
4636            judgmental_rate: default_judgmental_rate(),
4637            haphazard_rate: default_haphazard_rate(),
4638            complete_examination_rate: default_complete_examination_rate(),
4639        }
4640    }
4641}
4642
4643/// Audit team configuration.
4644#[derive(Debug, Clone, Serialize, Deserialize)]
4645pub struct AuditTeamConfig {
4646    /// Minimum team size
4647    #[serde(default = "default_min_team_size")]
4648    pub min_team_size: usize,
4649    /// Maximum team size
4650    #[serde(default = "default_max_team_size")]
4651    pub max_team_size: usize,
4652    /// Probability of having a specialist on the team
4653    #[serde(default = "default_specialist_probability")]
4654    pub specialist_probability: f64,
4655}
4656
4657fn default_min_team_size() -> usize {
4658    3
4659}
4660fn default_max_team_size() -> usize {
4661    8
4662}
4663fn default_specialist_probability() -> f64 {
4664    0.30
4665}
4666
4667impl Default for AuditTeamConfig {
4668    fn default() -> Self {
4669        Self {
4670            min_team_size: default_min_team_size(),
4671            max_team_size: default_max_team_size(),
4672            specialist_probability: default_specialist_probability(),
4673        }
4674    }
4675}
4676
4677/// Review workflow configuration.
4678#[derive(Debug, Clone, Serialize, Deserialize)]
4679pub struct ReviewWorkflowConfig {
4680    /// Average days between preparer completion and first review
4681    #[serde(default = "default_review_delay_days")]
4682    pub average_review_delay_days: u32,
4683    /// Probability of review notes requiring rework
4684    #[serde(default = "default_rework_probability_review")]
4685    pub rework_probability: f64,
4686    /// Require partner sign-off for all workpapers
4687    #[serde(default = "default_true")]
4688    pub require_partner_signoff: bool,
4689}
4690
4691fn default_review_delay_days() -> u32 {
4692    2
4693}
4694fn default_rework_probability_review() -> f64 {
4695    0.15
4696}
4697
4698impl Default for ReviewWorkflowConfig {
4699    fn default() -> Self {
4700        Self {
4701            average_review_delay_days: default_review_delay_days(),
4702            rework_probability: default_rework_probability_review(),
4703            require_partner_signoff: true,
4704        }
4705    }
4706}
4707
4708// =============================================================================
4709// Data Quality Configuration
4710// =============================================================================
4711
4712/// Data quality variation settings for realistic flakiness injection.
4713#[derive(Debug, Clone, Serialize, Deserialize)]
4714pub struct DataQualitySchemaConfig {
4715    /// Enable data quality variations
4716    #[serde(default)]
4717    pub enabled: bool,
4718    /// Preset to use (overrides individual settings if set)
4719    #[serde(default)]
4720    pub preset: DataQualityPreset,
4721    /// Missing value injection settings
4722    #[serde(default)]
4723    pub missing_values: MissingValuesSchemaConfig,
4724    /// Typo injection settings
4725    #[serde(default)]
4726    pub typos: TypoSchemaConfig,
4727    /// Format variation settings
4728    #[serde(default)]
4729    pub format_variations: FormatVariationSchemaConfig,
4730    /// Duplicate injection settings
4731    #[serde(default)]
4732    pub duplicates: DuplicateSchemaConfig,
4733    /// Encoding issue settings
4734    #[serde(default)]
4735    pub encoding_issues: EncodingIssueSchemaConfig,
4736    /// Generate quality issue labels for ML training
4737    #[serde(default)]
4738    pub generate_labels: bool,
4739    /// Per-sink quality profiles (different settings for CSV vs JSON etc.)
4740    #[serde(default)]
4741    pub sink_profiles: SinkQualityProfiles,
4742}
4743
4744impl Default for DataQualitySchemaConfig {
4745    fn default() -> Self {
4746        Self {
4747            enabled: false,
4748            preset: DataQualityPreset::None,
4749            missing_values: MissingValuesSchemaConfig::default(),
4750            typos: TypoSchemaConfig::default(),
4751            format_variations: FormatVariationSchemaConfig::default(),
4752            duplicates: DuplicateSchemaConfig::default(),
4753            encoding_issues: EncodingIssueSchemaConfig::default(),
4754            generate_labels: true,
4755            sink_profiles: SinkQualityProfiles::default(),
4756        }
4757    }
4758}
4759
4760impl DataQualitySchemaConfig {
4761    /// Creates a config for a specific preset profile.
4762    pub fn with_preset(preset: DataQualityPreset) -> Self {
4763        let mut config = Self {
4764            preset,
4765            ..Default::default()
4766        };
4767        config.apply_preset();
4768        config
4769    }
4770
4771    /// Applies the preset settings to the individual configuration fields.
4772    /// Call this after deserializing if preset is not Custom or None.
4773    pub fn apply_preset(&mut self) {
4774        if !self.preset.overrides_settings() {
4775            return;
4776        }
4777
4778        self.enabled = true;
4779
4780        // Missing values
4781        self.missing_values.enabled = self.preset.missing_rate() > 0.0;
4782        self.missing_values.rate = self.preset.missing_rate();
4783
4784        // Typos
4785        self.typos.enabled = self.preset.typo_rate() > 0.0;
4786        self.typos.char_error_rate = self.preset.typo_rate();
4787
4788        // Duplicates
4789        self.duplicates.enabled = self.preset.duplicate_rate() > 0.0;
4790        self.duplicates.exact_duplicate_ratio = self.preset.duplicate_rate() * 0.4;
4791        self.duplicates.near_duplicate_ratio = self.preset.duplicate_rate() * 0.4;
4792        self.duplicates.fuzzy_duplicate_ratio = self.preset.duplicate_rate() * 0.2;
4793
4794        // Format variations
4795        self.format_variations.enabled = self.preset.format_variations_enabled();
4796
4797        // Encoding issues
4798        self.encoding_issues.enabled = self.preset.encoding_issues_enabled();
4799        self.encoding_issues.rate = self.preset.encoding_issue_rate();
4800
4801        // OCR errors for typos in legacy preset
4802        if self.preset.ocr_errors_enabled() {
4803            self.typos.type_weights.ocr_errors = 0.3;
4804        }
4805    }
4806
4807    /// Returns the effective missing value rate (considering preset).
4808    pub fn effective_missing_rate(&self) -> f64 {
4809        if self.preset.overrides_settings() {
4810            self.preset.missing_rate()
4811        } else {
4812            self.missing_values.rate
4813        }
4814    }
4815
4816    /// Returns the effective typo rate (considering preset).
4817    pub fn effective_typo_rate(&self) -> f64 {
4818        if self.preset.overrides_settings() {
4819            self.preset.typo_rate()
4820        } else {
4821            self.typos.char_error_rate
4822        }
4823    }
4824
4825    /// Returns the effective duplicate rate (considering preset).
4826    pub fn effective_duplicate_rate(&self) -> f64 {
4827        if self.preset.overrides_settings() {
4828            self.preset.duplicate_rate()
4829        } else {
4830            self.duplicates.exact_duplicate_ratio
4831                + self.duplicates.near_duplicate_ratio
4832                + self.duplicates.fuzzy_duplicate_ratio
4833        }
4834    }
4835
4836    /// Creates a clean profile config.
4837    pub fn clean() -> Self {
4838        Self::with_preset(DataQualityPreset::Clean)
4839    }
4840
4841    /// Creates a noisy profile config.
4842    pub fn noisy() -> Self {
4843        Self::with_preset(DataQualityPreset::Noisy)
4844    }
4845
4846    /// Creates a legacy profile config.
4847    pub fn legacy() -> Self {
4848        Self::with_preset(DataQualityPreset::Legacy)
4849    }
4850}
4851
4852/// Preset configurations for common data quality scenarios.
4853#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
4854#[serde(rename_all = "snake_case")]
4855pub enum DataQualityPreset {
4856    /// No data quality variations (clean data)
4857    #[default]
4858    None,
4859    /// Minimal variations (very clean data with rare issues)
4860    Minimal,
4861    /// Normal variations (realistic enterprise data quality)
4862    Normal,
4863    /// High variations (messy data for stress testing)
4864    High,
4865    /// Custom (use individual settings)
4866    Custom,
4867
4868    // ========================================
4869    // ML-Oriented Profiles (Phase 2.1)
4870    // ========================================
4871    /// Clean profile for ML training - minimal data quality issues
4872    /// Missing: 0.1%, Typos: 0.05%, Duplicates: 0%, Format: None
4873    Clean,
4874    /// Noisy profile simulating typical production data issues
4875    /// Missing: 5%, Typos: 2%, Duplicates: 1%, Format: Medium
4876    Noisy,
4877    /// Legacy profile simulating migrated/OCR'd historical data
4878    /// Missing: 10%, Typos: 5%, Duplicates: 3%, Format: Heavy + OCR
4879    Legacy,
4880}
4881
4882impl DataQualityPreset {
4883    /// Returns the missing value rate for this preset.
4884    pub fn missing_rate(&self) -> f64 {
4885        match self {
4886            DataQualityPreset::None => 0.0,
4887            DataQualityPreset::Minimal => 0.005,
4888            DataQualityPreset::Normal => 0.02,
4889            DataQualityPreset::High => 0.08,
4890            DataQualityPreset::Custom => 0.01, // Use config value
4891            DataQualityPreset::Clean => 0.001,
4892            DataQualityPreset::Noisy => 0.05,
4893            DataQualityPreset::Legacy => 0.10,
4894        }
4895    }
4896
4897    /// Returns the typo rate for this preset.
4898    pub fn typo_rate(&self) -> f64 {
4899        match self {
4900            DataQualityPreset::None => 0.0,
4901            DataQualityPreset::Minimal => 0.0005,
4902            DataQualityPreset::Normal => 0.002,
4903            DataQualityPreset::High => 0.01,
4904            DataQualityPreset::Custom => 0.001, // Use config value
4905            DataQualityPreset::Clean => 0.0005,
4906            DataQualityPreset::Noisy => 0.02,
4907            DataQualityPreset::Legacy => 0.05,
4908        }
4909    }
4910
4911    /// Returns the duplicate rate for this preset.
4912    pub fn duplicate_rate(&self) -> f64 {
4913        match self {
4914            DataQualityPreset::None => 0.0,
4915            DataQualityPreset::Minimal => 0.001,
4916            DataQualityPreset::Normal => 0.005,
4917            DataQualityPreset::High => 0.02,
4918            DataQualityPreset::Custom => 0.0, // Use config value
4919            DataQualityPreset::Clean => 0.0,
4920            DataQualityPreset::Noisy => 0.01,
4921            DataQualityPreset::Legacy => 0.03,
4922        }
4923    }
4924
4925    /// Returns whether format variations are enabled for this preset.
4926    pub fn format_variations_enabled(&self) -> bool {
4927        match self {
4928            DataQualityPreset::None | DataQualityPreset::Clean => false,
4929            DataQualityPreset::Minimal => true,
4930            DataQualityPreset::Normal => true,
4931            DataQualityPreset::High => true,
4932            DataQualityPreset::Custom => true,
4933            DataQualityPreset::Noisy => true,
4934            DataQualityPreset::Legacy => true,
4935        }
4936    }
4937
4938    /// Returns whether OCR-style errors are enabled for this preset.
4939    pub fn ocr_errors_enabled(&self) -> bool {
4940        matches!(self, DataQualityPreset::Legacy | DataQualityPreset::High)
4941    }
4942
4943    /// Returns whether encoding issues are enabled for this preset.
4944    pub fn encoding_issues_enabled(&self) -> bool {
4945        matches!(
4946            self,
4947            DataQualityPreset::Legacy | DataQualityPreset::High | DataQualityPreset::Noisy
4948        )
4949    }
4950
4951    /// Returns the encoding issue rate for this preset.
4952    pub fn encoding_issue_rate(&self) -> f64 {
4953        match self {
4954            DataQualityPreset::None | DataQualityPreset::Clean | DataQualityPreset::Minimal => 0.0,
4955            DataQualityPreset::Normal => 0.002,
4956            DataQualityPreset::High => 0.01,
4957            DataQualityPreset::Custom => 0.0,
4958            DataQualityPreset::Noisy => 0.005,
4959            DataQualityPreset::Legacy => 0.02,
4960        }
4961    }
4962
4963    /// Returns true if this preset overrides individual settings.
4964    pub fn overrides_settings(&self) -> bool {
4965        !matches!(self, DataQualityPreset::Custom | DataQualityPreset::None)
4966    }
4967
4968    /// Returns a human-readable description of this preset.
4969    pub fn description(&self) -> &'static str {
4970        match self {
4971            DataQualityPreset::None => "No data quality issues (pristine data)",
4972            DataQualityPreset::Minimal => "Very rare data quality issues",
4973            DataQualityPreset::Normal => "Realistic enterprise data quality",
4974            DataQualityPreset::High => "Messy data for stress testing",
4975            DataQualityPreset::Custom => "Custom settings from configuration",
4976            DataQualityPreset::Clean => "ML-ready clean data with minimal issues",
4977            DataQualityPreset::Noisy => "Typical production data with moderate issues",
4978            DataQualityPreset::Legacy => "Legacy/migrated data with heavy issues and OCR errors",
4979        }
4980    }
4981}
4982
4983/// Missing value injection configuration.
4984#[derive(Debug, Clone, Serialize, Deserialize)]
4985pub struct MissingValuesSchemaConfig {
4986    /// Enable missing value injection
4987    #[serde(default)]
4988    pub enabled: bool,
4989    /// Global missing rate (0.0 to 1.0)
4990    #[serde(default = "default_missing_rate")]
4991    pub rate: f64,
4992    /// Missing value strategy
4993    #[serde(default)]
4994    pub strategy: MissingValueStrategy,
4995    /// Field-specific rates (field name -> rate)
4996    #[serde(default)]
4997    pub field_rates: std::collections::HashMap<String, f64>,
4998    /// Fields that should never have missing values
4999    #[serde(default)]
5000    pub protected_fields: Vec<String>,
5001}
5002
5003fn default_missing_rate() -> f64 {
5004    0.01
5005}
5006
5007impl Default for MissingValuesSchemaConfig {
5008    fn default() -> Self {
5009        Self {
5010            enabled: false,
5011            rate: default_missing_rate(),
5012            strategy: MissingValueStrategy::Mcar,
5013            field_rates: std::collections::HashMap::new(),
5014            protected_fields: vec![
5015                "document_id".to_string(),
5016                "company_code".to_string(),
5017                "posting_date".to_string(),
5018            ],
5019        }
5020    }
5021}
5022
5023/// Missing value strategy types.
5024#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
5025#[serde(rename_all = "snake_case")]
5026pub enum MissingValueStrategy {
5027    /// Missing Completely At Random - equal probability for all values
5028    #[default]
5029    Mcar,
5030    /// Missing At Random - depends on other observed values
5031    Mar,
5032    /// Missing Not At Random - depends on the value itself
5033    Mnar,
5034    /// Systematic - entire field groups missing together
5035    Systematic,
5036}
5037
5038/// Typo injection configuration.
5039#[derive(Debug, Clone, Serialize, Deserialize)]
5040pub struct TypoSchemaConfig {
5041    /// Enable typo injection
5042    #[serde(default)]
5043    pub enabled: bool,
5044    /// Character error rate (per character, not per field)
5045    #[serde(default = "default_typo_rate")]
5046    pub char_error_rate: f64,
5047    /// Typo type weights
5048    #[serde(default)]
5049    pub type_weights: TypoTypeWeights,
5050    /// Fields that should never have typos
5051    #[serde(default)]
5052    pub protected_fields: Vec<String>,
5053}
5054
5055fn default_typo_rate() -> f64 {
5056    0.001
5057}
5058
5059impl Default for TypoSchemaConfig {
5060    fn default() -> Self {
5061        Self {
5062            enabled: false,
5063            char_error_rate: default_typo_rate(),
5064            type_weights: TypoTypeWeights::default(),
5065            protected_fields: vec![
5066                "document_id".to_string(),
5067                "gl_account".to_string(),
5068                "company_code".to_string(),
5069            ],
5070        }
5071    }
5072}
5073
5074/// Weights for different typo types.
5075#[derive(Debug, Clone, Serialize, Deserialize)]
5076pub struct TypoTypeWeights {
5077    /// Keyboard-adjacent substitution (e.g., 'a' -> 's')
5078    #[serde(default = "default_substitution_weight")]
5079    pub substitution: f64,
5080    /// Adjacent character transposition (e.g., 'ab' -> 'ba')
5081    #[serde(default = "default_transposition_weight")]
5082    pub transposition: f64,
5083    /// Character insertion
5084    #[serde(default = "default_insertion_weight")]
5085    pub insertion: f64,
5086    /// Character deletion
5087    #[serde(default = "default_deletion_weight")]
5088    pub deletion: f64,
5089    /// OCR-style errors (e.g., '0' -> 'O')
5090    #[serde(default = "default_ocr_weight")]
5091    pub ocr_errors: f64,
5092    /// Homophone substitution (e.g., 'their' -> 'there')
5093    #[serde(default = "default_homophone_weight")]
5094    pub homophones: f64,
5095}
5096
5097fn default_substitution_weight() -> f64 {
5098    0.35
5099}
5100fn default_transposition_weight() -> f64 {
5101    0.25
5102}
5103fn default_insertion_weight() -> f64 {
5104    0.10
5105}
5106fn default_deletion_weight() -> f64 {
5107    0.15
5108}
5109fn default_ocr_weight() -> f64 {
5110    0.10
5111}
5112fn default_homophone_weight() -> f64 {
5113    0.05
5114}
5115
5116impl Default for TypoTypeWeights {
5117    fn default() -> Self {
5118        Self {
5119            substitution: default_substitution_weight(),
5120            transposition: default_transposition_weight(),
5121            insertion: default_insertion_weight(),
5122            deletion: default_deletion_weight(),
5123            ocr_errors: default_ocr_weight(),
5124            homophones: default_homophone_weight(),
5125        }
5126    }
5127}
5128
5129/// Format variation configuration.
5130#[derive(Debug, Clone, Serialize, Deserialize, Default)]
5131pub struct FormatVariationSchemaConfig {
5132    /// Enable format variations
5133    #[serde(default)]
5134    pub enabled: bool,
5135    /// Date format variation settings
5136    #[serde(default)]
5137    pub dates: DateFormatVariationConfig,
5138    /// Amount format variation settings
5139    #[serde(default)]
5140    pub amounts: AmountFormatVariationConfig,
5141    /// Identifier format variation settings
5142    #[serde(default)]
5143    pub identifiers: IdentifierFormatVariationConfig,
5144}
5145
5146/// Date format variation configuration.
5147#[derive(Debug, Clone, Serialize, Deserialize)]
5148pub struct DateFormatVariationConfig {
5149    /// Enable date format variations
5150    #[serde(default)]
5151    pub enabled: bool,
5152    /// Overall variation rate
5153    #[serde(default = "default_date_variation_rate")]
5154    pub rate: f64,
5155    /// Include ISO format (2024-01-15)
5156    #[serde(default = "default_true")]
5157    pub iso_format: bool,
5158    /// Include US format (01/15/2024)
5159    #[serde(default)]
5160    pub us_format: bool,
5161    /// Include EU format (15.01.2024)
5162    #[serde(default)]
5163    pub eu_format: bool,
5164    /// Include long format (January 15, 2024)
5165    #[serde(default)]
5166    pub long_format: bool,
5167}
5168
5169fn default_date_variation_rate() -> f64 {
5170    0.05
5171}
5172
5173impl Default for DateFormatVariationConfig {
5174    fn default() -> Self {
5175        Self {
5176            enabled: false,
5177            rate: default_date_variation_rate(),
5178            iso_format: true,
5179            us_format: false,
5180            eu_format: false,
5181            long_format: false,
5182        }
5183    }
5184}
5185
5186/// Amount format variation configuration.
5187#[derive(Debug, Clone, Serialize, Deserialize)]
5188pub struct AmountFormatVariationConfig {
5189    /// Enable amount format variations
5190    #[serde(default)]
5191    pub enabled: bool,
5192    /// Overall variation rate
5193    #[serde(default = "default_amount_variation_rate")]
5194    pub rate: f64,
5195    /// Include US comma format (1,234.56)
5196    #[serde(default)]
5197    pub us_comma_format: bool,
5198    /// Include EU format (1.234,56)
5199    #[serde(default)]
5200    pub eu_format: bool,
5201    /// Include currency prefix ($1,234.56)
5202    #[serde(default)]
5203    pub currency_prefix: bool,
5204    /// Include accounting format with parentheses for negatives
5205    #[serde(default)]
5206    pub accounting_format: bool,
5207}
5208
5209fn default_amount_variation_rate() -> f64 {
5210    0.02
5211}
5212
5213impl Default for AmountFormatVariationConfig {
5214    fn default() -> Self {
5215        Self {
5216            enabled: false,
5217            rate: default_amount_variation_rate(),
5218            us_comma_format: false,
5219            eu_format: false,
5220            currency_prefix: false,
5221            accounting_format: false,
5222        }
5223    }
5224}
5225
5226/// Identifier format variation configuration.
5227#[derive(Debug, Clone, Serialize, Deserialize)]
5228pub struct IdentifierFormatVariationConfig {
5229    /// Enable identifier format variations
5230    #[serde(default)]
5231    pub enabled: bool,
5232    /// Overall variation rate
5233    #[serde(default = "default_identifier_variation_rate")]
5234    pub rate: f64,
5235    /// Case variations (uppercase, lowercase, mixed)
5236    #[serde(default)]
5237    pub case_variations: bool,
5238    /// Padding variations (leading zeros)
5239    #[serde(default)]
5240    pub padding_variations: bool,
5241    /// Separator variations (dash vs underscore)
5242    #[serde(default)]
5243    pub separator_variations: bool,
5244}
5245
5246fn default_identifier_variation_rate() -> f64 {
5247    0.02
5248}
5249
5250impl Default for IdentifierFormatVariationConfig {
5251    fn default() -> Self {
5252        Self {
5253            enabled: false,
5254            rate: default_identifier_variation_rate(),
5255            case_variations: false,
5256            padding_variations: false,
5257            separator_variations: false,
5258        }
5259    }
5260}
5261
5262/// Duplicate injection configuration.
5263#[derive(Debug, Clone, Serialize, Deserialize)]
5264pub struct DuplicateSchemaConfig {
5265    /// Enable duplicate injection
5266    #[serde(default)]
5267    pub enabled: bool,
5268    /// Overall duplicate rate
5269    #[serde(default = "default_duplicate_rate")]
5270    pub rate: f64,
5271    /// Exact duplicate proportion (out of duplicates)
5272    #[serde(default = "default_exact_duplicate_ratio")]
5273    pub exact_duplicate_ratio: f64,
5274    /// Near duplicate proportion (slight variations)
5275    #[serde(default = "default_near_duplicate_ratio")]
5276    pub near_duplicate_ratio: f64,
5277    /// Fuzzy duplicate proportion (typos in key fields)
5278    #[serde(default = "default_fuzzy_duplicate_ratio")]
5279    pub fuzzy_duplicate_ratio: f64,
5280    /// Maximum date offset for near/fuzzy duplicates (days)
5281    #[serde(default = "default_max_date_offset")]
5282    pub max_date_offset_days: u32,
5283    /// Maximum amount variance for near duplicates (fraction)
5284    #[serde(default = "default_max_amount_variance")]
5285    pub max_amount_variance: f64,
5286}
5287
5288fn default_duplicate_rate() -> f64 {
5289    0.005
5290}
5291fn default_exact_duplicate_ratio() -> f64 {
5292    0.4
5293}
5294fn default_near_duplicate_ratio() -> f64 {
5295    0.35
5296}
5297fn default_fuzzy_duplicate_ratio() -> f64 {
5298    0.25
5299}
5300fn default_max_date_offset() -> u32 {
5301    3
5302}
5303fn default_max_amount_variance() -> f64 {
5304    0.01
5305}
5306
5307impl Default for DuplicateSchemaConfig {
5308    fn default() -> Self {
5309        Self {
5310            enabled: false,
5311            rate: default_duplicate_rate(),
5312            exact_duplicate_ratio: default_exact_duplicate_ratio(),
5313            near_duplicate_ratio: default_near_duplicate_ratio(),
5314            fuzzy_duplicate_ratio: default_fuzzy_duplicate_ratio(),
5315            max_date_offset_days: default_max_date_offset(),
5316            max_amount_variance: default_max_amount_variance(),
5317        }
5318    }
5319}
5320
5321/// Encoding issue configuration.
5322#[derive(Debug, Clone, Serialize, Deserialize)]
5323pub struct EncodingIssueSchemaConfig {
5324    /// Enable encoding issue injection
5325    #[serde(default)]
5326    pub enabled: bool,
5327    /// Overall encoding issue rate
5328    #[serde(default = "default_encoding_rate")]
5329    pub rate: f64,
5330    /// Include mojibake (UTF-8/Latin-1 confusion)
5331    #[serde(default)]
5332    pub mojibake: bool,
5333    /// Include HTML entity corruption
5334    #[serde(default)]
5335    pub html_entities: bool,
5336    /// Include BOM issues
5337    #[serde(default)]
5338    pub bom_issues: bool,
5339}
5340
5341fn default_encoding_rate() -> f64 {
5342    0.001
5343}
5344
5345impl Default for EncodingIssueSchemaConfig {
5346    fn default() -> Self {
5347        Self {
5348            enabled: false,
5349            rate: default_encoding_rate(),
5350            mojibake: false,
5351            html_entities: false,
5352            bom_issues: false,
5353        }
5354    }
5355}
5356
5357/// Per-sink quality profiles for different output formats.
5358#[derive(Debug, Clone, Serialize, Deserialize, Default)]
5359pub struct SinkQualityProfiles {
5360    /// CSV-specific quality settings
5361    #[serde(default)]
5362    pub csv: Option<SinkQualityOverride>,
5363    /// JSON-specific quality settings
5364    #[serde(default)]
5365    pub json: Option<SinkQualityOverride>,
5366    /// Parquet-specific quality settings
5367    #[serde(default)]
5368    pub parquet: Option<SinkQualityOverride>,
5369}
5370
5371/// Quality setting overrides for a specific sink type.
5372#[derive(Debug, Clone, Serialize, Deserialize)]
5373pub struct SinkQualityOverride {
5374    /// Override enabled state
5375    pub enabled: Option<bool>,
5376    /// Override missing value rate
5377    pub missing_rate: Option<f64>,
5378    /// Override typo rate
5379    pub typo_rate: Option<f64>,
5380    /// Override format variation rate
5381    pub format_variation_rate: Option<f64>,
5382    /// Override duplicate rate
5383    pub duplicate_rate: Option<f64>,
5384}
5385
5386// =============================================================================
5387// Accounting Standards Configuration
5388// =============================================================================
5389
5390/// Accounting standards framework configuration for generating standards-compliant data.
5391///
5392/// Supports US GAAP, IFRS, and French GAAP (PCG) frameworks with specific standards:
5393/// - ASC 606/IFRS 15/PCG: Revenue Recognition
5394/// - ASC 842/IFRS 16/PCG: Leases
5395/// - ASC 820/IFRS 13/PCG: Fair Value Measurement
5396/// - ASC 360/IAS 36/PCG: Impairment
5397#[derive(Debug, Clone, Serialize, Deserialize, Default)]
5398pub struct AccountingStandardsConfig {
5399    /// Enable accounting standards generation
5400    #[serde(default)]
5401    pub enabled: bool,
5402
5403    /// Accounting framework to use.
5404    /// When `None`, the country pack's `accounting.framework` is used as fallback;
5405    /// if that is also absent the orchestrator defaults to US GAAP.
5406    #[serde(default, skip_serializing_if = "Option::is_none")]
5407    pub framework: Option<AccountingFrameworkConfig>,
5408
5409    /// Revenue recognition configuration (ASC 606/IFRS 15)
5410    #[serde(default)]
5411    pub revenue_recognition: RevenueRecognitionConfig,
5412
5413    /// Lease accounting configuration (ASC 842/IFRS 16)
5414    #[serde(default)]
5415    pub leases: LeaseAccountingConfig,
5416
5417    /// Fair value measurement configuration (ASC 820/IFRS 13)
5418    #[serde(default)]
5419    pub fair_value: FairValueConfig,
5420
5421    /// Impairment testing configuration (ASC 360/IAS 36)
5422    #[serde(default)]
5423    pub impairment: ImpairmentConfig,
5424
5425    /// Business combination configuration (IFRS 3 / ASC 805)
5426    #[serde(default)]
5427    pub business_combinations: BusinessCombinationsConfig,
5428
5429    /// Expected Credit Loss configuration (IFRS 9 / ASC 326)
5430    #[serde(default)]
5431    pub expected_credit_loss: EclConfig,
5432
5433    /// Generate framework differences for dual reporting
5434    #[serde(default)]
5435    pub generate_differences: bool,
5436}
5437
5438/// Accounting framework selection.
5439#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
5440#[serde(rename_all = "snake_case")]
5441pub enum AccountingFrameworkConfig {
5442    /// US Generally Accepted Accounting Principles
5443    #[default]
5444    UsGaap,
5445    /// International Financial Reporting Standards
5446    Ifrs,
5447    /// Generate data for both frameworks with reconciliation
5448    DualReporting,
5449    /// French GAAP (Plan Comptable Général – PCG)
5450    FrenchGaap,
5451    /// German GAAP (Handelsgesetzbuch – HGB, §238-263)
5452    GermanGaap,
5453}
5454
5455/// Revenue recognition configuration (ASC 606/IFRS 15).
5456#[derive(Debug, Clone, Serialize, Deserialize)]
5457pub struct RevenueRecognitionConfig {
5458    /// Enable revenue recognition generation
5459    #[serde(default)]
5460    pub enabled: bool,
5461
5462    /// Generate customer contracts
5463    #[serde(default = "default_true")]
5464    pub generate_contracts: bool,
5465
5466    /// Average number of performance obligations per contract
5467    #[serde(default = "default_avg_obligations")]
5468    pub avg_obligations_per_contract: f64,
5469
5470    /// Rate of contracts with variable consideration
5471    #[serde(default = "default_variable_consideration_rate")]
5472    pub variable_consideration_rate: f64,
5473
5474    /// Rate of over-time revenue recognition (vs point-in-time)
5475    #[serde(default = "default_over_time_rate")]
5476    pub over_time_recognition_rate: f64,
5477
5478    /// Number of contracts to generate
5479    #[serde(default = "default_contract_count")]
5480    pub contract_count: usize,
5481}
5482
5483fn default_avg_obligations() -> f64 {
5484    2.0
5485}
5486
5487fn default_variable_consideration_rate() -> f64 {
5488    0.15
5489}
5490
5491fn default_over_time_rate() -> f64 {
5492    0.30
5493}
5494
5495fn default_contract_count() -> usize {
5496    100
5497}
5498
5499impl Default for RevenueRecognitionConfig {
5500    fn default() -> Self {
5501        Self {
5502            enabled: false,
5503            generate_contracts: true,
5504            avg_obligations_per_contract: default_avg_obligations(),
5505            variable_consideration_rate: default_variable_consideration_rate(),
5506            over_time_recognition_rate: default_over_time_rate(),
5507            contract_count: default_contract_count(),
5508        }
5509    }
5510}
5511
5512/// Lease accounting configuration (ASC 842/IFRS 16).
5513#[derive(Debug, Clone, Serialize, Deserialize)]
5514pub struct LeaseAccountingConfig {
5515    /// Enable lease accounting generation
5516    #[serde(default)]
5517    pub enabled: bool,
5518
5519    /// Number of leases to generate
5520    #[serde(default = "default_lease_count")]
5521    pub lease_count: usize,
5522
5523    /// Percentage of finance leases (vs operating)
5524    #[serde(default = "default_finance_lease_pct")]
5525    pub finance_lease_percent: f64,
5526
5527    /// Average lease term in months
5528    #[serde(default = "default_avg_lease_term")]
5529    pub avg_lease_term_months: u32,
5530
5531    /// Generate amortization schedules
5532    #[serde(default = "default_true")]
5533    pub generate_amortization: bool,
5534
5535    /// Real estate lease percentage
5536    #[serde(default = "default_real_estate_pct")]
5537    pub real_estate_percent: f64,
5538}
5539
5540fn default_lease_count() -> usize {
5541    50
5542}
5543
5544fn default_finance_lease_pct() -> f64 {
5545    0.30
5546}
5547
5548fn default_avg_lease_term() -> u32 {
5549    60
5550}
5551
5552fn default_real_estate_pct() -> f64 {
5553    0.40
5554}
5555
5556impl Default for LeaseAccountingConfig {
5557    fn default() -> Self {
5558        Self {
5559            enabled: false,
5560            lease_count: default_lease_count(),
5561            finance_lease_percent: default_finance_lease_pct(),
5562            avg_lease_term_months: default_avg_lease_term(),
5563            generate_amortization: true,
5564            real_estate_percent: default_real_estate_pct(),
5565        }
5566    }
5567}
5568
5569/// Fair value measurement configuration (ASC 820/IFRS 13).
5570#[derive(Debug, Clone, Serialize, Deserialize)]
5571pub struct FairValueConfig {
5572    /// Enable fair value measurement generation
5573    #[serde(default)]
5574    pub enabled: bool,
5575
5576    /// Number of fair value measurements to generate
5577    #[serde(default = "default_fv_count")]
5578    pub measurement_count: usize,
5579
5580    /// Level 1 (quoted prices) percentage
5581    #[serde(default = "default_level1_pct")]
5582    pub level1_percent: f64,
5583
5584    /// Level 2 (observable inputs) percentage
5585    #[serde(default = "default_level2_pct")]
5586    pub level2_percent: f64,
5587
5588    /// Level 3 (unobservable inputs) percentage
5589    #[serde(default = "default_level3_pct")]
5590    pub level3_percent: f64,
5591
5592    /// Include sensitivity analysis for Level 3
5593    #[serde(default)]
5594    pub include_sensitivity_analysis: bool,
5595}
5596
5597fn default_fv_count() -> usize {
5598    25
5599}
5600
5601fn default_level1_pct() -> f64 {
5602    0.40
5603}
5604
5605fn default_level2_pct() -> f64 {
5606    0.35
5607}
5608
5609fn default_level3_pct() -> f64 {
5610    0.25
5611}
5612
5613impl Default for FairValueConfig {
5614    fn default() -> Self {
5615        Self {
5616            enabled: false,
5617            measurement_count: default_fv_count(),
5618            level1_percent: default_level1_pct(),
5619            level2_percent: default_level2_pct(),
5620            level3_percent: default_level3_pct(),
5621            include_sensitivity_analysis: false,
5622        }
5623    }
5624}
5625
5626/// Impairment testing configuration (ASC 360/IAS 36).
5627#[derive(Debug, Clone, Serialize, Deserialize)]
5628pub struct ImpairmentConfig {
5629    /// Enable impairment testing generation
5630    #[serde(default)]
5631    pub enabled: bool,
5632
5633    /// Number of impairment tests to generate
5634    #[serde(default = "default_impairment_count")]
5635    pub test_count: usize,
5636
5637    /// Rate of tests resulting in impairment
5638    #[serde(default = "default_impairment_rate")]
5639    pub impairment_rate: f64,
5640
5641    /// Generate cash flow projections
5642    #[serde(default = "default_true")]
5643    pub generate_projections: bool,
5644
5645    /// Include goodwill impairment tests
5646    #[serde(default)]
5647    pub include_goodwill: bool,
5648}
5649
5650fn default_impairment_count() -> usize {
5651    15
5652}
5653
5654fn default_impairment_rate() -> f64 {
5655    0.10
5656}
5657
5658impl Default for ImpairmentConfig {
5659    fn default() -> Self {
5660        Self {
5661            enabled: false,
5662            test_count: default_impairment_count(),
5663            impairment_rate: default_impairment_rate(),
5664            generate_projections: true,
5665            include_goodwill: false,
5666        }
5667    }
5668}
5669
5670// =============================================================================
5671// Business Combinations Configuration (IFRS 3 / ASC 805)
5672// =============================================================================
5673
5674/// Configuration for generating business combination (acquisition) data.
5675#[derive(Debug, Clone, Serialize, Deserialize)]
5676pub struct BusinessCombinationsConfig {
5677    /// Enable business combination generation
5678    #[serde(default)]
5679    pub enabled: bool,
5680
5681    /// Number of acquisitions to generate per company (1-5)
5682    #[serde(default = "default_bc_acquisition_count")]
5683    pub acquisition_count: usize,
5684}
5685
5686fn default_bc_acquisition_count() -> usize {
5687    2
5688}
5689
5690impl Default for BusinessCombinationsConfig {
5691    fn default() -> Self {
5692        Self {
5693            enabled: false,
5694            acquisition_count: default_bc_acquisition_count(),
5695        }
5696    }
5697}
5698
5699// =============================================================================
5700// ECL Configuration (IFRS 9 / ASC 326)
5701// =============================================================================
5702
5703/// Configuration for Expected Credit Loss generation.
5704#[derive(Debug, Clone, Serialize, Deserialize)]
5705pub struct EclConfig {
5706    /// Enable ECL generation.
5707    #[serde(default)]
5708    pub enabled: bool,
5709
5710    /// Weight for base economic scenario (0–1).
5711    #[serde(default = "default_ecl_base_weight")]
5712    pub base_scenario_weight: f64,
5713
5714    /// Multiplier for base scenario (typically 1.0).
5715    #[serde(default = "default_ecl_base_multiplier")]
5716    pub base_scenario_multiplier: f64,
5717
5718    /// Weight for optimistic economic scenario (0–1).
5719    #[serde(default = "default_ecl_optimistic_weight")]
5720    pub optimistic_scenario_weight: f64,
5721
5722    /// Multiplier for optimistic scenario (< 1.0 means lower losses).
5723    #[serde(default = "default_ecl_optimistic_multiplier")]
5724    pub optimistic_scenario_multiplier: f64,
5725
5726    /// Weight for pessimistic economic scenario (0–1).
5727    #[serde(default = "default_ecl_pessimistic_weight")]
5728    pub pessimistic_scenario_weight: f64,
5729
5730    /// Multiplier for pessimistic scenario (> 1.0 means higher losses).
5731    #[serde(default = "default_ecl_pessimistic_multiplier")]
5732    pub pessimistic_scenario_multiplier: f64,
5733}
5734
5735fn default_ecl_base_weight() -> f64 {
5736    0.50
5737}
5738fn default_ecl_base_multiplier() -> f64 {
5739    1.0
5740}
5741fn default_ecl_optimistic_weight() -> f64 {
5742    0.30
5743}
5744fn default_ecl_optimistic_multiplier() -> f64 {
5745    0.8
5746}
5747fn default_ecl_pessimistic_weight() -> f64 {
5748    0.20
5749}
5750fn default_ecl_pessimistic_multiplier() -> f64 {
5751    1.4
5752}
5753
5754impl Default for EclConfig {
5755    fn default() -> Self {
5756        Self {
5757            enabled: false,
5758            base_scenario_weight: default_ecl_base_weight(),
5759            base_scenario_multiplier: default_ecl_base_multiplier(),
5760            optimistic_scenario_weight: default_ecl_optimistic_weight(),
5761            optimistic_scenario_multiplier: default_ecl_optimistic_multiplier(),
5762            pessimistic_scenario_weight: default_ecl_pessimistic_weight(),
5763            pessimistic_scenario_multiplier: default_ecl_pessimistic_multiplier(),
5764        }
5765    }
5766}
5767
5768// =============================================================================
5769// Audit Standards Configuration
5770// =============================================================================
5771
5772/// Audit standards framework configuration for generating standards-compliant audit data.
5773///
5774/// Supports ISA (International Standards on Auditing) and PCAOB standards:
5775/// - ISA 200-720: Complete coverage of audit standards
5776/// - ISA 520: Analytical Procedures
5777/// - ISA 505: External Confirmations
5778/// - ISA 700/705/706/701: Audit Reports
5779/// - PCAOB AS 2201: ICFR Auditing
5780#[derive(Debug, Clone, Serialize, Deserialize, Default)]
5781pub struct AuditStandardsConfig {
5782    /// Enable audit standards generation
5783    #[serde(default)]
5784    pub enabled: bool,
5785
5786    /// ISA compliance configuration
5787    #[serde(default)]
5788    pub isa_compliance: IsaComplianceConfig,
5789
5790    /// Analytical procedures configuration (ISA 520)
5791    #[serde(default)]
5792    pub analytical_procedures: AnalyticalProceduresConfig,
5793
5794    /// External confirmations configuration (ISA 505)
5795    #[serde(default)]
5796    pub confirmations: ConfirmationsConfig,
5797
5798    /// Audit opinion configuration (ISA 700/705/706/701)
5799    #[serde(default)]
5800    pub opinion: AuditOpinionConfig,
5801
5802    /// Generate complete audit trail with traceability
5803    #[serde(default)]
5804    pub generate_audit_trail: bool,
5805
5806    /// SOX 302/404 compliance configuration
5807    #[serde(default)]
5808    pub sox: SoxComplianceConfig,
5809
5810    /// PCAOB-specific configuration
5811    #[serde(default)]
5812    pub pcaob: PcaobConfig,
5813}
5814
5815/// ISA compliance level configuration.
5816#[derive(Debug, Clone, Serialize, Deserialize)]
5817pub struct IsaComplianceConfig {
5818    /// Enable ISA compliance tracking
5819    #[serde(default)]
5820    pub enabled: bool,
5821
5822    /// Compliance level: "basic", "standard", "comprehensive"
5823    #[serde(default = "default_compliance_level")]
5824    pub compliance_level: String,
5825
5826    /// Generate ISA requirement mappings
5827    #[serde(default = "default_true")]
5828    pub generate_isa_mappings: bool,
5829
5830    /// Generate ISA coverage summary
5831    #[serde(default = "default_true")]
5832    pub generate_coverage_summary: bool,
5833
5834    /// Include PCAOB standard mappings (for dual framework)
5835    #[serde(default)]
5836    pub include_pcaob: bool,
5837
5838    /// Framework to use: "isa", "pcaob", "dual"
5839    #[serde(default = "default_audit_framework")]
5840    pub framework: String,
5841}
5842
5843fn default_compliance_level() -> String {
5844    "standard".to_string()
5845}
5846
5847fn default_audit_framework() -> String {
5848    "isa".to_string()
5849}
5850
5851impl Default for IsaComplianceConfig {
5852    fn default() -> Self {
5853        Self {
5854            enabled: false,
5855            compliance_level: default_compliance_level(),
5856            generate_isa_mappings: true,
5857            generate_coverage_summary: true,
5858            include_pcaob: false,
5859            framework: default_audit_framework(),
5860        }
5861    }
5862}
5863
5864/// Analytical procedures configuration (ISA 520).
5865#[derive(Debug, Clone, Serialize, Deserialize)]
5866pub struct AnalyticalProceduresConfig {
5867    /// Enable analytical procedures generation
5868    #[serde(default)]
5869    pub enabled: bool,
5870
5871    /// Number of procedures per account/area
5872    #[serde(default = "default_procedures_per_account")]
5873    pub procedures_per_account: usize,
5874
5875    /// Probability of variance exceeding threshold
5876    #[serde(default = "default_variance_probability")]
5877    pub variance_probability: f64,
5878
5879    /// Include variance investigations
5880    #[serde(default = "default_true")]
5881    pub generate_investigations: bool,
5882
5883    /// Include financial ratio analysis
5884    #[serde(default = "default_true")]
5885    pub include_ratio_analysis: bool,
5886}
5887
5888fn default_procedures_per_account() -> usize {
5889    3
5890}
5891
5892fn default_variance_probability() -> f64 {
5893    0.20
5894}
5895
5896impl Default for AnalyticalProceduresConfig {
5897    fn default() -> Self {
5898        Self {
5899            enabled: false,
5900            procedures_per_account: default_procedures_per_account(),
5901            variance_probability: default_variance_probability(),
5902            generate_investigations: true,
5903            include_ratio_analysis: true,
5904        }
5905    }
5906}
5907
5908/// External confirmations configuration (ISA 505).
5909#[derive(Debug, Clone, Serialize, Deserialize)]
5910pub struct ConfirmationsConfig {
5911    /// Enable confirmation generation
5912    #[serde(default)]
5913    pub enabled: bool,
5914
5915    /// Number of confirmations to generate
5916    #[serde(default = "default_confirmation_count")]
5917    pub confirmation_count: usize,
5918
5919    /// Positive response rate
5920    #[serde(default = "default_positive_response_rate")]
5921    pub positive_response_rate: f64,
5922
5923    /// Exception rate (responses with differences)
5924    #[serde(default = "default_exception_rate_confirm")]
5925    pub exception_rate: f64,
5926
5927    /// Non-response rate
5928    #[serde(default = "default_non_response_rate")]
5929    pub non_response_rate: f64,
5930
5931    /// Generate alternative procedures for non-responses
5932    #[serde(default = "default_true")]
5933    pub generate_alternative_procedures: bool,
5934}
5935
5936fn default_confirmation_count() -> usize {
5937    50
5938}
5939
5940fn default_positive_response_rate() -> f64 {
5941    0.85
5942}
5943
5944fn default_exception_rate_confirm() -> f64 {
5945    0.10
5946}
5947
5948fn default_non_response_rate() -> f64 {
5949    0.05
5950}
5951
5952impl Default for ConfirmationsConfig {
5953    fn default() -> Self {
5954        Self {
5955            enabled: false,
5956            confirmation_count: default_confirmation_count(),
5957            positive_response_rate: default_positive_response_rate(),
5958            exception_rate: default_exception_rate_confirm(),
5959            non_response_rate: default_non_response_rate(),
5960            generate_alternative_procedures: true,
5961        }
5962    }
5963}
5964
5965/// Audit opinion configuration (ISA 700/705/706/701).
5966#[derive(Debug, Clone, Serialize, Deserialize)]
5967pub struct AuditOpinionConfig {
5968    /// Enable audit opinion generation
5969    #[serde(default)]
5970    pub enabled: bool,
5971
5972    /// Generate Key Audit Matters (KAM) / Critical Audit Matters (CAM)
5973    #[serde(default = "default_true")]
5974    pub generate_kam: bool,
5975
5976    /// Average number of KAMs/CAMs per opinion
5977    #[serde(default = "default_kam_count")]
5978    pub average_kam_count: usize,
5979
5980    /// Rate of modified opinions
5981    #[serde(default = "default_modified_opinion_rate")]
5982    pub modified_opinion_rate: f64,
5983
5984    /// Include emphasis of matter paragraphs
5985    #[serde(default)]
5986    pub include_emphasis_of_matter: bool,
5987
5988    /// Include going concern conclusions
5989    #[serde(default = "default_true")]
5990    pub include_going_concern: bool,
5991}
5992
5993fn default_kam_count() -> usize {
5994    3
5995}
5996
5997fn default_modified_opinion_rate() -> f64 {
5998    0.05
5999}
6000
6001impl Default for AuditOpinionConfig {
6002    fn default() -> Self {
6003        Self {
6004            enabled: false,
6005            generate_kam: true,
6006            average_kam_count: default_kam_count(),
6007            modified_opinion_rate: default_modified_opinion_rate(),
6008            include_emphasis_of_matter: false,
6009            include_going_concern: true,
6010        }
6011    }
6012}
6013
6014/// SOX compliance configuration (Sections 302/404).
6015#[derive(Debug, Clone, Serialize, Deserialize)]
6016pub struct SoxComplianceConfig {
6017    /// Enable SOX compliance generation
6018    #[serde(default)]
6019    pub enabled: bool,
6020
6021    /// Generate Section 302 CEO/CFO certifications
6022    #[serde(default = "default_true")]
6023    pub generate_302_certifications: bool,
6024
6025    /// Generate Section 404 ICFR assessments
6026    #[serde(default = "default_true")]
6027    pub generate_404_assessments: bool,
6028
6029    /// Materiality threshold for SOX testing
6030    #[serde(default = "default_sox_materiality_threshold")]
6031    pub materiality_threshold: f64,
6032
6033    /// Rate of material weaknesses
6034    #[serde(default = "default_material_weakness_rate")]
6035    pub material_weakness_rate: f64,
6036
6037    /// Rate of significant deficiencies
6038    #[serde(default = "default_significant_deficiency_rate")]
6039    pub significant_deficiency_rate: f64,
6040}
6041
6042fn default_material_weakness_rate() -> f64 {
6043    0.02
6044}
6045
6046fn default_significant_deficiency_rate() -> f64 {
6047    0.08
6048}
6049
6050impl Default for SoxComplianceConfig {
6051    fn default() -> Self {
6052        Self {
6053            enabled: false,
6054            generate_302_certifications: true,
6055            generate_404_assessments: true,
6056            materiality_threshold: default_sox_materiality_threshold(),
6057            material_weakness_rate: default_material_weakness_rate(),
6058            significant_deficiency_rate: default_significant_deficiency_rate(),
6059        }
6060    }
6061}
6062
6063/// PCAOB-specific configuration.
6064#[derive(Debug, Clone, Serialize, Deserialize)]
6065pub struct PcaobConfig {
6066    /// Enable PCAOB-specific elements
6067    #[serde(default)]
6068    pub enabled: bool,
6069
6070    /// Treat as PCAOB audit (vs ISA-only)
6071    #[serde(default)]
6072    pub is_pcaob_audit: bool,
6073
6074    /// Generate Critical Audit Matters (CAM)
6075    #[serde(default = "default_true")]
6076    pub generate_cam: bool,
6077
6078    /// Include ICFR opinion (for integrated audits)
6079    #[serde(default)]
6080    pub include_icfr_opinion: bool,
6081
6082    /// Generate PCAOB-ISA standard mappings
6083    #[serde(default)]
6084    pub generate_standard_mappings: bool,
6085}
6086
6087impl Default for PcaobConfig {
6088    fn default() -> Self {
6089        Self {
6090            enabled: false,
6091            is_pcaob_audit: false,
6092            generate_cam: true,
6093            include_icfr_opinion: false,
6094            generate_standard_mappings: false,
6095        }
6096    }
6097}
6098
6099// =============================================================================
6100// Advanced Distribution Configuration
6101// =============================================================================
6102
6103/// Advanced distribution configuration for realistic data generation.
6104///
6105/// This section enables sophisticated distribution models including:
6106/// - Mixture models (multi-modal distributions)
6107/// - Cross-field correlations
6108/// - Conditional distributions
6109/// - Regime changes and economic cycles
6110/// - Statistical validation
6111#[derive(Debug, Clone, Serialize, Deserialize, Default)]
6112pub struct AdvancedDistributionConfig {
6113    /// Enable advanced distribution features.
6114    #[serde(default)]
6115    pub enabled: bool,
6116
6117    /// Mixture model configuration for amounts.
6118    #[serde(default)]
6119    pub amounts: MixtureDistributionSchemaConfig,
6120
6121    /// Cross-field correlation configuration.
6122    #[serde(default)]
6123    pub correlations: CorrelationSchemaConfig,
6124
6125    /// Conditional distribution configurations.
6126    #[serde(default)]
6127    pub conditional: Vec<ConditionalDistributionSchemaConfig>,
6128
6129    /// Regime change configuration.
6130    #[serde(default)]
6131    pub regime_changes: RegimeChangeSchemaConfig,
6132
6133    /// Industry-specific distribution profile.
6134    #[serde(default)]
6135    pub industry_profile: Option<IndustryProfileType>,
6136
6137    /// Statistical validation configuration.
6138    #[serde(default)]
6139    pub validation: StatisticalValidationSchemaConfig,
6140
6141    /// v3.4.4+ — Pareto heavy-tailed distribution for monetary amounts.
6142    /// When set and `enabled`, overrides `amounts` mixture model for the
6143    /// non-fraud amount-sampling path (fraud patterns remain orthogonal).
6144    /// Useful for capex, strategic contracts, and any domain where a small
6145    /// number of very large values dominates the tail.
6146    #[serde(default)]
6147    pub pareto: Option<ParetoSchemaConfig>,
6148}
6149
6150/// Schema-level Pareto distribution configuration (v3.4.4+).
6151///
6152/// Thin wrapper around `datasynth_core::distributions::ParetoConfig` that
6153/// adds an `enabled` gate and serde-friendly field names.
6154#[derive(Debug, Clone, Serialize, Deserialize)]
6155pub struct ParetoSchemaConfig {
6156    /// Enable Pareto sampling. When true, replaces the `amounts` mixture
6157    /// model for the non-fraud amount-sampling path.
6158    #[serde(default)]
6159    pub enabled: bool,
6160
6161    /// Shape parameter (tail heaviness). Lower values → heavier tail.
6162    /// Typical range: 1.5-3.0. Default: 2.0.
6163    #[serde(default = "default_pareto_alpha")]
6164    pub alpha: f64,
6165
6166    /// Scale / minimum value. All samples are >= x_min.
6167    /// Typical: 1000 (for capex) to 100,000 (for large contracts). Default: 100.
6168    #[serde(default = "default_pareto_x_min")]
6169    pub x_min: f64,
6170
6171    /// Optional upper clamp. `None` = unbounded (recommended for realistic
6172    /// heavy tails).
6173    #[serde(default)]
6174    pub max_value: Option<f64>,
6175
6176    /// Decimal places for rounding. Default: 2.
6177    #[serde(default = "default_pareto_decimal_places")]
6178    pub decimal_places: u8,
6179}
6180
6181fn default_pareto_alpha() -> f64 {
6182    2.0
6183}
6184
6185fn default_pareto_x_min() -> f64 {
6186    100.0
6187}
6188
6189fn default_pareto_decimal_places() -> u8 {
6190    2
6191}
6192
6193impl Default for ParetoSchemaConfig {
6194    fn default() -> Self {
6195        Self {
6196            enabled: false,
6197            alpha: default_pareto_alpha(),
6198            x_min: default_pareto_x_min(),
6199            max_value: None,
6200            decimal_places: default_pareto_decimal_places(),
6201        }
6202    }
6203}
6204
6205impl ParetoSchemaConfig {
6206    /// Convert this schema config into a `datasynth_core::distributions::ParetoConfig`.
6207    pub fn to_core_config(&self) -> datasynth_core::distributions::ParetoConfig {
6208        datasynth_core::distributions::ParetoConfig {
6209            alpha: self.alpha,
6210            x_min: self.x_min,
6211            max_value: self.max_value,
6212            decimal_places: self.decimal_places,
6213        }
6214    }
6215}
6216
6217/// Industry profile types for pre-configured distribution settings.
6218#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6219#[serde(rename_all = "snake_case")]
6220pub enum IndustryProfileType {
6221    /// Retail industry profile (POS sales, inventory, seasonal)
6222    Retail,
6223    /// Manufacturing industry profile (raw materials, maintenance, capital)
6224    Manufacturing,
6225    /// Financial services profile (wire transfers, ACH, fee income)
6226    FinancialServices,
6227    /// Healthcare profile (claims, procedures, supplies)
6228    Healthcare,
6229    /// Technology profile (subscriptions, services, R&D)
6230    Technology,
6231}
6232
6233/// Mixture model distribution configuration.
6234#[derive(Debug, Clone, Serialize, Deserialize)]
6235pub struct MixtureDistributionSchemaConfig {
6236    /// Enable mixture model for amount generation.
6237    #[serde(default)]
6238    pub enabled: bool,
6239
6240    /// Distribution type: "gaussian" or "lognormal".
6241    #[serde(default = "default_mixture_type")]
6242    pub distribution_type: MixtureDistributionType,
6243
6244    /// Mixture components with weights.
6245    #[serde(default)]
6246    pub components: Vec<MixtureComponentConfig>,
6247
6248    /// Minimum value constraint.
6249    #[serde(default = "default_min_amount")]
6250    pub min_value: f64,
6251
6252    /// Maximum value constraint (optional).
6253    #[serde(default)]
6254    pub max_value: Option<f64>,
6255
6256    /// Decimal places for rounding.
6257    #[serde(default = "default_decimal_places")]
6258    pub decimal_places: u8,
6259}
6260
6261fn default_mixture_type() -> MixtureDistributionType {
6262    MixtureDistributionType::LogNormal
6263}
6264
6265fn default_min_amount() -> f64 {
6266    0.01
6267}
6268
6269fn default_decimal_places() -> u8 {
6270    2
6271}
6272
6273impl Default for MixtureDistributionSchemaConfig {
6274    fn default() -> Self {
6275        Self {
6276            enabled: false,
6277            distribution_type: MixtureDistributionType::LogNormal,
6278            components: Vec::new(),
6279            min_value: 0.01,
6280            max_value: None,
6281            decimal_places: 2,
6282        }
6283    }
6284}
6285
6286impl MixtureDistributionSchemaConfig {
6287    /// Convert this schema-level config into a [`LogNormalMixtureConfig`]
6288    /// suitable for `LogNormalMixtureSampler::new`. Returns `None` if there
6289    /// are no components (schema default is an empty list, which cannot
6290    /// drive a sampler).
6291    ///
6292    /// Callers should gate this with `self.enabled` before invoking.
6293    pub fn to_log_normal_config(
6294        &self,
6295    ) -> Option<datasynth_core::distributions::LogNormalMixtureConfig> {
6296        if self.components.is_empty() {
6297            return None;
6298        }
6299        Some(datasynth_core::distributions::LogNormalMixtureConfig {
6300            components: self
6301                .components
6302                .iter()
6303                .map(|c| match &c.label {
6304                    Some(lbl) => datasynth_core::distributions::LogNormalComponent::with_label(
6305                        c.weight,
6306                        c.mu,
6307                        c.sigma,
6308                        lbl.clone(),
6309                    ),
6310                    None => datasynth_core::distributions::LogNormalComponent::new(
6311                        c.weight, c.mu, c.sigma,
6312                    ),
6313                })
6314                .collect(),
6315            min_value: self.min_value,
6316            max_value: self.max_value,
6317            decimal_places: self.decimal_places,
6318        })
6319    }
6320
6321    /// Convert this schema-level config into a [`GaussianMixtureConfig`].
6322    /// Returns `None` if there are no components.
6323    pub fn to_gaussian_config(
6324        &self,
6325    ) -> Option<datasynth_core::distributions::GaussianMixtureConfig> {
6326        if self.components.is_empty() {
6327            return None;
6328        }
6329        Some(datasynth_core::distributions::GaussianMixtureConfig {
6330            components: self
6331                .components
6332                .iter()
6333                .map(|c| {
6334                    datasynth_core::distributions::GaussianComponent::new(c.weight, c.mu, c.sigma)
6335                })
6336                .collect(),
6337            allow_negative: true,
6338            min_value: Some(self.min_value),
6339            max_value: self.max_value,
6340        })
6341    }
6342}
6343
6344/// Mixture distribution type.
6345#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
6346#[serde(rename_all = "snake_case")]
6347pub enum MixtureDistributionType {
6348    /// Gaussian (normal) mixture
6349    Gaussian,
6350    /// Log-normal mixture (for positive amounts)
6351    #[default]
6352    LogNormal,
6353}
6354
6355/// Configuration for a single mixture component.
6356#[derive(Debug, Clone, Serialize, Deserialize)]
6357pub struct MixtureComponentConfig {
6358    /// Weight of this component (must sum to 1.0 across all components).
6359    pub weight: f64,
6360
6361    /// Location parameter (mean for Gaussian, mu for log-normal).
6362    pub mu: f64,
6363
6364    /// Scale parameter (std dev for Gaussian, sigma for log-normal).
6365    pub sigma: f64,
6366
6367    /// Optional label for this component (e.g., "routine", "significant", "major").
6368    #[serde(default)]
6369    pub label: Option<String>,
6370}
6371
6372/// Cross-field correlation configuration.
6373#[derive(Debug, Clone, Serialize, Deserialize)]
6374pub struct CorrelationSchemaConfig {
6375    /// Enable correlation modeling.
6376    #[serde(default)]
6377    pub enabled: bool,
6378
6379    /// Copula type for dependency modeling.
6380    #[serde(default)]
6381    pub copula_type: CopulaSchemaType,
6382
6383    /// Field definitions for correlation.
6384    #[serde(default)]
6385    pub fields: Vec<CorrelatedFieldConfig>,
6386
6387    /// Correlation matrix (upper triangular, row-major).
6388    /// For n fields, this should have n*(n-1)/2 values.
6389    #[serde(default)]
6390    pub matrix: Vec<f64>,
6391
6392    /// Expected correlations for validation.
6393    #[serde(default)]
6394    pub expected_correlations: Vec<ExpectedCorrelationConfig>,
6395}
6396
6397impl Default for CorrelationSchemaConfig {
6398    fn default() -> Self {
6399        Self {
6400            enabled: false,
6401            copula_type: CopulaSchemaType::Gaussian,
6402            fields: Vec::new(),
6403            matrix: Vec::new(),
6404            expected_correlations: Vec::new(),
6405        }
6406    }
6407}
6408
6409impl CorrelationSchemaConfig {
6410    /// v3.5.4+: extract the correlation for a specific field pair from
6411    /// either the upper-triangular flat matrix (n*(n-1)/2 values) or a
6412    /// full symmetric n×n matrix (n*n values). Returns `None` when the
6413    /// named fields aren't both present or the matrix shape doesn't
6414    /// match.
6415    pub fn correlation_between(&self, field_a: &str, field_b: &str) -> Option<f64> {
6416        let idx_a = self.fields.iter().position(|f| f.name == field_a)?;
6417        let idx_b = self.fields.iter().position(|f| f.name == field_b)?;
6418        if idx_a == idx_b {
6419            return Some(1.0);
6420        }
6421        let (i, j) = if idx_a < idx_b {
6422            (idx_a, idx_b)
6423        } else {
6424            (idx_b, idx_a)
6425        };
6426        let n = self.fields.len();
6427        // Full n×n symmetric matrix?
6428        if self.matrix.len() == n * n {
6429            return self.matrix.get(idx_a * n + idx_b).copied();
6430        }
6431        // Upper triangular flat (row-major, excluding diagonal)?
6432        let expected_tri = n * (n - 1) / 2;
6433        if self.matrix.len() == expected_tri {
6434            // Row i, col j where j > i: flat index is
6435            //   sum_{k=0..i}((n-1-k)) + (j - i - 1)
6436            // = i*(n-1) - i*(i-1)/2 + (j - i - 1)
6437            let flat = i * (n - 1) - i * (i.saturating_sub(1)) / 2 + (j - i - 1);
6438            return self.matrix.get(flat).copied();
6439        }
6440        None
6441    }
6442
6443    /// Convert this schema config to a core `CopulaConfig` when the
6444    /// declared field pair `(field_a, field_b)` has a valid correlation
6445    /// entry. Returns `None` when disabled, fields missing, or matrix
6446    /// malformed.
6447    pub fn to_core_config_for_pair(
6448        &self,
6449        field_a: &str,
6450        field_b: &str,
6451    ) -> Option<datasynth_core::distributions::CopulaConfig> {
6452        if !self.enabled {
6453            return None;
6454        }
6455        let rho = self.correlation_between(field_a, field_b)?;
6456        use datasynth_core::distributions::{CopulaConfig, CopulaType};
6457        let copula_type = match self.copula_type {
6458            CopulaSchemaType::Gaussian => CopulaType::Gaussian,
6459            CopulaSchemaType::Clayton => CopulaType::Clayton,
6460            CopulaSchemaType::Gumbel => CopulaType::Gumbel,
6461            CopulaSchemaType::Frank => CopulaType::Frank,
6462            CopulaSchemaType::StudentT => CopulaType::StudentT,
6463        };
6464        // Gaussian / StudentT interpret theta as correlation; others
6465        // as a shape parameter. Minimal v3.5.4 only wires Gaussian in
6466        // the runtime, but the converter is general so follow-ups can
6467        // light up the other copulas.
6468        let theta = rho.clamp(-0.999, 0.999);
6469        Some(CopulaConfig {
6470            copula_type,
6471            theta,
6472            degrees_of_freedom: 4.0,
6473        })
6474    }
6475}
6476
6477/// Copula type for dependency modeling.
6478#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
6479#[serde(rename_all = "snake_case")]
6480pub enum CopulaSchemaType {
6481    /// Gaussian copula (symmetric, no tail dependence)
6482    #[default]
6483    Gaussian,
6484    /// Clayton copula (lower tail dependence)
6485    Clayton,
6486    /// Gumbel copula (upper tail dependence)
6487    Gumbel,
6488    /// Frank copula (symmetric, no tail dependence)
6489    Frank,
6490    /// Student-t copula (both tail dependencies)
6491    StudentT,
6492}
6493
6494/// Configuration for a correlated field.
6495#[derive(Debug, Clone, Serialize, Deserialize)]
6496pub struct CorrelatedFieldConfig {
6497    /// Field name.
6498    pub name: String,
6499
6500    /// Marginal distribution type.
6501    #[serde(default)]
6502    pub distribution: MarginalDistributionConfig,
6503}
6504
6505/// Marginal distribution configuration.
6506#[derive(Debug, Clone, Serialize, Deserialize)]
6507#[serde(tag = "type", rename_all = "snake_case")]
6508pub enum MarginalDistributionConfig {
6509    /// Normal distribution.
6510    Normal {
6511        /// Mean
6512        mu: f64,
6513        /// Standard deviation
6514        sigma: f64,
6515    },
6516    /// Log-normal distribution.
6517    LogNormal {
6518        /// Location parameter
6519        mu: f64,
6520        /// Scale parameter
6521        sigma: f64,
6522    },
6523    /// Uniform distribution.
6524    Uniform {
6525        /// Minimum value
6526        min: f64,
6527        /// Maximum value
6528        max: f64,
6529    },
6530    /// Discrete uniform distribution.
6531    DiscreteUniform {
6532        /// Minimum integer value
6533        min: i32,
6534        /// Maximum integer value
6535        max: i32,
6536    },
6537}
6538
6539impl Default for MarginalDistributionConfig {
6540    fn default() -> Self {
6541        Self::Normal {
6542            mu: 0.0,
6543            sigma: 1.0,
6544        }
6545    }
6546}
6547
6548/// Expected correlation for validation.
6549#[derive(Debug, Clone, Serialize, Deserialize)]
6550pub struct ExpectedCorrelationConfig {
6551    /// First field name.
6552    pub field1: String,
6553    /// Second field name.
6554    pub field2: String,
6555    /// Expected correlation coefficient.
6556    pub expected_r: f64,
6557    /// Acceptable tolerance.
6558    #[serde(default = "default_correlation_tolerance")]
6559    pub tolerance: f64,
6560}
6561
6562fn default_correlation_tolerance() -> f64 {
6563    0.10
6564}
6565
6566/// Conditional distribution configuration.
6567#[derive(Debug, Clone, Serialize, Deserialize)]
6568pub struct ConditionalDistributionSchemaConfig {
6569    /// Output field name to generate.
6570    pub output_field: String,
6571
6572    /// Input field name that conditions the distribution.
6573    pub input_field: String,
6574
6575    /// Breakpoints defining distribution changes.
6576    #[serde(default)]
6577    pub breakpoints: Vec<ConditionalBreakpointConfig>,
6578
6579    /// Default distribution when below all breakpoints.
6580    #[serde(default)]
6581    pub default_distribution: ConditionalDistributionParamsConfig,
6582
6583    /// Minimum output value constraint.
6584    #[serde(default)]
6585    pub min_value: Option<f64>,
6586
6587    /// Maximum output value constraint.
6588    #[serde(default)]
6589    pub max_value: Option<f64>,
6590
6591    /// Decimal places for output rounding.
6592    #[serde(default = "default_decimal_places")]
6593    pub decimal_places: u8,
6594}
6595
6596/// Breakpoint for conditional distribution.
6597#[derive(Debug, Clone, Serialize, Deserialize)]
6598pub struct ConditionalBreakpointConfig {
6599    /// Input value threshold.
6600    pub threshold: f64,
6601
6602    /// Distribution to use when input >= threshold.
6603    pub distribution: ConditionalDistributionParamsConfig,
6604}
6605
6606impl ConditionalDistributionSchemaConfig {
6607    /// Convert this schema config into a core
6608    /// [`ConditionalDistributionConfig`] suitable for
6609    /// [`ConditionalSampler::new`]. v3.5.3+.
6610    pub fn to_core_config(&self) -> datasynth_core::distributions::ConditionalDistributionConfig {
6611        use datasynth_core::distributions::{
6612            Breakpoint, ConditionalDistributionConfig, ConditionalDistributionParams,
6613        };
6614
6615        let default_distribution = convert_conditional_params(&self.default_distribution);
6616        let breakpoints: Vec<Breakpoint> = self
6617            .breakpoints
6618            .iter()
6619            .map(|bp| Breakpoint {
6620                threshold: bp.threshold,
6621                distribution: convert_conditional_params(&bp.distribution),
6622            })
6623            .collect();
6624
6625        // Use a sentinel default_distribution when the schema default is
6626        // its factory default (Fixed { value: 0.0 })  and we have
6627        // breakpoints — we don't want to clobber data for values below
6628        // the first breakpoint.
6629        let final_default = if breakpoints.is_empty() {
6630            default_distribution
6631        } else {
6632            match default_distribution {
6633                ConditionalDistributionParams::Fixed { value: 0.0 } => {
6634                    // Reuse the first breakpoint's distribution as the
6635                    // default to avoid surprising zeros.
6636                    breakpoints[0].distribution.clone()
6637                }
6638                other => other,
6639            }
6640        };
6641
6642        ConditionalDistributionConfig {
6643            output_field: self.output_field.clone(),
6644            input_field: self.input_field.clone(),
6645            breakpoints,
6646            default_distribution: final_default,
6647            min_value: self.min_value,
6648            max_value: self.max_value,
6649            decimal_places: self.decimal_places,
6650        }
6651    }
6652}
6653
6654fn convert_conditional_params(
6655    p: &ConditionalDistributionParamsConfig,
6656) -> datasynth_core::distributions::ConditionalDistributionParams {
6657    use datasynth_core::distributions::ConditionalDistributionParams as Core;
6658    match p {
6659        ConditionalDistributionParamsConfig::Fixed { value } => Core::Fixed { value: *value },
6660        ConditionalDistributionParamsConfig::Normal { mu, sigma } => Core::Normal {
6661            mu: *mu,
6662            sigma: *sigma,
6663        },
6664        ConditionalDistributionParamsConfig::LogNormal { mu, sigma } => Core::LogNormal {
6665            mu: *mu,
6666            sigma: *sigma,
6667        },
6668        ConditionalDistributionParamsConfig::Uniform { min, max } => Core::Uniform {
6669            min: *min,
6670            max: *max,
6671        },
6672        ConditionalDistributionParamsConfig::Beta {
6673            alpha,
6674            beta,
6675            min,
6676            max,
6677        } => Core::Beta {
6678            alpha: *alpha,
6679            beta: *beta,
6680            min: *min,
6681            max: *max,
6682        },
6683        ConditionalDistributionParamsConfig::Discrete { values, weights } => Core::Discrete {
6684            values: values.clone(),
6685            weights: weights.clone(),
6686        },
6687    }
6688}
6689
6690/// Distribution parameters for conditional distributions.
6691#[derive(Debug, Clone, Serialize, Deserialize)]
6692#[serde(tag = "type", rename_all = "snake_case")]
6693pub enum ConditionalDistributionParamsConfig {
6694    /// Fixed value.
6695    Fixed {
6696        /// The fixed value
6697        value: f64,
6698    },
6699    /// Normal distribution.
6700    Normal {
6701        /// Mean
6702        mu: f64,
6703        /// Standard deviation
6704        sigma: f64,
6705    },
6706    /// Log-normal distribution.
6707    LogNormal {
6708        /// Location parameter
6709        mu: f64,
6710        /// Scale parameter
6711        sigma: f64,
6712    },
6713    /// Uniform distribution.
6714    Uniform {
6715        /// Minimum
6716        min: f64,
6717        /// Maximum
6718        max: f64,
6719    },
6720    /// Beta distribution (scaled).
6721    Beta {
6722        /// Alpha parameter
6723        alpha: f64,
6724        /// Beta parameter
6725        beta: f64,
6726        /// Minimum output value
6727        min: f64,
6728        /// Maximum output value
6729        max: f64,
6730    },
6731    /// Discrete values with weights.
6732    Discrete {
6733        /// Possible values
6734        values: Vec<f64>,
6735        /// Weights (should sum to 1.0)
6736        weights: Vec<f64>,
6737    },
6738}
6739
6740impl Default for ConditionalDistributionParamsConfig {
6741    fn default() -> Self {
6742        Self::Normal {
6743            mu: 0.0,
6744            sigma: 1.0,
6745        }
6746    }
6747}
6748
6749/// Regime change configuration.
6750#[derive(Debug, Clone, Serialize, Deserialize, Default)]
6751pub struct RegimeChangeSchemaConfig {
6752    /// Enable regime change modeling.
6753    #[serde(default)]
6754    pub enabled: bool,
6755
6756    /// List of regime changes.
6757    #[serde(default)]
6758    pub changes: Vec<RegimeChangeEventConfig>,
6759
6760    /// Economic cycle configuration.
6761    #[serde(default)]
6762    pub economic_cycle: Option<EconomicCycleSchemaConfig>,
6763
6764    /// Parameter drift configurations.
6765    #[serde(default)]
6766    pub parameter_drifts: Vec<ParameterDriftSchemaConfig>,
6767}
6768
6769/// A single regime change event.
6770#[derive(Debug, Clone, Serialize, Deserialize)]
6771pub struct RegimeChangeEventConfig {
6772    /// Date when the change occurs (ISO 8601 format).
6773    pub date: String,
6774
6775    /// Type of regime change.
6776    pub change_type: RegimeChangeTypeConfig,
6777
6778    /// Description of the change.
6779    #[serde(default)]
6780    pub description: Option<String>,
6781
6782    /// Effects of this regime change.
6783    #[serde(default)]
6784    pub effects: Vec<RegimeEffectConfig>,
6785}
6786
6787/// Type of regime change.
6788#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6789#[serde(rename_all = "snake_case")]
6790pub enum RegimeChangeTypeConfig {
6791    /// Acquisition - sudden volume and amount increase
6792    Acquisition,
6793    /// Divestiture - sudden volume and amount decrease
6794    Divestiture,
6795    /// Price increase - amounts increase
6796    PriceIncrease,
6797    /// Price decrease - amounts decrease
6798    PriceDecrease,
6799    /// New product launch - volume ramp-up
6800    ProductLaunch,
6801    /// Product discontinuation - volume ramp-down
6802    ProductDiscontinuation,
6803    /// Policy change - affects patterns
6804    PolicyChange,
6805    /// Competitor entry - market disruption
6806    CompetitorEntry,
6807    /// Custom effect
6808    Custom,
6809}
6810
6811/// Effect of a regime change on a specific field.
6812#[derive(Debug, Clone, Serialize, Deserialize)]
6813pub struct RegimeEffectConfig {
6814    /// Field being affected.
6815    pub field: String,
6816
6817    /// Multiplier to apply (1.0 = no change, 1.5 = 50% increase).
6818    pub multiplier: f64,
6819}
6820
6821/// Economic cycle configuration.
6822#[derive(Debug, Clone, Serialize, Deserialize)]
6823pub struct EconomicCycleSchemaConfig {
6824    /// Enable economic cycle modeling.
6825    #[serde(default)]
6826    pub enabled: bool,
6827
6828    /// Cycle period in months (e.g., 48 for 4-year business cycle).
6829    #[serde(default = "default_cycle_period")]
6830    pub period_months: u32,
6831
6832    /// Amplitude of cycle effect (0.0-1.0).
6833    #[serde(default = "default_cycle_amplitude")]
6834    pub amplitude: f64,
6835
6836    /// Phase offset in months.
6837    #[serde(default)]
6838    pub phase_offset: u32,
6839
6840    /// Recession periods (start_month, duration_months).
6841    #[serde(default)]
6842    pub recessions: Vec<RecessionPeriodConfig>,
6843}
6844
6845fn default_cycle_period() -> u32 {
6846    48
6847}
6848
6849fn default_cycle_amplitude() -> f64 {
6850    0.15
6851}
6852
6853impl Default for EconomicCycleSchemaConfig {
6854    fn default() -> Self {
6855        Self {
6856            enabled: false,
6857            period_months: 48,
6858            amplitude: 0.15,
6859            phase_offset: 0,
6860            recessions: Vec::new(),
6861        }
6862    }
6863}
6864
6865/// Recession period configuration.
6866#[derive(Debug, Clone, Serialize, Deserialize)]
6867pub struct RecessionPeriodConfig {
6868    /// Start month (0-indexed from generation start).
6869    pub start_month: u32,
6870
6871    /// Duration in months.
6872    pub duration_months: u32,
6873
6874    /// Severity (0.0-1.0, affects volume reduction).
6875    #[serde(default = "default_recession_severity")]
6876    pub severity: f64,
6877}
6878
6879impl RegimeChangeSchemaConfig {
6880    /// Populate the regime-change, economic-cycle, and parameter-drift
6881    /// slots on a `DriftConfig` from this schema config. v3.5.2+.
6882    ///
6883    /// `generation_start` must match `config.global.start_date` so that
6884    /// absolute regime-change dates can be mapped to 0-indexed periods.
6885    /// Unparseable / out-of-range dates are silently skipped to keep
6886    /// runtime robust against user typos.
6887    pub fn apply_to(
6888        &self,
6889        drift: &mut datasynth_core::distributions::DriftConfig,
6890        generation_start: chrono::NaiveDate,
6891    ) {
6892        if !self.enabled {
6893            return;
6894        }
6895
6896        // Enable drift if any regime-change feature wants it.
6897        drift.enabled = true;
6898
6899        // Regime-change events (absolute dates → period offsets).
6900        for event in &self.changes {
6901            let period = match chrono::NaiveDate::parse_from_str(&event.date, "%Y-%m-%d") {
6902                Ok(d) => {
6903                    let days = (d - generation_start).num_days();
6904                    if days < 0 {
6905                        continue;
6906                    }
6907                    // Approximate month by dividing by 30.4 so we don't
6908                    // need chrono::Months arithmetic.
6909                    (days as f64 / 30.4).round() as u32
6910                }
6911                Err(_) => continue,
6912            };
6913            let change_type = convert_regime_change_type(event.change_type);
6914            let core_effects = event
6915                .effects
6916                .iter()
6917                .map(|e| datasynth_core::distributions::RegimeEffect {
6918                    field: e.field.clone(),
6919                    multiplier: e.multiplier,
6920                })
6921                .collect();
6922            drift
6923                .regime_changes
6924                .push(datasynth_core::distributions::RegimeChange {
6925                    period,
6926                    change_type,
6927                    description: event.description.clone(),
6928                    effects: core_effects,
6929                    transition_periods: 0,
6930                });
6931        }
6932
6933        // Economic cycle.
6934        if let Some(ec) = &self.economic_cycle {
6935            if ec.enabled {
6936                let recession_periods: Vec<u32> = ec
6937                    .recessions
6938                    .iter()
6939                    .flat_map(|r| r.start_month..r.start_month + r.duration_months)
6940                    .collect();
6941                // Use the most-severe recession as the severity driver;
6942                // fall back to default when none declared.
6943                let severity = ec
6944                    .recessions
6945                    .iter()
6946                    .map(|r| 1.0 - r.severity)
6947                    .fold(0.75f64, f64::min);
6948                drift.economic_cycle = datasynth_core::distributions::EconomicCycleConfig {
6949                    enabled: true,
6950                    cycle_length: ec.period_months,
6951                    amplitude: ec.amplitude,
6952                    phase_offset: ec.phase_offset,
6953                    recession_periods,
6954                    recession_severity: severity,
6955                };
6956                drift.drift_type = datasynth_core::distributions::DriftType::Mixed;
6957            }
6958        }
6959
6960        // Parameter drifts.
6961        for pd in &self.parameter_drifts {
6962            let drift_type = match pd.drift_type {
6963                ParameterDriftTypeConfig::Linear => {
6964                    datasynth_core::distributions::ParameterDriftType::Linear
6965                }
6966                ParameterDriftTypeConfig::Exponential => {
6967                    datasynth_core::distributions::ParameterDriftType::Exponential
6968                }
6969                ParameterDriftTypeConfig::Logistic => {
6970                    datasynth_core::distributions::ParameterDriftType::Logistic
6971                }
6972                ParameterDriftTypeConfig::Step => {
6973                    datasynth_core::distributions::ParameterDriftType::Step
6974                }
6975            };
6976            drift
6977                .parameter_drifts
6978                .push(datasynth_core::distributions::ParameterDrift {
6979                    parameter: pd.parameter.clone(),
6980                    drift_type,
6981                    initial_value: pd.start_value,
6982                    target_or_rate: pd.end_value,
6983                    start_period: pd.start_period,
6984                    end_period: pd.end_period,
6985                    steepness: 1.0,
6986                });
6987        }
6988    }
6989}
6990
6991fn convert_regime_change_type(
6992    t: RegimeChangeTypeConfig,
6993) -> datasynth_core::distributions::RegimeChangeType {
6994    use datasynth_core::distributions::RegimeChangeType as Core;
6995    match t {
6996        RegimeChangeTypeConfig::Acquisition => Core::Acquisition,
6997        RegimeChangeTypeConfig::Divestiture => Core::Divestiture,
6998        RegimeChangeTypeConfig::PriceIncrease => Core::PriceIncrease,
6999        RegimeChangeTypeConfig::PriceDecrease => Core::PriceDecrease,
7000        RegimeChangeTypeConfig::ProductLaunch => Core::ProductLaunch,
7001        RegimeChangeTypeConfig::ProductDiscontinuation => Core::ProductDiscontinuation,
7002        RegimeChangeTypeConfig::PolicyChange => Core::PolicyChange,
7003        RegimeChangeTypeConfig::CompetitorEntry => Core::CompetitorEntry,
7004        RegimeChangeTypeConfig::Custom => Core::Custom,
7005    }
7006}
7007
7008fn default_recession_severity() -> f64 {
7009    0.20
7010}
7011
7012/// Parameter drift configuration.
7013#[derive(Debug, Clone, Serialize, Deserialize)]
7014pub struct ParameterDriftSchemaConfig {
7015    /// Parameter being drifted.
7016    pub parameter: String,
7017
7018    /// Drift type.
7019    pub drift_type: ParameterDriftTypeConfig,
7020
7021    /// Start value.
7022    pub start_value: f64,
7023
7024    /// End value.
7025    pub end_value: f64,
7026
7027    /// Start period (month, 0-indexed).
7028    #[serde(default)]
7029    pub start_period: u32,
7030
7031    /// End period (month, optional - defaults to end of generation).
7032    #[serde(default)]
7033    pub end_period: Option<u32>,
7034}
7035
7036/// Parameter drift type.
7037#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
7038#[serde(rename_all = "snake_case")]
7039pub enum ParameterDriftTypeConfig {
7040    /// Linear interpolation
7041    #[default]
7042    Linear,
7043    /// Exponential growth/decay
7044    Exponential,
7045    /// S-curve (logistic)
7046    Logistic,
7047    /// Step function
7048    Step,
7049}
7050
7051/// Statistical validation configuration.
7052#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7053pub struct StatisticalValidationSchemaConfig {
7054    /// Enable statistical validation.
7055    #[serde(default)]
7056    pub enabled: bool,
7057
7058    /// Statistical tests to run.
7059    #[serde(default)]
7060    pub tests: Vec<StatisticalTestConfig>,
7061
7062    /// Validation reporting configuration.
7063    #[serde(default)]
7064    pub reporting: ValidationReportingConfig,
7065}
7066
7067/// Statistical test configuration.
7068#[derive(Debug, Clone, Serialize, Deserialize)]
7069#[serde(tag = "type", rename_all = "snake_case")]
7070pub enum StatisticalTestConfig {
7071    /// Benford's Law first digit test.
7072    BenfordFirstDigit {
7073        /// Threshold MAD for failure.
7074        #[serde(default = "default_benford_threshold")]
7075        threshold_mad: f64,
7076        /// Warning MAD threshold.
7077        #[serde(default = "default_benford_warning")]
7078        warning_mad: f64,
7079    },
7080    /// Distribution fit test.
7081    DistributionFit {
7082        /// Target distribution to test.
7083        target: TargetDistributionConfig,
7084        /// K-S test significance level.
7085        #[serde(default = "default_ks_significance")]
7086        ks_significance: f64,
7087        /// Test method (ks, anderson_darling, chi_squared).
7088        #[serde(default)]
7089        method: DistributionFitMethod,
7090    },
7091    /// Correlation check.
7092    CorrelationCheck {
7093        /// Expected correlations to validate.
7094        expected_correlations: Vec<ExpectedCorrelationConfig>,
7095    },
7096    /// Chi-squared test.
7097    ChiSquared {
7098        /// Number of bins.
7099        #[serde(default = "default_chi_squared_bins")]
7100        bins: usize,
7101        /// Significance level.
7102        #[serde(default = "default_chi_squared_significance")]
7103        significance: f64,
7104    },
7105    /// Anderson-Darling test.
7106    AndersonDarling {
7107        /// Target distribution.
7108        target: TargetDistributionConfig,
7109        /// Significance level.
7110        #[serde(default = "default_ad_significance")]
7111        significance: f64,
7112    },
7113}
7114
7115fn default_benford_threshold() -> f64 {
7116    0.015
7117}
7118
7119fn default_benford_warning() -> f64 {
7120    0.010
7121}
7122
7123fn default_ks_significance() -> f64 {
7124    0.05
7125}
7126
7127fn default_chi_squared_bins() -> usize {
7128    10
7129}
7130
7131fn default_chi_squared_significance() -> f64 {
7132    0.05
7133}
7134
7135fn default_ad_significance() -> f64 {
7136    0.05
7137}
7138
7139/// Target distribution for fit tests.
7140#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
7141#[serde(rename_all = "snake_case")]
7142pub enum TargetDistributionConfig {
7143    /// Normal distribution
7144    Normal,
7145    /// Log-normal distribution
7146    #[default]
7147    LogNormal,
7148    /// Exponential distribution
7149    Exponential,
7150    /// Uniform distribution
7151    Uniform,
7152}
7153
7154/// Distribution fit test method.
7155#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
7156#[serde(rename_all = "snake_case")]
7157pub enum DistributionFitMethod {
7158    /// Kolmogorov-Smirnov test
7159    #[default]
7160    KolmogorovSmirnov,
7161    /// Anderson-Darling test
7162    AndersonDarling,
7163    /// Chi-squared test
7164    ChiSquared,
7165}
7166
7167/// Validation reporting configuration.
7168#[derive(Debug, Clone, Serialize, Deserialize)]
7169pub struct ValidationReportingConfig {
7170    /// Output validation report to file.
7171    #[serde(default)]
7172    pub output_report: bool,
7173
7174    /// Report format.
7175    #[serde(default)]
7176    pub format: ValidationReportFormat,
7177
7178    /// Fail generation if validation fails.
7179    #[serde(default)]
7180    pub fail_on_error: bool,
7181
7182    /// Include detailed statistics in report.
7183    #[serde(default = "default_true")]
7184    pub include_details: bool,
7185}
7186
7187impl Default for ValidationReportingConfig {
7188    fn default() -> Self {
7189        Self {
7190            output_report: false,
7191            format: ValidationReportFormat::Json,
7192            fail_on_error: false,
7193            include_details: true,
7194        }
7195    }
7196}
7197
7198/// Validation report format.
7199#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
7200#[serde(rename_all = "snake_case")]
7201pub enum ValidationReportFormat {
7202    /// JSON format
7203    #[default]
7204    Json,
7205    /// YAML format
7206    Yaml,
7207    /// HTML report
7208    Html,
7209}
7210
7211// =============================================================================
7212// Temporal Patterns Configuration
7213// =============================================================================
7214
7215/// Temporal patterns configuration for business days, period-end dynamics, and processing lags.
7216///
7217/// This section enables sophisticated temporal modeling including:
7218/// - Business day calculations and settlement dates
7219/// - Regional holiday calendars
7220/// - Period-end decay curves (non-flat volume spikes)
7221/// - Processing lag modeling (event-to-posting delays)
7222#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7223pub struct TemporalPatternsConfig {
7224    /// Enable temporal patterns features.
7225    #[serde(default)]
7226    pub enabled: bool,
7227
7228    /// Business day calculation configuration.
7229    #[serde(default)]
7230    pub business_days: BusinessDaySchemaConfig,
7231
7232    /// Regional calendar configuration.
7233    #[serde(default)]
7234    pub calendars: CalendarSchemaConfig,
7235
7236    /// Period-end dynamics configuration.
7237    #[serde(default)]
7238    pub period_end: PeriodEndSchemaConfig,
7239
7240    /// Processing lag configuration.
7241    #[serde(default)]
7242    pub processing_lags: ProcessingLagSchemaConfig,
7243
7244    /// Fiscal calendar configuration (custom year start, 4-4-5, 13-period).
7245    #[serde(default)]
7246    pub fiscal_calendar: FiscalCalendarSchemaConfig,
7247
7248    /// Intra-day patterns configuration (morning spike, lunch dip, EOD rush).
7249    #[serde(default)]
7250    pub intraday: IntraDaySchemaConfig,
7251
7252    /// Timezone handling configuration.
7253    #[serde(default)]
7254    pub timezones: TimezoneSchemaConfig,
7255}
7256
7257/// Business day calculation configuration.
7258#[derive(Debug, Clone, Serialize, Deserialize)]
7259pub struct BusinessDaySchemaConfig {
7260    /// Enable business day calculations.
7261    #[serde(default = "default_true")]
7262    pub enabled: bool,
7263
7264    /// Half-day policy: "full_day", "half_day", "non_business_day".
7265    #[serde(default = "default_half_day_policy")]
7266    pub half_day_policy: String,
7267
7268    /// Settlement rules configuration.
7269    #[serde(default)]
7270    pub settlement_rules: SettlementRulesSchemaConfig,
7271
7272    /// Month-end convention: "modified_following", "preceding", "following", "end_of_month".
7273    #[serde(default = "default_month_end_convention")]
7274    pub month_end_convention: String,
7275
7276    /// Weekend days (e.g., ["saturday", "sunday"] or ["friday", "saturday"] for Middle East).
7277    #[serde(default)]
7278    pub weekend_days: Option<Vec<String>>,
7279}
7280
7281fn default_half_day_policy() -> String {
7282    "half_day".to_string()
7283}
7284
7285fn default_month_end_convention() -> String {
7286    "modified_following".to_string()
7287}
7288
7289impl Default for BusinessDaySchemaConfig {
7290    fn default() -> Self {
7291        Self {
7292            enabled: true,
7293            half_day_policy: "half_day".to_string(),
7294            settlement_rules: SettlementRulesSchemaConfig::default(),
7295            month_end_convention: "modified_following".to_string(),
7296            weekend_days: None,
7297        }
7298    }
7299}
7300
7301/// Settlement rules configuration.
7302#[derive(Debug, Clone, Serialize, Deserialize)]
7303pub struct SettlementRulesSchemaConfig {
7304    /// Equity settlement days (T+N).
7305    #[serde(default = "default_settlement_2")]
7306    pub equity_days: i32,
7307
7308    /// Government bonds settlement days.
7309    #[serde(default = "default_settlement_1")]
7310    pub government_bonds_days: i32,
7311
7312    /// FX spot settlement days.
7313    #[serde(default = "default_settlement_2")]
7314    pub fx_spot_days: i32,
7315
7316    /// Corporate bonds settlement days.
7317    #[serde(default = "default_settlement_2")]
7318    pub corporate_bonds_days: i32,
7319
7320    /// Wire transfer cutoff time (HH:MM format).
7321    #[serde(default = "default_wire_cutoff")]
7322    pub wire_cutoff_time: String,
7323
7324    /// International wire settlement days.
7325    #[serde(default = "default_settlement_1")]
7326    pub wire_international_days: i32,
7327
7328    /// ACH settlement days.
7329    #[serde(default = "default_settlement_1")]
7330    pub ach_days: i32,
7331}
7332
7333fn default_settlement_1() -> i32 {
7334    1
7335}
7336
7337fn default_settlement_2() -> i32 {
7338    2
7339}
7340
7341fn default_wire_cutoff() -> String {
7342    "14:00".to_string()
7343}
7344
7345impl Default for SettlementRulesSchemaConfig {
7346    fn default() -> Self {
7347        Self {
7348            equity_days: 2,
7349            government_bonds_days: 1,
7350            fx_spot_days: 2,
7351            corporate_bonds_days: 2,
7352            wire_cutoff_time: "14:00".to_string(),
7353            wire_international_days: 1,
7354            ach_days: 1,
7355        }
7356    }
7357}
7358
7359/// Regional calendar configuration.
7360#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7361pub struct CalendarSchemaConfig {
7362    /// List of regions to include (e.g., ["US", "DE", "BR", "SG", "KR"]).
7363    #[serde(default)]
7364    pub regions: Vec<String>,
7365
7366    /// Custom holidays (in addition to regional calendars).
7367    #[serde(default)]
7368    pub custom_holidays: Vec<CustomHolidaySchemaConfig>,
7369}
7370
7371/// Custom holiday configuration.
7372#[derive(Debug, Clone, Serialize, Deserialize)]
7373pub struct CustomHolidaySchemaConfig {
7374    /// Holiday name.
7375    pub name: String,
7376    /// Month (1-12).
7377    pub month: u8,
7378    /// Day of month.
7379    pub day: u8,
7380    /// Activity multiplier (0.0-1.0, default 0.05).
7381    #[serde(default = "default_holiday_multiplier")]
7382    pub activity_multiplier: f64,
7383}
7384
7385fn default_holiday_multiplier() -> f64 {
7386    0.05
7387}
7388
7389/// Period-end dynamics configuration.
7390#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7391pub struct PeriodEndSchemaConfig {
7392    /// Model type: "flat", "exponential", "extended_crunch", "daily_profile".
7393    #[serde(default)]
7394    pub model: Option<String>,
7395
7396    /// Month-end configuration.
7397    #[serde(default)]
7398    pub month_end: Option<PeriodEndModelSchemaConfig>,
7399
7400    /// Quarter-end configuration.
7401    #[serde(default)]
7402    pub quarter_end: Option<PeriodEndModelSchemaConfig>,
7403
7404    /// Year-end configuration.
7405    #[serde(default)]
7406    pub year_end: Option<PeriodEndModelSchemaConfig>,
7407}
7408
7409/// Period-end model configuration.
7410#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7411pub struct PeriodEndModelSchemaConfig {
7412    /// Inherit configuration from another period (e.g., "month_end").
7413    #[serde(default)]
7414    pub inherit_from: Option<String>,
7415
7416    /// Additional multiplier on top of inherited/base model.
7417    #[serde(default)]
7418    pub additional_multiplier: Option<f64>,
7419
7420    /// Days before period end to start acceleration (negative, e.g., -10).
7421    #[serde(default)]
7422    pub start_day: Option<i32>,
7423
7424    /// Base multiplier at start of acceleration.
7425    #[serde(default)]
7426    pub base_multiplier: Option<f64>,
7427
7428    /// Peak multiplier on last day.
7429    #[serde(default)]
7430    pub peak_multiplier: Option<f64>,
7431
7432    /// Decay rate for exponential model (0.1-0.5 typical).
7433    #[serde(default)]
7434    pub decay_rate: Option<f64>,
7435
7436    /// Sustained high days for crunch model.
7437    #[serde(default)]
7438    pub sustained_high_days: Option<i32>,
7439}
7440
7441/// Processing lag configuration.
7442#[derive(Debug, Clone, Serialize, Deserialize)]
7443pub struct ProcessingLagSchemaConfig {
7444    /// Enable processing lag calculations.
7445    #[serde(default = "default_true")]
7446    pub enabled: bool,
7447
7448    /// Sales order lag configuration (log-normal mu, sigma).
7449    #[serde(default)]
7450    pub sales_order_lag: Option<LagDistributionSchemaConfig>,
7451
7452    /// Purchase order lag configuration.
7453    #[serde(default)]
7454    pub purchase_order_lag: Option<LagDistributionSchemaConfig>,
7455
7456    /// Goods receipt lag configuration.
7457    #[serde(default)]
7458    pub goods_receipt_lag: Option<LagDistributionSchemaConfig>,
7459
7460    /// Invoice receipt lag configuration.
7461    #[serde(default)]
7462    pub invoice_receipt_lag: Option<LagDistributionSchemaConfig>,
7463
7464    /// Invoice issue lag configuration.
7465    #[serde(default)]
7466    pub invoice_issue_lag: Option<LagDistributionSchemaConfig>,
7467
7468    /// Payment lag configuration.
7469    #[serde(default)]
7470    pub payment_lag: Option<LagDistributionSchemaConfig>,
7471
7472    /// Journal entry lag configuration.
7473    #[serde(default)]
7474    pub journal_entry_lag: Option<LagDistributionSchemaConfig>,
7475
7476    /// Cross-day posting configuration.
7477    #[serde(default)]
7478    pub cross_day_posting: Option<CrossDayPostingSchemaConfig>,
7479}
7480
7481impl Default for ProcessingLagSchemaConfig {
7482    fn default() -> Self {
7483        Self {
7484            enabled: true,
7485            sales_order_lag: None,
7486            purchase_order_lag: None,
7487            goods_receipt_lag: None,
7488            invoice_receipt_lag: None,
7489            invoice_issue_lag: None,
7490            payment_lag: None,
7491            journal_entry_lag: None,
7492            cross_day_posting: None,
7493        }
7494    }
7495}
7496
7497/// Lag distribution configuration (log-normal parameters).
7498#[derive(Debug, Clone, Serialize, Deserialize)]
7499pub struct LagDistributionSchemaConfig {
7500    /// Log-scale mean (mu for log-normal).
7501    pub mu: f64,
7502    /// Log-scale standard deviation (sigma for log-normal).
7503    pub sigma: f64,
7504    /// Minimum lag in hours.
7505    #[serde(default)]
7506    pub min_hours: Option<f64>,
7507    /// Maximum lag in hours.
7508    #[serde(default)]
7509    pub max_hours: Option<f64>,
7510}
7511
7512/// Cross-day posting configuration.
7513#[derive(Debug, Clone, Serialize, Deserialize)]
7514pub struct CrossDayPostingSchemaConfig {
7515    /// Enable cross-day posting logic.
7516    #[serde(default = "default_true")]
7517    pub enabled: bool,
7518
7519    /// Probability of next-day posting by hour (map of hour -> probability).
7520    /// E.g., { 17: 0.7, 19: 0.9, 21: 0.99 }
7521    #[serde(default)]
7522    pub probability_by_hour: std::collections::HashMap<u8, f64>,
7523}
7524
7525impl Default for CrossDayPostingSchemaConfig {
7526    fn default() -> Self {
7527        let mut probability_by_hour = std::collections::HashMap::new();
7528        probability_by_hour.insert(17, 0.3);
7529        probability_by_hour.insert(18, 0.6);
7530        probability_by_hour.insert(19, 0.8);
7531        probability_by_hour.insert(20, 0.9);
7532        probability_by_hour.insert(21, 0.95);
7533        probability_by_hour.insert(22, 0.99);
7534
7535        Self {
7536            enabled: true,
7537            probability_by_hour,
7538        }
7539    }
7540}
7541
7542// =============================================================================
7543// Fiscal Calendar Configuration (P2)
7544// =============================================================================
7545
7546/// Fiscal calendar configuration.
7547///
7548/// Supports calendar year, custom year start, 4-4-5 retail calendar,
7549/// and 13-period calendars.
7550#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7551pub struct FiscalCalendarSchemaConfig {
7552    /// Enable non-standard fiscal calendar.
7553    #[serde(default)]
7554    pub enabled: bool,
7555
7556    /// Fiscal calendar type: "calendar_year", "custom", "four_four_five", "thirteen_period".
7557    #[serde(default = "default_fiscal_calendar_type")]
7558    pub calendar_type: String,
7559
7560    /// Month the fiscal year starts (1-12). Used for custom year start.
7561    #[serde(default)]
7562    pub year_start_month: Option<u8>,
7563
7564    /// Day the fiscal year starts (1-31). Used for custom year start.
7565    #[serde(default)]
7566    pub year_start_day: Option<u8>,
7567
7568    /// 4-4-5 calendar configuration (if calendar_type is "four_four_five").
7569    #[serde(default)]
7570    pub four_four_five: Option<FourFourFiveSchemaConfig>,
7571}
7572
7573fn default_fiscal_calendar_type() -> String {
7574    "calendar_year".to_string()
7575}
7576
7577/// 4-4-5 retail calendar configuration.
7578#[derive(Debug, Clone, Serialize, Deserialize)]
7579pub struct FourFourFiveSchemaConfig {
7580    /// Week pattern: "four_four_five", "four_five_four", "five_four_four".
7581    #[serde(default = "default_week_pattern")]
7582    pub pattern: String,
7583
7584    /// Anchor type: "first_sunday", "last_saturday", "nearest_saturday".
7585    #[serde(default = "default_anchor_type")]
7586    pub anchor_type: String,
7587
7588    /// Anchor month (1-12).
7589    #[serde(default = "default_anchor_month")]
7590    pub anchor_month: u8,
7591
7592    /// Where to place leap week: "q4_period3" or "q1_period1".
7593    #[serde(default = "default_leap_week_placement")]
7594    pub leap_week_placement: String,
7595}
7596
7597fn default_week_pattern() -> String {
7598    "four_four_five".to_string()
7599}
7600
7601fn default_anchor_type() -> String {
7602    "last_saturday".to_string()
7603}
7604
7605fn default_anchor_month() -> u8 {
7606    1 // January
7607}
7608
7609fn default_leap_week_placement() -> String {
7610    "q4_period3".to_string()
7611}
7612
7613impl Default for FourFourFiveSchemaConfig {
7614    fn default() -> Self {
7615        Self {
7616            pattern: "four_four_five".to_string(),
7617            anchor_type: "last_saturday".to_string(),
7618            anchor_month: 1,
7619            leap_week_placement: "q4_period3".to_string(),
7620        }
7621    }
7622}
7623
7624// =============================================================================
7625// Intra-Day Patterns Configuration (P2)
7626// =============================================================================
7627
7628/// Intra-day patterns configuration.
7629///
7630/// Defines time-of-day segments with different activity multipliers
7631/// for realistic modeling of morning spikes, lunch dips, and end-of-day rushes.
7632#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7633pub struct IntraDaySchemaConfig {
7634    /// Enable intra-day patterns.
7635    #[serde(default)]
7636    pub enabled: bool,
7637
7638    /// Custom intra-day segments.
7639    #[serde(default)]
7640    pub segments: Vec<IntraDaySegmentSchemaConfig>,
7641}
7642
7643/// Intra-day segment configuration.
7644#[derive(Debug, Clone, Serialize, Deserialize)]
7645pub struct IntraDaySegmentSchemaConfig {
7646    /// Name of the segment (e.g., "morning_spike", "lunch_dip").
7647    pub name: String,
7648
7649    /// Start time (HH:MM format).
7650    pub start: String,
7651
7652    /// End time (HH:MM format).
7653    pub end: String,
7654
7655    /// Activity multiplier (1.0 = normal).
7656    #[serde(default = "default_multiplier")]
7657    pub multiplier: f64,
7658
7659    /// Posting type: "human", "system", "both".
7660    #[serde(default = "default_posting_type")]
7661    pub posting_type: String,
7662}
7663
7664fn default_multiplier() -> f64 {
7665    1.0
7666}
7667
7668fn default_posting_type() -> String {
7669    "both".to_string()
7670}
7671
7672// =============================================================================
7673// Timezone Configuration
7674// =============================================================================
7675
7676/// Timezone handling configuration for multi-region entities.
7677#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7678pub struct TimezoneSchemaConfig {
7679    /// Enable timezone handling.
7680    #[serde(default)]
7681    pub enabled: bool,
7682
7683    /// Default timezone (IANA format, e.g., "America/New_York").
7684    #[serde(default = "default_timezone")]
7685    pub default_timezone: String,
7686
7687    /// Consolidation timezone for group reporting (IANA format).
7688    #[serde(default = "default_consolidation_timezone")]
7689    pub consolidation_timezone: String,
7690
7691    /// Entity-to-timezone mappings.
7692    /// Supports patterns like "EU_*" -> "Europe/London".
7693    #[serde(default)]
7694    pub entity_mappings: Vec<EntityTimezoneMapping>,
7695}
7696
7697fn default_timezone() -> String {
7698    "America/New_York".to_string()
7699}
7700
7701fn default_consolidation_timezone() -> String {
7702    "UTC".to_string()
7703}
7704
7705/// Mapping from entity pattern to timezone.
7706#[derive(Debug, Clone, Serialize, Deserialize)]
7707pub struct EntityTimezoneMapping {
7708    /// Entity code pattern (e.g., "EU_*", "*_APAC", "1000").
7709    pub pattern: String,
7710
7711    /// Timezone (IANA format, e.g., "Europe/London").
7712    pub timezone: String,
7713}
7714
7715// =============================================================================
7716// Vendor Network Configuration
7717// =============================================================================
7718
7719/// Configuration for multi-tier vendor network generation.
7720#[derive(Debug, Clone, Serialize, Deserialize)]
7721pub struct VendorNetworkSchemaConfig {
7722    /// Enable vendor network generation.
7723    #[serde(default)]
7724    pub enabled: bool,
7725
7726    /// Maximum depth of supply chain tiers (1-3).
7727    #[serde(default = "default_vendor_tier_depth")]
7728    pub depth: u8,
7729
7730    /// Tier 1 vendor count configuration.
7731    #[serde(default)]
7732    pub tier1: TierCountSchemaConfig,
7733
7734    /// Tier 2 vendors per Tier 1 parent.
7735    #[serde(default)]
7736    pub tier2_per_parent: TierCountSchemaConfig,
7737
7738    /// Tier 3 vendors per Tier 2 parent.
7739    #[serde(default)]
7740    pub tier3_per_parent: TierCountSchemaConfig,
7741
7742    /// Vendor cluster distribution.
7743    #[serde(default)]
7744    pub clusters: VendorClusterSchemaConfig,
7745
7746    /// Concentration limits.
7747    #[serde(default)]
7748    pub dependencies: DependencySchemaConfig,
7749}
7750
7751fn default_vendor_tier_depth() -> u8 {
7752    3
7753}
7754
7755impl Default for VendorNetworkSchemaConfig {
7756    fn default() -> Self {
7757        Self {
7758            enabled: false,
7759            depth: 3,
7760            tier1: TierCountSchemaConfig { min: 50, max: 100 },
7761            tier2_per_parent: TierCountSchemaConfig { min: 4, max: 10 },
7762            tier3_per_parent: TierCountSchemaConfig { min: 2, max: 5 },
7763            clusters: VendorClusterSchemaConfig::default(),
7764            dependencies: DependencySchemaConfig::default(),
7765        }
7766    }
7767}
7768
7769/// Tier count configuration.
7770#[derive(Debug, Clone, Serialize, Deserialize)]
7771pub struct TierCountSchemaConfig {
7772    /// Minimum count.
7773    #[serde(default = "default_tier_min")]
7774    pub min: usize,
7775
7776    /// Maximum count.
7777    #[serde(default = "default_tier_max")]
7778    pub max: usize,
7779}
7780
7781fn default_tier_min() -> usize {
7782    5
7783}
7784
7785fn default_tier_max() -> usize {
7786    20
7787}
7788
7789impl Default for TierCountSchemaConfig {
7790    fn default() -> Self {
7791        Self {
7792            min: default_tier_min(),
7793            max: default_tier_max(),
7794        }
7795    }
7796}
7797
7798/// Vendor cluster distribution configuration.
7799#[derive(Debug, Clone, Serialize, Deserialize)]
7800pub struct VendorClusterSchemaConfig {
7801    /// Reliable strategic vendors percentage (default: 0.20).
7802    #[serde(default = "default_reliable_strategic")]
7803    pub reliable_strategic: f64,
7804
7805    /// Standard operational vendors percentage (default: 0.50).
7806    #[serde(default = "default_standard_operational")]
7807    pub standard_operational: f64,
7808
7809    /// Transactional vendors percentage (default: 0.25).
7810    #[serde(default = "default_transactional")]
7811    pub transactional: f64,
7812
7813    /// Problematic vendors percentage (default: 0.05).
7814    #[serde(default = "default_problematic")]
7815    pub problematic: f64,
7816}
7817
7818fn default_reliable_strategic() -> f64 {
7819    0.20
7820}
7821
7822fn default_standard_operational() -> f64 {
7823    0.50
7824}
7825
7826fn default_transactional() -> f64 {
7827    0.25
7828}
7829
7830fn default_problematic() -> f64 {
7831    0.05
7832}
7833
7834impl Default for VendorClusterSchemaConfig {
7835    fn default() -> Self {
7836        Self {
7837            reliable_strategic: 0.20,
7838            standard_operational: 0.50,
7839            transactional: 0.25,
7840            problematic: 0.05,
7841        }
7842    }
7843}
7844
7845/// Dependency and concentration limits configuration.
7846#[derive(Debug, Clone, Serialize, Deserialize)]
7847pub struct DependencySchemaConfig {
7848    /// Maximum concentration for a single vendor (default: 0.15).
7849    #[serde(default = "default_max_single_vendor")]
7850    pub max_single_vendor_concentration: f64,
7851
7852    /// Maximum concentration for top 5 vendors (default: 0.45).
7853    #[serde(default = "default_max_top5")]
7854    pub top_5_concentration: f64,
7855
7856    /// Percentage of single-source vendors (default: 0.05).
7857    #[serde(default = "default_single_source_percent")]
7858    pub single_source_percent: f64,
7859}
7860
7861fn default_max_single_vendor() -> f64 {
7862    0.15
7863}
7864
7865fn default_max_top5() -> f64 {
7866    0.45
7867}
7868
7869fn default_single_source_percent() -> f64 {
7870    0.05
7871}
7872
7873impl Default for DependencySchemaConfig {
7874    fn default() -> Self {
7875        Self {
7876            max_single_vendor_concentration: 0.15,
7877            top_5_concentration: 0.45,
7878            single_source_percent: 0.05,
7879        }
7880    }
7881}
7882
7883// =============================================================================
7884// Customer Segmentation Configuration
7885// =============================================================================
7886
7887/// Configuration for customer segmentation generation.
7888#[derive(Debug, Clone, Default, Serialize, Deserialize)]
7889pub struct CustomerSegmentationSchemaConfig {
7890    /// Enable customer segmentation generation.
7891    #[serde(default)]
7892    pub enabled: bool,
7893
7894    /// Value segment distribution.
7895    #[serde(default)]
7896    pub value_segments: ValueSegmentsSchemaConfig,
7897
7898    /// Lifecycle stage configuration.
7899    #[serde(default)]
7900    pub lifecycle: LifecycleSchemaConfig,
7901
7902    /// Network (referrals, hierarchies) configuration.
7903    #[serde(default)]
7904    pub networks: CustomerNetworksSchemaConfig,
7905}
7906
7907/// Customer value segments distribution configuration.
7908#[derive(Debug, Clone, Serialize, Deserialize)]
7909pub struct ValueSegmentsSchemaConfig {
7910    /// Enterprise segment configuration.
7911    #[serde(default)]
7912    pub enterprise: SegmentDetailSchemaConfig,
7913
7914    /// Mid-market segment configuration.
7915    #[serde(default)]
7916    pub mid_market: SegmentDetailSchemaConfig,
7917
7918    /// SMB segment configuration.
7919    #[serde(default)]
7920    pub smb: SegmentDetailSchemaConfig,
7921
7922    /// Consumer segment configuration.
7923    #[serde(default)]
7924    pub consumer: SegmentDetailSchemaConfig,
7925}
7926
7927impl Default for ValueSegmentsSchemaConfig {
7928    fn default() -> Self {
7929        Self {
7930            enterprise: SegmentDetailSchemaConfig {
7931                revenue_share: 0.40,
7932                customer_share: 0.05,
7933                avg_order_value_range: "50000+".to_string(),
7934            },
7935            mid_market: SegmentDetailSchemaConfig {
7936                revenue_share: 0.35,
7937                customer_share: 0.20,
7938                avg_order_value_range: "5000-50000".to_string(),
7939            },
7940            smb: SegmentDetailSchemaConfig {
7941                revenue_share: 0.20,
7942                customer_share: 0.50,
7943                avg_order_value_range: "500-5000".to_string(),
7944            },
7945            consumer: SegmentDetailSchemaConfig {
7946                revenue_share: 0.05,
7947                customer_share: 0.25,
7948                avg_order_value_range: "50-500".to_string(),
7949            },
7950        }
7951    }
7952}
7953
7954/// Individual segment detail configuration.
7955#[derive(Debug, Clone, Serialize, Deserialize)]
7956pub struct SegmentDetailSchemaConfig {
7957    /// Revenue share for this segment.
7958    #[serde(default)]
7959    pub revenue_share: f64,
7960
7961    /// Customer share for this segment.
7962    #[serde(default)]
7963    pub customer_share: f64,
7964
7965    /// Average order value range (e.g., "5000-50000" or "50000+").
7966    #[serde(default)]
7967    pub avg_order_value_range: String,
7968}
7969
7970impl Default for SegmentDetailSchemaConfig {
7971    fn default() -> Self {
7972        Self {
7973            revenue_share: 0.25,
7974            customer_share: 0.25,
7975            avg_order_value_range: "1000-10000".to_string(),
7976        }
7977    }
7978}
7979
7980/// Customer lifecycle stage configuration.
7981#[derive(Debug, Clone, Serialize, Deserialize)]
7982pub struct LifecycleSchemaConfig {
7983    /// Prospect stage rate.
7984    #[serde(default)]
7985    pub prospect_rate: f64,
7986
7987    /// New customer stage rate.
7988    #[serde(default = "default_new_rate")]
7989    pub new_rate: f64,
7990
7991    /// Growth stage rate.
7992    #[serde(default = "default_growth_rate")]
7993    pub growth_rate: f64,
7994
7995    /// Mature stage rate.
7996    #[serde(default = "default_mature_rate")]
7997    pub mature_rate: f64,
7998
7999    /// At-risk stage rate.
8000    #[serde(default = "default_at_risk_rate")]
8001    pub at_risk_rate: f64,
8002
8003    /// Churned stage rate.
8004    #[serde(default = "default_churned_rate")]
8005    pub churned_rate: f64,
8006
8007    /// Won-back stage rate (churned customers reacquired).
8008    #[serde(default)]
8009    pub won_back_rate: f64,
8010}
8011
8012fn default_new_rate() -> f64 {
8013    0.10
8014}
8015
8016fn default_growth_rate() -> f64 {
8017    0.15
8018}
8019
8020fn default_mature_rate() -> f64 {
8021    0.60
8022}
8023
8024fn default_at_risk_rate() -> f64 {
8025    0.10
8026}
8027
8028fn default_churned_rate() -> f64 {
8029    0.05
8030}
8031
8032impl Default for LifecycleSchemaConfig {
8033    fn default() -> Self {
8034        Self {
8035            prospect_rate: 0.0,
8036            new_rate: 0.10,
8037            growth_rate: 0.15,
8038            mature_rate: 0.60,
8039            at_risk_rate: 0.10,
8040            churned_rate: 0.05,
8041            won_back_rate: 0.0,
8042        }
8043    }
8044}
8045
8046/// Customer networks configuration (referrals, hierarchies).
8047#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8048pub struct CustomerNetworksSchemaConfig {
8049    /// Referral network configuration.
8050    #[serde(default)]
8051    pub referrals: ReferralSchemaConfig,
8052
8053    /// Corporate hierarchy configuration.
8054    #[serde(default)]
8055    pub corporate_hierarchies: HierarchySchemaConfig,
8056}
8057
8058/// Referral network configuration.
8059#[derive(Debug, Clone, Serialize, Deserialize)]
8060pub struct ReferralSchemaConfig {
8061    /// Enable referral generation.
8062    #[serde(default = "default_true")]
8063    pub enabled: bool,
8064
8065    /// Rate of customers acquired via referral.
8066    #[serde(default = "default_referral_rate")]
8067    pub referral_rate: f64,
8068}
8069
8070fn default_referral_rate() -> f64 {
8071    0.15
8072}
8073
8074impl Default for ReferralSchemaConfig {
8075    fn default() -> Self {
8076        Self {
8077            enabled: true,
8078            referral_rate: 0.15,
8079        }
8080    }
8081}
8082
8083/// Corporate hierarchy configuration.
8084#[derive(Debug, Clone, Serialize, Deserialize)]
8085pub struct HierarchySchemaConfig {
8086    /// Enable corporate hierarchy generation.
8087    #[serde(default = "default_true")]
8088    pub enabled: bool,
8089
8090    /// Rate of customers in hierarchies.
8091    #[serde(default = "default_hierarchy_rate")]
8092    pub probability: f64,
8093}
8094
8095fn default_hierarchy_rate() -> f64 {
8096    0.30
8097}
8098
8099impl Default for HierarchySchemaConfig {
8100    fn default() -> Self {
8101        Self {
8102            enabled: true,
8103            probability: 0.30,
8104        }
8105    }
8106}
8107
8108// =============================================================================
8109// Relationship Strength Configuration
8110// =============================================================================
8111
8112/// Configuration for relationship strength calculation.
8113#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8114pub struct RelationshipStrengthSchemaConfig {
8115    /// Enable relationship strength calculation.
8116    #[serde(default)]
8117    pub enabled: bool,
8118
8119    /// Calculation weights.
8120    #[serde(default)]
8121    pub calculation: StrengthCalculationSchemaConfig,
8122
8123    /// Strength thresholds for classification.
8124    #[serde(default)]
8125    pub thresholds: StrengthThresholdsSchemaConfig,
8126}
8127
8128/// Strength calculation weights configuration.
8129#[derive(Debug, Clone, Serialize, Deserialize)]
8130pub struct StrengthCalculationSchemaConfig {
8131    /// Weight for transaction volume (default: 0.30).
8132    #[serde(default = "default_volume_weight")]
8133    pub transaction_volume_weight: f64,
8134
8135    /// Weight for transaction count (default: 0.25).
8136    #[serde(default = "default_count_weight")]
8137    pub transaction_count_weight: f64,
8138
8139    /// Weight for relationship duration (default: 0.20).
8140    #[serde(default = "default_duration_weight")]
8141    pub relationship_duration_weight: f64,
8142
8143    /// Weight for recency (default: 0.15).
8144    #[serde(default = "default_recency_weight")]
8145    pub recency_weight: f64,
8146
8147    /// Weight for mutual connections (default: 0.10).
8148    #[serde(default = "default_mutual_weight")]
8149    pub mutual_connections_weight: f64,
8150
8151    /// Recency half-life in days (default: 90).
8152    #[serde(default = "default_recency_half_life")]
8153    pub recency_half_life_days: u32,
8154}
8155
8156fn default_volume_weight() -> f64 {
8157    0.30
8158}
8159
8160fn default_count_weight() -> f64 {
8161    0.25
8162}
8163
8164fn default_duration_weight() -> f64 {
8165    0.20
8166}
8167
8168fn default_recency_weight() -> f64 {
8169    0.15
8170}
8171
8172fn default_mutual_weight() -> f64 {
8173    0.10
8174}
8175
8176fn default_recency_half_life() -> u32 {
8177    90
8178}
8179
8180impl Default for StrengthCalculationSchemaConfig {
8181    fn default() -> Self {
8182        Self {
8183            transaction_volume_weight: 0.30,
8184            transaction_count_weight: 0.25,
8185            relationship_duration_weight: 0.20,
8186            recency_weight: 0.15,
8187            mutual_connections_weight: 0.10,
8188            recency_half_life_days: 90,
8189        }
8190    }
8191}
8192
8193/// Strength thresholds for relationship classification.
8194#[derive(Debug, Clone, Serialize, Deserialize)]
8195pub struct StrengthThresholdsSchemaConfig {
8196    /// Threshold for strong relationships (default: 0.7).
8197    #[serde(default = "default_strong_threshold")]
8198    pub strong: f64,
8199
8200    /// Threshold for moderate relationships (default: 0.4).
8201    #[serde(default = "default_moderate_threshold")]
8202    pub moderate: f64,
8203
8204    /// Threshold for weak relationships (default: 0.1).
8205    #[serde(default = "default_weak_threshold")]
8206    pub weak: f64,
8207}
8208
8209fn default_strong_threshold() -> f64 {
8210    0.7
8211}
8212
8213fn default_moderate_threshold() -> f64 {
8214    0.4
8215}
8216
8217fn default_weak_threshold() -> f64 {
8218    0.1
8219}
8220
8221impl Default for StrengthThresholdsSchemaConfig {
8222    fn default() -> Self {
8223        Self {
8224            strong: 0.7,
8225            moderate: 0.4,
8226            weak: 0.1,
8227        }
8228    }
8229}
8230
8231// =============================================================================
8232// Cross-Process Links Configuration
8233// =============================================================================
8234
8235/// Configuration for cross-process linkages.
8236#[derive(Debug, Clone, Serialize, Deserialize)]
8237pub struct CrossProcessLinksSchemaConfig {
8238    /// Enable cross-process link generation.
8239    #[serde(default)]
8240    pub enabled: bool,
8241
8242    /// Enable inventory links between P2P and O2C.
8243    #[serde(default = "default_true")]
8244    pub inventory_p2p_o2c: bool,
8245
8246    /// Enable payment to bank reconciliation links.
8247    #[serde(default = "default_true")]
8248    pub payment_bank_reconciliation: bool,
8249
8250    /// Enable intercompany bilateral matching.
8251    #[serde(default = "default_true")]
8252    pub intercompany_bilateral: bool,
8253
8254    /// Percentage of GR/Deliveries to link via inventory (0.0 - 1.0).
8255    #[serde(default = "default_inventory_link_rate")]
8256    pub inventory_link_rate: f64,
8257}
8258
8259fn default_inventory_link_rate() -> f64 {
8260    0.30
8261}
8262
8263impl Default for CrossProcessLinksSchemaConfig {
8264    fn default() -> Self {
8265        Self {
8266            enabled: false,
8267            inventory_p2p_o2c: true,
8268            payment_bank_reconciliation: true,
8269            intercompany_bilateral: true,
8270            inventory_link_rate: 0.30,
8271        }
8272    }
8273}
8274
8275// =============================================================================
8276// Organizational Events Configuration
8277// =============================================================================
8278
8279/// Configuration for organizational events (acquisitions, divestitures, etc.).
8280#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8281pub struct OrganizationalEventsSchemaConfig {
8282    /// Enable organizational events.
8283    #[serde(default)]
8284    pub enabled: bool,
8285
8286    /// Effect blending mode (multiplicative, additive, maximum, minimum).
8287    #[serde(default)]
8288    pub effect_blending: EffectBlendingModeConfig,
8289
8290    /// Organizational events (acquisitions, divestitures, reorganizations, etc.).
8291    #[serde(default)]
8292    pub events: Vec<OrganizationalEventSchemaConfig>,
8293
8294    /// Process evolution events.
8295    #[serde(default)]
8296    pub process_evolution: Vec<ProcessEvolutionSchemaConfig>,
8297
8298    /// Technology transition events.
8299    #[serde(default)]
8300    pub technology_transitions: Vec<TechnologyTransitionSchemaConfig>,
8301}
8302
8303/// Effect blending mode for combining multiple event effects.
8304#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
8305#[serde(rename_all = "snake_case")]
8306pub enum EffectBlendingModeConfig {
8307    /// Multiply effects together.
8308    #[default]
8309    Multiplicative,
8310    /// Add effects together.
8311    Additive,
8312    /// Take the maximum effect.
8313    Maximum,
8314    /// Take the minimum effect.
8315    Minimum,
8316}
8317
8318/// Configuration for a single organizational event.
8319#[derive(Debug, Clone, Serialize, Deserialize)]
8320pub struct OrganizationalEventSchemaConfig {
8321    /// Event ID.
8322    pub id: String,
8323
8324    /// Event type and configuration.
8325    pub event_type: OrganizationalEventTypeSchemaConfig,
8326
8327    /// Effective date.
8328    pub effective_date: String,
8329
8330    /// Transition duration in months.
8331    #[serde(default = "default_org_transition_months")]
8332    pub transition_months: u32,
8333
8334    /// Description.
8335    #[serde(default)]
8336    pub description: Option<String>,
8337}
8338
8339fn default_org_transition_months() -> u32 {
8340    6
8341}
8342
8343/// Organizational event type configuration.
8344#[derive(Debug, Clone, Serialize, Deserialize)]
8345#[serde(tag = "type", rename_all = "snake_case")]
8346pub enum OrganizationalEventTypeSchemaConfig {
8347    /// Acquisition event.
8348    Acquisition {
8349        /// Acquired entity code.
8350        acquired_entity: String,
8351        /// Volume increase multiplier.
8352        #[serde(default = "default_acquisition_volume")]
8353        volume_increase: f64,
8354        /// Integration error rate.
8355        #[serde(default = "default_acquisition_error")]
8356        integration_error_rate: f64,
8357        /// Parallel posting days.
8358        #[serde(default = "default_parallel_days")]
8359        parallel_posting_days: u32,
8360    },
8361    /// Divestiture event.
8362    Divestiture {
8363        /// Divested entity code.
8364        divested_entity: String,
8365        /// Volume reduction factor.
8366        #[serde(default = "default_divestiture_volume")]
8367        volume_reduction: f64,
8368        /// Remove entity from generation.
8369        #[serde(default = "default_true_val")]
8370        remove_entity: bool,
8371    },
8372    /// Reorganization event.
8373    Reorganization {
8374        /// Cost center remapping.
8375        #[serde(default)]
8376        cost_center_remapping: std::collections::HashMap<String, String>,
8377        /// Transition error rate.
8378        #[serde(default = "default_reorg_error")]
8379        transition_error_rate: f64,
8380    },
8381    /// Leadership change event.
8382    LeadershipChange {
8383        /// Role that changed.
8384        role: String,
8385        /// Policy changes.
8386        #[serde(default)]
8387        policy_changes: Vec<String>,
8388    },
8389    /// Workforce reduction event.
8390    WorkforceReduction {
8391        /// Reduction percentage.
8392        #[serde(default = "default_workforce_reduction")]
8393        reduction_percent: f64,
8394        /// Error rate increase.
8395        #[serde(default = "default_workforce_error")]
8396        error_rate_increase: f64,
8397    },
8398    /// Merger event.
8399    Merger {
8400        /// Merged entity code.
8401        merged_entity: String,
8402        /// Volume increase multiplier.
8403        #[serde(default = "default_merger_volume")]
8404        volume_increase: f64,
8405    },
8406}
8407
8408fn default_acquisition_volume() -> f64 {
8409    1.35
8410}
8411
8412fn default_acquisition_error() -> f64 {
8413    0.05
8414}
8415
8416fn default_parallel_days() -> u32 {
8417    30
8418}
8419
8420fn default_divestiture_volume() -> f64 {
8421    0.70
8422}
8423
8424fn default_true_val() -> bool {
8425    true
8426}
8427
8428fn default_reorg_error() -> f64 {
8429    0.04
8430}
8431
8432fn default_workforce_reduction() -> f64 {
8433    0.10
8434}
8435
8436fn default_workforce_error() -> f64 {
8437    0.05
8438}
8439
8440fn default_merger_volume() -> f64 {
8441    1.80
8442}
8443
8444/// Configuration for a process evolution event.
8445#[derive(Debug, Clone, Serialize, Deserialize)]
8446pub struct ProcessEvolutionSchemaConfig {
8447    /// Event ID.
8448    pub id: String,
8449
8450    /// Event type.
8451    pub event_type: ProcessEvolutionTypeSchemaConfig,
8452
8453    /// Effective date.
8454    pub effective_date: String,
8455
8456    /// Description.
8457    #[serde(default)]
8458    pub description: Option<String>,
8459}
8460
8461/// Process evolution type configuration.
8462#[derive(Debug, Clone, Serialize, Deserialize)]
8463#[serde(tag = "type", rename_all = "snake_case")]
8464pub enum ProcessEvolutionTypeSchemaConfig {
8465    /// Process automation.
8466    ProcessAutomation {
8467        /// Process name.
8468        process_name: String,
8469        /// Manual rate before.
8470        #[serde(default = "default_manual_before")]
8471        manual_rate_before: f64,
8472        /// Manual rate after.
8473        #[serde(default = "default_manual_after")]
8474        manual_rate_after: f64,
8475    },
8476    /// Approval workflow change.
8477    ApprovalWorkflowChange {
8478        /// Description.
8479        description: String,
8480    },
8481    /// Control enhancement.
8482    ControlEnhancement {
8483        /// Control ID.
8484        control_id: String,
8485        /// Error reduction.
8486        #[serde(default = "default_error_reduction")]
8487        error_reduction: f64,
8488    },
8489}
8490
8491fn default_manual_before() -> f64 {
8492    0.80
8493}
8494
8495fn default_manual_after() -> f64 {
8496    0.15
8497}
8498
8499fn default_error_reduction() -> f64 {
8500    0.02
8501}
8502
8503/// Configuration for a technology transition event.
8504#[derive(Debug, Clone, Serialize, Deserialize)]
8505pub struct TechnologyTransitionSchemaConfig {
8506    /// Event ID.
8507    pub id: String,
8508
8509    /// Event type.
8510    pub event_type: TechnologyTransitionTypeSchemaConfig,
8511
8512    /// Description.
8513    #[serde(default)]
8514    pub description: Option<String>,
8515}
8516
8517/// Technology transition type configuration.
8518#[derive(Debug, Clone, Serialize, Deserialize)]
8519#[serde(tag = "type", rename_all = "snake_case")]
8520pub enum TechnologyTransitionTypeSchemaConfig {
8521    /// ERP migration.
8522    ErpMigration {
8523        /// Source system.
8524        source_system: String,
8525        /// Target system.
8526        target_system: String,
8527        /// Cutover date.
8528        cutover_date: String,
8529        /// Stabilization end date.
8530        stabilization_end: String,
8531        /// Duplicate rate during migration.
8532        #[serde(default = "default_erp_duplicate_rate")]
8533        duplicate_rate: f64,
8534        /// Format mismatch rate.
8535        #[serde(default = "default_format_mismatch")]
8536        format_mismatch_rate: f64,
8537    },
8538    /// Module implementation.
8539    ModuleImplementation {
8540        /// Module name.
8541        module_name: String,
8542        /// Go-live date.
8543        go_live_date: String,
8544    },
8545}
8546
8547fn default_erp_duplicate_rate() -> f64 {
8548    0.02
8549}
8550
8551fn default_format_mismatch() -> f64 {
8552    0.03
8553}
8554
8555// =============================================================================
8556// Behavioral Drift Configuration
8557// =============================================================================
8558
8559/// Configuration for behavioral drift (vendor, customer, employee behavior).
8560///
8561/// **Deprecated (v4.1.2):** this schema section is currently
8562/// validated-but-inert — no runtime code consumes its fields. Users
8563/// who want behavioral drift-style effects should reach for
8564/// `distributions.regime_changes` (v3.5.2+), which drives the
8565/// `DriftController` via the parameter-drift path. The schema type
8566/// remains for backward-compatible YAML loading; it will be removed
8567/// in a future major version once `regime_changes` gains per-entity
8568/// (vendor / customer / employee) targeting.
8569#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8570pub struct BehavioralDriftSchemaConfig {
8571    /// Enable behavioral drift.
8572    #[serde(default)]
8573    pub enabled: bool,
8574
8575    /// Vendor behavior drift.
8576    #[serde(default)]
8577    pub vendor_behavior: VendorBehaviorSchemaConfig,
8578
8579    /// Customer behavior drift.
8580    #[serde(default)]
8581    pub customer_behavior: CustomerBehaviorSchemaConfig,
8582
8583    /// Employee behavior drift.
8584    #[serde(default)]
8585    pub employee_behavior: EmployeeBehaviorSchemaConfig,
8586
8587    /// Collective behavior drift.
8588    #[serde(default)]
8589    pub collective: CollectiveBehaviorSchemaConfig,
8590}
8591
8592/// Vendor behavior drift configuration.
8593#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8594pub struct VendorBehaviorSchemaConfig {
8595    /// Payment terms drift.
8596    #[serde(default)]
8597    pub payment_terms_drift: PaymentTermsDriftSchemaConfig,
8598
8599    /// Quality drift.
8600    #[serde(default)]
8601    pub quality_drift: QualityDriftSchemaConfig,
8602}
8603
8604/// Payment terms drift configuration.
8605#[derive(Debug, Clone, Serialize, Deserialize)]
8606pub struct PaymentTermsDriftSchemaConfig {
8607    /// Extension rate per year (days).
8608    #[serde(default = "default_extension_rate")]
8609    pub extension_rate_per_year: f64,
8610
8611    /// Economic sensitivity.
8612    #[serde(default = "default_economic_sensitivity")]
8613    pub economic_sensitivity: f64,
8614}
8615
8616fn default_extension_rate() -> f64 {
8617    2.5
8618}
8619
8620fn default_economic_sensitivity() -> f64 {
8621    1.0
8622}
8623
8624impl Default for PaymentTermsDriftSchemaConfig {
8625    fn default() -> Self {
8626        Self {
8627            extension_rate_per_year: 2.5,
8628            economic_sensitivity: 1.0,
8629        }
8630    }
8631}
8632
8633/// Quality drift configuration.
8634#[derive(Debug, Clone, Serialize, Deserialize)]
8635pub struct QualityDriftSchemaConfig {
8636    /// New vendor improvement rate (per year).
8637    #[serde(default = "default_improvement_rate")]
8638    pub new_vendor_improvement_rate: f64,
8639
8640    /// Complacency decline rate (per year after first year).
8641    #[serde(default = "default_decline_rate")]
8642    pub complacency_decline_rate: f64,
8643}
8644
8645fn default_improvement_rate() -> f64 {
8646    0.02
8647}
8648
8649fn default_decline_rate() -> f64 {
8650    0.01
8651}
8652
8653impl Default for QualityDriftSchemaConfig {
8654    fn default() -> Self {
8655        Self {
8656            new_vendor_improvement_rate: 0.02,
8657            complacency_decline_rate: 0.01,
8658        }
8659    }
8660}
8661
8662/// Customer behavior drift configuration.
8663#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8664pub struct CustomerBehaviorSchemaConfig {
8665    /// Payment drift.
8666    #[serde(default)]
8667    pub payment_drift: CustomerPaymentDriftSchemaConfig,
8668
8669    /// Order drift.
8670    #[serde(default)]
8671    pub order_drift: OrderDriftSchemaConfig,
8672}
8673
8674/// Customer payment drift configuration.
8675#[derive(Debug, Clone, Serialize, Deserialize)]
8676pub struct CustomerPaymentDriftSchemaConfig {
8677    /// Days extension during downturn (min, max).
8678    #[serde(default = "default_downturn_extension")]
8679    pub downturn_days_extension: (u32, u32),
8680
8681    /// Bad debt increase during downturn.
8682    #[serde(default = "default_bad_debt_increase")]
8683    pub downturn_bad_debt_increase: f64,
8684}
8685
8686fn default_downturn_extension() -> (u32, u32) {
8687    (5, 15)
8688}
8689
8690fn default_bad_debt_increase() -> f64 {
8691    0.02
8692}
8693
8694impl Default for CustomerPaymentDriftSchemaConfig {
8695    fn default() -> Self {
8696        Self {
8697            downturn_days_extension: (5, 15),
8698            downturn_bad_debt_increase: 0.02,
8699        }
8700    }
8701}
8702
8703/// Order drift configuration.
8704#[derive(Debug, Clone, Serialize, Deserialize)]
8705pub struct OrderDriftSchemaConfig {
8706    /// Digital shift rate (per year).
8707    #[serde(default = "default_digital_shift")]
8708    pub digital_shift_rate: f64,
8709}
8710
8711fn default_digital_shift() -> f64 {
8712    0.05
8713}
8714
8715impl Default for OrderDriftSchemaConfig {
8716    fn default() -> Self {
8717        Self {
8718            digital_shift_rate: 0.05,
8719        }
8720    }
8721}
8722
8723/// Employee behavior drift configuration.
8724#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8725pub struct EmployeeBehaviorSchemaConfig {
8726    /// Approval drift.
8727    #[serde(default)]
8728    pub approval_drift: ApprovalDriftSchemaConfig,
8729
8730    /// Error drift.
8731    #[serde(default)]
8732    pub error_drift: ErrorDriftSchemaConfig,
8733}
8734
8735/// Approval drift configuration.
8736#[derive(Debug, Clone, Serialize, Deserialize)]
8737pub struct ApprovalDriftSchemaConfig {
8738    /// EOM intensity increase per year.
8739    #[serde(default = "default_eom_intensity")]
8740    pub eom_intensity_increase_per_year: f64,
8741
8742    /// Rubber stamp volume threshold.
8743    #[serde(default = "default_rubber_stamp")]
8744    pub rubber_stamp_volume_threshold: u32,
8745}
8746
8747fn default_eom_intensity() -> f64 {
8748    0.05
8749}
8750
8751fn default_rubber_stamp() -> u32 {
8752    50
8753}
8754
8755impl Default for ApprovalDriftSchemaConfig {
8756    fn default() -> Self {
8757        Self {
8758            eom_intensity_increase_per_year: 0.05,
8759            rubber_stamp_volume_threshold: 50,
8760        }
8761    }
8762}
8763
8764/// Error drift configuration.
8765#[derive(Debug, Clone, Serialize, Deserialize)]
8766pub struct ErrorDriftSchemaConfig {
8767    /// New employee error rate.
8768    #[serde(default = "default_new_error")]
8769    pub new_employee_error_rate: f64,
8770
8771    /// Learning curve months.
8772    #[serde(default = "default_learning_months")]
8773    pub learning_curve_months: u32,
8774}
8775
8776fn default_new_error() -> f64 {
8777    0.08
8778}
8779
8780fn default_learning_months() -> u32 {
8781    6
8782}
8783
8784impl Default for ErrorDriftSchemaConfig {
8785    fn default() -> Self {
8786        Self {
8787            new_employee_error_rate: 0.08,
8788            learning_curve_months: 6,
8789        }
8790    }
8791}
8792
8793/// Collective behavior drift configuration.
8794#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8795pub struct CollectiveBehaviorSchemaConfig {
8796    /// Automation adoption configuration.
8797    #[serde(default)]
8798    pub automation_adoption: AutomationAdoptionSchemaConfig,
8799}
8800
8801/// Automation adoption configuration.
8802#[derive(Debug, Clone, Serialize, Deserialize)]
8803pub struct AutomationAdoptionSchemaConfig {
8804    /// Enable S-curve adoption model.
8805    #[serde(default)]
8806    pub s_curve_enabled: bool,
8807
8808    /// Adoption midpoint in months.
8809    #[serde(default = "default_midpoint")]
8810    pub adoption_midpoint_months: u32,
8811
8812    /// Steepness of adoption curve.
8813    #[serde(default = "default_steepness")]
8814    pub steepness: f64,
8815}
8816
8817fn default_midpoint() -> u32 {
8818    24
8819}
8820
8821fn default_steepness() -> f64 {
8822    0.15
8823}
8824
8825impl Default for AutomationAdoptionSchemaConfig {
8826    fn default() -> Self {
8827        Self {
8828            s_curve_enabled: false,
8829            adoption_midpoint_months: 24,
8830            steepness: 0.15,
8831        }
8832    }
8833}
8834
8835// =============================================================================
8836// Market Drift Configuration
8837// =============================================================================
8838
8839/// Configuration for market drift (economic cycles, commodities, price shocks).
8840///
8841/// **Deprecated (v4.1.2):** validated-but-inert. Use
8842/// `distributions.regime_changes.economic_cycle` +
8843/// `distributions.regime_changes.parameter_drifts` for the
8844/// equivalent runtime behaviour (shipped in v3.5.2). The schema
8845/// type remains for backward-compatible YAML loading; will be
8846/// removed in v5.0.
8847#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8848pub struct MarketDriftSchemaConfig {
8849    /// Enable market drift.
8850    #[serde(default)]
8851    pub enabled: bool,
8852
8853    /// Economic cycle configuration.
8854    #[serde(default)]
8855    pub economic_cycle: MarketEconomicCycleSchemaConfig,
8856
8857    /// Industry-specific cycles.
8858    #[serde(default)]
8859    pub industry_cycles: std::collections::HashMap<String, IndustryCycleSchemaConfig>,
8860
8861    /// Commodity drift configuration.
8862    #[serde(default)]
8863    pub commodities: CommoditiesSchemaConfig,
8864}
8865
8866/// Market economic cycle configuration.
8867#[derive(Debug, Clone, Serialize, Deserialize)]
8868pub struct MarketEconomicCycleSchemaConfig {
8869    /// Enable economic cycle.
8870    #[serde(default)]
8871    pub enabled: bool,
8872
8873    /// Cycle type.
8874    #[serde(default)]
8875    pub cycle_type: CycleTypeSchemaConfig,
8876
8877    /// Cycle period in months.
8878    #[serde(default = "default_market_cycle_period")]
8879    pub period_months: u32,
8880
8881    /// Amplitude.
8882    #[serde(default = "default_market_amplitude")]
8883    pub amplitude: f64,
8884
8885    /// Recession configuration.
8886    #[serde(default)]
8887    pub recession: RecessionSchemaConfig,
8888}
8889
8890fn default_market_cycle_period() -> u32 {
8891    48
8892}
8893
8894fn default_market_amplitude() -> f64 {
8895    0.15
8896}
8897
8898impl Default for MarketEconomicCycleSchemaConfig {
8899    fn default() -> Self {
8900        Self {
8901            enabled: false,
8902            cycle_type: CycleTypeSchemaConfig::Sinusoidal,
8903            period_months: 48,
8904            amplitude: 0.15,
8905            recession: RecessionSchemaConfig::default(),
8906        }
8907    }
8908}
8909
8910/// Cycle type configuration.
8911#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
8912#[serde(rename_all = "snake_case")]
8913pub enum CycleTypeSchemaConfig {
8914    /// Sinusoidal cycle.
8915    #[default]
8916    Sinusoidal,
8917    /// Asymmetric cycle.
8918    Asymmetric,
8919    /// Mean-reverting cycle.
8920    MeanReverting,
8921}
8922
8923/// Recession configuration.
8924#[derive(Debug, Clone, Serialize, Deserialize)]
8925pub struct RecessionSchemaConfig {
8926    /// Enable recession simulation.
8927    #[serde(default)]
8928    pub enabled: bool,
8929
8930    /// Probability per year.
8931    #[serde(default = "default_recession_prob")]
8932    pub probability_per_year: f64,
8933
8934    /// Severity.
8935    #[serde(default)]
8936    pub severity: RecessionSeveritySchemaConfig,
8937
8938    /// Specific recession periods.
8939    #[serde(default)]
8940    pub recession_periods: Vec<RecessionPeriodSchemaConfig>,
8941}
8942
8943fn default_recession_prob() -> f64 {
8944    0.10
8945}
8946
8947impl Default for RecessionSchemaConfig {
8948    fn default() -> Self {
8949        Self {
8950            enabled: false,
8951            probability_per_year: 0.10,
8952            severity: RecessionSeveritySchemaConfig::Moderate,
8953            recession_periods: Vec::new(),
8954        }
8955    }
8956}
8957
8958/// Recession severity configuration.
8959#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
8960#[serde(rename_all = "snake_case")]
8961pub enum RecessionSeveritySchemaConfig {
8962    /// Mild recession.
8963    Mild,
8964    /// Moderate recession.
8965    #[default]
8966    Moderate,
8967    /// Severe recession.
8968    Severe,
8969}
8970
8971/// Recession period configuration.
8972#[derive(Debug, Clone, Serialize, Deserialize)]
8973pub struct RecessionPeriodSchemaConfig {
8974    /// Start month.
8975    pub start_month: u32,
8976    /// Duration in months.
8977    pub duration_months: u32,
8978}
8979
8980/// Industry cycle configuration.
8981#[derive(Debug, Clone, Serialize, Deserialize)]
8982pub struct IndustryCycleSchemaConfig {
8983    /// Period in months.
8984    #[serde(default = "default_industry_period")]
8985    pub period_months: u32,
8986
8987    /// Amplitude.
8988    #[serde(default = "default_industry_amp")]
8989    pub amplitude: f64,
8990}
8991
8992fn default_industry_period() -> u32 {
8993    36
8994}
8995
8996fn default_industry_amp() -> f64 {
8997    0.20
8998}
8999
9000/// Commodities drift configuration.
9001#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9002pub struct CommoditiesSchemaConfig {
9003    /// Enable commodity drift.
9004    #[serde(default)]
9005    pub enabled: bool,
9006
9007    /// Commodity items.
9008    #[serde(default)]
9009    pub items: Vec<CommodityItemSchemaConfig>,
9010}
9011
9012/// Commodity item configuration.
9013#[derive(Debug, Clone, Serialize, Deserialize)]
9014pub struct CommodityItemSchemaConfig {
9015    /// Commodity name.
9016    pub name: String,
9017
9018    /// Volatility.
9019    #[serde(default = "default_volatility")]
9020    pub volatility: f64,
9021
9022    /// COGS pass-through.
9023    #[serde(default)]
9024    pub cogs_pass_through: f64,
9025
9026    /// Overhead pass-through.
9027    #[serde(default)]
9028    pub overhead_pass_through: f64,
9029}
9030
9031fn default_volatility() -> f64 {
9032    0.20
9033}
9034
9035// =============================================================================
9036// Drift Labeling Configuration
9037// =============================================================================
9038
9039/// Configuration for drift ground truth labeling.
9040///
9041/// **Deprecated (v4.1.2):** validated-but-inert. The v3.3.0
9042/// analytics-metadata phase (`DriftEventGenerator` +
9043/// `AnalyticsMetadataSnapshot.drift_events`) produces drift labels
9044/// at runtime — configure it via `analytics_metadata.drift_events`
9045/// instead. The schema type remains for backward-compatible YAML
9046/// loading; will be removed in v5.0.
9047#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9048pub struct DriftLabelingSchemaConfig {
9049    /// Enable drift labeling.
9050    #[serde(default)]
9051    pub enabled: bool,
9052
9053    /// Statistical drift labeling.
9054    #[serde(default)]
9055    pub statistical: StatisticalDriftLabelingSchemaConfig,
9056
9057    /// Categorical drift labeling.
9058    #[serde(default)]
9059    pub categorical: CategoricalDriftLabelingSchemaConfig,
9060
9061    /// Temporal drift labeling.
9062    #[serde(default)]
9063    pub temporal: TemporalDriftLabelingSchemaConfig,
9064
9065    /// Regulatory calendar preset.
9066    #[serde(default)]
9067    pub regulatory_calendar_preset: Option<String>,
9068}
9069
9070/// Statistical drift labeling configuration.
9071#[derive(Debug, Clone, Serialize, Deserialize)]
9072pub struct StatisticalDriftLabelingSchemaConfig {
9073    /// Enable statistical drift labeling.
9074    #[serde(default = "default_true_val")]
9075    pub enabled: bool,
9076
9077    /// Minimum magnitude threshold.
9078    #[serde(default = "default_min_magnitude")]
9079    pub min_magnitude_threshold: f64,
9080}
9081
9082fn default_min_magnitude() -> f64 {
9083    0.05
9084}
9085
9086impl Default for StatisticalDriftLabelingSchemaConfig {
9087    fn default() -> Self {
9088        Self {
9089            enabled: true,
9090            min_magnitude_threshold: 0.05,
9091        }
9092    }
9093}
9094
9095/// Categorical drift labeling configuration.
9096#[derive(Debug, Clone, Serialize, Deserialize)]
9097pub struct CategoricalDriftLabelingSchemaConfig {
9098    /// Enable categorical drift labeling.
9099    #[serde(default = "default_true_val")]
9100    pub enabled: bool,
9101}
9102
9103impl Default for CategoricalDriftLabelingSchemaConfig {
9104    fn default() -> Self {
9105        Self { enabled: true }
9106    }
9107}
9108
9109/// Temporal drift labeling configuration.
9110#[derive(Debug, Clone, Serialize, Deserialize)]
9111pub struct TemporalDriftLabelingSchemaConfig {
9112    /// Enable temporal drift labeling.
9113    #[serde(default = "default_true_val")]
9114    pub enabled: bool,
9115}
9116
9117impl Default for TemporalDriftLabelingSchemaConfig {
9118    fn default() -> Self {
9119        Self { enabled: true }
9120    }
9121}
9122
9123// =============================================================================
9124// Enhanced Anomaly Injection Configuration
9125// =============================================================================
9126
9127/// Enhanced anomaly injection configuration.
9128///
9129/// Provides comprehensive anomaly injection capabilities including:
9130/// - Multi-stage fraud schemes (embezzlement, revenue manipulation, kickbacks)
9131/// - Correlated anomaly injection (co-occurrence patterns, error cascades)
9132/// - Near-miss generation for false positive reduction
9133/// - Detection difficulty classification
9134/// - Context-aware injection based on entity behavior
9135#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9136pub struct EnhancedAnomalyConfig {
9137    /// Enable enhanced anomaly injection.
9138    #[serde(default)]
9139    pub enabled: bool,
9140
9141    /// Base anomaly rates.
9142    #[serde(default)]
9143    pub rates: AnomalyRateConfig,
9144
9145    /// Multi-stage fraud scheme configuration.
9146    #[serde(default)]
9147    pub multi_stage_schemes: MultiStageSchemeConfig,
9148
9149    /// Correlated anomaly injection configuration.
9150    #[serde(default)]
9151    pub correlated_injection: CorrelatedInjectionConfig,
9152
9153    /// Near-miss generation configuration.
9154    #[serde(default)]
9155    pub near_miss: NearMissConfig,
9156
9157    /// Detection difficulty classification configuration.
9158    #[serde(default)]
9159    pub difficulty_classification: DifficultyClassificationConfig,
9160
9161    /// Context-aware injection configuration.
9162    #[serde(default)]
9163    pub context_aware: ContextAwareConfig,
9164
9165    /// Enhanced labeling configuration.
9166    #[serde(default)]
9167    pub labeling: EnhancedLabelingConfig,
9168}
9169
9170/// Base anomaly rate configuration.
9171#[derive(Debug, Clone, Serialize, Deserialize)]
9172pub struct AnomalyRateConfig {
9173    /// Total anomaly rate (0.0 to 1.0).
9174    #[serde(default = "default_total_anomaly_rate")]
9175    pub total_rate: f64,
9176
9177    /// Fraud anomaly rate.
9178    #[serde(default = "default_fraud_anomaly_rate")]
9179    pub fraud_rate: f64,
9180
9181    /// Error anomaly rate.
9182    #[serde(default = "default_error_anomaly_rate")]
9183    pub error_rate: f64,
9184
9185    /// Process issue rate.
9186    #[serde(default = "default_process_anomaly_rate")]
9187    pub process_rate: f64,
9188}
9189
9190fn default_total_anomaly_rate() -> f64 {
9191    0.03
9192}
9193fn default_fraud_anomaly_rate() -> f64 {
9194    0.01
9195}
9196fn default_error_anomaly_rate() -> f64 {
9197    0.015
9198}
9199fn default_process_anomaly_rate() -> f64 {
9200    0.005
9201}
9202
9203impl Default for AnomalyRateConfig {
9204    fn default() -> Self {
9205        Self {
9206            total_rate: default_total_anomaly_rate(),
9207            fraud_rate: default_fraud_anomaly_rate(),
9208            error_rate: default_error_anomaly_rate(),
9209            process_rate: default_process_anomaly_rate(),
9210        }
9211    }
9212}
9213
9214/// Multi-stage fraud scheme configuration.
9215#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9216pub struct MultiStageSchemeConfig {
9217    /// Enable multi-stage fraud schemes.
9218    #[serde(default)]
9219    pub enabled: bool,
9220
9221    /// Embezzlement scheme configuration.
9222    #[serde(default)]
9223    pub embezzlement: EmbezzlementSchemeConfig,
9224
9225    /// Revenue manipulation scheme configuration.
9226    #[serde(default)]
9227    pub revenue_manipulation: RevenueManipulationSchemeConfig,
9228
9229    /// Vendor kickback scheme configuration.
9230    #[serde(default)]
9231    pub kickback: KickbackSchemeConfig,
9232}
9233
9234/// Embezzlement scheme configuration.
9235#[derive(Debug, Clone, Serialize, Deserialize)]
9236pub struct EmbezzlementSchemeConfig {
9237    /// Probability of starting an embezzlement scheme per perpetrator per year.
9238    #[serde(default = "default_embezzlement_probability")]
9239    pub probability: f64,
9240
9241    /// Testing stage configuration.
9242    #[serde(default)]
9243    pub testing_stage: SchemeStageConfig,
9244
9245    /// Escalation stage configuration.
9246    #[serde(default)]
9247    pub escalation_stage: SchemeStageConfig,
9248
9249    /// Acceleration stage configuration.
9250    #[serde(default)]
9251    pub acceleration_stage: SchemeStageConfig,
9252
9253    /// Desperation stage configuration.
9254    #[serde(default)]
9255    pub desperation_stage: SchemeStageConfig,
9256}
9257
9258fn default_embezzlement_probability() -> f64 {
9259    0.02
9260}
9261
9262impl Default for EmbezzlementSchemeConfig {
9263    fn default() -> Self {
9264        Self {
9265            probability: default_embezzlement_probability(),
9266            testing_stage: SchemeStageConfig {
9267                duration_months: 2,
9268                amount_min: 100.0,
9269                amount_max: 500.0,
9270                transaction_count_min: 2,
9271                transaction_count_max: 5,
9272                difficulty: "hard".to_string(),
9273            },
9274            escalation_stage: SchemeStageConfig {
9275                duration_months: 6,
9276                amount_min: 500.0,
9277                amount_max: 2000.0,
9278                transaction_count_min: 3,
9279                transaction_count_max: 8,
9280                difficulty: "moderate".to_string(),
9281            },
9282            acceleration_stage: SchemeStageConfig {
9283                duration_months: 3,
9284                amount_min: 2000.0,
9285                amount_max: 10000.0,
9286                transaction_count_min: 5,
9287                transaction_count_max: 12,
9288                difficulty: "easy".to_string(),
9289            },
9290            desperation_stage: SchemeStageConfig {
9291                duration_months: 1,
9292                amount_min: 10000.0,
9293                amount_max: 50000.0,
9294                transaction_count_min: 3,
9295                transaction_count_max: 6,
9296                difficulty: "trivial".to_string(),
9297            },
9298        }
9299    }
9300}
9301
9302/// Revenue manipulation scheme configuration.
9303#[derive(Debug, Clone, Serialize, Deserialize)]
9304pub struct RevenueManipulationSchemeConfig {
9305    /// Probability of starting a revenue manipulation scheme per period.
9306    #[serde(default = "default_revenue_manipulation_probability")]
9307    pub probability: f64,
9308
9309    /// Early revenue recognition inflation target (Q4).
9310    #[serde(default = "default_early_recognition_target")]
9311    pub early_recognition_target: f64,
9312
9313    /// Expense deferral inflation target (Q1).
9314    #[serde(default = "default_expense_deferral_target")]
9315    pub expense_deferral_target: f64,
9316
9317    /// Reserve release inflation target (Q2).
9318    #[serde(default = "default_reserve_release_target")]
9319    pub reserve_release_target: f64,
9320
9321    /// Channel stuffing inflation target (Q4).
9322    #[serde(default = "default_channel_stuffing_target")]
9323    pub channel_stuffing_target: f64,
9324}
9325
9326fn default_revenue_manipulation_probability() -> f64 {
9327    0.01
9328}
9329fn default_early_recognition_target() -> f64 {
9330    0.02
9331}
9332fn default_expense_deferral_target() -> f64 {
9333    0.03
9334}
9335fn default_reserve_release_target() -> f64 {
9336    0.02
9337}
9338fn default_channel_stuffing_target() -> f64 {
9339    0.05
9340}
9341
9342impl Default for RevenueManipulationSchemeConfig {
9343    fn default() -> Self {
9344        Self {
9345            probability: default_revenue_manipulation_probability(),
9346            early_recognition_target: default_early_recognition_target(),
9347            expense_deferral_target: default_expense_deferral_target(),
9348            reserve_release_target: default_reserve_release_target(),
9349            channel_stuffing_target: default_channel_stuffing_target(),
9350        }
9351    }
9352}
9353
9354/// Vendor kickback scheme configuration.
9355#[derive(Debug, Clone, Serialize, Deserialize)]
9356pub struct KickbackSchemeConfig {
9357    /// Probability of starting a kickback scheme.
9358    #[serde(default = "default_kickback_probability")]
9359    pub probability: f64,
9360
9361    /// Minimum price inflation percentage.
9362    #[serde(default = "default_kickback_inflation_min")]
9363    pub inflation_min: f64,
9364
9365    /// Maximum price inflation percentage.
9366    #[serde(default = "default_kickback_inflation_max")]
9367    pub inflation_max: f64,
9368
9369    /// Kickback percentage (of inflation).
9370    #[serde(default = "default_kickback_percent")]
9371    pub kickback_percent: f64,
9372
9373    /// Setup duration in months.
9374    #[serde(default = "default_kickback_setup_months")]
9375    pub setup_months: u32,
9376
9377    /// Main operation duration in months.
9378    #[serde(default = "default_kickback_operation_months")]
9379    pub operation_months: u32,
9380}
9381
9382fn default_kickback_probability() -> f64 {
9383    0.01
9384}
9385fn default_kickback_inflation_min() -> f64 {
9386    0.10
9387}
9388fn default_kickback_inflation_max() -> f64 {
9389    0.25
9390}
9391fn default_kickback_percent() -> f64 {
9392    0.50
9393}
9394fn default_kickback_setup_months() -> u32 {
9395    3
9396}
9397fn default_kickback_operation_months() -> u32 {
9398    12
9399}
9400
9401impl Default for KickbackSchemeConfig {
9402    fn default() -> Self {
9403        Self {
9404            probability: default_kickback_probability(),
9405            inflation_min: default_kickback_inflation_min(),
9406            inflation_max: default_kickback_inflation_max(),
9407            kickback_percent: default_kickback_percent(),
9408            setup_months: default_kickback_setup_months(),
9409            operation_months: default_kickback_operation_months(),
9410        }
9411    }
9412}
9413
9414/// Individual scheme stage configuration.
9415#[derive(Debug, Clone, Serialize, Deserialize)]
9416pub struct SchemeStageConfig {
9417    /// Duration in months.
9418    pub duration_months: u32,
9419
9420    /// Minimum transaction amount.
9421    pub amount_min: f64,
9422
9423    /// Maximum transaction amount.
9424    pub amount_max: f64,
9425
9426    /// Minimum number of transactions.
9427    pub transaction_count_min: u32,
9428
9429    /// Maximum number of transactions.
9430    pub transaction_count_max: u32,
9431
9432    /// Detection difficulty level (trivial, easy, moderate, hard, expert).
9433    pub difficulty: String,
9434}
9435
9436impl Default for SchemeStageConfig {
9437    fn default() -> Self {
9438        Self {
9439            duration_months: 3,
9440            amount_min: 100.0,
9441            amount_max: 1000.0,
9442            transaction_count_min: 2,
9443            transaction_count_max: 10,
9444            difficulty: "moderate".to_string(),
9445        }
9446    }
9447}
9448
9449/// Correlated anomaly injection configuration.
9450#[derive(Debug, Clone, Serialize, Deserialize)]
9451pub struct CorrelatedInjectionConfig {
9452    /// Enable correlated anomaly injection.
9453    #[serde(default)]
9454    pub enabled: bool,
9455
9456    /// Enable fraud concealment co-occurrence patterns.
9457    #[serde(default = "default_true_val")]
9458    pub fraud_concealment: bool,
9459
9460    /// Enable error cascade patterns.
9461    #[serde(default = "default_true_val")]
9462    pub error_cascade: bool,
9463
9464    /// Enable temporal clustering (period-end spikes).
9465    #[serde(default = "default_true_val")]
9466    pub temporal_clustering: bool,
9467
9468    /// Temporal clustering configuration.
9469    #[serde(default)]
9470    pub temporal_clustering_config: TemporalClusteringConfig,
9471
9472    /// Co-occurrence patterns.
9473    #[serde(default)]
9474    pub co_occurrence_patterns: Vec<CoOccurrencePatternConfig>,
9475}
9476
9477impl Default for CorrelatedInjectionConfig {
9478    fn default() -> Self {
9479        Self {
9480            enabled: false,
9481            fraud_concealment: true,
9482            error_cascade: true,
9483            temporal_clustering: true,
9484            temporal_clustering_config: TemporalClusteringConfig::default(),
9485            co_occurrence_patterns: Vec::new(),
9486        }
9487    }
9488}
9489
9490/// Temporal clustering configuration.
9491#[derive(Debug, Clone, Serialize, Deserialize)]
9492pub struct TemporalClusteringConfig {
9493    /// Period-end error multiplier.
9494    #[serde(default = "default_period_end_multiplier")]
9495    pub period_end_multiplier: f64,
9496
9497    /// Number of business days before period end to apply multiplier.
9498    #[serde(default = "default_period_end_days")]
9499    pub period_end_days: u32,
9500
9501    /// Quarter-end additional multiplier.
9502    #[serde(default = "default_quarter_end_multiplier")]
9503    pub quarter_end_multiplier: f64,
9504
9505    /// Year-end additional multiplier.
9506    #[serde(default = "default_year_end_multiplier")]
9507    pub year_end_multiplier: f64,
9508}
9509
9510fn default_period_end_multiplier() -> f64 {
9511    2.5
9512}
9513fn default_period_end_days() -> u32 {
9514    5
9515}
9516fn default_quarter_end_multiplier() -> f64 {
9517    1.5
9518}
9519fn default_year_end_multiplier() -> f64 {
9520    2.0
9521}
9522
9523impl Default for TemporalClusteringConfig {
9524    fn default() -> Self {
9525        Self {
9526            period_end_multiplier: default_period_end_multiplier(),
9527            period_end_days: default_period_end_days(),
9528            quarter_end_multiplier: default_quarter_end_multiplier(),
9529            year_end_multiplier: default_year_end_multiplier(),
9530        }
9531    }
9532}
9533
9534/// Co-occurrence pattern configuration.
9535#[derive(Debug, Clone, Serialize, Deserialize)]
9536pub struct CoOccurrencePatternConfig {
9537    /// Pattern name.
9538    pub name: String,
9539
9540    /// Primary anomaly type that triggers the pattern.
9541    pub primary_type: String,
9542
9543    /// Correlated anomalies.
9544    pub correlated: Vec<CorrelatedAnomalyConfig>,
9545}
9546
9547/// Correlated anomaly configuration.
9548#[derive(Debug, Clone, Serialize, Deserialize)]
9549pub struct CorrelatedAnomalyConfig {
9550    /// Anomaly type.
9551    pub anomaly_type: String,
9552
9553    /// Probability of occurrence (0.0 to 1.0).
9554    pub probability: f64,
9555
9556    /// Minimum lag in days.
9557    pub lag_days_min: i32,
9558
9559    /// Maximum lag in days.
9560    pub lag_days_max: i32,
9561}
9562
9563/// Near-miss generation configuration.
9564#[derive(Debug, Clone, Serialize, Deserialize)]
9565pub struct NearMissConfig {
9566    /// Enable near-miss generation.
9567    #[serde(default)]
9568    pub enabled: bool,
9569
9570    /// Proportion of "anomalies" that are actually near-misses (0.0 to 1.0).
9571    #[serde(default = "default_near_miss_proportion")]
9572    pub proportion: f64,
9573
9574    /// Enable near-duplicate pattern.
9575    #[serde(default = "default_true_val")]
9576    pub near_duplicate: bool,
9577
9578    /// Near-duplicate date difference range in days.
9579    #[serde(default)]
9580    pub near_duplicate_days: NearDuplicateDaysConfig,
9581
9582    /// Enable threshold proximity pattern.
9583    #[serde(default = "default_true_val")]
9584    pub threshold_proximity: bool,
9585
9586    /// Threshold proximity range (e.g., 0.90-0.99 of threshold).
9587    #[serde(default)]
9588    pub threshold_proximity_range: ThresholdProximityRangeConfig,
9589
9590    /// Enable unusual but legitimate patterns.
9591    #[serde(default = "default_true_val")]
9592    pub unusual_legitimate: bool,
9593
9594    /// Types of unusual legitimate patterns to generate.
9595    #[serde(default = "default_unusual_legitimate_types")]
9596    pub unusual_legitimate_types: Vec<String>,
9597
9598    /// Enable corrected error patterns.
9599    #[serde(default = "default_true_val")]
9600    pub corrected_errors: bool,
9601
9602    /// Corrected error correction lag range in days.
9603    #[serde(default)]
9604    pub corrected_error_lag: CorrectedErrorLagConfig,
9605}
9606
9607fn default_near_miss_proportion() -> f64 {
9608    0.30
9609}
9610
9611fn default_unusual_legitimate_types() -> Vec<String> {
9612    vec![
9613        "year_end_bonus".to_string(),
9614        "contract_prepayment".to_string(),
9615        "insurance_claim".to_string(),
9616        "settlement_payment".to_string(),
9617    ]
9618}
9619
9620impl Default for NearMissConfig {
9621    fn default() -> Self {
9622        Self {
9623            enabled: false,
9624            proportion: default_near_miss_proportion(),
9625            near_duplicate: true,
9626            near_duplicate_days: NearDuplicateDaysConfig::default(),
9627            threshold_proximity: true,
9628            threshold_proximity_range: ThresholdProximityRangeConfig::default(),
9629            unusual_legitimate: true,
9630            unusual_legitimate_types: default_unusual_legitimate_types(),
9631            corrected_errors: true,
9632            corrected_error_lag: CorrectedErrorLagConfig::default(),
9633        }
9634    }
9635}
9636
9637/// Near-duplicate days configuration.
9638#[derive(Debug, Clone, Serialize, Deserialize)]
9639pub struct NearDuplicateDaysConfig {
9640    /// Minimum days apart.
9641    #[serde(default = "default_near_duplicate_min")]
9642    pub min: u32,
9643
9644    /// Maximum days apart.
9645    #[serde(default = "default_near_duplicate_max")]
9646    pub max: u32,
9647}
9648
9649fn default_near_duplicate_min() -> u32 {
9650    1
9651}
9652fn default_near_duplicate_max() -> u32 {
9653    3
9654}
9655
9656impl Default for NearDuplicateDaysConfig {
9657    fn default() -> Self {
9658        Self {
9659            min: default_near_duplicate_min(),
9660            max: default_near_duplicate_max(),
9661        }
9662    }
9663}
9664
9665/// Threshold proximity range configuration.
9666#[derive(Debug, Clone, Serialize, Deserialize)]
9667pub struct ThresholdProximityRangeConfig {
9668    /// Minimum proximity (e.g., 0.90 = 90% of threshold).
9669    #[serde(default = "default_threshold_proximity_min")]
9670    pub min: f64,
9671
9672    /// Maximum proximity (e.g., 0.99 = 99% of threshold).
9673    #[serde(default = "default_threshold_proximity_max")]
9674    pub max: f64,
9675}
9676
9677fn default_threshold_proximity_min() -> f64 {
9678    0.90
9679}
9680fn default_threshold_proximity_max() -> f64 {
9681    0.99
9682}
9683
9684impl Default for ThresholdProximityRangeConfig {
9685    fn default() -> Self {
9686        Self {
9687            min: default_threshold_proximity_min(),
9688            max: default_threshold_proximity_max(),
9689        }
9690    }
9691}
9692
9693/// Corrected error lag configuration.
9694#[derive(Debug, Clone, Serialize, Deserialize)]
9695pub struct CorrectedErrorLagConfig {
9696    /// Minimum correction lag in days.
9697    #[serde(default = "default_corrected_error_lag_min")]
9698    pub min: u32,
9699
9700    /// Maximum correction lag in days.
9701    #[serde(default = "default_corrected_error_lag_max")]
9702    pub max: u32,
9703}
9704
9705fn default_corrected_error_lag_min() -> u32 {
9706    1
9707}
9708fn default_corrected_error_lag_max() -> u32 {
9709    5
9710}
9711
9712impl Default for CorrectedErrorLagConfig {
9713    fn default() -> Self {
9714        Self {
9715            min: default_corrected_error_lag_min(),
9716            max: default_corrected_error_lag_max(),
9717        }
9718    }
9719}
9720
9721/// Detection difficulty classification configuration.
9722#[derive(Debug, Clone, Serialize, Deserialize)]
9723pub struct DifficultyClassificationConfig {
9724    /// Enable detection difficulty classification.
9725    #[serde(default)]
9726    pub enabled: bool,
9727
9728    /// Target distribution of difficulty levels.
9729    #[serde(default)]
9730    pub target_distribution: DifficultyDistributionConfig,
9731}
9732
9733impl Default for DifficultyClassificationConfig {
9734    fn default() -> Self {
9735        Self {
9736            enabled: true,
9737            target_distribution: DifficultyDistributionConfig::default(),
9738        }
9739    }
9740}
9741
9742/// Target distribution of detection difficulty levels.
9743#[derive(Debug, Clone, Serialize, Deserialize)]
9744pub struct DifficultyDistributionConfig {
9745    /// Proportion of trivial anomalies (expected 99% detection).
9746    #[serde(default = "default_difficulty_trivial")]
9747    pub trivial: f64,
9748
9749    /// Proportion of easy anomalies (expected 90% detection).
9750    #[serde(default = "default_difficulty_easy")]
9751    pub easy: f64,
9752
9753    /// Proportion of moderate anomalies (expected 70% detection).
9754    #[serde(default = "default_difficulty_moderate")]
9755    pub moderate: f64,
9756
9757    /// Proportion of hard anomalies (expected 40% detection).
9758    #[serde(default = "default_difficulty_hard")]
9759    pub hard: f64,
9760
9761    /// Proportion of expert anomalies (expected 15% detection).
9762    #[serde(default = "default_difficulty_expert")]
9763    pub expert: f64,
9764}
9765
9766fn default_difficulty_trivial() -> f64 {
9767    0.15
9768}
9769fn default_difficulty_easy() -> f64 {
9770    0.25
9771}
9772fn default_difficulty_moderate() -> f64 {
9773    0.30
9774}
9775fn default_difficulty_hard() -> f64 {
9776    0.20
9777}
9778fn default_difficulty_expert() -> f64 {
9779    0.10
9780}
9781
9782impl Default for DifficultyDistributionConfig {
9783    fn default() -> Self {
9784        Self {
9785            trivial: default_difficulty_trivial(),
9786            easy: default_difficulty_easy(),
9787            moderate: default_difficulty_moderate(),
9788            hard: default_difficulty_hard(),
9789            expert: default_difficulty_expert(),
9790        }
9791    }
9792}
9793
9794/// Context-aware injection configuration.
9795#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9796pub struct ContextAwareConfig {
9797    /// Enable context-aware injection.
9798    #[serde(default)]
9799    pub enabled: bool,
9800
9801    /// Vendor-specific anomaly rules.
9802    #[serde(default)]
9803    pub vendor_rules: VendorAnomalyRulesConfig,
9804
9805    /// Employee-specific anomaly rules.
9806    #[serde(default)]
9807    pub employee_rules: EmployeeAnomalyRulesConfig,
9808
9809    /// Account-specific anomaly rules.
9810    #[serde(default)]
9811    pub account_rules: AccountAnomalyRulesConfig,
9812
9813    /// Behavioral baseline configuration.
9814    #[serde(default)]
9815    pub behavioral_baseline: BehavioralBaselineConfig,
9816}
9817
9818/// Vendor-specific anomaly rules configuration.
9819#[derive(Debug, Clone, Serialize, Deserialize)]
9820pub struct VendorAnomalyRulesConfig {
9821    /// Error rate multiplier for new vendors (< threshold days).
9822    #[serde(default = "default_new_vendor_multiplier")]
9823    pub new_vendor_error_multiplier: f64,
9824
9825    /// Days threshold for "new" vendor classification.
9826    #[serde(default = "default_new_vendor_threshold")]
9827    pub new_vendor_threshold_days: u32,
9828
9829    /// Error rate multiplier for international vendors.
9830    #[serde(default = "default_international_multiplier")]
9831    pub international_error_multiplier: f64,
9832
9833    /// Strategic vendor anomaly types (may differ from general vendors).
9834    #[serde(default = "default_strategic_vendor_types")]
9835    pub strategic_vendor_anomaly_types: Vec<String>,
9836}
9837
9838fn default_new_vendor_multiplier() -> f64 {
9839    2.5
9840}
9841fn default_new_vendor_threshold() -> u32 {
9842    90
9843}
9844fn default_international_multiplier() -> f64 {
9845    1.5
9846}
9847fn default_strategic_vendor_types() -> Vec<String> {
9848    vec![
9849        "pricing_dispute".to_string(),
9850        "contract_violation".to_string(),
9851    ]
9852}
9853
9854impl Default for VendorAnomalyRulesConfig {
9855    fn default() -> Self {
9856        Self {
9857            new_vendor_error_multiplier: default_new_vendor_multiplier(),
9858            new_vendor_threshold_days: default_new_vendor_threshold(),
9859            international_error_multiplier: default_international_multiplier(),
9860            strategic_vendor_anomaly_types: default_strategic_vendor_types(),
9861        }
9862    }
9863}
9864
9865/// Employee-specific anomaly rules configuration.
9866#[derive(Debug, Clone, Serialize, Deserialize)]
9867pub struct EmployeeAnomalyRulesConfig {
9868    /// Error rate for new employees (< threshold days).
9869    #[serde(default = "default_new_employee_rate")]
9870    pub new_employee_error_rate: f64,
9871
9872    /// Days threshold for "new" employee classification.
9873    #[serde(default = "default_new_employee_threshold")]
9874    pub new_employee_threshold_days: u32,
9875
9876    /// Transaction volume threshold for fatigue errors.
9877    #[serde(default = "default_volume_fatigue_threshold")]
9878    pub volume_fatigue_threshold: u32,
9879
9880    /// Error rate multiplier when primary approver is absent.
9881    #[serde(default = "default_coverage_multiplier")]
9882    pub coverage_error_multiplier: f64,
9883}
9884
9885fn default_new_employee_rate() -> f64 {
9886    0.05
9887}
9888fn default_new_employee_threshold() -> u32 {
9889    180
9890}
9891fn default_volume_fatigue_threshold() -> u32 {
9892    50
9893}
9894fn default_coverage_multiplier() -> f64 {
9895    1.8
9896}
9897
9898impl Default for EmployeeAnomalyRulesConfig {
9899    fn default() -> Self {
9900        Self {
9901            new_employee_error_rate: default_new_employee_rate(),
9902            new_employee_threshold_days: default_new_employee_threshold(),
9903            volume_fatigue_threshold: default_volume_fatigue_threshold(),
9904            coverage_error_multiplier: default_coverage_multiplier(),
9905        }
9906    }
9907}
9908
9909/// Account-specific anomaly rules configuration.
9910#[derive(Debug, Clone, Serialize, Deserialize)]
9911pub struct AccountAnomalyRulesConfig {
9912    /// Error rate multiplier for high-risk accounts.
9913    #[serde(default = "default_high_risk_multiplier")]
9914    pub high_risk_account_multiplier: f64,
9915
9916    /// Account codes considered high-risk.
9917    #[serde(default = "default_high_risk_accounts")]
9918    pub high_risk_accounts: Vec<String>,
9919
9920    /// Error rate multiplier for suspense accounts.
9921    #[serde(default = "default_suspense_multiplier")]
9922    pub suspense_account_multiplier: f64,
9923
9924    /// Account codes considered suspense accounts.
9925    #[serde(default = "default_suspense_accounts")]
9926    pub suspense_accounts: Vec<String>,
9927
9928    /// Error rate multiplier for intercompany accounts.
9929    #[serde(default = "default_intercompany_multiplier")]
9930    pub intercompany_account_multiplier: f64,
9931}
9932
9933fn default_high_risk_multiplier() -> f64 {
9934    2.0
9935}
9936fn default_high_risk_accounts() -> Vec<String> {
9937    vec![
9938        "1100".to_string(), // AR Control
9939        "2000".to_string(), // AP Control
9940        "3000".to_string(), // Cash
9941    ]
9942}
9943fn default_suspense_multiplier() -> f64 {
9944    3.0
9945}
9946fn default_suspense_accounts() -> Vec<String> {
9947    vec!["9999".to_string(), "9998".to_string()]
9948}
9949fn default_intercompany_multiplier() -> f64 {
9950    1.5
9951}
9952
9953impl Default for AccountAnomalyRulesConfig {
9954    fn default() -> Self {
9955        Self {
9956            high_risk_account_multiplier: default_high_risk_multiplier(),
9957            high_risk_accounts: default_high_risk_accounts(),
9958            suspense_account_multiplier: default_suspense_multiplier(),
9959            suspense_accounts: default_suspense_accounts(),
9960            intercompany_account_multiplier: default_intercompany_multiplier(),
9961        }
9962    }
9963}
9964
9965/// Behavioral baseline configuration.
9966#[derive(Debug, Clone, Serialize, Deserialize)]
9967pub struct BehavioralBaselineConfig {
9968    /// Enable behavioral baseline tracking.
9969    #[serde(default)]
9970    pub enabled: bool,
9971
9972    /// Number of days to build baseline from.
9973    #[serde(default = "default_baseline_period")]
9974    pub baseline_period_days: u32,
9975
9976    /// Standard deviation threshold for amount anomalies.
9977    #[serde(default = "default_deviation_threshold")]
9978    pub deviation_threshold_std: f64,
9979
9980    /// Standard deviation threshold for frequency anomalies.
9981    #[serde(default = "default_frequency_deviation")]
9982    pub frequency_deviation_threshold: f64,
9983}
9984
9985fn default_baseline_period() -> u32 {
9986    90
9987}
9988fn default_deviation_threshold() -> f64 {
9989    3.0
9990}
9991fn default_frequency_deviation() -> f64 {
9992    2.0
9993}
9994
9995impl Default for BehavioralBaselineConfig {
9996    fn default() -> Self {
9997        Self {
9998            enabled: false,
9999            baseline_period_days: default_baseline_period(),
10000            deviation_threshold_std: default_deviation_threshold(),
10001            frequency_deviation_threshold: default_frequency_deviation(),
10002        }
10003    }
10004}
10005
10006/// Enhanced labeling configuration.
10007#[derive(Debug, Clone, Serialize, Deserialize)]
10008pub struct EnhancedLabelingConfig {
10009    /// Enable severity scoring.
10010    #[serde(default = "default_true_val")]
10011    pub severity_scoring: bool,
10012
10013    /// Enable difficulty classification.
10014    #[serde(default = "default_true_val")]
10015    pub difficulty_classification: bool,
10016
10017    /// Materiality thresholds for severity classification.
10018    #[serde(default)]
10019    pub materiality_thresholds: MaterialityThresholdsConfig,
10020}
10021
10022impl Default for EnhancedLabelingConfig {
10023    fn default() -> Self {
10024        Self {
10025            severity_scoring: true,
10026            difficulty_classification: true,
10027            materiality_thresholds: MaterialityThresholdsConfig::default(),
10028        }
10029    }
10030}
10031
10032/// Materiality thresholds configuration.
10033#[derive(Debug, Clone, Serialize, Deserialize)]
10034pub struct MaterialityThresholdsConfig {
10035    /// Threshold for trivial impact (as percentage of total).
10036    #[serde(default = "default_materiality_trivial")]
10037    pub trivial: f64,
10038
10039    /// Threshold for immaterial impact.
10040    #[serde(default = "default_materiality_immaterial")]
10041    pub immaterial: f64,
10042
10043    /// Threshold for material impact.
10044    #[serde(default = "default_materiality_material")]
10045    pub material: f64,
10046
10047    /// Threshold for highly material impact.
10048    #[serde(default = "default_materiality_highly_material")]
10049    pub highly_material: f64,
10050}
10051
10052fn default_materiality_trivial() -> f64 {
10053    0.001
10054}
10055fn default_materiality_immaterial() -> f64 {
10056    0.01
10057}
10058fn default_materiality_material() -> f64 {
10059    0.05
10060}
10061fn default_materiality_highly_material() -> f64 {
10062    0.10
10063}
10064
10065impl Default for MaterialityThresholdsConfig {
10066    fn default() -> Self {
10067        Self {
10068            trivial: default_materiality_trivial(),
10069            immaterial: default_materiality_immaterial(),
10070            material: default_materiality_material(),
10071            highly_material: default_materiality_highly_material(),
10072        }
10073    }
10074}
10075
10076// =============================================================================
10077// Industry-Specific Configuration
10078// =============================================================================
10079
10080/// Industry-specific transaction and anomaly generation configuration.
10081///
10082/// This configuration enables generation of industry-authentic:
10083/// - Transaction types with appropriate terminology
10084/// - Master data (BOM, routings, clinical codes, etc.)
10085/// - Industry-specific anomaly patterns
10086/// - Regulatory framework compliance
10087#[derive(Debug, Clone, Serialize, Deserialize, Default)]
10088pub struct IndustrySpecificConfig {
10089    /// Enable industry-specific generation.
10090    #[serde(default)]
10091    pub enabled: bool,
10092
10093    /// Manufacturing industry settings.
10094    #[serde(default)]
10095    pub manufacturing: ManufacturingConfig,
10096
10097    /// Retail industry settings.
10098    #[serde(default)]
10099    pub retail: RetailConfig,
10100
10101    /// Healthcare industry settings.
10102    #[serde(default)]
10103    pub healthcare: HealthcareConfig,
10104
10105    /// Technology industry settings.
10106    #[serde(default)]
10107    pub technology: TechnologyConfig,
10108
10109    /// Financial services industry settings.
10110    #[serde(default)]
10111    pub financial_services: FinancialServicesConfig,
10112
10113    /// Professional services industry settings.
10114    #[serde(default)]
10115    pub professional_services: ProfessionalServicesConfig,
10116}
10117
10118/// Manufacturing industry configuration.
10119#[derive(Debug, Clone, Serialize, Deserialize)]
10120pub struct ManufacturingConfig {
10121    /// Enable manufacturing-specific generation.
10122    #[serde(default)]
10123    pub enabled: bool,
10124
10125    /// Bill of Materials depth (typical: 3-7).
10126    #[serde(default = "default_bom_depth")]
10127    pub bom_depth: u32,
10128
10129    /// Whether to use just-in-time inventory.
10130    #[serde(default)]
10131    pub just_in_time: bool,
10132
10133    /// Production order types to generate.
10134    #[serde(default = "default_production_order_types")]
10135    pub production_order_types: Vec<String>,
10136
10137    /// Quality framework (ISO_9001, Six_Sigma, etc.).
10138    #[serde(default)]
10139    pub quality_framework: Option<String>,
10140
10141    /// Number of supplier tiers to model (1-3).
10142    #[serde(default = "default_supplier_tiers")]
10143    pub supplier_tiers: u32,
10144
10145    /// Standard cost update frequency.
10146    #[serde(default = "default_cost_frequency")]
10147    pub standard_cost_frequency: String,
10148
10149    /// Target yield rate (0.95-0.99 typical).
10150    #[serde(default = "default_yield_rate")]
10151    pub target_yield_rate: f64,
10152
10153    /// Scrap percentage threshold for alerts.
10154    #[serde(default = "default_scrap_threshold")]
10155    pub scrap_alert_threshold: f64,
10156
10157    /// Manufacturing anomaly injection rates.
10158    #[serde(default)]
10159    pub anomaly_rates: ManufacturingAnomalyRates,
10160
10161    /// Cost accounting configuration (WIP → FG → COGS pipeline).
10162    #[serde(default)]
10163    pub cost_accounting: ManufacturingCostAccountingConfig,
10164}
10165
10166/// Configuration for manufacturing cost accounting JE generation.
10167#[derive(Debug, Clone, Serialize, Deserialize)]
10168pub struct ManufacturingCostAccountingConfig {
10169    /// Enable multi-stage cost flow (WIP → FG → COGS) instead of flat JEs.
10170    #[serde(default = "default_true")]
10171    pub enabled: bool,
10172
10173    /// Generate standard cost variance JEs.
10174    #[serde(default = "default_true")]
10175    pub variance_accounts_enabled: bool,
10176
10177    /// Generate warranty provisions from quality inspection failures.
10178    #[serde(default = "default_true")]
10179    pub warranty_provisions_enabled: bool,
10180
10181    /// Minimum defect rate (0.0-1.0) to trigger warranty provision generation.
10182    #[serde(default = "default_warranty_defect_threshold")]
10183    pub warranty_defect_threshold: f64,
10184}
10185
10186fn default_warranty_defect_threshold() -> f64 {
10187    0.01
10188}
10189
10190impl Default for ManufacturingCostAccountingConfig {
10191    fn default() -> Self {
10192        Self {
10193            enabled: true,
10194            variance_accounts_enabled: true,
10195            warranty_provisions_enabled: true,
10196            warranty_defect_threshold: 0.01,
10197        }
10198    }
10199}
10200
10201fn default_bom_depth() -> u32 {
10202    4
10203}
10204
10205fn default_production_order_types() -> Vec<String> {
10206    vec![
10207        "standard".to_string(),
10208        "rework".to_string(),
10209        "prototype".to_string(),
10210    ]
10211}
10212
10213fn default_supplier_tiers() -> u32 {
10214    2
10215}
10216
10217fn default_cost_frequency() -> String {
10218    "quarterly".to_string()
10219}
10220
10221fn default_yield_rate() -> f64 {
10222    0.97
10223}
10224
10225fn default_scrap_threshold() -> f64 {
10226    0.03
10227}
10228
10229impl Default for ManufacturingConfig {
10230    fn default() -> Self {
10231        Self {
10232            enabled: false,
10233            bom_depth: default_bom_depth(),
10234            just_in_time: false,
10235            production_order_types: default_production_order_types(),
10236            quality_framework: Some("ISO_9001".to_string()),
10237            supplier_tiers: default_supplier_tiers(),
10238            standard_cost_frequency: default_cost_frequency(),
10239            target_yield_rate: default_yield_rate(),
10240            scrap_alert_threshold: default_scrap_threshold(),
10241            anomaly_rates: ManufacturingAnomalyRates::default(),
10242            cost_accounting: ManufacturingCostAccountingConfig::default(),
10243        }
10244    }
10245}
10246
10247/// Manufacturing anomaly injection rates.
10248#[derive(Debug, Clone, Serialize, Deserialize)]
10249pub struct ManufacturingAnomalyRates {
10250    /// Yield manipulation rate.
10251    #[serde(default = "default_mfg_yield_rate")]
10252    pub yield_manipulation: f64,
10253
10254    /// Labor misallocation rate.
10255    #[serde(default = "default_mfg_labor_rate")]
10256    pub labor_misallocation: f64,
10257
10258    /// Phantom production rate.
10259    #[serde(default = "default_mfg_phantom_rate")]
10260    pub phantom_production: f64,
10261
10262    /// Standard cost manipulation rate.
10263    #[serde(default = "default_mfg_cost_rate")]
10264    pub standard_cost_manipulation: f64,
10265
10266    /// Inventory fraud rate.
10267    #[serde(default = "default_mfg_inventory_rate")]
10268    pub inventory_fraud: f64,
10269}
10270
10271fn default_mfg_yield_rate() -> f64 {
10272    0.015
10273}
10274
10275fn default_mfg_labor_rate() -> f64 {
10276    0.02
10277}
10278
10279fn default_mfg_phantom_rate() -> f64 {
10280    0.005
10281}
10282
10283fn default_mfg_cost_rate() -> f64 {
10284    0.01
10285}
10286
10287fn default_mfg_inventory_rate() -> f64 {
10288    0.008
10289}
10290
10291impl Default for ManufacturingAnomalyRates {
10292    fn default() -> Self {
10293        Self {
10294            yield_manipulation: default_mfg_yield_rate(),
10295            labor_misallocation: default_mfg_labor_rate(),
10296            phantom_production: default_mfg_phantom_rate(),
10297            standard_cost_manipulation: default_mfg_cost_rate(),
10298            inventory_fraud: default_mfg_inventory_rate(),
10299        }
10300    }
10301}
10302
10303/// Retail industry configuration.
10304#[derive(Debug, Clone, Serialize, Deserialize)]
10305pub struct RetailConfig {
10306    /// Enable retail-specific generation.
10307    #[serde(default)]
10308    pub enabled: bool,
10309
10310    /// Store type distribution.
10311    #[serde(default)]
10312    pub store_types: RetailStoreTypeConfig,
10313
10314    /// Average daily transactions per store.
10315    #[serde(default = "default_retail_daily_txns")]
10316    pub avg_daily_transactions: u32,
10317
10318    /// Enable loss prevention tracking.
10319    #[serde(default = "default_true")]
10320    pub loss_prevention: bool,
10321
10322    /// Shrinkage rate (0.01-0.03 typical).
10323    #[serde(default = "default_shrinkage_rate")]
10324    pub shrinkage_rate: f64,
10325
10326    /// Retail anomaly injection rates.
10327    #[serde(default)]
10328    pub anomaly_rates: RetailAnomalyRates,
10329}
10330
10331fn default_retail_daily_txns() -> u32 {
10332    500
10333}
10334
10335fn default_shrinkage_rate() -> f64 {
10336    0.015
10337}
10338
10339impl Default for RetailConfig {
10340    fn default() -> Self {
10341        Self {
10342            enabled: false,
10343            store_types: RetailStoreTypeConfig::default(),
10344            avg_daily_transactions: default_retail_daily_txns(),
10345            loss_prevention: true,
10346            shrinkage_rate: default_shrinkage_rate(),
10347            anomaly_rates: RetailAnomalyRates::default(),
10348        }
10349    }
10350}
10351
10352/// Retail store type distribution.
10353#[derive(Debug, Clone, Serialize, Deserialize)]
10354pub struct RetailStoreTypeConfig {
10355    /// Percentage of flagship stores.
10356    #[serde(default = "default_flagship_pct")]
10357    pub flagship: f64,
10358
10359    /// Percentage of regional stores.
10360    #[serde(default = "default_regional_pct")]
10361    pub regional: f64,
10362
10363    /// Percentage of outlet stores.
10364    #[serde(default = "default_outlet_pct")]
10365    pub outlet: f64,
10366
10367    /// Percentage of e-commerce.
10368    #[serde(default = "default_ecommerce_pct")]
10369    pub ecommerce: f64,
10370}
10371
10372fn default_flagship_pct() -> f64 {
10373    0.10
10374}
10375
10376fn default_regional_pct() -> f64 {
10377    0.50
10378}
10379
10380fn default_outlet_pct() -> f64 {
10381    0.25
10382}
10383
10384fn default_ecommerce_pct() -> f64 {
10385    0.15
10386}
10387
10388impl Default for RetailStoreTypeConfig {
10389    fn default() -> Self {
10390        Self {
10391            flagship: default_flagship_pct(),
10392            regional: default_regional_pct(),
10393            outlet: default_outlet_pct(),
10394            ecommerce: default_ecommerce_pct(),
10395        }
10396    }
10397}
10398
10399/// Retail anomaly injection rates.
10400#[derive(Debug, Clone, Serialize, Deserialize)]
10401pub struct RetailAnomalyRates {
10402    /// Sweethearting rate.
10403    #[serde(default = "default_sweethearting_rate")]
10404    pub sweethearting: f64,
10405
10406    /// Skimming rate.
10407    #[serde(default = "default_skimming_rate")]
10408    pub skimming: f64,
10409
10410    /// Refund fraud rate.
10411    #[serde(default = "default_refund_fraud_rate")]
10412    pub refund_fraud: f64,
10413
10414    /// Void abuse rate.
10415    #[serde(default = "default_void_abuse_rate")]
10416    pub void_abuse: f64,
10417
10418    /// Gift card fraud rate.
10419    #[serde(default = "default_gift_card_rate")]
10420    pub gift_card_fraud: f64,
10421
10422    /// Vendor kickback rate.
10423    #[serde(default = "default_retail_kickback_rate")]
10424    pub vendor_kickback: f64,
10425}
10426
10427fn default_sweethearting_rate() -> f64 {
10428    0.02
10429}
10430
10431fn default_skimming_rate() -> f64 {
10432    0.005
10433}
10434
10435fn default_refund_fraud_rate() -> f64 {
10436    0.015
10437}
10438
10439fn default_void_abuse_rate() -> f64 {
10440    0.01
10441}
10442
10443fn default_gift_card_rate() -> f64 {
10444    0.008
10445}
10446
10447fn default_retail_kickback_rate() -> f64 {
10448    0.003
10449}
10450
10451impl Default for RetailAnomalyRates {
10452    fn default() -> Self {
10453        Self {
10454            sweethearting: default_sweethearting_rate(),
10455            skimming: default_skimming_rate(),
10456            refund_fraud: default_refund_fraud_rate(),
10457            void_abuse: default_void_abuse_rate(),
10458            gift_card_fraud: default_gift_card_rate(),
10459            vendor_kickback: default_retail_kickback_rate(),
10460        }
10461    }
10462}
10463
10464/// Healthcare industry configuration.
10465#[derive(Debug, Clone, Serialize, Deserialize)]
10466pub struct HealthcareConfig {
10467    /// Enable healthcare-specific generation.
10468    #[serde(default)]
10469    pub enabled: bool,
10470
10471    /// Healthcare facility type.
10472    #[serde(default = "default_facility_type")]
10473    pub facility_type: String,
10474
10475    /// Payer mix distribution.
10476    #[serde(default)]
10477    pub payer_mix: HealthcarePayerMix,
10478
10479    /// Coding systems enabled.
10480    #[serde(default)]
10481    pub coding_systems: HealthcareCodingSystems,
10482
10483    /// Healthcare compliance settings.
10484    #[serde(default)]
10485    pub compliance: HealthcareComplianceConfig,
10486
10487    /// Average daily encounters.
10488    #[serde(default = "default_daily_encounters")]
10489    pub avg_daily_encounters: u32,
10490
10491    /// Average charges per encounter.
10492    #[serde(default = "default_charges_per_encounter")]
10493    pub avg_charges_per_encounter: u32,
10494
10495    /// Denial rate (0.0-1.0).
10496    #[serde(default = "default_hc_denial_rate")]
10497    pub denial_rate: f64,
10498
10499    /// Bad debt rate (0.0-1.0).
10500    #[serde(default = "default_hc_bad_debt_rate")]
10501    pub bad_debt_rate: f64,
10502
10503    /// Charity care rate (0.0-1.0).
10504    #[serde(default = "default_hc_charity_care_rate")]
10505    pub charity_care_rate: f64,
10506
10507    /// Healthcare anomaly injection rates.
10508    #[serde(default)]
10509    pub anomaly_rates: HealthcareAnomalyRates,
10510}
10511
10512fn default_facility_type() -> String {
10513    "hospital".to_string()
10514}
10515
10516fn default_daily_encounters() -> u32 {
10517    150
10518}
10519
10520fn default_charges_per_encounter() -> u32 {
10521    8
10522}
10523
10524fn default_hc_denial_rate() -> f64 {
10525    0.05
10526}
10527
10528fn default_hc_bad_debt_rate() -> f64 {
10529    0.03
10530}
10531
10532fn default_hc_charity_care_rate() -> f64 {
10533    0.02
10534}
10535
10536impl Default for HealthcareConfig {
10537    fn default() -> Self {
10538        Self {
10539            enabled: false,
10540            facility_type: default_facility_type(),
10541            payer_mix: HealthcarePayerMix::default(),
10542            coding_systems: HealthcareCodingSystems::default(),
10543            compliance: HealthcareComplianceConfig::default(),
10544            avg_daily_encounters: default_daily_encounters(),
10545            avg_charges_per_encounter: default_charges_per_encounter(),
10546            denial_rate: default_hc_denial_rate(),
10547            bad_debt_rate: default_hc_bad_debt_rate(),
10548            charity_care_rate: default_hc_charity_care_rate(),
10549            anomaly_rates: HealthcareAnomalyRates::default(),
10550        }
10551    }
10552}
10553
10554/// Healthcare payer mix distribution.
10555#[derive(Debug, Clone, Serialize, Deserialize)]
10556pub struct HealthcarePayerMix {
10557    /// Medicare percentage.
10558    #[serde(default = "default_medicare_pct")]
10559    pub medicare: f64,
10560
10561    /// Medicaid percentage.
10562    #[serde(default = "default_medicaid_pct")]
10563    pub medicaid: f64,
10564
10565    /// Commercial insurance percentage.
10566    #[serde(default = "default_commercial_pct")]
10567    pub commercial: f64,
10568
10569    /// Self-pay percentage.
10570    #[serde(default = "default_self_pay_pct")]
10571    pub self_pay: f64,
10572}
10573
10574fn default_medicare_pct() -> f64 {
10575    0.40
10576}
10577
10578fn default_medicaid_pct() -> f64 {
10579    0.20
10580}
10581
10582fn default_commercial_pct() -> f64 {
10583    0.30
10584}
10585
10586fn default_self_pay_pct() -> f64 {
10587    0.10
10588}
10589
10590impl Default for HealthcarePayerMix {
10591    fn default() -> Self {
10592        Self {
10593            medicare: default_medicare_pct(),
10594            medicaid: default_medicaid_pct(),
10595            commercial: default_commercial_pct(),
10596            self_pay: default_self_pay_pct(),
10597        }
10598    }
10599}
10600
10601/// Healthcare coding systems configuration.
10602#[derive(Debug, Clone, Serialize, Deserialize)]
10603pub struct HealthcareCodingSystems {
10604    /// Enable ICD-10 diagnosis coding.
10605    #[serde(default = "default_true")]
10606    pub icd10: bool,
10607
10608    /// Enable CPT procedure coding.
10609    #[serde(default = "default_true")]
10610    pub cpt: bool,
10611
10612    /// Enable DRG grouping.
10613    #[serde(default = "default_true")]
10614    pub drg: bool,
10615
10616    /// Enable HCPCS Level II coding.
10617    #[serde(default = "default_true")]
10618    pub hcpcs: bool,
10619
10620    /// Enable revenue codes.
10621    #[serde(default = "default_true")]
10622    pub revenue_codes: bool,
10623}
10624
10625impl Default for HealthcareCodingSystems {
10626    fn default() -> Self {
10627        Self {
10628            icd10: true,
10629            cpt: true,
10630            drg: true,
10631            hcpcs: true,
10632            revenue_codes: true,
10633        }
10634    }
10635}
10636
10637/// Healthcare compliance configuration.
10638#[derive(Debug, Clone, Serialize, Deserialize)]
10639pub struct HealthcareComplianceConfig {
10640    /// Enable HIPAA compliance.
10641    #[serde(default = "default_true")]
10642    pub hipaa: bool,
10643
10644    /// Enable Stark Law compliance.
10645    #[serde(default = "default_true")]
10646    pub stark_law: bool,
10647
10648    /// Enable Anti-Kickback Statute compliance.
10649    #[serde(default = "default_true")]
10650    pub anti_kickback: bool,
10651
10652    /// Enable False Claims Act compliance.
10653    #[serde(default = "default_true")]
10654    pub false_claims_act: bool,
10655
10656    /// Enable EMTALA compliance (for hospitals).
10657    #[serde(default = "default_true")]
10658    pub emtala: bool,
10659}
10660
10661impl Default for HealthcareComplianceConfig {
10662    fn default() -> Self {
10663        Self {
10664            hipaa: true,
10665            stark_law: true,
10666            anti_kickback: true,
10667            false_claims_act: true,
10668            emtala: true,
10669        }
10670    }
10671}
10672
10673/// Healthcare anomaly injection rates.
10674#[derive(Debug, Clone, Serialize, Deserialize)]
10675pub struct HealthcareAnomalyRates {
10676    /// Upcoding rate.
10677    #[serde(default = "default_upcoding_rate")]
10678    pub upcoding: f64,
10679
10680    /// Unbundling rate.
10681    #[serde(default = "default_unbundling_rate")]
10682    pub unbundling: f64,
10683
10684    /// Phantom billing rate.
10685    #[serde(default = "default_phantom_billing_rate")]
10686    pub phantom_billing: f64,
10687
10688    /// Kickback rate.
10689    #[serde(default = "default_healthcare_kickback_rate")]
10690    pub kickbacks: f64,
10691
10692    /// Duplicate billing rate.
10693    #[serde(default = "default_duplicate_billing_rate")]
10694    pub duplicate_billing: f64,
10695
10696    /// Medical necessity abuse rate.
10697    #[serde(default = "default_med_necessity_rate")]
10698    pub medical_necessity_abuse: f64,
10699}
10700
10701fn default_upcoding_rate() -> f64 {
10702    0.02
10703}
10704
10705fn default_unbundling_rate() -> f64 {
10706    0.015
10707}
10708
10709fn default_phantom_billing_rate() -> f64 {
10710    0.005
10711}
10712
10713fn default_healthcare_kickback_rate() -> f64 {
10714    0.003
10715}
10716
10717fn default_duplicate_billing_rate() -> f64 {
10718    0.008
10719}
10720
10721fn default_med_necessity_rate() -> f64 {
10722    0.01
10723}
10724
10725impl Default for HealthcareAnomalyRates {
10726    fn default() -> Self {
10727        Self {
10728            upcoding: default_upcoding_rate(),
10729            unbundling: default_unbundling_rate(),
10730            phantom_billing: default_phantom_billing_rate(),
10731            kickbacks: default_healthcare_kickback_rate(),
10732            duplicate_billing: default_duplicate_billing_rate(),
10733            medical_necessity_abuse: default_med_necessity_rate(),
10734        }
10735    }
10736}
10737
10738/// Technology industry configuration.
10739#[derive(Debug, Clone, Serialize, Deserialize)]
10740pub struct TechnologyConfig {
10741    /// Enable technology-specific generation.
10742    #[serde(default)]
10743    pub enabled: bool,
10744
10745    /// Revenue model type.
10746    #[serde(default = "default_revenue_model")]
10747    pub revenue_model: String,
10748
10749    /// Subscription revenue percentage (for SaaS).
10750    #[serde(default = "default_subscription_pct")]
10751    pub subscription_revenue_pct: f64,
10752
10753    /// License revenue percentage.
10754    #[serde(default = "default_license_pct")]
10755    pub license_revenue_pct: f64,
10756
10757    /// Services revenue percentage.
10758    #[serde(default = "default_services_pct")]
10759    pub services_revenue_pct: f64,
10760
10761    /// R&D capitalization settings.
10762    #[serde(default)]
10763    pub rd_capitalization: RdCapitalizationConfig,
10764
10765    /// Technology anomaly injection rates.
10766    #[serde(default)]
10767    pub anomaly_rates: TechnologyAnomalyRates,
10768}
10769
10770fn default_revenue_model() -> String {
10771    "saas".to_string()
10772}
10773
10774fn default_subscription_pct() -> f64 {
10775    0.60
10776}
10777
10778fn default_license_pct() -> f64 {
10779    0.25
10780}
10781
10782fn default_services_pct() -> f64 {
10783    0.15
10784}
10785
10786impl Default for TechnologyConfig {
10787    fn default() -> Self {
10788        Self {
10789            enabled: false,
10790            revenue_model: default_revenue_model(),
10791            subscription_revenue_pct: default_subscription_pct(),
10792            license_revenue_pct: default_license_pct(),
10793            services_revenue_pct: default_services_pct(),
10794            rd_capitalization: RdCapitalizationConfig::default(),
10795            anomaly_rates: TechnologyAnomalyRates::default(),
10796        }
10797    }
10798}
10799
10800/// R&D capitalization configuration.
10801#[derive(Debug, Clone, Serialize, Deserialize)]
10802pub struct RdCapitalizationConfig {
10803    /// Enable R&D capitalization.
10804    #[serde(default = "default_true")]
10805    pub enabled: bool,
10806
10807    /// Capitalization rate (0.0-1.0).
10808    #[serde(default = "default_cap_rate")]
10809    pub capitalization_rate: f64,
10810
10811    /// Useful life in years.
10812    #[serde(default = "default_useful_life")]
10813    pub useful_life_years: u32,
10814}
10815
10816fn default_cap_rate() -> f64 {
10817    0.30
10818}
10819
10820fn default_useful_life() -> u32 {
10821    3
10822}
10823
10824impl Default for RdCapitalizationConfig {
10825    fn default() -> Self {
10826        Self {
10827            enabled: true,
10828            capitalization_rate: default_cap_rate(),
10829            useful_life_years: default_useful_life(),
10830        }
10831    }
10832}
10833
10834/// Technology anomaly injection rates.
10835#[derive(Debug, Clone, Serialize, Deserialize)]
10836pub struct TechnologyAnomalyRates {
10837    /// Premature revenue recognition rate.
10838    #[serde(default = "default_premature_rev_rate")]
10839    pub premature_revenue: f64,
10840
10841    /// Side letter abuse rate.
10842    #[serde(default = "default_side_letter_rate")]
10843    pub side_letter_abuse: f64,
10844
10845    /// Channel stuffing rate.
10846    #[serde(default = "default_channel_stuffing_rate")]
10847    pub channel_stuffing: f64,
10848
10849    /// Improper capitalization rate.
10850    #[serde(default = "default_improper_cap_rate")]
10851    pub improper_capitalization: f64,
10852}
10853
10854fn default_premature_rev_rate() -> f64 {
10855    0.015
10856}
10857
10858fn default_side_letter_rate() -> f64 {
10859    0.008
10860}
10861
10862fn default_channel_stuffing_rate() -> f64 {
10863    0.01
10864}
10865
10866fn default_improper_cap_rate() -> f64 {
10867    0.012
10868}
10869
10870impl Default for TechnologyAnomalyRates {
10871    fn default() -> Self {
10872        Self {
10873            premature_revenue: default_premature_rev_rate(),
10874            side_letter_abuse: default_side_letter_rate(),
10875            channel_stuffing: default_channel_stuffing_rate(),
10876            improper_capitalization: default_improper_cap_rate(),
10877        }
10878    }
10879}
10880
10881/// Financial services industry configuration.
10882#[derive(Debug, Clone, Serialize, Deserialize)]
10883pub struct FinancialServicesConfig {
10884    /// Enable financial services-specific generation.
10885    #[serde(default)]
10886    pub enabled: bool,
10887
10888    /// Financial institution type.
10889    #[serde(default = "default_fi_type")]
10890    pub institution_type: String,
10891
10892    /// Regulatory framework.
10893    #[serde(default = "default_fi_regulatory")]
10894    pub regulatory_framework: String,
10895
10896    /// Financial services anomaly injection rates.
10897    #[serde(default)]
10898    pub anomaly_rates: FinancialServicesAnomalyRates,
10899}
10900
10901fn default_fi_type() -> String {
10902    "commercial_bank".to_string()
10903}
10904
10905fn default_fi_regulatory() -> String {
10906    "us_banking".to_string()
10907}
10908
10909impl Default for FinancialServicesConfig {
10910    fn default() -> Self {
10911        Self {
10912            enabled: false,
10913            institution_type: default_fi_type(),
10914            regulatory_framework: default_fi_regulatory(),
10915            anomaly_rates: FinancialServicesAnomalyRates::default(),
10916        }
10917    }
10918}
10919
10920/// Financial services anomaly injection rates.
10921#[derive(Debug, Clone, Serialize, Deserialize)]
10922pub struct FinancialServicesAnomalyRates {
10923    /// Loan fraud rate.
10924    #[serde(default = "default_loan_fraud_rate")]
10925    pub loan_fraud: f64,
10926
10927    /// Trading fraud rate.
10928    #[serde(default = "default_trading_fraud_rate")]
10929    pub trading_fraud: f64,
10930
10931    /// Insurance fraud rate.
10932    #[serde(default = "default_insurance_fraud_rate")]
10933    pub insurance_fraud: f64,
10934
10935    /// Account manipulation rate.
10936    #[serde(default = "default_account_manip_rate")]
10937    pub account_manipulation: f64,
10938}
10939
10940fn default_loan_fraud_rate() -> f64 {
10941    0.01
10942}
10943
10944fn default_trading_fraud_rate() -> f64 {
10945    0.008
10946}
10947
10948fn default_insurance_fraud_rate() -> f64 {
10949    0.012
10950}
10951
10952fn default_account_manip_rate() -> f64 {
10953    0.005
10954}
10955
10956impl Default for FinancialServicesAnomalyRates {
10957    fn default() -> Self {
10958        Self {
10959            loan_fraud: default_loan_fraud_rate(),
10960            trading_fraud: default_trading_fraud_rate(),
10961            insurance_fraud: default_insurance_fraud_rate(),
10962            account_manipulation: default_account_manip_rate(),
10963        }
10964    }
10965}
10966
10967/// Professional services industry configuration.
10968#[derive(Debug, Clone, Serialize, Deserialize)]
10969pub struct ProfessionalServicesConfig {
10970    /// Enable professional services-specific generation.
10971    #[serde(default)]
10972    pub enabled: bool,
10973
10974    /// Firm type.
10975    #[serde(default = "default_firm_type")]
10976    pub firm_type: String,
10977
10978    /// Billing model.
10979    #[serde(default = "default_billing_model")]
10980    pub billing_model: String,
10981
10982    /// Average hourly rate.
10983    #[serde(default = "default_hourly_rate")]
10984    pub avg_hourly_rate: f64,
10985
10986    /// Trust account settings (for law firms).
10987    #[serde(default)]
10988    pub trust_accounting: TrustAccountingConfig,
10989
10990    /// Professional services anomaly injection rates.
10991    #[serde(default)]
10992    pub anomaly_rates: ProfessionalServicesAnomalyRates,
10993}
10994
10995fn default_firm_type() -> String {
10996    "consulting".to_string()
10997}
10998
10999fn default_billing_model() -> String {
11000    "time_and_materials".to_string()
11001}
11002
11003fn default_hourly_rate() -> f64 {
11004    250.0
11005}
11006
11007impl Default for ProfessionalServicesConfig {
11008    fn default() -> Self {
11009        Self {
11010            enabled: false,
11011            firm_type: default_firm_type(),
11012            billing_model: default_billing_model(),
11013            avg_hourly_rate: default_hourly_rate(),
11014            trust_accounting: TrustAccountingConfig::default(),
11015            anomaly_rates: ProfessionalServicesAnomalyRates::default(),
11016        }
11017    }
11018}
11019
11020/// Trust accounting configuration for law firms.
11021#[derive(Debug, Clone, Serialize, Deserialize)]
11022pub struct TrustAccountingConfig {
11023    /// Enable trust accounting.
11024    #[serde(default)]
11025    pub enabled: bool,
11026
11027    /// Require three-way reconciliation.
11028    #[serde(default = "default_true")]
11029    pub require_three_way_reconciliation: bool,
11030}
11031
11032impl Default for TrustAccountingConfig {
11033    fn default() -> Self {
11034        Self {
11035            enabled: false,
11036            require_three_way_reconciliation: true,
11037        }
11038    }
11039}
11040
11041/// Professional services anomaly injection rates.
11042#[derive(Debug, Clone, Serialize, Deserialize)]
11043pub struct ProfessionalServicesAnomalyRates {
11044    /// Time billing fraud rate.
11045    #[serde(default = "default_time_fraud_rate")]
11046    pub time_billing_fraud: f64,
11047
11048    /// Expense report fraud rate.
11049    #[serde(default = "default_expense_fraud_rate")]
11050    pub expense_fraud: f64,
11051
11052    /// Trust misappropriation rate.
11053    #[serde(default = "default_trust_misappropriation_rate")]
11054    pub trust_misappropriation: f64,
11055}
11056
11057fn default_time_fraud_rate() -> f64 {
11058    0.02
11059}
11060
11061fn default_expense_fraud_rate() -> f64 {
11062    0.015
11063}
11064
11065fn default_trust_misappropriation_rate() -> f64 {
11066    0.003
11067}
11068
11069impl Default for ProfessionalServicesAnomalyRates {
11070    fn default() -> Self {
11071        Self {
11072            time_billing_fraud: default_time_fraud_rate(),
11073            expense_fraud: default_expense_fraud_rate(),
11074            trust_misappropriation: default_trust_misappropriation_rate(),
11075        }
11076    }
11077}
11078
11079/// Fingerprint privacy configuration for extraction and synthesis.
11080///
11081/// Controls the privacy parameters used when extracting fingerprints
11082/// from sensitive data. Supports predefined levels or custom (epsilon, delta) tuples.
11083///
11084/// ```yaml
11085/// fingerprint_privacy:
11086///   level: custom
11087///   epsilon: 0.5
11088///   delta: 1.0e-5
11089///   k_anonymity: 10
11090///   composition_method: renyi_dp
11091/// ```
11092#[derive(Debug, Clone, Serialize, Deserialize)]
11093pub struct FingerprintPrivacyConfig {
11094    /// Privacy level preset. Use "custom" for user-specified epsilon/delta.
11095    #[serde(default)]
11096    pub level: String,
11097    /// Custom epsilon value (only used when level = "custom").
11098    #[serde(default = "default_epsilon")]
11099    pub epsilon: f64,
11100    /// Custom delta value for (epsilon, delta)-DP (only used with RDP/zCDP).
11101    #[serde(default = "default_delta")]
11102    pub delta: f64,
11103    /// K-anonymity threshold.
11104    #[serde(default = "default_k_anonymity")]
11105    pub k_anonymity: u32,
11106    /// Composition method: "naive", "advanced", "renyi_dp", "zcdp".
11107    #[serde(default)]
11108    pub composition_method: String,
11109}
11110
11111fn default_epsilon() -> f64 {
11112    1.0
11113}
11114
11115fn default_delta() -> f64 {
11116    1e-5
11117}
11118
11119fn default_k_anonymity() -> u32 {
11120    5
11121}
11122
11123impl Default for FingerprintPrivacyConfig {
11124    fn default() -> Self {
11125        Self {
11126            level: "standard".to_string(),
11127            epsilon: default_epsilon(),
11128            delta: default_delta(),
11129            k_anonymity: default_k_anonymity(),
11130            composition_method: "naive".to_string(),
11131        }
11132    }
11133}
11134
11135/// Quality gates configuration for pass/fail thresholds on generation runs.
11136///
11137/// ```yaml
11138/// quality_gates:
11139///   enabled: true
11140///   profile: strict  # strict, default, lenient, custom
11141///   fail_on_violation: true
11142///   custom_gates:
11143///     - name: benford_compliance
11144///       metric: benford_mad
11145///       threshold: 0.015
11146///       comparison: lte
11147/// ```
11148#[derive(Debug, Clone, Serialize, Deserialize)]
11149pub struct QualityGatesSchemaConfig {
11150    /// Enable quality gate evaluation.
11151    #[serde(default)]
11152    pub enabled: bool,
11153    /// Gate profile: "strict", "default", "lenient", or "custom".
11154    #[serde(default = "default_gate_profile_name")]
11155    pub profile: String,
11156    /// Whether to fail the generation on gate violations.
11157    #[serde(default)]
11158    pub fail_on_violation: bool,
11159    /// Custom gate definitions (used when profile = "custom").
11160    #[serde(default)]
11161    pub custom_gates: Vec<QualityGateEntry>,
11162}
11163
11164fn default_gate_profile_name() -> String {
11165    "default".to_string()
11166}
11167
11168impl Default for QualityGatesSchemaConfig {
11169    fn default() -> Self {
11170        Self {
11171            enabled: false,
11172            profile: default_gate_profile_name(),
11173            fail_on_violation: false,
11174            custom_gates: Vec::new(),
11175        }
11176    }
11177}
11178
11179/// A single quality gate entry in configuration.
11180#[derive(Debug, Clone, Serialize, Deserialize)]
11181pub struct QualityGateEntry {
11182    /// Gate name.
11183    pub name: String,
11184    /// Metric to check: benford_mad, balance_coherence, document_chain_integrity,
11185    /// correlation_preservation, temporal_consistency, privacy_mia_auc,
11186    /// completion_rate, duplicate_rate, referential_integrity, ic_match_rate.
11187    pub metric: String,
11188    /// Threshold value.
11189    pub threshold: f64,
11190    /// Upper threshold for "between" comparison.
11191    #[serde(default)]
11192    pub upper_threshold: Option<f64>,
11193    /// Comparison operator: "gte", "lte", "eq", "between".
11194    #[serde(default = "default_gate_comparison")]
11195    pub comparison: String,
11196}
11197
11198fn default_gate_comparison() -> String {
11199    "gte".to_string()
11200}
11201
11202/// Compliance configuration for regulatory requirements.
11203///
11204/// ```yaml
11205/// compliance:
11206///   content_marking:
11207///     enabled: true
11208///     format: embedded  # embedded, sidecar, both
11209///   article10_report: true
11210/// ```
11211#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11212pub struct ComplianceSchemaConfig {
11213    /// Synthetic content marking configuration (EU AI Act Article 50).
11214    #[serde(default)]
11215    pub content_marking: ContentMarkingSchemaConfig,
11216    /// Generate Article 10 data governance report.
11217    #[serde(default)]
11218    pub article10_report: bool,
11219    /// Certificate configuration for proving DP guarantees.
11220    #[serde(default)]
11221    pub certificates: CertificateSchemaConfig,
11222}
11223
11224/// Configuration for synthetic data certificates.
11225#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11226pub struct CertificateSchemaConfig {
11227    /// Whether certificate generation is enabled.
11228    #[serde(default)]
11229    pub enabled: bool,
11230    /// Environment variable name for the signing key.
11231    #[serde(default)]
11232    pub signing_key_env: Option<String>,
11233    /// Whether to include quality metrics in the certificate.
11234    #[serde(default)]
11235    pub include_quality_metrics: bool,
11236}
11237
11238/// Content marking configuration for synthetic data output.
11239#[derive(Debug, Clone, Serialize, Deserialize)]
11240pub struct ContentMarkingSchemaConfig {
11241    /// Whether content marking is enabled.
11242    #[serde(default = "default_true")]
11243    pub enabled: bool,
11244    /// Marking format: "embedded", "sidecar", or "both".
11245    #[serde(default = "default_marking_format")]
11246    pub format: String,
11247}
11248
11249fn default_marking_format() -> String {
11250    "embedded".to_string()
11251}
11252
11253impl Default for ContentMarkingSchemaConfig {
11254    fn default() -> Self {
11255        Self {
11256            enabled: true,
11257            format: default_marking_format(),
11258        }
11259    }
11260}
11261
11262/// Webhook notification configuration.
11263#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11264pub struct WebhookSchemaConfig {
11265    /// Whether webhooks are enabled.
11266    #[serde(default)]
11267    pub enabled: bool,
11268    /// Webhook endpoint configurations.
11269    #[serde(default)]
11270    pub endpoints: Vec<WebhookEndpointConfig>,
11271}
11272
11273/// Configuration for a single webhook endpoint.
11274#[derive(Debug, Clone, Serialize, Deserialize)]
11275pub struct WebhookEndpointConfig {
11276    /// Target URL for the webhook.
11277    pub url: String,
11278    /// Event types this endpoint subscribes to.
11279    #[serde(default)]
11280    pub events: Vec<String>,
11281    /// Optional secret for HMAC-SHA256 signature.
11282    #[serde(default)]
11283    pub secret: Option<String>,
11284    /// Maximum retry attempts (default: 3).
11285    #[serde(default = "default_webhook_retries")]
11286    pub max_retries: u32,
11287    /// Timeout in seconds (default: 10).
11288    #[serde(default = "default_webhook_timeout")]
11289    pub timeout_secs: u64,
11290}
11291
11292fn default_webhook_retries() -> u32 {
11293    3
11294}
11295fn default_webhook_timeout() -> u64 {
11296    10
11297}
11298
11299// ===== Enterprise Process Chain Config Structs =====
11300
11301// ----- Source-to-Pay (S2C/S2P) -----
11302
11303/// Source-to-Pay configuration covering the entire sourcing lifecycle.
11304#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11305pub struct SourceToPayConfig {
11306    /// Enable source-to-pay generation
11307    #[serde(default)]
11308    pub enabled: bool,
11309    /// Spend analysis configuration
11310    #[serde(default)]
11311    pub spend_analysis: SpendAnalysisConfig,
11312    /// Sourcing project configuration
11313    #[serde(default)]
11314    pub sourcing: SourcingConfig,
11315    /// Supplier qualification configuration
11316    #[serde(default)]
11317    pub qualification: QualificationConfig,
11318    /// RFx event configuration
11319    #[serde(default)]
11320    pub rfx: RfxConfig,
11321    /// Contract configuration
11322    #[serde(default)]
11323    pub contracts: ContractConfig,
11324    /// Catalog configuration
11325    #[serde(default)]
11326    pub catalog: CatalogConfig,
11327    /// Scorecard configuration
11328    #[serde(default)]
11329    pub scorecards: ScorecardConfig,
11330    /// P2P integration settings
11331    #[serde(default)]
11332    pub p2p_integration: P2PIntegrationConfig,
11333}
11334
11335/// Spend analysis configuration.
11336#[derive(Debug, Clone, Serialize, Deserialize)]
11337pub struct SpendAnalysisConfig {
11338    /// HHI threshold for triggering sourcing project
11339    #[serde(default = "default_hhi_threshold")]
11340    pub hhi_threshold: f64,
11341    /// Target spend coverage under contracts
11342    #[serde(default = "default_contract_coverage_target")]
11343    pub contract_coverage_target: f64,
11344}
11345
11346impl Default for SpendAnalysisConfig {
11347    fn default() -> Self {
11348        Self {
11349            hhi_threshold: default_hhi_threshold(),
11350            contract_coverage_target: default_contract_coverage_target(),
11351        }
11352    }
11353}
11354
11355fn default_hhi_threshold() -> f64 {
11356    2500.0
11357}
11358fn default_contract_coverage_target() -> f64 {
11359    0.80
11360}
11361
11362/// Sourcing project configuration.
11363#[derive(Debug, Clone, Serialize, Deserialize)]
11364pub struct SourcingConfig {
11365    /// Number of sourcing projects per year
11366    #[serde(default = "default_sourcing_projects_per_year")]
11367    pub projects_per_year: u32,
11368    /// Months before expiry to trigger renewal project
11369    #[serde(default = "default_renewal_horizon_months")]
11370    pub renewal_horizon_months: u32,
11371    /// Average project duration in months
11372    #[serde(default = "default_project_duration_months")]
11373    pub project_duration_months: u32,
11374}
11375
11376impl Default for SourcingConfig {
11377    fn default() -> Self {
11378        Self {
11379            projects_per_year: default_sourcing_projects_per_year(),
11380            renewal_horizon_months: default_renewal_horizon_months(),
11381            project_duration_months: default_project_duration_months(),
11382        }
11383    }
11384}
11385
11386fn default_sourcing_projects_per_year() -> u32 {
11387    10
11388}
11389fn default_renewal_horizon_months() -> u32 {
11390    3
11391}
11392fn default_project_duration_months() -> u32 {
11393    4
11394}
11395
11396/// Supplier qualification configuration.
11397#[derive(Debug, Clone, Serialize, Deserialize)]
11398pub struct QualificationConfig {
11399    /// Pass rate for qualification
11400    #[serde(default = "default_qualification_pass_rate")]
11401    pub pass_rate: f64,
11402    /// Qualification validity in days
11403    #[serde(default = "default_qualification_validity_days")]
11404    pub validity_days: u32,
11405    /// Financial stability weight
11406    #[serde(default = "default_financial_weight")]
11407    pub financial_weight: f64,
11408    /// Quality management weight
11409    #[serde(default = "default_quality_weight")]
11410    pub quality_weight: f64,
11411    /// Delivery performance weight
11412    #[serde(default = "default_delivery_weight")]
11413    pub delivery_weight: f64,
11414    /// Compliance weight
11415    #[serde(default = "default_compliance_weight")]
11416    pub compliance_weight: f64,
11417}
11418
11419impl Default for QualificationConfig {
11420    fn default() -> Self {
11421        Self {
11422            pass_rate: default_qualification_pass_rate(),
11423            validity_days: default_qualification_validity_days(),
11424            financial_weight: default_financial_weight(),
11425            quality_weight: default_quality_weight(),
11426            delivery_weight: default_delivery_weight(),
11427            compliance_weight: default_compliance_weight(),
11428        }
11429    }
11430}
11431
11432fn default_qualification_pass_rate() -> f64 {
11433    0.75
11434}
11435fn default_qualification_validity_days() -> u32 {
11436    365
11437}
11438fn default_financial_weight() -> f64 {
11439    0.25
11440}
11441fn default_quality_weight() -> f64 {
11442    0.30
11443}
11444fn default_delivery_weight() -> f64 {
11445    0.25
11446}
11447fn default_compliance_weight() -> f64 {
11448    0.20
11449}
11450
11451/// RFx event configuration.
11452#[derive(Debug, Clone, Serialize, Deserialize)]
11453pub struct RfxConfig {
11454    /// Spend threshold above which RFI is required before RFP
11455    #[serde(default = "default_rfi_threshold")]
11456    pub rfi_threshold: f64,
11457    /// Minimum vendors invited per RFx
11458    #[serde(default = "default_min_invited_vendors")]
11459    pub min_invited_vendors: u32,
11460    /// Maximum vendors invited per RFx
11461    #[serde(default = "default_max_invited_vendors")]
11462    pub max_invited_vendors: u32,
11463    /// Response rate (% of invited vendors that submit bids)
11464    #[serde(default = "default_response_rate")]
11465    pub response_rate: f64,
11466    /// Default price weight in evaluation
11467    #[serde(default = "default_price_weight")]
11468    pub default_price_weight: f64,
11469    /// Default quality weight in evaluation
11470    #[serde(default = "default_rfx_quality_weight")]
11471    pub default_quality_weight: f64,
11472    /// Default delivery weight in evaluation
11473    #[serde(default = "default_rfx_delivery_weight")]
11474    pub default_delivery_weight: f64,
11475}
11476
11477impl Default for RfxConfig {
11478    fn default() -> Self {
11479        Self {
11480            rfi_threshold: default_rfi_threshold(),
11481            min_invited_vendors: default_min_invited_vendors(),
11482            max_invited_vendors: default_max_invited_vendors(),
11483            response_rate: default_response_rate(),
11484            default_price_weight: default_price_weight(),
11485            default_quality_weight: default_rfx_quality_weight(),
11486            default_delivery_weight: default_rfx_delivery_weight(),
11487        }
11488    }
11489}
11490
11491fn default_rfi_threshold() -> f64 {
11492    100_000.0
11493}
11494fn default_min_invited_vendors() -> u32 {
11495    3
11496}
11497fn default_max_invited_vendors() -> u32 {
11498    8
11499}
11500fn default_response_rate() -> f64 {
11501    0.70
11502}
11503fn default_price_weight() -> f64 {
11504    0.40
11505}
11506fn default_rfx_quality_weight() -> f64 {
11507    0.35
11508}
11509fn default_rfx_delivery_weight() -> f64 {
11510    0.25
11511}
11512
11513/// Contract configuration.
11514#[derive(Debug, Clone, Serialize, Deserialize)]
11515pub struct ContractConfig {
11516    /// Minimum contract duration in months
11517    #[serde(default = "default_min_contract_months")]
11518    pub min_duration_months: u32,
11519    /// Maximum contract duration in months
11520    #[serde(default = "default_max_contract_months")]
11521    pub max_duration_months: u32,
11522    /// Auto-renewal rate
11523    #[serde(default = "default_auto_renewal_rate")]
11524    pub auto_renewal_rate: f64,
11525    /// Amendment rate (% of contracts with at least one amendment)
11526    #[serde(default = "default_amendment_rate")]
11527    pub amendment_rate: f64,
11528    /// Distribution of contract types
11529    #[serde(default)]
11530    pub type_distribution: ContractTypeDistribution,
11531}
11532
11533impl Default for ContractConfig {
11534    fn default() -> Self {
11535        Self {
11536            min_duration_months: default_min_contract_months(),
11537            max_duration_months: default_max_contract_months(),
11538            auto_renewal_rate: default_auto_renewal_rate(),
11539            amendment_rate: default_amendment_rate(),
11540            type_distribution: ContractTypeDistribution::default(),
11541        }
11542    }
11543}
11544
11545fn default_min_contract_months() -> u32 {
11546    12
11547}
11548fn default_max_contract_months() -> u32 {
11549    36
11550}
11551fn default_auto_renewal_rate() -> f64 {
11552    0.40
11553}
11554fn default_amendment_rate() -> f64 {
11555    0.20
11556}
11557
11558/// Distribution of contract types.
11559#[derive(Debug, Clone, Serialize, Deserialize)]
11560pub struct ContractTypeDistribution {
11561    /// Fixed price percentage
11562    #[serde(default = "default_fixed_price_pct")]
11563    pub fixed_price: f64,
11564    /// Blanket/framework percentage
11565    #[serde(default = "default_blanket_pct")]
11566    pub blanket: f64,
11567    /// Time and materials percentage
11568    #[serde(default = "default_time_materials_pct")]
11569    pub time_and_materials: f64,
11570    /// Service agreement percentage
11571    #[serde(default = "default_service_agreement_pct")]
11572    pub service_agreement: f64,
11573}
11574
11575impl Default for ContractTypeDistribution {
11576    fn default() -> Self {
11577        Self {
11578            fixed_price: default_fixed_price_pct(),
11579            blanket: default_blanket_pct(),
11580            time_and_materials: default_time_materials_pct(),
11581            service_agreement: default_service_agreement_pct(),
11582        }
11583    }
11584}
11585
11586fn default_fixed_price_pct() -> f64 {
11587    0.40
11588}
11589fn default_blanket_pct() -> f64 {
11590    0.30
11591}
11592fn default_time_materials_pct() -> f64 {
11593    0.15
11594}
11595fn default_service_agreement_pct() -> f64 {
11596    0.15
11597}
11598
11599/// Catalog configuration.
11600#[derive(Debug, Clone, Serialize, Deserialize)]
11601pub struct CatalogConfig {
11602    /// Percentage of catalog items marked as preferred
11603    #[serde(default = "default_preferred_vendor_flag_rate")]
11604    pub preferred_vendor_flag_rate: f64,
11605    /// Rate of materials with multiple sources in catalog
11606    #[serde(default = "default_multi_source_rate")]
11607    pub multi_source_rate: f64,
11608}
11609
11610impl Default for CatalogConfig {
11611    fn default() -> Self {
11612        Self {
11613            preferred_vendor_flag_rate: default_preferred_vendor_flag_rate(),
11614            multi_source_rate: default_multi_source_rate(),
11615        }
11616    }
11617}
11618
11619fn default_preferred_vendor_flag_rate() -> f64 {
11620    0.70
11621}
11622fn default_multi_source_rate() -> f64 {
11623    0.25
11624}
11625
11626/// Scorecard configuration.
11627#[derive(Debug, Clone, Serialize, Deserialize)]
11628pub struct ScorecardConfig {
11629    /// Scorecard review frequency (quarterly, monthly)
11630    #[serde(default = "default_scorecard_frequency")]
11631    pub frequency: String,
11632    /// On-time delivery weight in overall score
11633    #[serde(default = "default_otd_weight")]
11634    pub on_time_delivery_weight: f64,
11635    /// Quality weight in overall score
11636    #[serde(default = "default_quality_score_weight")]
11637    pub quality_weight: f64,
11638    /// Price competitiveness weight
11639    #[serde(default = "default_price_score_weight")]
11640    pub price_weight: f64,
11641    /// Responsiveness weight
11642    #[serde(default = "default_responsiveness_weight")]
11643    pub responsiveness_weight: f64,
11644    /// Grade A threshold (score >= this)
11645    #[serde(default = "default_grade_a_threshold")]
11646    pub grade_a_threshold: f64,
11647    /// Grade B threshold
11648    #[serde(default = "default_grade_b_threshold")]
11649    pub grade_b_threshold: f64,
11650    /// Grade C threshold
11651    #[serde(default = "default_grade_c_threshold")]
11652    pub grade_c_threshold: f64,
11653}
11654
11655impl Default for ScorecardConfig {
11656    fn default() -> Self {
11657        Self {
11658            frequency: default_scorecard_frequency(),
11659            on_time_delivery_weight: default_otd_weight(),
11660            quality_weight: default_quality_score_weight(),
11661            price_weight: default_price_score_weight(),
11662            responsiveness_weight: default_responsiveness_weight(),
11663            grade_a_threshold: default_grade_a_threshold(),
11664            grade_b_threshold: default_grade_b_threshold(),
11665            grade_c_threshold: default_grade_c_threshold(),
11666        }
11667    }
11668}
11669
11670fn default_scorecard_frequency() -> String {
11671    "quarterly".to_string()
11672}
11673fn default_otd_weight() -> f64 {
11674    0.30
11675}
11676fn default_quality_score_weight() -> f64 {
11677    0.30
11678}
11679fn default_price_score_weight() -> f64 {
11680    0.25
11681}
11682fn default_responsiveness_weight() -> f64 {
11683    0.15
11684}
11685fn default_grade_a_threshold() -> f64 {
11686    90.0
11687}
11688fn default_grade_b_threshold() -> f64 {
11689    75.0
11690}
11691fn default_grade_c_threshold() -> f64 {
11692    60.0
11693}
11694
11695/// P2P integration settings for contract enforcement.
11696#[derive(Debug, Clone, Serialize, Deserialize)]
11697pub struct P2PIntegrationConfig {
11698    /// Rate of off-contract (maverick) purchases
11699    #[serde(default = "default_off_contract_rate")]
11700    pub off_contract_rate: f64,
11701    /// Price tolerance for contract price validation
11702    #[serde(default = "default_price_tolerance")]
11703    pub price_tolerance: f64,
11704    /// Whether to enforce catalog ordering
11705    #[serde(default)]
11706    pub catalog_enforcement: bool,
11707}
11708
11709impl Default for P2PIntegrationConfig {
11710    fn default() -> Self {
11711        Self {
11712            off_contract_rate: default_off_contract_rate(),
11713            price_tolerance: default_price_tolerance(),
11714            catalog_enforcement: false,
11715        }
11716    }
11717}
11718
11719fn default_off_contract_rate() -> f64 {
11720    0.15
11721}
11722fn default_price_tolerance() -> f64 {
11723    0.02
11724}
11725
11726// ----- Financial Reporting -----
11727
11728/// Financial reporting configuration.
11729#[derive(Debug, Clone, Serialize, Deserialize)]
11730pub struct FinancialReportingConfig {
11731    /// Enable financial reporting generation
11732    #[serde(default)]
11733    pub enabled: bool,
11734    /// Generate balance sheet
11735    #[serde(default = "default_true")]
11736    pub generate_balance_sheet: bool,
11737    /// Generate income statement
11738    #[serde(default = "default_true")]
11739    pub generate_income_statement: bool,
11740    /// Generate cash flow statement
11741    #[serde(default = "default_true")]
11742    pub generate_cash_flow: bool,
11743    /// Generate changes in equity statement
11744    #[serde(default = "default_true")]
11745    pub generate_changes_in_equity: bool,
11746    /// Number of comparative periods
11747    #[serde(default = "default_comparative_periods")]
11748    pub comparative_periods: u32,
11749    /// Management KPIs configuration
11750    #[serde(default)]
11751    pub management_kpis: ManagementKpisConfig,
11752    /// Budget configuration
11753    #[serde(default)]
11754    pub budgets: BudgetConfig,
11755}
11756
11757impl Default for FinancialReportingConfig {
11758    fn default() -> Self {
11759        Self {
11760            enabled: false,
11761            generate_balance_sheet: true,
11762            generate_income_statement: true,
11763            generate_cash_flow: true,
11764            generate_changes_in_equity: true,
11765            comparative_periods: default_comparative_periods(),
11766            management_kpis: ManagementKpisConfig::default(),
11767            budgets: BudgetConfig::default(),
11768        }
11769    }
11770}
11771
11772fn default_comparative_periods() -> u32 {
11773    1
11774}
11775
11776/// Management KPIs configuration.
11777#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11778pub struct ManagementKpisConfig {
11779    /// Enable KPI generation
11780    #[serde(default)]
11781    pub enabled: bool,
11782    /// KPI calculation frequency (monthly, quarterly)
11783    #[serde(default = "default_kpi_frequency")]
11784    pub frequency: String,
11785}
11786
11787fn default_kpi_frequency() -> String {
11788    "monthly".to_string()
11789}
11790
11791/// Budget configuration.
11792#[derive(Debug, Clone, Serialize, Deserialize)]
11793pub struct BudgetConfig {
11794    /// Enable budget generation
11795    #[serde(default)]
11796    pub enabled: bool,
11797    /// Expected revenue growth rate for budgeting
11798    #[serde(default = "default_revenue_growth_rate")]
11799    pub revenue_growth_rate: f64,
11800    /// Expected expense inflation rate
11801    #[serde(default = "default_expense_inflation_rate")]
11802    pub expense_inflation_rate: f64,
11803    /// Random noise to add to budget vs actual
11804    #[serde(default = "default_variance_noise")]
11805    pub variance_noise: f64,
11806}
11807
11808impl Default for BudgetConfig {
11809    fn default() -> Self {
11810        Self {
11811            enabled: false,
11812            revenue_growth_rate: default_revenue_growth_rate(),
11813            expense_inflation_rate: default_expense_inflation_rate(),
11814            variance_noise: default_variance_noise(),
11815        }
11816    }
11817}
11818
11819fn default_revenue_growth_rate() -> f64 {
11820    0.05
11821}
11822fn default_expense_inflation_rate() -> f64 {
11823    0.03
11824}
11825fn default_variance_noise() -> f64 {
11826    0.10
11827}
11828
11829// ----- HR Configuration -----
11830
11831/// HR (Hire-to-Retire) process configuration.
11832#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11833pub struct HrConfig {
11834    /// Enable HR generation
11835    #[serde(default)]
11836    pub enabled: bool,
11837    /// Payroll configuration
11838    #[serde(default)]
11839    pub payroll: PayrollConfig,
11840    /// Time and attendance configuration
11841    #[serde(default)]
11842    pub time_attendance: TimeAttendanceConfig,
11843    /// Expense management configuration
11844    #[serde(default)]
11845    pub expenses: ExpenseConfig,
11846}
11847
11848/// Payroll configuration.
11849#[derive(Debug, Clone, Serialize, Deserialize)]
11850pub struct PayrollConfig {
11851    /// Enable payroll generation
11852    #[serde(default = "default_true")]
11853    pub enabled: bool,
11854    /// Pay frequency (monthly, biweekly, weekly)
11855    #[serde(default = "default_pay_frequency")]
11856    pub pay_frequency: String,
11857    /// Salary ranges by job level
11858    #[serde(default)]
11859    pub salary_ranges: PayrollSalaryRanges,
11860    /// Effective tax rates
11861    #[serde(default)]
11862    pub tax_rates: PayrollTaxRates,
11863    /// Benefits enrollment rate
11864    #[serde(default = "default_benefits_enrollment_rate")]
11865    pub benefits_enrollment_rate: f64,
11866    /// Retirement plan participation rate
11867    #[serde(default = "default_retirement_participation_rate")]
11868    pub retirement_participation_rate: f64,
11869}
11870
11871impl Default for PayrollConfig {
11872    fn default() -> Self {
11873        Self {
11874            enabled: true,
11875            pay_frequency: default_pay_frequency(),
11876            salary_ranges: PayrollSalaryRanges::default(),
11877            tax_rates: PayrollTaxRates::default(),
11878            benefits_enrollment_rate: default_benefits_enrollment_rate(),
11879            retirement_participation_rate: default_retirement_participation_rate(),
11880        }
11881    }
11882}
11883
11884fn default_pay_frequency() -> String {
11885    "monthly".to_string()
11886}
11887fn default_benefits_enrollment_rate() -> f64 {
11888    0.60
11889}
11890fn default_retirement_participation_rate() -> f64 {
11891    0.45
11892}
11893
11894/// Salary ranges by job level.
11895#[derive(Debug, Clone, Serialize, Deserialize)]
11896pub struct PayrollSalaryRanges {
11897    /// Staff level min/max
11898    #[serde(default = "default_staff_min")]
11899    pub staff_min: f64,
11900    #[serde(default = "default_staff_max")]
11901    pub staff_max: f64,
11902    /// Manager level min/max
11903    #[serde(default = "default_manager_min")]
11904    pub manager_min: f64,
11905    #[serde(default = "default_manager_max")]
11906    pub manager_max: f64,
11907    /// Director level min/max
11908    #[serde(default = "default_director_min")]
11909    pub director_min: f64,
11910    #[serde(default = "default_director_max")]
11911    pub director_max: f64,
11912    /// Executive level min/max
11913    #[serde(default = "default_executive_min")]
11914    pub executive_min: f64,
11915    #[serde(default = "default_executive_max")]
11916    pub executive_max: f64,
11917}
11918
11919impl Default for PayrollSalaryRanges {
11920    fn default() -> Self {
11921        Self {
11922            staff_min: default_staff_min(),
11923            staff_max: default_staff_max(),
11924            manager_min: default_manager_min(),
11925            manager_max: default_manager_max(),
11926            director_min: default_director_min(),
11927            director_max: default_director_max(),
11928            executive_min: default_executive_min(),
11929            executive_max: default_executive_max(),
11930        }
11931    }
11932}
11933
11934fn default_staff_min() -> f64 {
11935    50_000.0
11936}
11937fn default_staff_max() -> f64 {
11938    70_000.0
11939}
11940fn default_manager_min() -> f64 {
11941    80_000.0
11942}
11943fn default_manager_max() -> f64 {
11944    120_000.0
11945}
11946fn default_director_min() -> f64 {
11947    120_000.0
11948}
11949fn default_director_max() -> f64 {
11950    180_000.0
11951}
11952fn default_executive_min() -> f64 {
11953    180_000.0
11954}
11955fn default_executive_max() -> f64 {
11956    350_000.0
11957}
11958
11959/// Effective tax rates for payroll.
11960#[derive(Debug, Clone, Serialize, Deserialize)]
11961pub struct PayrollTaxRates {
11962    /// Federal effective tax rate
11963    #[serde(default = "default_federal_rate")]
11964    pub federal_effective: f64,
11965    /// State effective tax rate
11966    #[serde(default = "default_state_rate")]
11967    pub state_effective: f64,
11968    /// FICA/social security rate
11969    #[serde(default = "default_fica_rate")]
11970    pub fica: f64,
11971}
11972
11973impl Default for PayrollTaxRates {
11974    fn default() -> Self {
11975        Self {
11976            federal_effective: default_federal_rate(),
11977            state_effective: default_state_rate(),
11978            fica: default_fica_rate(),
11979        }
11980    }
11981}
11982
11983fn default_federal_rate() -> f64 {
11984    0.22
11985}
11986fn default_state_rate() -> f64 {
11987    0.05
11988}
11989fn default_fica_rate() -> f64 {
11990    0.0765
11991}
11992
11993/// Time and attendance configuration.
11994#[derive(Debug, Clone, Serialize, Deserialize)]
11995pub struct TimeAttendanceConfig {
11996    /// Enable time tracking
11997    #[serde(default = "default_true")]
11998    pub enabled: bool,
11999    /// Overtime rate (% of employees with overtime in a period)
12000    #[serde(default = "default_overtime_rate")]
12001    pub overtime_rate: f64,
12002}
12003
12004impl Default for TimeAttendanceConfig {
12005    fn default() -> Self {
12006        Self {
12007            enabled: true,
12008            overtime_rate: default_overtime_rate(),
12009        }
12010    }
12011}
12012
12013fn default_overtime_rate() -> f64 {
12014    0.10
12015}
12016
12017/// Expense management configuration.
12018#[derive(Debug, Clone, Serialize, Deserialize)]
12019pub struct ExpenseConfig {
12020    /// Enable expense report generation
12021    #[serde(default = "default_true")]
12022    pub enabled: bool,
12023    /// Rate of employees submitting expenses per month
12024    #[serde(default = "default_expense_submission_rate")]
12025    pub submission_rate: f64,
12026    /// Rate of policy violations
12027    #[serde(default = "default_policy_violation_rate")]
12028    pub policy_violation_rate: f64,
12029}
12030
12031impl Default for ExpenseConfig {
12032    fn default() -> Self {
12033        Self {
12034            enabled: true,
12035            submission_rate: default_expense_submission_rate(),
12036            policy_violation_rate: default_policy_violation_rate(),
12037        }
12038    }
12039}
12040
12041fn default_expense_submission_rate() -> f64 {
12042    0.30
12043}
12044fn default_policy_violation_rate() -> f64 {
12045    0.08
12046}
12047
12048// ----- Manufacturing Configuration -----
12049
12050/// Manufacturing process configuration (production orders, WIP, routing).
12051#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12052pub struct ManufacturingProcessConfig {
12053    /// Enable manufacturing generation
12054    #[serde(default)]
12055    pub enabled: bool,
12056    /// Production order configuration
12057    #[serde(default)]
12058    pub production_orders: ProductionOrderConfig,
12059    /// Costing configuration
12060    #[serde(default)]
12061    pub costing: ManufacturingCostingConfig,
12062    /// Routing configuration
12063    #[serde(default)]
12064    pub routing: RoutingConfig,
12065}
12066
12067/// Production order configuration.
12068#[derive(Debug, Clone, Serialize, Deserialize)]
12069pub struct ProductionOrderConfig {
12070    /// Orders per month
12071    #[serde(default = "default_prod_orders_per_month")]
12072    pub orders_per_month: u32,
12073    /// Average batch size
12074    #[serde(default = "default_prod_avg_batch_size")]
12075    pub avg_batch_size: u32,
12076    /// Yield rate
12077    #[serde(default = "default_prod_yield_rate")]
12078    pub yield_rate: f64,
12079    /// Make-to-order rate (vs make-to-stock)
12080    #[serde(default = "default_prod_make_to_order_rate")]
12081    pub make_to_order_rate: f64,
12082    /// Rework rate
12083    #[serde(default = "default_prod_rework_rate")]
12084    pub rework_rate: f64,
12085}
12086
12087impl Default for ProductionOrderConfig {
12088    fn default() -> Self {
12089        Self {
12090            orders_per_month: default_prod_orders_per_month(),
12091            avg_batch_size: default_prod_avg_batch_size(),
12092            yield_rate: default_prod_yield_rate(),
12093            make_to_order_rate: default_prod_make_to_order_rate(),
12094            rework_rate: default_prod_rework_rate(),
12095        }
12096    }
12097}
12098
12099fn default_prod_orders_per_month() -> u32 {
12100    50
12101}
12102fn default_prod_avg_batch_size() -> u32 {
12103    100
12104}
12105fn default_prod_yield_rate() -> f64 {
12106    0.97
12107}
12108fn default_prod_make_to_order_rate() -> f64 {
12109    0.20
12110}
12111fn default_prod_rework_rate() -> f64 {
12112    0.03
12113}
12114
12115/// Manufacturing costing configuration.
12116#[derive(Debug, Clone, Serialize, Deserialize)]
12117pub struct ManufacturingCostingConfig {
12118    /// Labor rate per hour
12119    #[serde(default = "default_labor_rate")]
12120    pub labor_rate_per_hour: f64,
12121    /// Overhead application rate (multiplier on direct labor)
12122    #[serde(default = "default_overhead_rate")]
12123    pub overhead_rate: f64,
12124    /// Standard cost update frequency
12125    #[serde(default = "default_cost_update_frequency")]
12126    pub standard_cost_update_frequency: String,
12127}
12128
12129impl Default for ManufacturingCostingConfig {
12130    fn default() -> Self {
12131        Self {
12132            labor_rate_per_hour: default_labor_rate(),
12133            overhead_rate: default_overhead_rate(),
12134            standard_cost_update_frequency: default_cost_update_frequency(),
12135        }
12136    }
12137}
12138
12139fn default_labor_rate() -> f64 {
12140    35.0
12141}
12142fn default_overhead_rate() -> f64 {
12143    1.50
12144}
12145fn default_cost_update_frequency() -> String {
12146    "quarterly".to_string()
12147}
12148
12149/// Routing configuration for production operations.
12150#[derive(Debug, Clone, Serialize, Deserialize)]
12151pub struct RoutingConfig {
12152    /// Average number of operations per routing
12153    #[serde(default = "default_avg_operations")]
12154    pub avg_operations: u32,
12155    /// Average setup time in hours
12156    #[serde(default = "default_setup_time")]
12157    pub setup_time_hours: f64,
12158    /// Run time variation coefficient
12159    #[serde(default = "default_run_time_variation")]
12160    pub run_time_variation: f64,
12161}
12162
12163impl Default for RoutingConfig {
12164    fn default() -> Self {
12165        Self {
12166            avg_operations: default_avg_operations(),
12167            setup_time_hours: default_setup_time(),
12168            run_time_variation: default_run_time_variation(),
12169        }
12170    }
12171}
12172
12173fn default_avg_operations() -> u32 {
12174    4
12175}
12176fn default_setup_time() -> f64 {
12177    1.5
12178}
12179fn default_run_time_variation() -> f64 {
12180    0.15
12181}
12182
12183// ----- Sales Quote Configuration -----
12184
12185/// Sales quote (quote-to-order) pipeline configuration.
12186#[derive(Debug, Clone, Serialize, Deserialize)]
12187pub struct SalesQuoteConfig {
12188    /// Enable sales quote generation
12189    #[serde(default)]
12190    pub enabled: bool,
12191    /// Quotes per month
12192    #[serde(default = "default_quotes_per_month")]
12193    pub quotes_per_month: u32,
12194    /// Win rate (fraction of quotes that convert to orders)
12195    #[serde(default = "default_quote_win_rate")]
12196    pub win_rate: f64,
12197    /// Average quote validity in days
12198    #[serde(default = "default_quote_validity_days")]
12199    pub validity_days: u32,
12200}
12201
12202impl Default for SalesQuoteConfig {
12203    fn default() -> Self {
12204        Self {
12205            enabled: false,
12206            quotes_per_month: default_quotes_per_month(),
12207            win_rate: default_quote_win_rate(),
12208            validity_days: default_quote_validity_days(),
12209        }
12210    }
12211}
12212
12213fn default_quotes_per_month() -> u32 {
12214    30
12215}
12216fn default_quote_win_rate() -> f64 {
12217    0.35
12218}
12219fn default_quote_validity_days() -> u32 {
12220    30
12221}
12222
12223// =============================================================================
12224// Tax Accounting Configuration
12225// =============================================================================
12226
12227/// Tax accounting configuration.
12228///
12229/// Controls generation of tax-related data including VAT/GST, sales tax,
12230/// withholding tax, tax provisions, and payroll tax across multiple jurisdictions.
12231#[derive(Debug, Clone, Serialize, Deserialize)]
12232pub struct TaxConfig {
12233    /// Whether tax generation is enabled.
12234    #[serde(default)]
12235    pub enabled: bool,
12236    /// Tax jurisdiction configuration.
12237    #[serde(default)]
12238    pub jurisdictions: TaxJurisdictionConfig,
12239    /// VAT/GST configuration.
12240    #[serde(default)]
12241    pub vat_gst: VatGstConfig,
12242    /// Sales tax configuration.
12243    #[serde(default)]
12244    pub sales_tax: SalesTaxConfig,
12245    /// Withholding tax configuration.
12246    #[serde(default)]
12247    pub withholding: WithholdingTaxSchemaConfig,
12248    /// Tax provision configuration.
12249    #[serde(default)]
12250    pub provisions: TaxProvisionSchemaConfig,
12251    /// Payroll tax configuration.
12252    #[serde(default)]
12253    pub payroll_tax: PayrollTaxSchemaConfig,
12254    /// Anomaly injection rate for tax data (0.0 to 1.0).
12255    #[serde(default = "default_tax_anomaly_rate")]
12256    pub anomaly_rate: f64,
12257}
12258
12259fn default_tax_anomaly_rate() -> f64 {
12260    0.03
12261}
12262
12263impl Default for TaxConfig {
12264    fn default() -> Self {
12265        Self {
12266            enabled: false,
12267            jurisdictions: TaxJurisdictionConfig::default(),
12268            vat_gst: VatGstConfig::default(),
12269            sales_tax: SalesTaxConfig::default(),
12270            withholding: WithholdingTaxSchemaConfig::default(),
12271            provisions: TaxProvisionSchemaConfig::default(),
12272            payroll_tax: PayrollTaxSchemaConfig::default(),
12273            anomaly_rate: default_tax_anomaly_rate(),
12274        }
12275    }
12276}
12277
12278/// Tax jurisdiction configuration.
12279///
12280/// Specifies which countries and subnational jurisdictions to include
12281/// when generating tax data.
12282#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12283pub struct TaxJurisdictionConfig {
12284    /// List of country codes to include (e.g., ["US", "DE", "GB"]).
12285    #[serde(default)]
12286    pub countries: Vec<String>,
12287    /// Whether to include subnational jurisdictions (e.g., US states, Canadian provinces).
12288    #[serde(default)]
12289    pub include_subnational: bool,
12290}
12291
12292/// VAT/GST configuration.
12293///
12294/// Controls generation of Value Added Tax / Goods and Services Tax data,
12295/// including standard and reduced rates, exempt categories, and reverse charge.
12296#[derive(Debug, Clone, Serialize, Deserialize)]
12297pub struct VatGstConfig {
12298    /// Whether VAT/GST generation is enabled.
12299    #[serde(default)]
12300    pub enabled: bool,
12301    /// Standard VAT/GST rates by country code (e.g., {"DE": 0.19, "GB": 0.20}).
12302    #[serde(default)]
12303    pub standard_rates: std::collections::HashMap<String, f64>,
12304    /// Reduced VAT/GST rates by country code (e.g., {"DE": 0.07, "GB": 0.05}).
12305    #[serde(default)]
12306    pub reduced_rates: std::collections::HashMap<String, f64>,
12307    /// Categories exempt from VAT/GST (e.g., ["financial_services", "healthcare"]).
12308    #[serde(default)]
12309    pub exempt_categories: Vec<String>,
12310    /// Whether to apply reverse charge mechanism for cross-border B2B transactions.
12311    #[serde(default = "default_true")]
12312    pub reverse_charge: bool,
12313}
12314
12315impl Default for VatGstConfig {
12316    fn default() -> Self {
12317        Self {
12318            enabled: false,
12319            standard_rates: std::collections::HashMap::new(),
12320            reduced_rates: std::collections::HashMap::new(),
12321            exempt_categories: Vec::new(),
12322            reverse_charge: true,
12323        }
12324    }
12325}
12326
12327/// Sales tax configuration.
12328///
12329/// Controls generation of US-style sales tax data including nexus determination.
12330#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12331pub struct SalesTaxConfig {
12332    /// Whether sales tax generation is enabled.
12333    #[serde(default)]
12334    pub enabled: bool,
12335    /// US states where the company has nexus (e.g., ["CA", "NY", "TX"]).
12336    #[serde(default)]
12337    pub nexus_states: Vec<String>,
12338}
12339
12340/// Withholding tax configuration.
12341///
12342/// Controls generation of withholding tax data for cross-border payments,
12343/// including treaty network and rate overrides.
12344#[derive(Debug, Clone, Serialize, Deserialize)]
12345pub struct WithholdingTaxSchemaConfig {
12346    /// Whether withholding tax generation is enabled.
12347    #[serde(default)]
12348    pub enabled: bool,
12349    /// Whether to simulate a treaty network with reduced rates.
12350    #[serde(default = "default_true")]
12351    pub treaty_network: bool,
12352    /// Default withholding tax rate for non-treaty countries (0.0 to 1.0).
12353    #[serde(default = "default_withholding_rate")]
12354    pub default_rate: f64,
12355    /// Reduced withholding tax rate for treaty countries (0.0 to 1.0).
12356    #[serde(default = "default_treaty_reduced_rate")]
12357    pub treaty_reduced_rate: f64,
12358}
12359
12360fn default_withholding_rate() -> f64 {
12361    0.30
12362}
12363
12364fn default_treaty_reduced_rate() -> f64 {
12365    0.15
12366}
12367
12368impl Default for WithholdingTaxSchemaConfig {
12369    fn default() -> Self {
12370        Self {
12371            enabled: false,
12372            treaty_network: true,
12373            default_rate: default_withholding_rate(),
12374            treaty_reduced_rate: default_treaty_reduced_rate(),
12375        }
12376    }
12377}
12378
12379/// Tax provision configuration.
12380///
12381/// Controls generation of tax provision data including statutory rates
12382/// and uncertain tax positions (ASC 740 / IAS 12).
12383#[derive(Debug, Clone, Serialize, Deserialize)]
12384pub struct TaxProvisionSchemaConfig {
12385    /// Whether tax provision generation is enabled.
12386    /// Defaults to true when tax is enabled, as provisions are typically required.
12387    #[serde(default = "default_true")]
12388    pub enabled: bool,
12389    /// Statutory corporate tax rate (0.0 to 1.0).
12390    #[serde(default = "default_statutory_rate")]
12391    pub statutory_rate: f64,
12392    /// Whether to generate uncertain tax positions (FIN 48 / IFRIC 23).
12393    #[serde(default = "default_true")]
12394    pub uncertain_positions: bool,
12395}
12396
12397fn default_statutory_rate() -> f64 {
12398    0.21
12399}
12400
12401impl Default for TaxProvisionSchemaConfig {
12402    fn default() -> Self {
12403        Self {
12404            enabled: true,
12405            statutory_rate: default_statutory_rate(),
12406            uncertain_positions: true,
12407        }
12408    }
12409}
12410
12411/// Payroll tax configuration.
12412///
12413/// Controls generation of payroll tax data (employer/employee contributions,
12414/// social security, Medicare, etc.).
12415#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12416pub struct PayrollTaxSchemaConfig {
12417    /// Whether payroll tax generation is enabled.
12418    #[serde(default)]
12419    pub enabled: bool,
12420}
12421
12422// ---------------------------------------------------------------------------
12423// Treasury & Cash Management Configuration
12424// ---------------------------------------------------------------------------
12425
12426/// Treasury and cash management configuration.
12427///
12428/// Controls generation of cash positions, forecasts, pooling, hedging
12429/// instruments (ASC 815 / IFRS 9), debt instruments with covenants,
12430/// bank guarantees, and intercompany netting runs.
12431#[derive(Debug, Clone, Serialize, Deserialize)]
12432pub struct TreasuryConfig {
12433    /// Whether treasury generation is enabled.
12434    #[serde(default)]
12435    pub enabled: bool,
12436    /// Cash positioning configuration.
12437    #[serde(default)]
12438    pub cash_positioning: CashPositioningConfig,
12439    /// Cash forecasting configuration.
12440    #[serde(default)]
12441    pub cash_forecasting: CashForecastingConfig,
12442    /// Cash pooling configuration.
12443    #[serde(default)]
12444    pub cash_pooling: CashPoolingConfig,
12445    /// Hedging configuration (FX forwards, IR swaps, etc.).
12446    #[serde(default)]
12447    pub hedging: HedgingSchemaConfig,
12448    /// Debt instrument and covenant configuration.
12449    #[serde(default)]
12450    pub debt: DebtSchemaConfig,
12451    /// Intercompany netting configuration.
12452    #[serde(default)]
12453    pub netting: NettingSchemaConfig,
12454    /// Bank guarantee / letter of credit configuration.
12455    #[serde(default)]
12456    pub bank_guarantees: BankGuaranteeSchemaConfig,
12457    /// Anomaly injection rate for treasury data (0.0 to 1.0).
12458    #[serde(default = "default_treasury_anomaly_rate")]
12459    pub anomaly_rate: f64,
12460}
12461
12462fn default_treasury_anomaly_rate() -> f64 {
12463    0.02
12464}
12465
12466impl Default for TreasuryConfig {
12467    fn default() -> Self {
12468        Self {
12469            enabled: false,
12470            cash_positioning: CashPositioningConfig::default(),
12471            cash_forecasting: CashForecastingConfig::default(),
12472            cash_pooling: CashPoolingConfig::default(),
12473            hedging: HedgingSchemaConfig::default(),
12474            debt: DebtSchemaConfig::default(),
12475            netting: NettingSchemaConfig::default(),
12476            bank_guarantees: BankGuaranteeSchemaConfig::default(),
12477            anomaly_rate: default_treasury_anomaly_rate(),
12478        }
12479    }
12480}
12481
12482/// Cash positioning configuration.
12483///
12484/// Controls daily cash position generation per entity/bank account.
12485#[derive(Debug, Clone, Serialize, Deserialize)]
12486pub struct CashPositioningConfig {
12487    /// Whether cash positioning is enabled.
12488    #[serde(default = "default_true")]
12489    pub enabled: bool,
12490    /// Position generation frequency.
12491    #[serde(default = "default_cash_frequency")]
12492    pub frequency: String,
12493    /// Minimum cash balance policy threshold.
12494    #[serde(default = "default_minimum_balance_policy")]
12495    pub minimum_balance_policy: f64,
12496}
12497
12498fn default_cash_frequency() -> String {
12499    "daily".to_string()
12500}
12501
12502fn default_minimum_balance_policy() -> f64 {
12503    100_000.0
12504}
12505
12506impl Default for CashPositioningConfig {
12507    fn default() -> Self {
12508        Self {
12509            enabled: true,
12510            frequency: default_cash_frequency(),
12511            minimum_balance_policy: default_minimum_balance_policy(),
12512        }
12513    }
12514}
12515
12516/// Cash forecasting configuration.
12517///
12518/// Controls forward-looking cash forecast generation with probability-weighted items.
12519#[derive(Debug, Clone, Serialize, Deserialize)]
12520pub struct CashForecastingConfig {
12521    /// Whether cash forecasting is enabled.
12522    #[serde(default = "default_true")]
12523    pub enabled: bool,
12524    /// Number of days to forecast into the future.
12525    #[serde(default = "default_horizon_days")]
12526    pub horizon_days: u32,
12527    /// AR collection probability curve type ("aging" or "flat").
12528    #[serde(default = "default_ar_probability_curve")]
12529    pub ar_collection_probability_curve: String,
12530    /// Confidence interval for the forecast (0.0 to 1.0).
12531    #[serde(default = "default_confidence_interval")]
12532    pub confidence_interval: f64,
12533}
12534
12535fn default_horizon_days() -> u32 {
12536    90
12537}
12538
12539fn default_ar_probability_curve() -> String {
12540    "aging".to_string()
12541}
12542
12543fn default_confidence_interval() -> f64 {
12544    0.90
12545}
12546
12547impl Default for CashForecastingConfig {
12548    fn default() -> Self {
12549        Self {
12550            enabled: true,
12551            horizon_days: default_horizon_days(),
12552            ar_collection_probability_curve: default_ar_probability_curve(),
12553            confidence_interval: default_confidence_interval(),
12554        }
12555    }
12556}
12557
12558/// Cash pooling configuration.
12559///
12560/// Controls cash pool structure generation (physical, notional, zero-balancing).
12561#[derive(Debug, Clone, Serialize, Deserialize)]
12562pub struct CashPoolingConfig {
12563    /// Whether cash pooling is enabled.
12564    #[serde(default)]
12565    pub enabled: bool,
12566    /// Pool type: "physical_pooling", "notional_pooling", or "zero_balancing".
12567    #[serde(default = "default_pool_type")]
12568    pub pool_type: String,
12569    /// Time of day when sweeps occur (HH:MM format).
12570    #[serde(default = "default_sweep_time")]
12571    pub sweep_time: String,
12572}
12573
12574fn default_pool_type() -> String {
12575    "zero_balancing".to_string()
12576}
12577
12578fn default_sweep_time() -> String {
12579    "16:00".to_string()
12580}
12581
12582impl Default for CashPoolingConfig {
12583    fn default() -> Self {
12584        Self {
12585            enabled: false,
12586            pool_type: default_pool_type(),
12587            sweep_time: default_sweep_time(),
12588        }
12589    }
12590}
12591
12592/// Hedging configuration.
12593///
12594/// Controls generation of hedging instruments and hedge relationship designations
12595/// under ASC 815 / IFRS 9.
12596#[derive(Debug, Clone, Serialize, Deserialize)]
12597pub struct HedgingSchemaConfig {
12598    /// Whether hedging generation is enabled.
12599    #[serde(default)]
12600    pub enabled: bool,
12601    /// Target hedge ratio (0.0 to 1.0). Proportion of FX exposure to hedge.
12602    #[serde(default = "default_hedge_ratio")]
12603    pub hedge_ratio: f64,
12604    /// Types of instruments to generate (e.g., ["fx_forward", "interest_rate_swap"]).
12605    #[serde(default = "default_hedge_instruments")]
12606    pub instruments: Vec<String>,
12607    /// Whether to designate formal hedge accounting relationships.
12608    #[serde(default = "default_true")]
12609    pub hedge_accounting: bool,
12610    /// Effectiveness testing method: "dollar_offset", "regression", or "critical_terms".
12611    #[serde(default = "default_effectiveness_method")]
12612    pub effectiveness_method: String,
12613}
12614
12615fn default_hedge_ratio() -> f64 {
12616    0.75
12617}
12618
12619fn default_hedge_instruments() -> Vec<String> {
12620    vec!["fx_forward".to_string(), "interest_rate_swap".to_string()]
12621}
12622
12623fn default_effectiveness_method() -> String {
12624    "regression".to_string()
12625}
12626
12627impl Default for HedgingSchemaConfig {
12628    fn default() -> Self {
12629        Self {
12630            enabled: false,
12631            hedge_ratio: default_hedge_ratio(),
12632            instruments: default_hedge_instruments(),
12633            hedge_accounting: true,
12634            effectiveness_method: default_effectiveness_method(),
12635        }
12636    }
12637}
12638
12639/// Debt instrument configuration.
12640///
12641/// Controls generation of debt instruments (term loans, revolving credit, bonds)
12642/// with amortization schedules and financial covenants.
12643#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12644pub struct DebtSchemaConfig {
12645    /// Whether debt instrument generation is enabled.
12646    #[serde(default)]
12647    pub enabled: bool,
12648    /// Debt instrument definitions.
12649    #[serde(default)]
12650    pub instruments: Vec<DebtInstrumentDef>,
12651    /// Covenant definitions.
12652    #[serde(default)]
12653    pub covenants: Vec<CovenantDef>,
12654}
12655
12656/// Definition of a debt instrument in configuration.
12657#[derive(Debug, Clone, Serialize, Deserialize)]
12658pub struct DebtInstrumentDef {
12659    /// Instrument type: "term_loan", "revolving_credit", "bond", "commercial_paper", "bridge_loan".
12660    #[serde(rename = "type")]
12661    pub instrument_type: String,
12662    /// Principal amount (for term loans, bonds).
12663    #[serde(default)]
12664    pub principal: Option<f64>,
12665    /// Interest rate (annual, as decimal fraction).
12666    #[serde(default)]
12667    pub rate: Option<f64>,
12668    /// Maturity in months.
12669    #[serde(default)]
12670    pub maturity_months: Option<u32>,
12671    /// Facility limit (for revolving credit).
12672    #[serde(default)]
12673    pub facility: Option<f64>,
12674}
12675
12676/// Definition of a debt covenant in configuration.
12677#[derive(Debug, Clone, Serialize, Deserialize)]
12678pub struct CovenantDef {
12679    /// Covenant type: "debt_to_equity", "interest_coverage", "current_ratio",
12680    /// "net_worth", "debt_to_ebitda", "fixed_charge_coverage".
12681    #[serde(rename = "type")]
12682    pub covenant_type: String,
12683    /// Covenant threshold value.
12684    pub threshold: f64,
12685}
12686
12687/// Intercompany netting configuration.
12688///
12689/// Controls generation of multilateral netting runs.
12690#[derive(Debug, Clone, Serialize, Deserialize)]
12691pub struct NettingSchemaConfig {
12692    /// Whether netting generation is enabled.
12693    #[serde(default)]
12694    pub enabled: bool,
12695    /// Netting cycle: "daily", "weekly", or "monthly".
12696    #[serde(default = "default_netting_cycle")]
12697    pub cycle: String,
12698}
12699
12700fn default_netting_cycle() -> String {
12701    "monthly".to_string()
12702}
12703
12704impl Default for NettingSchemaConfig {
12705    fn default() -> Self {
12706        Self {
12707            enabled: false,
12708            cycle: default_netting_cycle(),
12709        }
12710    }
12711}
12712
12713/// Bank guarantee and letter of credit configuration.
12714///
12715/// Controls generation of bank guarantees, standby LCs, and performance bonds.
12716#[derive(Debug, Clone, Serialize, Deserialize)]
12717pub struct BankGuaranteeSchemaConfig {
12718    /// Whether bank guarantee generation is enabled.
12719    #[serde(default)]
12720    pub enabled: bool,
12721    /// Number of guarantees to generate.
12722    #[serde(default = "default_guarantee_count")]
12723    pub count: u32,
12724}
12725
12726fn default_guarantee_count() -> u32 {
12727    5
12728}
12729
12730impl Default for BankGuaranteeSchemaConfig {
12731    fn default() -> Self {
12732        Self {
12733            enabled: false,
12734            count: default_guarantee_count(),
12735        }
12736    }
12737}
12738
12739// ===========================================================================
12740// Project Accounting Configuration
12741// ===========================================================================
12742
12743/// Project accounting configuration.
12744///
12745/// Controls generation of project cost lines, revenue recognition,
12746/// milestones, change orders, retainage, and earned value metrics.
12747#[derive(Debug, Clone, Serialize, Deserialize)]
12748pub struct ProjectAccountingConfig {
12749    /// Whether project accounting is enabled.
12750    #[serde(default)]
12751    pub enabled: bool,
12752    /// Number of projects to generate.
12753    #[serde(default = "default_project_count")]
12754    pub project_count: u32,
12755    /// Distribution of project types (capital, internal, customer, r_and_d, maintenance, technology).
12756    #[serde(default)]
12757    pub project_types: ProjectTypeDistribution,
12758    /// WBS structure configuration.
12759    #[serde(default)]
12760    pub wbs: WbsSchemaConfig,
12761    /// Cost allocation rates (what % of source documents get project-tagged).
12762    #[serde(default)]
12763    pub cost_allocation: CostAllocationConfig,
12764    /// Revenue recognition configuration for project accounting.
12765    #[serde(default)]
12766    pub revenue_recognition: ProjectRevenueRecognitionConfig,
12767    /// Milestone configuration.
12768    #[serde(default)]
12769    pub milestones: MilestoneSchemaConfig,
12770    /// Change order configuration.
12771    #[serde(default)]
12772    pub change_orders: ChangeOrderSchemaConfig,
12773    /// Retainage configuration.
12774    #[serde(default)]
12775    pub retainage: RetainageSchemaConfig,
12776    /// Earned value management configuration.
12777    #[serde(default)]
12778    pub earned_value: EarnedValueSchemaConfig,
12779    /// Anomaly injection rate for project accounting data (0.0 to 1.0).
12780    #[serde(default = "default_project_anomaly_rate")]
12781    pub anomaly_rate: f64,
12782}
12783
12784fn default_project_count() -> u32 {
12785    10
12786}
12787
12788fn default_project_anomaly_rate() -> f64 {
12789    0.03
12790}
12791
12792impl Default for ProjectAccountingConfig {
12793    fn default() -> Self {
12794        Self {
12795            enabled: false,
12796            project_count: default_project_count(),
12797            project_types: ProjectTypeDistribution::default(),
12798            wbs: WbsSchemaConfig::default(),
12799            cost_allocation: CostAllocationConfig::default(),
12800            revenue_recognition: ProjectRevenueRecognitionConfig::default(),
12801            milestones: MilestoneSchemaConfig::default(),
12802            change_orders: ChangeOrderSchemaConfig::default(),
12803            retainage: RetainageSchemaConfig::default(),
12804            earned_value: EarnedValueSchemaConfig::default(),
12805            anomaly_rate: default_project_anomaly_rate(),
12806        }
12807    }
12808}
12809
12810/// Distribution of project types by weight.
12811#[derive(Debug, Clone, Serialize, Deserialize)]
12812pub struct ProjectTypeDistribution {
12813    /// Weight for capital projects (default 0.25).
12814    #[serde(default = "default_capital_weight")]
12815    pub capital: f64,
12816    /// Weight for internal projects (default 0.20).
12817    #[serde(default = "default_internal_weight")]
12818    pub internal: f64,
12819    /// Weight for customer projects (default 0.30).
12820    #[serde(default = "default_customer_weight")]
12821    pub customer: f64,
12822    /// Weight for R&D projects (default 0.10).
12823    #[serde(default = "default_rnd_weight")]
12824    pub r_and_d: f64,
12825    /// Weight for maintenance projects (default 0.10).
12826    #[serde(default = "default_maintenance_weight")]
12827    pub maintenance: f64,
12828    /// Weight for technology projects (default 0.05).
12829    #[serde(default = "default_technology_weight")]
12830    pub technology: f64,
12831}
12832
12833fn default_capital_weight() -> f64 {
12834    0.25
12835}
12836fn default_internal_weight() -> f64 {
12837    0.20
12838}
12839fn default_customer_weight() -> f64 {
12840    0.30
12841}
12842fn default_rnd_weight() -> f64 {
12843    0.10
12844}
12845fn default_maintenance_weight() -> f64 {
12846    0.10
12847}
12848fn default_technology_weight() -> f64 {
12849    0.05
12850}
12851
12852impl Default for ProjectTypeDistribution {
12853    fn default() -> Self {
12854        Self {
12855            capital: default_capital_weight(),
12856            internal: default_internal_weight(),
12857            customer: default_customer_weight(),
12858            r_and_d: default_rnd_weight(),
12859            maintenance: default_maintenance_weight(),
12860            technology: default_technology_weight(),
12861        }
12862    }
12863}
12864
12865/// WBS structure configuration.
12866#[derive(Debug, Clone, Serialize, Deserialize)]
12867pub struct WbsSchemaConfig {
12868    /// Maximum depth of WBS hierarchy (default 3).
12869    #[serde(default = "default_wbs_max_depth")]
12870    pub max_depth: u32,
12871    /// Minimum elements per level-1 WBS (default 2).
12872    #[serde(default = "default_wbs_min_elements")]
12873    pub min_elements_per_level: u32,
12874    /// Maximum elements per level-1 WBS (default 6).
12875    #[serde(default = "default_wbs_max_elements")]
12876    pub max_elements_per_level: u32,
12877}
12878
12879fn default_wbs_max_depth() -> u32 {
12880    3
12881}
12882fn default_wbs_min_elements() -> u32 {
12883    2
12884}
12885fn default_wbs_max_elements() -> u32 {
12886    6
12887}
12888
12889impl Default for WbsSchemaConfig {
12890    fn default() -> Self {
12891        Self {
12892            max_depth: default_wbs_max_depth(),
12893            min_elements_per_level: default_wbs_min_elements(),
12894            max_elements_per_level: default_wbs_max_elements(),
12895        }
12896    }
12897}
12898
12899/// Cost allocation rates — what fraction of each document type gets linked to a project.
12900#[derive(Debug, Clone, Serialize, Deserialize)]
12901pub struct CostAllocationConfig {
12902    /// Fraction of time entries assigned to projects (0.0 to 1.0).
12903    #[serde(default = "default_time_entry_rate")]
12904    pub time_entry_project_rate: f64,
12905    /// Fraction of expense reports assigned to projects (0.0 to 1.0).
12906    #[serde(default = "default_expense_rate")]
12907    pub expense_project_rate: f64,
12908    /// Fraction of purchase orders assigned to projects (0.0 to 1.0).
12909    #[serde(default = "default_po_rate")]
12910    pub purchase_order_project_rate: f64,
12911    /// Fraction of vendor invoices assigned to projects (0.0 to 1.0).
12912    #[serde(default = "default_vi_rate")]
12913    pub vendor_invoice_project_rate: f64,
12914}
12915
12916fn default_time_entry_rate() -> f64 {
12917    0.60
12918}
12919fn default_expense_rate() -> f64 {
12920    0.30
12921}
12922fn default_po_rate() -> f64 {
12923    0.40
12924}
12925fn default_vi_rate() -> f64 {
12926    0.35
12927}
12928
12929impl Default for CostAllocationConfig {
12930    fn default() -> Self {
12931        Self {
12932            time_entry_project_rate: default_time_entry_rate(),
12933            expense_project_rate: default_expense_rate(),
12934            purchase_order_project_rate: default_po_rate(),
12935            vendor_invoice_project_rate: default_vi_rate(),
12936        }
12937    }
12938}
12939
12940/// Revenue recognition configuration for project accounting.
12941#[derive(Debug, Clone, Serialize, Deserialize)]
12942pub struct ProjectRevenueRecognitionConfig {
12943    /// Whether revenue recognition is enabled for customer projects.
12944    #[serde(default = "default_true")]
12945    pub enabled: bool,
12946    /// Default method: "percentage_of_completion", "completed_contract", "milestone_based".
12947    #[serde(default = "default_revenue_method")]
12948    pub method: String,
12949    /// Default completion measure: "cost_to_cost", "labor_hours", "physical_completion".
12950    #[serde(default = "default_completion_measure")]
12951    pub completion_measure: String,
12952    /// Average contract value for customer projects.
12953    #[serde(default = "default_avg_contract_value")]
12954    pub avg_contract_value: f64,
12955}
12956
12957fn default_revenue_method() -> String {
12958    "percentage_of_completion".to_string()
12959}
12960fn default_completion_measure() -> String {
12961    "cost_to_cost".to_string()
12962}
12963fn default_avg_contract_value() -> f64 {
12964    500_000.0
12965}
12966
12967impl Default for ProjectRevenueRecognitionConfig {
12968    fn default() -> Self {
12969        Self {
12970            enabled: true,
12971            method: default_revenue_method(),
12972            completion_measure: default_completion_measure(),
12973            avg_contract_value: default_avg_contract_value(),
12974        }
12975    }
12976}
12977
12978/// Milestone configuration.
12979#[derive(Debug, Clone, Serialize, Deserialize)]
12980pub struct MilestoneSchemaConfig {
12981    /// Whether milestone generation is enabled.
12982    #[serde(default = "default_true")]
12983    pub enabled: bool,
12984    /// Average number of milestones per project.
12985    #[serde(default = "default_milestones_per_project")]
12986    pub avg_per_project: u32,
12987    /// Fraction of milestones that are payment milestones (0.0 to 1.0).
12988    #[serde(default = "default_payment_milestone_rate")]
12989    pub payment_milestone_rate: f64,
12990}
12991
12992fn default_milestones_per_project() -> u32 {
12993    4
12994}
12995fn default_payment_milestone_rate() -> f64 {
12996    0.50
12997}
12998
12999impl Default for MilestoneSchemaConfig {
13000    fn default() -> Self {
13001        Self {
13002            enabled: true,
13003            avg_per_project: default_milestones_per_project(),
13004            payment_milestone_rate: default_payment_milestone_rate(),
13005        }
13006    }
13007}
13008
13009/// Change order configuration.
13010#[derive(Debug, Clone, Serialize, Deserialize)]
13011pub struct ChangeOrderSchemaConfig {
13012    /// Whether change order generation is enabled.
13013    #[serde(default = "default_true")]
13014    pub enabled: bool,
13015    /// Probability that a project will have at least one change order (0.0 to 1.0).
13016    #[serde(default = "default_change_order_probability")]
13017    pub probability: f64,
13018    /// Maximum change orders per project.
13019    #[serde(default = "default_max_change_orders")]
13020    pub max_per_project: u32,
13021    /// Approval rate for change orders (0.0 to 1.0).
13022    #[serde(default = "default_change_order_approval_rate")]
13023    pub approval_rate: f64,
13024}
13025
13026fn default_change_order_probability() -> f64 {
13027    0.40
13028}
13029fn default_max_change_orders() -> u32 {
13030    3
13031}
13032fn default_change_order_approval_rate() -> f64 {
13033    0.75
13034}
13035
13036impl Default for ChangeOrderSchemaConfig {
13037    fn default() -> Self {
13038        Self {
13039            enabled: true,
13040            probability: default_change_order_probability(),
13041            max_per_project: default_max_change_orders(),
13042            approval_rate: default_change_order_approval_rate(),
13043        }
13044    }
13045}
13046
13047/// Retainage configuration.
13048#[derive(Debug, Clone, Serialize, Deserialize)]
13049pub struct RetainageSchemaConfig {
13050    /// Whether retainage is enabled.
13051    #[serde(default)]
13052    pub enabled: bool,
13053    /// Default retainage percentage (0.0 to 1.0, e.g., 0.10 for 10%).
13054    #[serde(default = "default_retainage_pct")]
13055    pub default_percentage: f64,
13056}
13057
13058fn default_retainage_pct() -> f64 {
13059    0.10
13060}
13061
13062impl Default for RetainageSchemaConfig {
13063    fn default() -> Self {
13064        Self {
13065            enabled: false,
13066            default_percentage: default_retainage_pct(),
13067        }
13068    }
13069}
13070
13071/// Earned value management (EVM) configuration.
13072#[derive(Debug, Clone, Serialize, Deserialize)]
13073pub struct EarnedValueSchemaConfig {
13074    /// Whether EVM metrics are generated.
13075    #[serde(default = "default_true")]
13076    pub enabled: bool,
13077    /// Measurement frequency: "weekly", "biweekly", "monthly".
13078    #[serde(default = "default_evm_frequency")]
13079    pub frequency: String,
13080}
13081
13082fn default_evm_frequency() -> String {
13083    "monthly".to_string()
13084}
13085
13086impl Default for EarnedValueSchemaConfig {
13087    fn default() -> Self {
13088        Self {
13089            enabled: true,
13090            frequency: default_evm_frequency(),
13091        }
13092    }
13093}
13094
13095// =============================================================================
13096// ESG / Sustainability Configuration
13097// =============================================================================
13098
13099/// Top-level ESG / sustainability reporting configuration.
13100#[derive(Debug, Clone, Serialize, Deserialize)]
13101pub struct EsgConfig {
13102    /// Whether ESG generation is enabled.
13103    #[serde(default)]
13104    pub enabled: bool,
13105    /// Environmental metrics (emissions, energy, water, waste).
13106    #[serde(default)]
13107    pub environmental: EnvironmentalConfig,
13108    /// Social metrics (diversity, pay equity, safety).
13109    #[serde(default)]
13110    pub social: SocialConfig,
13111    /// Governance metrics (board composition, ethics, compliance).
13112    #[serde(default)]
13113    pub governance: GovernanceSchemaConfig,
13114    /// Supply-chain ESG assessment settings.
13115    #[serde(default)]
13116    pub supply_chain_esg: SupplyChainEsgConfig,
13117    /// ESG reporting / disclosure framework settings.
13118    #[serde(default)]
13119    pub reporting: EsgReportingConfig,
13120    /// Climate scenario analysis settings.
13121    #[serde(default)]
13122    pub climate_scenarios: ClimateScenarioConfig,
13123    /// Anomaly injection rate for ESG data (0.0 to 1.0).
13124    #[serde(default = "default_esg_anomaly_rate")]
13125    pub anomaly_rate: f64,
13126}
13127
13128fn default_esg_anomaly_rate() -> f64 {
13129    0.02
13130}
13131
13132impl Default for EsgConfig {
13133    fn default() -> Self {
13134        Self {
13135            enabled: false,
13136            environmental: EnvironmentalConfig::default(),
13137            social: SocialConfig::default(),
13138            governance: GovernanceSchemaConfig::default(),
13139            supply_chain_esg: SupplyChainEsgConfig::default(),
13140            reporting: EsgReportingConfig::default(),
13141            climate_scenarios: ClimateScenarioConfig::default(),
13142            anomaly_rate: default_esg_anomaly_rate(),
13143        }
13144    }
13145}
13146
13147/// Country pack configuration.
13148///
13149/// Controls where to load additional country packs and per-country overrides.
13150/// When omitted, only the built-in packs (_default, US, DE, GB) are used.
13151#[derive(Debug, Clone, Serialize, Deserialize, Default)]
13152pub struct CountryPacksSchemaConfig {
13153    /// Optional directory containing additional `*.json` country packs.
13154    #[serde(default)]
13155    pub external_dir: Option<PathBuf>,
13156    /// Per-country overrides applied after loading.
13157    /// Keys are ISO 3166-1 alpha-2 codes; values are partial JSON objects
13158    /// that are deep-merged on top of the loaded pack.
13159    #[serde(default)]
13160    pub overrides: std::collections::HashMap<String, serde_json::Value>,
13161}
13162
13163/// Environmental metrics configuration.
13164#[derive(Debug, Clone, Serialize, Deserialize)]
13165pub struct EnvironmentalConfig {
13166    /// Whether environmental metrics are generated.
13167    #[serde(default = "default_true")]
13168    pub enabled: bool,
13169    /// Scope 1 (direct) emission generation settings.
13170    #[serde(default)]
13171    pub scope1: EmissionScopeConfig,
13172    /// Scope 2 (purchased energy) emission generation settings.
13173    #[serde(default)]
13174    pub scope2: EmissionScopeConfig,
13175    /// Scope 3 (value chain) emission generation settings.
13176    #[serde(default)]
13177    pub scope3: Scope3Config,
13178    /// Energy consumption tracking settings.
13179    #[serde(default)]
13180    pub energy: EnergySchemaConfig,
13181    /// Water usage tracking settings.
13182    #[serde(default)]
13183    pub water: WaterSchemaConfig,
13184    /// Waste management tracking settings.
13185    #[serde(default)]
13186    pub waste: WasteSchemaConfig,
13187}
13188
13189impl Default for EnvironmentalConfig {
13190    fn default() -> Self {
13191        Self {
13192            enabled: true,
13193            scope1: EmissionScopeConfig::default(),
13194            scope2: EmissionScopeConfig::default(),
13195            scope3: Scope3Config::default(),
13196            energy: EnergySchemaConfig::default(),
13197            water: WaterSchemaConfig::default(),
13198            waste: WasteSchemaConfig::default(),
13199        }
13200    }
13201}
13202
13203/// Configuration for a single emission scope (Scope 1 or 2).
13204#[derive(Debug, Clone, Serialize, Deserialize)]
13205pub struct EmissionScopeConfig {
13206    /// Whether this scope is enabled.
13207    #[serde(default = "default_true")]
13208    pub enabled: bool,
13209    /// Emission factor region (e.g., "US", "EU", "global").
13210    #[serde(default = "default_emission_region")]
13211    pub factor_region: String,
13212}
13213
13214fn default_emission_region() -> String {
13215    "US".to_string()
13216}
13217
13218impl Default for EmissionScopeConfig {
13219    fn default() -> Self {
13220        Self {
13221            enabled: true,
13222            factor_region: default_emission_region(),
13223        }
13224    }
13225}
13226
13227/// Scope 3 (value chain) emission configuration.
13228#[derive(Debug, Clone, Serialize, Deserialize)]
13229pub struct Scope3Config {
13230    /// Whether Scope 3 emissions are generated.
13231    #[serde(default = "default_true")]
13232    pub enabled: bool,
13233    /// Categories to include (e.g., "purchased_goods", "business_travel", "commuting").
13234    #[serde(default = "default_scope3_categories")]
13235    pub categories: Vec<String>,
13236    /// Spend-based emission intensity (kg CO2e per USD).
13237    #[serde(default = "default_spend_intensity")]
13238    pub default_spend_intensity_kg_per_usd: f64,
13239}
13240
13241fn default_scope3_categories() -> Vec<String> {
13242    vec![
13243        "purchased_goods".to_string(),
13244        "business_travel".to_string(),
13245        "employee_commuting".to_string(),
13246    ]
13247}
13248
13249fn default_spend_intensity() -> f64 {
13250    0.5
13251}
13252
13253impl Default for Scope3Config {
13254    fn default() -> Self {
13255        Self {
13256            enabled: true,
13257            categories: default_scope3_categories(),
13258            default_spend_intensity_kg_per_usd: default_spend_intensity(),
13259        }
13260    }
13261}
13262
13263/// Energy consumption configuration.
13264#[derive(Debug, Clone, Serialize, Deserialize)]
13265pub struct EnergySchemaConfig {
13266    /// Whether energy consumption tracking is enabled.
13267    #[serde(default = "default_true")]
13268    pub enabled: bool,
13269    /// Number of facilities to generate.
13270    #[serde(default = "default_facility_count")]
13271    pub facility_count: u32,
13272    /// Target percentage of energy from renewable sources (0.0 to 1.0).
13273    #[serde(default = "default_renewable_target")]
13274    pub renewable_target: f64,
13275}
13276
13277fn default_facility_count() -> u32 {
13278    5
13279}
13280
13281fn default_renewable_target() -> f64 {
13282    0.30
13283}
13284
13285impl Default for EnergySchemaConfig {
13286    fn default() -> Self {
13287        Self {
13288            enabled: true,
13289            facility_count: default_facility_count(),
13290            renewable_target: default_renewable_target(),
13291        }
13292    }
13293}
13294
13295/// Water usage configuration.
13296#[derive(Debug, Clone, Serialize, Deserialize)]
13297pub struct WaterSchemaConfig {
13298    /// Whether water usage tracking is enabled.
13299    #[serde(default = "default_true")]
13300    pub enabled: bool,
13301    /// Number of facilities with water tracking.
13302    #[serde(default = "default_water_facility_count")]
13303    pub facility_count: u32,
13304}
13305
13306fn default_water_facility_count() -> u32 {
13307    3
13308}
13309
13310impl Default for WaterSchemaConfig {
13311    fn default() -> Self {
13312        Self {
13313            enabled: true,
13314            facility_count: default_water_facility_count(),
13315        }
13316    }
13317}
13318
13319/// Waste management configuration.
13320#[derive(Debug, Clone, Serialize, Deserialize)]
13321pub struct WasteSchemaConfig {
13322    /// Whether waste tracking is enabled.
13323    #[serde(default = "default_true")]
13324    pub enabled: bool,
13325    /// Target diversion rate (0.0 to 1.0).
13326    #[serde(default = "default_diversion_target")]
13327    pub diversion_target: f64,
13328}
13329
13330fn default_diversion_target() -> f64 {
13331    0.50
13332}
13333
13334impl Default for WasteSchemaConfig {
13335    fn default() -> Self {
13336        Self {
13337            enabled: true,
13338            diversion_target: default_diversion_target(),
13339        }
13340    }
13341}
13342
13343/// Social metrics configuration.
13344#[derive(Debug, Clone, Serialize, Deserialize)]
13345pub struct SocialConfig {
13346    /// Whether social metrics are generated.
13347    #[serde(default = "default_true")]
13348    pub enabled: bool,
13349    /// Workforce diversity tracking settings.
13350    #[serde(default)]
13351    pub diversity: DiversitySchemaConfig,
13352    /// Pay equity analysis settings.
13353    #[serde(default)]
13354    pub pay_equity: PayEquitySchemaConfig,
13355    /// Safety incident and metrics settings.
13356    #[serde(default)]
13357    pub safety: SafetySchemaConfig,
13358}
13359
13360impl Default for SocialConfig {
13361    fn default() -> Self {
13362        Self {
13363            enabled: true,
13364            diversity: DiversitySchemaConfig::default(),
13365            pay_equity: PayEquitySchemaConfig::default(),
13366            safety: SafetySchemaConfig::default(),
13367        }
13368    }
13369}
13370
13371/// Workforce diversity configuration.
13372#[derive(Debug, Clone, Serialize, Deserialize)]
13373pub struct DiversitySchemaConfig {
13374    /// Whether diversity metrics are generated.
13375    #[serde(default = "default_true")]
13376    pub enabled: bool,
13377    /// Dimensions to track (e.g., "gender", "ethnicity", "age_group").
13378    #[serde(default = "default_diversity_dimensions")]
13379    pub dimensions: Vec<String>,
13380}
13381
13382fn default_diversity_dimensions() -> Vec<String> {
13383    vec![
13384        "gender".to_string(),
13385        "ethnicity".to_string(),
13386        "age_group".to_string(),
13387    ]
13388}
13389
13390impl Default for DiversitySchemaConfig {
13391    fn default() -> Self {
13392        Self {
13393            enabled: true,
13394            dimensions: default_diversity_dimensions(),
13395        }
13396    }
13397}
13398
13399/// Pay equity analysis configuration.
13400#[derive(Debug, Clone, Serialize, Deserialize)]
13401pub struct PayEquitySchemaConfig {
13402    /// Whether pay equity analysis is generated.
13403    #[serde(default = "default_true")]
13404    pub enabled: bool,
13405    /// Target pay gap threshold for flagging (e.g., 0.05 = 5% gap).
13406    #[serde(default = "default_pay_gap_threshold")]
13407    pub gap_threshold: f64,
13408}
13409
13410fn default_pay_gap_threshold() -> f64 {
13411    0.05
13412}
13413
13414impl Default for PayEquitySchemaConfig {
13415    fn default() -> Self {
13416        Self {
13417            enabled: true,
13418            gap_threshold: default_pay_gap_threshold(),
13419        }
13420    }
13421}
13422
13423/// Safety metrics configuration.
13424#[derive(Debug, Clone, Serialize, Deserialize)]
13425pub struct SafetySchemaConfig {
13426    /// Whether safety metrics are generated.
13427    #[serde(default = "default_true")]
13428    pub enabled: bool,
13429    /// Average annual recordable incidents per 200,000 hours.
13430    #[serde(default = "default_trir_target")]
13431    pub target_trir: f64,
13432    /// Number of safety incidents to generate.
13433    #[serde(default = "default_incident_count")]
13434    pub incident_count: u32,
13435}
13436
13437fn default_trir_target() -> f64 {
13438    2.5
13439}
13440
13441fn default_incident_count() -> u32 {
13442    20
13443}
13444
13445impl Default for SafetySchemaConfig {
13446    fn default() -> Self {
13447        Self {
13448            enabled: true,
13449            target_trir: default_trir_target(),
13450            incident_count: default_incident_count(),
13451        }
13452    }
13453}
13454
13455/// Governance metrics configuration.
13456#[derive(Debug, Clone, Serialize, Deserialize)]
13457pub struct GovernanceSchemaConfig {
13458    /// Whether governance metrics are generated.
13459    #[serde(default = "default_true")]
13460    pub enabled: bool,
13461    /// Number of board members.
13462    #[serde(default = "default_board_size")]
13463    pub board_size: u32,
13464    /// Target independent director ratio (0.0 to 1.0).
13465    #[serde(default = "default_independence_target")]
13466    pub independence_target: f64,
13467}
13468
13469fn default_board_size() -> u32 {
13470    11
13471}
13472
13473fn default_independence_target() -> f64 {
13474    0.67
13475}
13476
13477impl Default for GovernanceSchemaConfig {
13478    fn default() -> Self {
13479        Self {
13480            enabled: true,
13481            board_size: default_board_size(),
13482            independence_target: default_independence_target(),
13483        }
13484    }
13485}
13486
13487/// Supply-chain ESG assessment configuration.
13488#[derive(Debug, Clone, Serialize, Deserialize)]
13489pub struct SupplyChainEsgConfig {
13490    /// Whether supply chain ESG assessments are generated.
13491    #[serde(default = "default_true")]
13492    pub enabled: bool,
13493    /// Proportion of vendors to assess (0.0 to 1.0).
13494    #[serde(default = "default_assessment_coverage")]
13495    pub assessment_coverage: f64,
13496    /// High-risk country codes for automatic flagging.
13497    #[serde(default = "default_high_risk_countries")]
13498    pub high_risk_countries: Vec<String>,
13499}
13500
13501fn default_assessment_coverage() -> f64 {
13502    0.80
13503}
13504
13505fn default_high_risk_countries() -> Vec<String> {
13506    vec!["CN".to_string(), "BD".to_string(), "MM".to_string()]
13507}
13508
13509impl Default for SupplyChainEsgConfig {
13510    fn default() -> Self {
13511        Self {
13512            enabled: true,
13513            assessment_coverage: default_assessment_coverage(),
13514            high_risk_countries: default_high_risk_countries(),
13515        }
13516    }
13517}
13518
13519/// ESG reporting / disclosure framework configuration.
13520#[derive(Debug, Clone, Serialize, Deserialize)]
13521pub struct EsgReportingConfig {
13522    /// Whether ESG disclosures are generated.
13523    #[serde(default = "default_true")]
13524    pub enabled: bool,
13525    /// Frameworks to generate disclosures for.
13526    #[serde(default = "default_esg_frameworks")]
13527    pub frameworks: Vec<String>,
13528    /// Whether materiality assessment is performed.
13529    #[serde(default = "default_true")]
13530    pub materiality_assessment: bool,
13531    /// Materiality threshold for impact dimension (0.0 to 1.0).
13532    #[serde(default = "default_materiality_threshold")]
13533    pub impact_threshold: f64,
13534    /// Materiality threshold for financial dimension (0.0 to 1.0).
13535    #[serde(default = "default_materiality_threshold")]
13536    pub financial_threshold: f64,
13537}
13538
13539fn default_esg_frameworks() -> Vec<String> {
13540    vec!["GRI".to_string(), "ESRS".to_string()]
13541}
13542
13543fn default_materiality_threshold() -> f64 {
13544    0.6
13545}
13546
13547impl Default for EsgReportingConfig {
13548    fn default() -> Self {
13549        Self {
13550            enabled: true,
13551            frameworks: default_esg_frameworks(),
13552            materiality_assessment: true,
13553            impact_threshold: default_materiality_threshold(),
13554            financial_threshold: default_materiality_threshold(),
13555        }
13556    }
13557}
13558
13559/// Climate scenario analysis configuration.
13560#[derive(Debug, Clone, Serialize, Deserialize)]
13561pub struct ClimateScenarioConfig {
13562    /// Whether climate scenario analysis is generated.
13563    #[serde(default)]
13564    pub enabled: bool,
13565    /// Scenarios to model (e.g., "net_zero_2050", "stated_policies", "current_trajectory").
13566    #[serde(default = "default_climate_scenarios")]
13567    pub scenarios: Vec<String>,
13568    /// Time horizons in years to project.
13569    #[serde(default = "default_time_horizons")]
13570    pub time_horizons: Vec<u32>,
13571}
13572
13573fn default_climate_scenarios() -> Vec<String> {
13574    vec![
13575        "net_zero_2050".to_string(),
13576        "stated_policies".to_string(),
13577        "current_trajectory".to_string(),
13578    ]
13579}
13580
13581fn default_time_horizons() -> Vec<u32> {
13582    vec![5, 10, 30]
13583}
13584
13585impl Default for ClimateScenarioConfig {
13586    fn default() -> Self {
13587        Self {
13588            enabled: false,
13589            scenarios: default_climate_scenarios(),
13590            time_horizons: default_time_horizons(),
13591        }
13592    }
13593}
13594
13595// ===== Counterfactual Simulation Scenarios =====
13596
13597/// Configuration for counterfactual simulation scenarios.
13598#[derive(Debug, Clone, Serialize, Deserialize, Default)]
13599pub struct ScenariosConfig {
13600    /// Whether scenario generation is enabled.
13601    #[serde(default)]
13602    pub enabled: bool,
13603    /// List of scenario definitions.
13604    #[serde(default)]
13605    pub scenarios: Vec<ScenarioSchemaConfig>,
13606    /// Causal model configuration.
13607    #[serde(default)]
13608    pub causal_model: CausalModelSchemaConfig,
13609    /// Default settings applied to all scenarios.
13610    #[serde(default)]
13611    pub defaults: ScenarioDefaultsConfig,
13612    /// Generate counterfactual (original, mutated) JE pairs for ML training.
13613    /// When true, the orchestrator produces paired clean/anomalous journal entries.
13614    #[serde(default)]
13615    pub generate_counterfactuals: bool,
13616}
13617
13618/// A single scenario definition in the config.
13619#[derive(Debug, Clone, Serialize, Deserialize)]
13620pub struct ScenarioSchemaConfig {
13621    /// Scenario name (must be unique).
13622    pub name: String,
13623    /// Human-readable description.
13624    #[serde(default)]
13625    pub description: String,
13626    /// Tags for categorization.
13627    #[serde(default)]
13628    pub tags: Vec<String>,
13629    /// Base scenario name (None = default config).
13630    pub base: Option<String>,
13631    /// IFRS 9-style probability weight.
13632    pub probability_weight: Option<f64>,
13633    /// List of interventions to apply.
13634    #[serde(default)]
13635    pub interventions: Vec<InterventionSchemaConfig>,
13636    /// Constraint overrides for this scenario.
13637    #[serde(default)]
13638    pub constraints: ScenarioConstraintsSchemaConfig,
13639    /// Output configuration for this scenario.
13640    #[serde(default)]
13641    pub output: ScenarioOutputSchemaConfig,
13642    /// Arbitrary metadata.
13643    #[serde(default)]
13644    pub metadata: std::collections::HashMap<String, String>,
13645}
13646
13647/// An intervention definition in the config.
13648#[derive(Debug, Clone, Serialize, Deserialize)]
13649pub struct InterventionSchemaConfig {
13650    /// Intervention type and parameters (flattened tagged enum).
13651    #[serde(flatten)]
13652    pub intervention_type: serde_json::Value,
13653    /// Timing configuration.
13654    #[serde(default)]
13655    pub timing: InterventionTimingSchemaConfig,
13656    /// Human-readable label.
13657    pub label: Option<String>,
13658    /// Priority for conflict resolution (higher wins).
13659    #[serde(default)]
13660    pub priority: u32,
13661}
13662
13663/// Timing configuration for an intervention.
13664#[derive(Debug, Clone, Serialize, Deserialize)]
13665pub struct InterventionTimingSchemaConfig {
13666    /// Month offset from start (1-indexed).
13667    #[serde(default = "default_start_month")]
13668    pub start_month: u32,
13669    /// Duration in months.
13670    pub duration_months: Option<u32>,
13671    /// Onset type: "sudden", "gradual", "oscillating", "custom".
13672    #[serde(default = "default_onset")]
13673    pub onset: String,
13674    /// Ramp period in months.
13675    pub ramp_months: Option<u32>,
13676}
13677
13678fn default_start_month() -> u32 {
13679    1
13680}
13681
13682fn default_onset() -> String {
13683    "sudden".to_string()
13684}
13685
13686impl Default for InterventionTimingSchemaConfig {
13687    fn default() -> Self {
13688        Self {
13689            start_month: 1,
13690            duration_months: None,
13691            onset: "sudden".to_string(),
13692            ramp_months: None,
13693        }
13694    }
13695}
13696
13697/// Scenario constraint overrides.
13698#[derive(Debug, Clone, Serialize, Deserialize)]
13699pub struct ScenarioConstraintsSchemaConfig {
13700    #[serde(default = "default_true")]
13701    pub preserve_accounting_identity: bool,
13702    #[serde(default = "default_true")]
13703    pub preserve_document_chains: bool,
13704    #[serde(default = "default_true")]
13705    pub preserve_period_close: bool,
13706    #[serde(default = "default_true")]
13707    pub preserve_balance_coherence: bool,
13708    #[serde(default)]
13709    pub custom: Vec<CustomConstraintSchemaConfig>,
13710}
13711
13712impl Default for ScenarioConstraintsSchemaConfig {
13713    fn default() -> Self {
13714        Self {
13715            preserve_accounting_identity: true,
13716            preserve_document_chains: true,
13717            preserve_period_close: true,
13718            preserve_balance_coherence: true,
13719            custom: Vec::new(),
13720        }
13721    }
13722}
13723
13724/// Custom constraint in config.
13725#[derive(Debug, Clone, Serialize, Deserialize)]
13726pub struct CustomConstraintSchemaConfig {
13727    pub config_path: String,
13728    pub min: Option<f64>,
13729    pub max: Option<f64>,
13730    #[serde(default)]
13731    pub description: String,
13732}
13733
13734/// Output configuration for a scenario.
13735#[derive(Debug, Clone, Serialize, Deserialize)]
13736pub struct ScenarioOutputSchemaConfig {
13737    #[serde(default = "default_true")]
13738    pub paired: bool,
13739    #[serde(default = "default_diff_formats_schema")]
13740    pub diff_formats: Vec<String>,
13741    #[serde(default)]
13742    pub diff_scope: Vec<String>,
13743}
13744
13745fn default_diff_formats_schema() -> Vec<String> {
13746    vec!["summary".to_string(), "aggregate".to_string()]
13747}
13748
13749impl Default for ScenarioOutputSchemaConfig {
13750    fn default() -> Self {
13751        Self {
13752            paired: true,
13753            diff_formats: default_diff_formats_schema(),
13754            diff_scope: Vec::new(),
13755        }
13756    }
13757}
13758
13759/// Causal model configuration.
13760#[derive(Debug, Clone, Serialize, Deserialize)]
13761pub struct CausalModelSchemaConfig {
13762    /// Preset name: "default", "minimal", or "custom".
13763    #[serde(default = "default_causal_preset")]
13764    pub preset: String,
13765    /// Custom nodes (merged with preset).
13766    #[serde(default)]
13767    pub nodes: Vec<serde_json::Value>,
13768    /// Custom edges (merged with preset).
13769    #[serde(default)]
13770    pub edges: Vec<serde_json::Value>,
13771}
13772
13773fn default_causal_preset() -> String {
13774    "default".to_string()
13775}
13776
13777impl Default for CausalModelSchemaConfig {
13778    fn default() -> Self {
13779        Self {
13780            preset: "default".to_string(),
13781            nodes: Vec::new(),
13782            edges: Vec::new(),
13783        }
13784    }
13785}
13786
13787/// Default settings applied to all scenarios.
13788#[derive(Debug, Clone, Serialize, Deserialize, Default)]
13789pub struct ScenarioDefaultsConfig {
13790    #[serde(default)]
13791    pub constraints: ScenarioConstraintsSchemaConfig,
13792    #[serde(default)]
13793    pub output: ScenarioOutputSchemaConfig,
13794}
13795
13796// =====================================================================
13797// Compliance Regulations Framework Configuration
13798// =====================================================================
13799
13800/// Top-level configuration for the compliance regulations framework.
13801///
13802/// Controls standards registry, jurisdiction profiles, temporal versioning,
13803/// audit procedure templates, compliance graph integration, and output settings.
13804///
13805/// # Example
13806///
13807/// ```yaml
13808/// compliance_regulations:
13809///   enabled: true
13810///   jurisdictions: [US, DE, GB]
13811///   reference_date: "2025-06-30"
13812///   standards_selection:
13813///     categories: [accounting, auditing, regulatory]
13814///     include: ["IFRS-16", "ASC-606"]
13815///   audit_procedures:
13816///     enabled: true
13817///     procedures_per_standard: 3
13818///   findings:
13819///     enabled: true
13820///     finding_rate: 0.05
13821///   filings:
13822///     enabled: true
13823///   graph:
13824///     enabled: true
13825///     include_compliance_nodes: true
13826///     include_compliance_edges: true
13827/// ```
13828#[derive(Debug, Clone, Default, Serialize, Deserialize)]
13829pub struct ComplianceRegulationsConfig {
13830    /// Master switch for the compliance regulations framework.
13831    #[serde(default)]
13832    pub enabled: bool,
13833    /// Jurisdictions to generate compliance data for (ISO 3166-1 alpha-2 codes).
13834    /// If empty, inferred from company countries in the config.
13835    #[serde(default)]
13836    pub jurisdictions: Vec<String>,
13837    /// Reference date for temporal standard resolution (YYYY-MM-DD).
13838    /// Defaults to the global start_date if not set.
13839    #[serde(default)]
13840    pub reference_date: Option<String>,
13841    /// Standards selection filters.
13842    #[serde(default)]
13843    pub standards_selection: StandardsSelectionConfig,
13844    /// Audit procedure generation settings.
13845    #[serde(default)]
13846    pub audit_procedures: AuditProcedureGenConfig,
13847    /// Compliance finding generation settings.
13848    #[serde(default)]
13849    pub findings: ComplianceFindingGenConfig,
13850    /// Regulatory filing generation settings.
13851    #[serde(default)]
13852    pub filings: ComplianceFilingGenConfig,
13853    /// Compliance graph integration settings.
13854    #[serde(default)]
13855    pub graph: ComplianceGraphConfig,
13856    /// Output settings for compliance-specific files.
13857    #[serde(default)]
13858    pub output: ComplianceOutputConfig,
13859    /// v3.3.0: legal-document generation (engagement letters,
13860    /// management reps, legal opinions, regulatory filings, board
13861    /// resolutions). Requires `compliance_regulations.enabled = true`
13862    /// AND `legal_documents.enabled = true` to take effect.
13863    #[serde(default)]
13864    pub legal_documents: LegalDocumentsConfig,
13865}
13866
13867/// Legal-document generation settings (v3.3.0+).
13868///
13869/// Wires `LegalDocumentGenerator` into the orchestrator. Generates one
13870/// batch per audit engagement when enabled.
13871#[derive(Debug, Clone, Serialize, Deserialize)]
13872pub struct LegalDocumentsConfig {
13873    /// Master switch.
13874    #[serde(default)]
13875    pub enabled: bool,
13876    /// Probability of including a legal-opinion document in an engagement.
13877    #[serde(default = "default_legal_opinion_probability")]
13878    pub legal_opinion_probability: f64,
13879}
13880
13881fn default_legal_opinion_probability() -> f64 {
13882    0.40
13883}
13884
13885impl Default for LegalDocumentsConfig {
13886    fn default() -> Self {
13887        Self {
13888            enabled: false,
13889            legal_opinion_probability: default_legal_opinion_probability(),
13890        }
13891    }
13892}
13893
13894/// Filters which standards are included in the generation.
13895#[derive(Debug, Clone, Default, Serialize, Deserialize)]
13896pub struct StandardsSelectionConfig {
13897    /// Standard categories to include (accounting, auditing, regulatory, tax, esg).
13898    /// Empty = all categories.
13899    #[serde(default)]
13900    pub categories: Vec<String>,
13901    /// Explicit standard IDs to include (e.g., ["IFRS-16", "ASC-606"]).
13902    /// When non-empty, only these standards (plus mandatory ones for selected jurisdictions) are used.
13903    #[serde(default)]
13904    pub include: Vec<String>,
13905    /// Standard IDs to exclude.
13906    #[serde(default)]
13907    pub exclude: Vec<String>,
13908    /// Include superseded standards in the output (for historical analysis).
13909    #[serde(default)]
13910    pub include_superseded: bool,
13911}
13912
13913/// Configuration for audit procedure template generation.
13914#[derive(Debug, Clone, Serialize, Deserialize)]
13915pub struct AuditProcedureGenConfig {
13916    /// Whether audit procedure generation is enabled.
13917    #[serde(default)]
13918    pub enabled: bool,
13919    /// Number of procedures to generate per applicable standard.
13920    #[serde(default = "default_procedures_per_standard")]
13921    pub procedures_per_standard: usize,
13922    /// Sampling methodology: "statistical", "non_statistical", "mixed".
13923    #[serde(default = "default_sampling_method")]
13924    pub sampling_method: String,
13925    /// Confidence level for statistical sampling (0.0-1.0).
13926    #[serde(default = "default_confidence_level")]
13927    pub confidence_level: f64,
13928    /// Tolerable misstatement rate for sampling (0.0-1.0).
13929    #[serde(default = "default_tolerable_misstatement")]
13930    pub tolerable_misstatement: f64,
13931}
13932
13933fn default_procedures_per_standard() -> usize {
13934    3
13935}
13936
13937fn default_sampling_method() -> String {
13938    "statistical".to_string()
13939}
13940
13941fn default_confidence_level() -> f64 {
13942    0.95
13943}
13944
13945fn default_tolerable_misstatement() -> f64 {
13946    0.05
13947}
13948
13949impl Default for AuditProcedureGenConfig {
13950    fn default() -> Self {
13951        Self {
13952            enabled: false,
13953            procedures_per_standard: default_procedures_per_standard(),
13954            sampling_method: default_sampling_method(),
13955            confidence_level: default_confidence_level(),
13956            tolerable_misstatement: default_tolerable_misstatement(),
13957        }
13958    }
13959}
13960
13961/// Configuration for compliance finding generation.
13962#[derive(Debug, Clone, Serialize, Deserialize)]
13963pub struct ComplianceFindingGenConfig {
13964    /// Whether finding generation is enabled.
13965    #[serde(default)]
13966    pub enabled: bool,
13967    /// Rate of findings per audit procedure (0.0-1.0).
13968    #[serde(default = "default_finding_rate")]
13969    pub finding_rate: f64,
13970    /// Rate of material weakness findings among all findings (0.0-1.0).
13971    #[serde(default = "default_cr_material_weakness_rate")]
13972    pub material_weakness_rate: f64,
13973    /// Rate of significant deficiency findings among all findings (0.0-1.0).
13974    #[serde(default = "default_cr_significant_deficiency_rate")]
13975    pub significant_deficiency_rate: f64,
13976    /// Whether to generate remediation plans for findings.
13977    #[serde(default = "default_true")]
13978    pub generate_remediation: bool,
13979}
13980
13981fn default_finding_rate() -> f64 {
13982    0.05
13983}
13984
13985fn default_cr_material_weakness_rate() -> f64 {
13986    0.02
13987}
13988
13989fn default_cr_significant_deficiency_rate() -> f64 {
13990    0.08
13991}
13992
13993impl Default for ComplianceFindingGenConfig {
13994    fn default() -> Self {
13995        Self {
13996            enabled: false,
13997            finding_rate: default_finding_rate(),
13998            material_weakness_rate: default_cr_material_weakness_rate(),
13999            significant_deficiency_rate: default_cr_significant_deficiency_rate(),
14000            generate_remediation: true,
14001        }
14002    }
14003}
14004
14005/// Configuration for regulatory filing generation.
14006#[derive(Debug, Clone, Serialize, Deserialize)]
14007pub struct ComplianceFilingGenConfig {
14008    /// Whether filing generation is enabled.
14009    #[serde(default)]
14010    pub enabled: bool,
14011    /// Filing types to include (e.g., ["10-K", "10-Q", "Jahresabschluss"]).
14012    /// Empty = all applicable filings for the selected jurisdictions.
14013    #[serde(default)]
14014    pub filing_types: Vec<String>,
14015    /// Generate filing status progression (draft → filed → accepted).
14016    #[serde(default = "default_true")]
14017    pub generate_status_progression: bool,
14018}
14019
14020impl Default for ComplianceFilingGenConfig {
14021    fn default() -> Self {
14022        Self {
14023            enabled: false,
14024            filing_types: Vec::new(),
14025            generate_status_progression: true,
14026        }
14027    }
14028}
14029
14030/// Configuration for compliance graph integration.
14031#[derive(Debug, Clone, Serialize, Deserialize)]
14032pub struct ComplianceGraphConfig {
14033    /// Whether compliance graph integration is enabled.
14034    #[serde(default)]
14035    pub enabled: bool,
14036    /// Include compliance nodes (Standard, Regulation, Jurisdiction, etc.).
14037    #[serde(default = "default_true")]
14038    pub include_compliance_nodes: bool,
14039    /// Include compliance edges (MapsToStandard, TestsControl, etc.).
14040    #[serde(default = "default_true")]
14041    pub include_compliance_edges: bool,
14042    /// Include cross-reference edges between standards.
14043    #[serde(default = "default_true")]
14044    pub include_cross_references: bool,
14045    /// Include temporal supersession edges.
14046    #[serde(default)]
14047    pub include_supersession_edges: bool,
14048    /// Include edges linking standards to the GL account types they govern.
14049    #[serde(default = "default_true")]
14050    pub include_account_links: bool,
14051    /// Include edges linking standards to the internal controls that implement them.
14052    #[serde(default = "default_true")]
14053    pub include_control_links: bool,
14054    /// Include edges linking filings and jurisdictions to the originating company.
14055    #[serde(default = "default_true")]
14056    pub include_company_links: bool,
14057}
14058
14059impl Default for ComplianceGraphConfig {
14060    fn default() -> Self {
14061        Self {
14062            enabled: false,
14063            include_compliance_nodes: true,
14064            include_compliance_edges: true,
14065            include_cross_references: true,
14066            include_supersession_edges: false,
14067            include_account_links: true,
14068            include_control_links: true,
14069            include_company_links: true,
14070        }
14071    }
14072}
14073
14074/// Output settings for compliance-specific data files.
14075#[derive(Debug, Clone, Serialize, Deserialize)]
14076pub struct ComplianceOutputConfig {
14077    /// Export the standards registry catalog.
14078    #[serde(default = "default_true")]
14079    pub export_registry: bool,
14080    /// Export jurisdiction profiles.
14081    #[serde(default = "default_true")]
14082    pub export_jurisdictions: bool,
14083    /// Export cross-reference map.
14084    #[serde(default = "default_true")]
14085    pub export_cross_references: bool,
14086    /// Export temporal version history.
14087    #[serde(default)]
14088    pub export_version_history: bool,
14089}
14090
14091impl Default for ComplianceOutputConfig {
14092    fn default() -> Self {
14093        Self {
14094            export_registry: true,
14095            export_jurisdictions: true,
14096            export_cross_references: true,
14097            export_version_history: false,
14098        }
14099    }
14100}
14101
14102#[cfg(test)]
14103#[allow(clippy::unwrap_used)]
14104mod tests {
14105    use super::*;
14106    use crate::presets::demo_preset;
14107
14108    // ==========================================================================
14109    // Serialization/Deserialization Tests
14110    // ==========================================================================
14111
14112    #[test]
14113    fn test_config_yaml_roundtrip() {
14114        let config = demo_preset();
14115        let yaml = serde_yaml::to_string(&config).expect("Failed to serialize to YAML");
14116        let deserialized: GeneratorConfig =
14117            serde_yaml::from_str(&yaml).expect("Failed to deserialize from YAML");
14118
14119        assert_eq!(
14120            config.global.period_months,
14121            deserialized.global.period_months
14122        );
14123        assert_eq!(config.global.industry, deserialized.global.industry);
14124        assert_eq!(config.companies.len(), deserialized.companies.len());
14125        assert_eq!(config.companies[0].code, deserialized.companies[0].code);
14126    }
14127
14128    #[test]
14129    fn test_config_json_roundtrip() {
14130        // Create a config without infinity values (JSON can't serialize f64::INFINITY)
14131        let mut config = demo_preset();
14132        // Replace infinity with a large but finite value for JSON compatibility
14133        config.master_data.employees.approval_limits.executive = 1e12;
14134
14135        let json = serde_json::to_string(&config).expect("Failed to serialize to JSON");
14136        let deserialized: GeneratorConfig =
14137            serde_json::from_str(&json).expect("Failed to deserialize from JSON");
14138
14139        assert_eq!(
14140            config.global.period_months,
14141            deserialized.global.period_months
14142        );
14143        assert_eq!(config.global.industry, deserialized.global.industry);
14144        assert_eq!(config.companies.len(), deserialized.companies.len());
14145    }
14146
14147    #[test]
14148    fn test_transaction_volume_serialization() {
14149        // Test various transaction volumes serialize correctly
14150        let volumes = vec![
14151            (TransactionVolume::TenK, "ten_k"),
14152            (TransactionVolume::HundredK, "hundred_k"),
14153            (TransactionVolume::OneM, "one_m"),
14154            (TransactionVolume::TenM, "ten_m"),
14155            (TransactionVolume::HundredM, "hundred_m"),
14156        ];
14157
14158        for (volume, expected_key) in volumes {
14159            let json = serde_json::to_string(&volume).expect("Failed to serialize");
14160            assert!(
14161                json.contains(expected_key),
14162                "Expected {} in JSON: {}",
14163                expected_key,
14164                json
14165            );
14166        }
14167    }
14168
14169    #[test]
14170    fn test_transaction_volume_custom_serialization() {
14171        let volume = TransactionVolume::Custom(12345);
14172        let json = serde_json::to_string(&volume).expect("Failed to serialize");
14173        let deserialized: TransactionVolume =
14174            serde_json::from_str(&json).expect("Failed to deserialize");
14175        assert_eq!(deserialized.count(), 12345);
14176    }
14177
14178    #[test]
14179    fn test_output_mode_serialization() {
14180        let modes = vec![
14181            OutputMode::Streaming,
14182            OutputMode::FlatFile,
14183            OutputMode::Both,
14184        ];
14185
14186        for mode in modes {
14187            let json = serde_json::to_string(&mode).expect("Failed to serialize");
14188            let deserialized: OutputMode =
14189                serde_json::from_str(&json).expect("Failed to deserialize");
14190            assert!(format!("{:?}", mode) == format!("{:?}", deserialized));
14191        }
14192    }
14193
14194    #[test]
14195    fn test_file_format_serialization() {
14196        let formats = vec![
14197            FileFormat::Csv,
14198            FileFormat::Parquet,
14199            FileFormat::Json,
14200            FileFormat::JsonLines,
14201        ];
14202
14203        for format in formats {
14204            let json = serde_json::to_string(&format).expect("Failed to serialize");
14205            let deserialized: FileFormat =
14206                serde_json::from_str(&json).expect("Failed to deserialize");
14207            assert!(format!("{:?}", format) == format!("{:?}", deserialized));
14208        }
14209    }
14210
14211    #[test]
14212    fn test_compression_algorithm_serialization() {
14213        let algos = vec![
14214            CompressionAlgorithm::Gzip,
14215            CompressionAlgorithm::Zstd,
14216            CompressionAlgorithm::Lz4,
14217            CompressionAlgorithm::Snappy,
14218        ];
14219
14220        for algo in algos {
14221            let json = serde_json::to_string(&algo).expect("Failed to serialize");
14222            let deserialized: CompressionAlgorithm =
14223                serde_json::from_str(&json).expect("Failed to deserialize");
14224            assert!(format!("{:?}", algo) == format!("{:?}", deserialized));
14225        }
14226    }
14227
14228    #[test]
14229    fn test_transfer_pricing_method_serialization() {
14230        let methods = vec![
14231            TransferPricingMethod::CostPlus,
14232            TransferPricingMethod::ComparableUncontrolled,
14233            TransferPricingMethod::ResalePrice,
14234            TransferPricingMethod::TransactionalNetMargin,
14235            TransferPricingMethod::ProfitSplit,
14236        ];
14237
14238        for method in methods {
14239            let json = serde_json::to_string(&method).expect("Failed to serialize");
14240            let deserialized: TransferPricingMethod =
14241                serde_json::from_str(&json).expect("Failed to deserialize");
14242            assert!(format!("{:?}", method) == format!("{:?}", deserialized));
14243        }
14244    }
14245
14246    #[test]
14247    fn test_benford_exemption_serialization() {
14248        let exemptions = vec![
14249            BenfordExemption::Recurring,
14250            BenfordExemption::Payroll,
14251            BenfordExemption::FixedFees,
14252            BenfordExemption::RoundAmounts,
14253        ];
14254
14255        for exemption in exemptions {
14256            let json = serde_json::to_string(&exemption).expect("Failed to serialize");
14257            let deserialized: BenfordExemption =
14258                serde_json::from_str(&json).expect("Failed to deserialize");
14259            assert!(format!("{:?}", exemption) == format!("{:?}", deserialized));
14260        }
14261    }
14262
14263    // ==========================================================================
14264    // Default Value Tests
14265    // ==========================================================================
14266
14267    #[test]
14268    fn test_global_config_defaults() {
14269        let yaml = r#"
14270            industry: manufacturing
14271            start_date: "2024-01-01"
14272            period_months: 6
14273        "#;
14274        let config: GlobalConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14275        assert_eq!(config.group_currency, "USD");
14276        assert!(config.parallel);
14277        assert_eq!(config.worker_threads, 0);
14278        assert_eq!(config.memory_limit_mb, 0);
14279    }
14280
14281    #[test]
14282    fn test_fraud_config_defaults() {
14283        let config = FraudConfig::default();
14284        assert!(!config.enabled);
14285        assert_eq!(config.fraud_rate, 0.005);
14286        assert!(!config.clustering_enabled);
14287    }
14288
14289    #[test]
14290    fn test_internal_controls_config_defaults() {
14291        let config = InternalControlsConfig::default();
14292        assert!(!config.enabled);
14293        assert_eq!(config.exception_rate, 0.02);
14294        assert_eq!(config.sod_violation_rate, 0.01);
14295        assert!(config.export_control_master_data);
14296        assert_eq!(config.sox_materiality_threshold, 10000.0);
14297        // COSO fields
14298        assert!(config.coso_enabled);
14299        assert!(!config.include_entity_level_controls);
14300        assert_eq!(config.target_maturity_level, "mixed");
14301    }
14302
14303    #[test]
14304    fn test_output_config_defaults() {
14305        let config = OutputConfig::default();
14306        assert!(matches!(config.mode, OutputMode::FlatFile));
14307        assert_eq!(config.formats, vec![FileFormat::Parquet]);
14308        assert!(config.compression.enabled);
14309        assert!(matches!(
14310            config.compression.algorithm,
14311            CompressionAlgorithm::Zstd
14312        ));
14313        assert!(config.include_acdoca);
14314        assert!(!config.include_bseg);
14315        assert!(config.partition_by_period);
14316        assert!(!config.partition_by_company);
14317    }
14318
14319    #[test]
14320    fn test_approval_config_defaults() {
14321        let config = ApprovalConfig::default();
14322        assert!(!config.enabled);
14323        assert_eq!(config.auto_approve_threshold, 1000.0);
14324        assert_eq!(config.rejection_rate, 0.02);
14325        assert_eq!(config.revision_rate, 0.05);
14326        assert_eq!(config.average_approval_delay_hours, 4.0);
14327        assert_eq!(config.thresholds.len(), 4);
14328    }
14329
14330    #[test]
14331    fn test_p2p_flow_config_defaults() {
14332        let config = P2PFlowConfig::default();
14333        assert!(config.enabled);
14334        assert_eq!(config.three_way_match_rate, 0.95);
14335        assert_eq!(config.partial_delivery_rate, 0.15);
14336        assert_eq!(config.average_po_to_gr_days, 14);
14337    }
14338
14339    #[test]
14340    fn test_o2c_flow_config_defaults() {
14341        let config = O2CFlowConfig::default();
14342        assert!(config.enabled);
14343        assert_eq!(config.credit_check_failure_rate, 0.02);
14344        assert_eq!(config.return_rate, 0.03);
14345        assert_eq!(config.bad_debt_rate, 0.01);
14346    }
14347
14348    #[test]
14349    fn test_balance_config_defaults() {
14350        let config = BalanceConfig::default();
14351        assert!(!config.generate_opening_balances);
14352        assert!(config.generate_trial_balances);
14353        assert_eq!(config.target_gross_margin, 0.35);
14354        assert!(config.validate_balance_equation);
14355        assert!(config.reconcile_subledgers);
14356    }
14357
14358    // ==========================================================================
14359    // Partial Config Deserialization Tests
14360    // ==========================================================================
14361
14362    #[test]
14363    fn test_partial_config_with_defaults() {
14364        // Minimal config that should use all defaults
14365        let yaml = r#"
14366            global:
14367              industry: manufacturing
14368              start_date: "2024-01-01"
14369              period_months: 3
14370            companies:
14371              - code: "TEST"
14372                name: "Test Company"
14373                currency: "USD"
14374                country: "US"
14375                annual_transaction_volume: ten_k
14376            chart_of_accounts:
14377              complexity: small
14378            output:
14379              output_directory: "./output"
14380        "#;
14381
14382        let config: GeneratorConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14383        assert_eq!(config.global.period_months, 3);
14384        assert_eq!(config.companies.len(), 1);
14385        assert!(!config.fraud.enabled); // Default
14386        assert!(!config.internal_controls.enabled); // Default
14387    }
14388
14389    #[test]
14390    fn test_config_with_fraud_enabled() {
14391        let yaml = r#"
14392            global:
14393              industry: retail
14394              start_date: "2024-01-01"
14395              period_months: 12
14396            companies:
14397              - code: "RETAIL"
14398                name: "Retail Co"
14399                currency: "USD"
14400                country: "US"
14401                annual_transaction_volume: hundred_k
14402            chart_of_accounts:
14403              complexity: medium
14404            output:
14405              output_directory: "./output"
14406            fraud:
14407              enabled: true
14408              fraud_rate: 0.05
14409              clustering_enabled: true
14410        "#;
14411
14412        let config: GeneratorConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14413        assert!(config.fraud.enabled);
14414        assert_eq!(config.fraud.fraud_rate, 0.05);
14415        assert!(config.fraud.clustering_enabled);
14416    }
14417
14418    #[test]
14419    fn test_config_with_multiple_companies() {
14420        let yaml = r#"
14421            global:
14422              industry: manufacturing
14423              start_date: "2024-01-01"
14424              period_months: 6
14425            companies:
14426              - code: "HQ"
14427                name: "Headquarters"
14428                currency: "USD"
14429                country: "US"
14430                annual_transaction_volume: hundred_k
14431                volume_weight: 1.0
14432              - code: "EU"
14433                name: "European Subsidiary"
14434                currency: "EUR"
14435                country: "DE"
14436                annual_transaction_volume: hundred_k
14437                volume_weight: 0.5
14438              - code: "APAC"
14439                name: "Asia Pacific"
14440                currency: "JPY"
14441                country: "JP"
14442                annual_transaction_volume: ten_k
14443                volume_weight: 0.3
14444            chart_of_accounts:
14445              complexity: large
14446            output:
14447              output_directory: "./output"
14448        "#;
14449
14450        let config: GeneratorConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14451        assert_eq!(config.companies.len(), 3);
14452        assert_eq!(config.companies[0].code, "HQ");
14453        assert_eq!(config.companies[1].currency, "EUR");
14454        assert_eq!(config.companies[2].volume_weight, 0.3);
14455    }
14456
14457    #[test]
14458    fn test_intercompany_config() {
14459        let yaml = r#"
14460            enabled: true
14461            ic_transaction_rate: 0.20
14462            transfer_pricing_method: cost_plus
14463            markup_percent: 0.08
14464            generate_matched_pairs: true
14465            generate_eliminations: true
14466        "#;
14467
14468        let config: IntercompanyConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14469        assert!(config.enabled);
14470        assert_eq!(config.ic_transaction_rate, 0.20);
14471        assert!(matches!(
14472            config.transfer_pricing_method,
14473            TransferPricingMethod::CostPlus
14474        ));
14475        assert_eq!(config.markup_percent, 0.08);
14476        assert!(config.generate_eliminations);
14477    }
14478
14479    // ==========================================================================
14480    // Company Config Tests
14481    // ==========================================================================
14482
14483    #[test]
14484    fn test_company_config_defaults() {
14485        let yaml = r#"
14486            code: "TEST"
14487            name: "Test Company"
14488            currency: "USD"
14489            country: "US"
14490            annual_transaction_volume: ten_k
14491        "#;
14492
14493        let config: CompanyConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14494        assert_eq!(config.fiscal_year_variant, "K4"); // Default
14495        assert_eq!(config.volume_weight, 1.0); // Default
14496    }
14497
14498    // ==========================================================================
14499    // Chart of Accounts Config Tests
14500    // ==========================================================================
14501
14502    #[test]
14503    fn test_coa_config_defaults() {
14504        let yaml = r#"
14505            complexity: medium
14506        "#;
14507
14508        let config: ChartOfAccountsConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14509        assert!(config.industry_specific); // Default true
14510        assert!(config.custom_accounts.is_none());
14511        assert_eq!(config.min_hierarchy_depth, 2); // Default
14512        assert_eq!(config.max_hierarchy_depth, 5); // Default
14513    }
14514
14515    // ==========================================================================
14516    // Accounting Standards Config Tests
14517    // ==========================================================================
14518
14519    #[test]
14520    fn test_accounting_standards_config_defaults() {
14521        let config = AccountingStandardsConfig::default();
14522        assert!(!config.enabled);
14523        assert!(config.framework.is_none());
14524        assert!(!config.revenue_recognition.enabled);
14525        assert!(!config.leases.enabled);
14526        assert!(!config.fair_value.enabled);
14527        assert!(!config.impairment.enabled);
14528        assert!(!config.generate_differences);
14529    }
14530
14531    #[test]
14532    fn test_accounting_standards_config_yaml() {
14533        let yaml = r#"
14534            enabled: true
14535            framework: ifrs
14536            revenue_recognition:
14537              enabled: true
14538              generate_contracts: true
14539              avg_obligations_per_contract: 2.5
14540              variable_consideration_rate: 0.20
14541              over_time_recognition_rate: 0.35
14542              contract_count: 150
14543            leases:
14544              enabled: true
14545              lease_count: 75
14546              finance_lease_percent: 0.25
14547              avg_lease_term_months: 48
14548            generate_differences: true
14549        "#;
14550
14551        let config: AccountingStandardsConfig =
14552            serde_yaml::from_str(yaml).expect("Failed to parse");
14553        assert!(config.enabled);
14554        assert!(matches!(
14555            config.framework,
14556            Some(AccountingFrameworkConfig::Ifrs)
14557        ));
14558        assert!(config.revenue_recognition.enabled);
14559        assert_eq!(config.revenue_recognition.contract_count, 150);
14560        assert_eq!(config.revenue_recognition.avg_obligations_per_contract, 2.5);
14561        assert!(config.leases.enabled);
14562        assert_eq!(config.leases.lease_count, 75);
14563        assert_eq!(config.leases.finance_lease_percent, 0.25);
14564        assert!(config.generate_differences);
14565    }
14566
14567    #[test]
14568    fn test_accounting_framework_serialization() {
14569        let frameworks = [
14570            AccountingFrameworkConfig::UsGaap,
14571            AccountingFrameworkConfig::Ifrs,
14572            AccountingFrameworkConfig::DualReporting,
14573            AccountingFrameworkConfig::FrenchGaap,
14574            AccountingFrameworkConfig::GermanGaap,
14575        ];
14576
14577        for framework in frameworks {
14578            let json = serde_json::to_string(&framework).expect("Failed to serialize");
14579            let deserialized: AccountingFrameworkConfig =
14580                serde_json::from_str(&json).expect("Failed to deserialize");
14581            assert!(format!("{:?}", framework) == format!("{:?}", deserialized));
14582        }
14583    }
14584
14585    #[test]
14586    fn test_revenue_recognition_config_defaults() {
14587        let config = RevenueRecognitionConfig::default();
14588        assert!(!config.enabled);
14589        assert!(config.generate_contracts);
14590        assert_eq!(config.avg_obligations_per_contract, 2.0);
14591        assert_eq!(config.variable_consideration_rate, 0.15);
14592        assert_eq!(config.over_time_recognition_rate, 0.30);
14593        assert_eq!(config.contract_count, 100);
14594    }
14595
14596    #[test]
14597    fn test_lease_accounting_config_defaults() {
14598        let config = LeaseAccountingConfig::default();
14599        assert!(!config.enabled);
14600        assert_eq!(config.lease_count, 50);
14601        assert_eq!(config.finance_lease_percent, 0.30);
14602        assert_eq!(config.avg_lease_term_months, 60);
14603        assert!(config.generate_amortization);
14604        assert_eq!(config.real_estate_percent, 0.40);
14605    }
14606
14607    #[test]
14608    fn test_fair_value_config_defaults() {
14609        let config = FairValueConfig::default();
14610        assert!(!config.enabled);
14611        assert_eq!(config.measurement_count, 25);
14612        assert_eq!(config.level1_percent, 0.40);
14613        assert_eq!(config.level2_percent, 0.35);
14614        assert_eq!(config.level3_percent, 0.25);
14615        assert!(!config.include_sensitivity_analysis);
14616    }
14617
14618    #[test]
14619    fn test_impairment_config_defaults() {
14620        let config = ImpairmentConfig::default();
14621        assert!(!config.enabled);
14622        assert_eq!(config.test_count, 15);
14623        assert_eq!(config.impairment_rate, 0.10);
14624        assert!(config.generate_projections);
14625        assert!(!config.include_goodwill);
14626    }
14627
14628    // ==========================================================================
14629    // Audit Standards Config Tests
14630    // ==========================================================================
14631
14632    #[test]
14633    fn test_audit_standards_config_defaults() {
14634        let config = AuditStandardsConfig::default();
14635        assert!(!config.enabled);
14636        assert!(!config.isa_compliance.enabled);
14637        assert!(!config.analytical_procedures.enabled);
14638        assert!(!config.confirmations.enabled);
14639        assert!(!config.opinion.enabled);
14640        assert!(!config.generate_audit_trail);
14641        assert!(!config.sox.enabled);
14642        assert!(!config.pcaob.enabled);
14643    }
14644
14645    #[test]
14646    fn test_audit_standards_config_yaml() {
14647        let yaml = r#"
14648            enabled: true
14649            isa_compliance:
14650              enabled: true
14651              compliance_level: comprehensive
14652              generate_isa_mappings: true
14653              include_pcaob: true
14654              framework: dual
14655            analytical_procedures:
14656              enabled: true
14657              procedures_per_account: 5
14658              variance_probability: 0.25
14659            confirmations:
14660              enabled: true
14661              confirmation_count: 75
14662              positive_response_rate: 0.90
14663              exception_rate: 0.08
14664            opinion:
14665              enabled: true
14666              generate_kam: true
14667              average_kam_count: 4
14668            sox:
14669              enabled: true
14670              generate_302_certifications: true
14671              generate_404_assessments: true
14672              material_weakness_rate: 0.03
14673            pcaob:
14674              enabled: true
14675              is_pcaob_audit: true
14676              include_icfr_opinion: true
14677            generate_audit_trail: true
14678        "#;
14679
14680        let config: AuditStandardsConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14681        assert!(config.enabled);
14682        assert!(config.isa_compliance.enabled);
14683        assert_eq!(config.isa_compliance.compliance_level, "comprehensive");
14684        assert!(config.isa_compliance.include_pcaob);
14685        assert_eq!(config.isa_compliance.framework, "dual");
14686        assert!(config.analytical_procedures.enabled);
14687        assert_eq!(config.analytical_procedures.procedures_per_account, 5);
14688        assert!(config.confirmations.enabled);
14689        assert_eq!(config.confirmations.confirmation_count, 75);
14690        assert!(config.opinion.enabled);
14691        assert_eq!(config.opinion.average_kam_count, 4);
14692        assert!(config.sox.enabled);
14693        assert!(config.sox.generate_302_certifications);
14694        assert_eq!(config.sox.material_weakness_rate, 0.03);
14695        assert!(config.pcaob.enabled);
14696        assert!(config.pcaob.is_pcaob_audit);
14697        assert!(config.pcaob.include_icfr_opinion);
14698        assert!(config.generate_audit_trail);
14699    }
14700
14701    #[test]
14702    fn test_isa_compliance_config_defaults() {
14703        let config = IsaComplianceConfig::default();
14704        assert!(!config.enabled);
14705        assert_eq!(config.compliance_level, "standard");
14706        assert!(config.generate_isa_mappings);
14707        assert!(config.generate_coverage_summary);
14708        assert!(!config.include_pcaob);
14709        assert_eq!(config.framework, "isa");
14710    }
14711
14712    #[test]
14713    fn test_sox_compliance_config_defaults() {
14714        let config = SoxComplianceConfig::default();
14715        assert!(!config.enabled);
14716        assert!(config.generate_302_certifications);
14717        assert!(config.generate_404_assessments);
14718        assert_eq!(config.materiality_threshold, 10000.0);
14719        assert_eq!(config.material_weakness_rate, 0.02);
14720        assert_eq!(config.significant_deficiency_rate, 0.08);
14721    }
14722
14723    #[test]
14724    fn test_pcaob_config_defaults() {
14725        let config = PcaobConfig::default();
14726        assert!(!config.enabled);
14727        assert!(!config.is_pcaob_audit);
14728        assert!(config.generate_cam);
14729        assert!(!config.include_icfr_opinion);
14730        assert!(!config.generate_standard_mappings);
14731    }
14732
14733    #[test]
14734    fn test_config_with_standards_enabled() {
14735        let yaml = r#"
14736            global:
14737              industry: financial_services
14738              start_date: "2024-01-01"
14739              period_months: 12
14740            companies:
14741              - code: "BANK"
14742                name: "Test Bank"
14743                currency: "USD"
14744                country: "US"
14745                annual_transaction_volume: hundred_k
14746            chart_of_accounts:
14747              complexity: large
14748            output:
14749              output_directory: "./output"
14750            accounting_standards:
14751              enabled: true
14752              framework: us_gaap
14753              revenue_recognition:
14754                enabled: true
14755              leases:
14756                enabled: true
14757            audit_standards:
14758              enabled: true
14759              isa_compliance:
14760                enabled: true
14761              sox:
14762                enabled: true
14763        "#;
14764
14765        let config: GeneratorConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14766        assert!(config.accounting_standards.enabled);
14767        assert!(matches!(
14768            config.accounting_standards.framework,
14769            Some(AccountingFrameworkConfig::UsGaap)
14770        ));
14771        assert!(config.accounting_standards.revenue_recognition.enabled);
14772        assert!(config.accounting_standards.leases.enabled);
14773        assert!(config.audit_standards.enabled);
14774        assert!(config.audit_standards.isa_compliance.enabled);
14775        assert!(config.audit_standards.sox.enabled);
14776    }
14777
14778    // ==========================================================================
14779    // Industry-Specific Config Tests
14780    // ==========================================================================
14781
14782    #[test]
14783    fn test_industry_specific_config_defaults() {
14784        let config = IndustrySpecificConfig::default();
14785        assert!(!config.enabled);
14786        assert!(!config.manufacturing.enabled);
14787        assert!(!config.retail.enabled);
14788        assert!(!config.healthcare.enabled);
14789        assert!(!config.technology.enabled);
14790        assert!(!config.financial_services.enabled);
14791        assert!(!config.professional_services.enabled);
14792    }
14793
14794    #[test]
14795    fn test_manufacturing_config_defaults() {
14796        let config = ManufacturingConfig::default();
14797        assert!(!config.enabled);
14798        assert_eq!(config.bom_depth, 4);
14799        assert!(!config.just_in_time);
14800        assert_eq!(config.supplier_tiers, 2);
14801        assert_eq!(config.target_yield_rate, 0.97);
14802        assert_eq!(config.scrap_alert_threshold, 0.03);
14803    }
14804
14805    #[test]
14806    fn test_retail_config_defaults() {
14807        let config = RetailConfig::default();
14808        assert!(!config.enabled);
14809        assert_eq!(config.avg_daily_transactions, 500);
14810        assert!(config.loss_prevention);
14811        assert_eq!(config.shrinkage_rate, 0.015);
14812    }
14813
14814    #[test]
14815    fn test_healthcare_config_defaults() {
14816        let config = HealthcareConfig::default();
14817        assert!(!config.enabled);
14818        assert_eq!(config.facility_type, "hospital");
14819        assert_eq!(config.avg_daily_encounters, 150);
14820        assert!(config.compliance.hipaa);
14821        assert!(config.compliance.stark_law);
14822        assert!(config.coding_systems.icd10);
14823        assert!(config.coding_systems.cpt);
14824    }
14825
14826    #[test]
14827    fn test_technology_config_defaults() {
14828        let config = TechnologyConfig::default();
14829        assert!(!config.enabled);
14830        assert_eq!(config.revenue_model, "saas");
14831        assert_eq!(config.subscription_revenue_pct, 0.60);
14832        assert!(config.rd_capitalization.enabled);
14833    }
14834
14835    #[test]
14836    fn test_config_with_industry_specific() {
14837        let yaml = r#"
14838            global:
14839              industry: healthcare
14840              start_date: "2024-01-01"
14841              period_months: 12
14842            companies:
14843              - code: "HOSP"
14844                name: "Test Hospital"
14845                currency: "USD"
14846                country: "US"
14847                annual_transaction_volume: hundred_k
14848            chart_of_accounts:
14849              complexity: medium
14850            output:
14851              output_directory: "./output"
14852            industry_specific:
14853              enabled: true
14854              healthcare:
14855                enabled: true
14856                facility_type: hospital
14857                payer_mix:
14858                  medicare: 0.45
14859                  medicaid: 0.15
14860                  commercial: 0.35
14861                  self_pay: 0.05
14862                coding_systems:
14863                  icd10: true
14864                  cpt: true
14865                  drg: true
14866                compliance:
14867                  hipaa: true
14868                  stark_law: true
14869                anomaly_rates:
14870                  upcoding: 0.03
14871                  unbundling: 0.02
14872        "#;
14873
14874        let config: GeneratorConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14875        assert!(config.industry_specific.enabled);
14876        assert!(config.industry_specific.healthcare.enabled);
14877        assert_eq!(
14878            config.industry_specific.healthcare.facility_type,
14879            "hospital"
14880        );
14881        assert_eq!(config.industry_specific.healthcare.payer_mix.medicare, 0.45);
14882        assert_eq!(config.industry_specific.healthcare.payer_mix.self_pay, 0.05);
14883        assert!(config.industry_specific.healthcare.coding_systems.icd10);
14884        assert!(config.industry_specific.healthcare.compliance.hipaa);
14885        assert_eq!(
14886            config.industry_specific.healthcare.anomaly_rates.upcoding,
14887            0.03
14888        );
14889    }
14890
14891    #[test]
14892    fn test_config_with_manufacturing_specific() {
14893        let yaml = r#"
14894            global:
14895              industry: manufacturing
14896              start_date: "2024-01-01"
14897              period_months: 12
14898            companies:
14899              - code: "MFG"
14900                name: "Test Manufacturing"
14901                currency: "USD"
14902                country: "US"
14903                annual_transaction_volume: hundred_k
14904            chart_of_accounts:
14905              complexity: medium
14906            output:
14907              output_directory: "./output"
14908            industry_specific:
14909              enabled: true
14910              manufacturing:
14911                enabled: true
14912                bom_depth: 5
14913                just_in_time: true
14914                supplier_tiers: 3
14915                target_yield_rate: 0.98
14916                anomaly_rates:
14917                  yield_manipulation: 0.02
14918                  phantom_production: 0.01
14919        "#;
14920
14921        let config: GeneratorConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
14922        assert!(config.industry_specific.enabled);
14923        assert!(config.industry_specific.manufacturing.enabled);
14924        assert_eq!(config.industry_specific.manufacturing.bom_depth, 5);
14925        assert!(config.industry_specific.manufacturing.just_in_time);
14926        assert_eq!(config.industry_specific.manufacturing.supplier_tiers, 3);
14927        assert_eq!(
14928            config.industry_specific.manufacturing.target_yield_rate,
14929            0.98
14930        );
14931        assert_eq!(
14932            config
14933                .industry_specific
14934                .manufacturing
14935                .anomaly_rates
14936                .yield_manipulation,
14937            0.02
14938        );
14939    }
14940
14941    // ==========================================================================
14942    // Tax Configuration Tests
14943    // ==========================================================================
14944
14945    #[test]
14946    fn test_tax_config_defaults() {
14947        let tax = TaxConfig::default();
14948        assert!(!tax.enabled);
14949        assert!(tax.jurisdictions.countries.is_empty());
14950        assert!(!tax.jurisdictions.include_subnational);
14951        assert!(!tax.vat_gst.enabled);
14952        assert!(tax.vat_gst.standard_rates.is_empty());
14953        assert!(tax.vat_gst.reduced_rates.is_empty());
14954        assert!(tax.vat_gst.exempt_categories.is_empty());
14955        assert!(tax.vat_gst.reverse_charge);
14956        assert!(!tax.sales_tax.enabled);
14957        assert!(tax.sales_tax.nexus_states.is_empty());
14958        assert!(!tax.withholding.enabled);
14959        assert!(tax.withholding.treaty_network);
14960        assert_eq!(tax.withholding.default_rate, 0.30);
14961        assert_eq!(tax.withholding.treaty_reduced_rate, 0.15);
14962        assert!(tax.provisions.enabled);
14963        assert_eq!(tax.provisions.statutory_rate, 0.21);
14964        assert!(tax.provisions.uncertain_positions);
14965        assert!(!tax.payroll_tax.enabled);
14966        assert_eq!(tax.anomaly_rate, 0.03);
14967    }
14968
14969    #[test]
14970    fn test_tax_config_from_yaml() {
14971        let yaml = r#"
14972            global:
14973              seed: 42
14974              start_date: "2024-01-01"
14975              period_months: 12
14976              industry: retail
14977            companies:
14978              - code: C001
14979                name: Test Corp
14980                currency: USD
14981                country: US
14982                annual_transaction_volume: ten_k
14983            chart_of_accounts:
14984              complexity: small
14985            output:
14986              output_directory: ./output
14987            tax:
14988              enabled: true
14989              anomaly_rate: 0.05
14990              jurisdictions:
14991                countries: ["US", "DE", "GB"]
14992                include_subnational: true
14993              vat_gst:
14994                enabled: true
14995                standard_rates:
14996                  DE: 0.19
14997                  GB: 0.20
14998                reduced_rates:
14999                  DE: 0.07
15000                  GB: 0.05
15001                exempt_categories:
15002                  - financial_services
15003                  - healthcare
15004                reverse_charge: false
15005              sales_tax:
15006                enabled: true
15007                nexus_states: ["CA", "NY", "TX"]
15008              withholding:
15009                enabled: true
15010                treaty_network: false
15011                default_rate: 0.25
15012                treaty_reduced_rate: 0.10
15013              provisions:
15014                enabled: false
15015                statutory_rate: 0.28
15016                uncertain_positions: false
15017              payroll_tax:
15018                enabled: true
15019        "#;
15020
15021        let config: GeneratorConfig = serde_yaml::from_str(yaml).expect("Failed to parse");
15022        assert!(config.tax.enabled);
15023        assert_eq!(config.tax.anomaly_rate, 0.05);
15024
15025        // Jurisdictions
15026        assert_eq!(config.tax.jurisdictions.countries.len(), 3);
15027        assert!(config
15028            .tax
15029            .jurisdictions
15030            .countries
15031            .contains(&"DE".to_string()));
15032        assert!(config.tax.jurisdictions.include_subnational);
15033
15034        // VAT/GST
15035        assert!(config.tax.vat_gst.enabled);
15036        assert_eq!(config.tax.vat_gst.standard_rates.get("DE"), Some(&0.19));
15037        assert_eq!(config.tax.vat_gst.standard_rates.get("GB"), Some(&0.20));
15038        assert_eq!(config.tax.vat_gst.reduced_rates.get("DE"), Some(&0.07));
15039        assert_eq!(config.tax.vat_gst.exempt_categories.len(), 2);
15040        assert!(!config.tax.vat_gst.reverse_charge);
15041
15042        // Sales tax
15043        assert!(config.tax.sales_tax.enabled);
15044        assert_eq!(config.tax.sales_tax.nexus_states.len(), 3);
15045        assert!(config
15046            .tax
15047            .sales_tax
15048            .nexus_states
15049            .contains(&"CA".to_string()));
15050
15051        // Withholding
15052        assert!(config.tax.withholding.enabled);
15053        assert!(!config.tax.withholding.treaty_network);
15054        assert_eq!(config.tax.withholding.default_rate, 0.25);
15055        assert_eq!(config.tax.withholding.treaty_reduced_rate, 0.10);
15056
15057        // Provisions
15058        assert!(!config.tax.provisions.enabled);
15059        assert_eq!(config.tax.provisions.statutory_rate, 0.28);
15060        assert!(!config.tax.provisions.uncertain_positions);
15061
15062        // Payroll tax
15063        assert!(config.tax.payroll_tax.enabled);
15064    }
15065
15066    #[test]
15067    fn test_generator_config_with_tax_default() {
15068        let yaml = r#"
15069            global:
15070              seed: 42
15071              start_date: "2024-01-01"
15072              period_months: 12
15073              industry: retail
15074            companies:
15075              - code: C001
15076                name: Test Corp
15077                currency: USD
15078                country: US
15079                annual_transaction_volume: ten_k
15080            chart_of_accounts:
15081              complexity: small
15082            output:
15083              output_directory: ./output
15084        "#;
15085
15086        let config: GeneratorConfig =
15087            serde_yaml::from_str(yaml).expect("Failed to parse config without tax section");
15088        // Tax should be present with defaults when not specified in YAML
15089        assert!(!config.tax.enabled);
15090        assert!(config.tax.jurisdictions.countries.is_empty());
15091        assert_eq!(config.tax.anomaly_rate, 0.03);
15092        assert!(config.tax.provisions.enabled); // provisions default to enabled=true
15093        assert_eq!(config.tax.provisions.statutory_rate, 0.21);
15094    }
15095
15096    // ==========================================================================
15097    // SessionSchemaConfig Tests
15098    // ==========================================================================
15099
15100    #[test]
15101    fn test_session_config_default_disabled() {
15102        let yaml = "{}";
15103        let config: SessionSchemaConfig =
15104            serde_yaml::from_str(yaml).expect("Failed to parse empty session config");
15105        assert!(!config.enabled);
15106        assert!(config.checkpoint_path.is_none());
15107        assert!(config.per_period_output);
15108        assert!(config.consolidated_output);
15109    }
15110
15111    #[test]
15112    fn test_config_backward_compatible_without_session() {
15113        let yaml = r#"
15114            global:
15115              seed: 42
15116              start_date: "2024-01-01"
15117              period_months: 12
15118              industry: retail
15119            companies:
15120              - code: C001
15121                name: Test Corp
15122                currency: USD
15123                country: US
15124                annual_transaction_volume: ten_k
15125            chart_of_accounts:
15126              complexity: small
15127            output:
15128              output_directory: ./output
15129        "#;
15130
15131        let config: GeneratorConfig =
15132            serde_yaml::from_str(yaml).expect("Failed to parse config without session");
15133        // Session should default to disabled
15134        assert!(!config.session.enabled);
15135        assert!(config.session.per_period_output);
15136        assert!(config.session.consolidated_output);
15137        // fiscal_year_months should be None
15138        assert!(config.global.fiscal_year_months.is_none());
15139    }
15140
15141    #[test]
15142    fn test_fiscal_year_months_parsed() {
15143        let yaml = r#"
15144            global:
15145              seed: 42
15146              start_date: "2024-01-01"
15147              period_months: 24
15148              industry: retail
15149              fiscal_year_months: 12
15150            companies:
15151              - code: C001
15152                name: Test Corp
15153                currency: USD
15154                country: US
15155                annual_transaction_volume: ten_k
15156            chart_of_accounts:
15157              complexity: small
15158            output:
15159              output_directory: ./output
15160            session:
15161              enabled: true
15162              checkpoint_path: /tmp/checkpoints
15163              per_period_output: true
15164              consolidated_output: false
15165        "#;
15166
15167        let config: GeneratorConfig =
15168            serde_yaml::from_str(yaml).expect("Failed to parse config with fiscal_year_months");
15169        assert_eq!(config.global.fiscal_year_months, Some(12));
15170        assert!(config.session.enabled);
15171        assert_eq!(
15172            config.session.checkpoint_path,
15173            Some("/tmp/checkpoints".to_string())
15174        );
15175        assert!(config.session.per_period_output);
15176        assert!(!config.session.consolidated_output);
15177    }
15178}