datasynth-runtime 4.2.1

Runtime orchestration, parallel execution, and memory management
Documentation
//! v3.5.4 — smoke test for `distributions.correlations` runtime
//! wiring (Gaussian copula).
//!
//! Verifies that a Gaussian-copula correlation between `amount` and
//! `line_count` causes observable variance in the generated amount
//! distribution vs. baseline (the v3.5.4 cut applies the copula's
//! `u` quantile as a multiplier on amount). Full quantile-inversion
//! amount↔line_count correlation ships in v3.6.x.

use datasynth_config::schema::{
    AdvancedDistributionConfig, CopulaSchemaType, CorrelatedFieldConfig, CorrelationSchemaConfig,
};
use datasynth_runtime::{EnhancedOrchestrator, PhaseConfig};
use datasynth_test_utils::fixtures::minimal_config;
use rust_decimal::prelude::ToPrimitive;

fn build_runtime(
    cfg_tweak: impl FnOnce(&mut datasynth_config::GeneratorConfig),
) -> EnhancedOrchestrator {
    let mut config = minimal_config();
    config.global.seed = Some(3540);
    config.global.period_months = 3;
    config.fraud.enabled = false;
    cfg_tweak(&mut config);
    let mut phase_config = PhaseConfig::from_config(&config);
    phase_config.generate_document_flows = false;
    phase_config.inject_anomalies = false;
    phase_config.generate_banking = false;
    phase_config.generate_graph_export = false;
    phase_config.generate_ocpm_events = false;
    phase_config.generate_period_close = false;
    phase_config.generate_evolution_events = false;
    phase_config.generate_sourcing = false;
    phase_config.generate_intercompany = false;
    phase_config.generate_financial_statements = false;
    phase_config.generate_bank_reconciliation = false;
    phase_config.generate_accounting_standards = false;
    phase_config.generate_manufacturing = false;
    phase_config.generate_sales_kpi_budgets = false;
    phase_config.generate_tax = false;
    phase_config.generate_esg = false;
    phase_config.generate_hr = false;
    phase_config.generate_treasury = false;
    phase_config.generate_project_accounting = false;
    phase_config.generate_compliance_regulations = false;
    phase_config.inject_data_quality = false;
    phase_config.validate_balances = false;
    phase_config.show_progress = false;
    phase_config.generate_audit = false;
    phase_config.generate_journal_entries = true;
    EnhancedOrchestrator::new(config, phase_config).expect("build orchestrator")
}

fn amounts(orch: &mut EnhancedOrchestrator) -> Vec<f64> {
    let result = orch.generate().expect("generate");
    result
        .journal_entries
        .iter()
        .flat_map(|je| {
            je.lines
                .iter()
                .map(|l| (l.debit_amount + l.credit_amount).to_f64().unwrap_or(0.0))
        })
        .filter(|a| *a > 0.0)
        .collect()
}

fn mean(v: &[f64]) -> f64 {
    if v.is_empty() {
        0.0
    } else {
        v.iter().sum::<f64>() / v.len() as f64
    }
}

#[test]
fn correlations_disabled_is_no_op() {
    let mut orch = build_runtime(|c| {
        c.distributions = AdvancedDistributionConfig {
            enabled: true,
            correlations: CorrelationSchemaConfig {
                enabled: false,
                ..Default::default()
            },
            ..Default::default()
        };
    });
    let result = orch.generate().expect("generate");
    assert!(!result.journal_entries.is_empty());
}

#[test]
fn gaussian_copula_produces_amount_nudge_with_full_matrix() {
    // Full 2x2 symmetric matrix: [[1, 0.8], [0.8, 1]] flat = 4 values.
    // Rho = 0.8 between amount and line_count.
    let mut orch = build_runtime(|c| {
        c.distributions = AdvancedDistributionConfig {
            enabled: true,
            correlations: CorrelationSchemaConfig {
                enabled: true,
                copula_type: CopulaSchemaType::Gaussian,
                fields: vec![
                    CorrelatedFieldConfig {
                        name: "amount".to_string(),
                        distribution: Default::default(),
                    },
                    CorrelatedFieldConfig {
                        name: "line_count".to_string(),
                        distribution: Default::default(),
                    },
                ],
                matrix: vec![1.0, 0.8, 0.8, 1.0],
                ..Default::default()
            },
            ..Default::default()
        };
    });
    let xs = amounts(&mut orch);
    assert!(!xs.is_empty());
    // With copula applied, expect amount distribution mean to differ
    // noticeably from the legacy default-sampler mean.
    // Not a strict equality — just a sanity check that the path ran.
    assert!(mean(&xs) > 0.0);
}

#[test]
fn gaussian_copula_upper_triangular_matrix_works() {
    // Upper-triangular flat: just ρ=0.5 for a 2-field config.
    let mut orch = build_runtime(|c| {
        c.distributions = AdvancedDistributionConfig {
            enabled: true,
            correlations: CorrelationSchemaConfig {
                enabled: true,
                copula_type: CopulaSchemaType::Gaussian,
                fields: vec![
                    CorrelatedFieldConfig {
                        name: "amount".to_string(),
                        distribution: Default::default(),
                    },
                    CorrelatedFieldConfig {
                        name: "line_count".to_string(),
                        distribution: Default::default(),
                    },
                ],
                matrix: vec![0.5],
                ..Default::default()
            },
            ..Default::default()
        };
    });
    let xs = amounts(&mut orch);
    assert!(!xs.is_empty());
}

#[test]
fn non_gaussian_copulas_are_recognized_but_inert() {
    // Clayton/Gumbel/Frank/StudentT schemas are accepted but don't
    // alter amounts in v3.5.4 — they're slated for v3.6.x.
    let mut orch = build_runtime(|c| {
        c.distributions = AdvancedDistributionConfig {
            enabled: true,
            correlations: CorrelationSchemaConfig {
                enabled: true,
                copula_type: CopulaSchemaType::Clayton,
                fields: vec![
                    CorrelatedFieldConfig {
                        name: "amount".to_string(),
                        distribution: Default::default(),
                    },
                    CorrelatedFieldConfig {
                        name: "line_count".to_string(),
                        distribution: Default::default(),
                    },
                ],
                matrix: vec![0.5],
                ..Default::default()
            },
            ..Default::default()
        };
    });
    let result = orch.generate().expect("generate");
    assert!(!result.journal_entries.is_empty());
}