Configuration System Architecture

Overview

The configuration system provides a modern, flexible, and maintainable approach to managing simulation parameters. It uses Pydantic v2 models for strict validation and type safety and supports a three-tier file architecture (profiles, modules, presets).

Key source files:

  • ergodic_insurance/config.py – All Pydantic models (Config, sub-models, presets, industry configs)

  • ergodic_insurance/config_manager.pyConfigManager (main interface for the configuration system)

  • ergodic_insurance/config_loader.pyConfigLoader (deprecated legacy interface)

  • ergodic_insurance/config_migrator.pyConfigMigrator (automated file migration)

  • ergodic_insurance/stochastic_processes.pyStochasticConfig (stochastic process parameters)

  • ergodic_insurance/reporting/config.pyReportConfig and related reporting models

Architecture Diagram

        graph TB
    %% Main Components
    subgraph ConfigSystem["Configuration System"]
        CM["ConfigManager<br/>Main Interface"]
        CFG["Config<br/>Pydantic Models"]
        CL["ConfigLoader<br/>Legacy (Deprecated)"]
        MIGRATOR["ConfigMigrator<br/>Migration Tool"]
    end

    %% File Structure
    subgraph FileSystem["File System"]
        subgraph Profiles["Profiles/"]
            DEFAULT["default.yaml"]
            CONSERV["conservative.yaml"]
            AGGRESS["aggressive.yaml"]
            CUSTOM["custom/*.yaml"]
        end

        subgraph Modules["Modules/"]
            MOD_INS["insurance.yaml"]
            MOD_LOSS["losses.yaml"]
            MOD_STOCH["stochastic.yaml"]
            MOD_BUS["business.yaml"]
        end

        subgraph Presets["Presets/"]
            PRE_MARKET["market_conditions.yaml"]
            PRE_LAYER["layer_structures.yaml"]
            PRE_RISK["risk_scenarios.yaml"]
        end
    end

    %% Data Models
    subgraph Models["Configuration Models"]
        PROF_META["ProfileMetadata"]
        MANU_CFG["ManufacturerConfig"]
        WC_CFG["WorkingCapitalConfig"]
        INS_CFG["InsuranceConfig"]
        SIM_CFG["SimulationConfig"]
        LOSS_CFG["LossDistributionConfig"]
        GROWTH_CFG["GrowthConfig"]
        DEBT_CFG["DebtConfig"]
        OUTPUT_CFG["OutputConfig"]
        LOG_CFG["LoggingConfig"]
        EXCEL_CFG["ExcelReportConfig"]
        WCR_CFG["WorkingCapitalRatiosConfig"]
        EXP_CFG["ExpenseRatioConfig"]
        DEP_CFG["DepreciationConfig"]
    end

    %% Relationships
    CM --> CFG
    CL --> CM
    MIGRATOR --> Profiles
    MIGRATOR --> Modules
    MIGRATOR --> Presets

    CM --> Profiles
    CM --> Modules
    CM --> Presets

    Profiles --> DEFAULT
    Profiles --> CONSERV
    Profiles --> AGGRESS
    Profiles --> CUSTOM

    DEFAULT -.includes.-> Modules
    CONSERV -.includes.-> Modules
    AGGRESS -.includes.-> Modules

    Modules --> MOD_INS
    Modules --> MOD_LOSS
    Modules --> MOD_STOCH
    Modules --> MOD_BUS

    DEFAULT -.applies.-> Presets
    CONSERV -.applies.-> Presets
    AGGRESS -.applies.-> Presets

    CFG --> Models
    Models --> PROF_META
    Models --> MANU_CFG
    Models --> WC_CFG
    Models --> INS_CFG
    Models --> SIM_CFG
    Models --> LOSS_CFG
    Models --> GROWTH_CFG
    Models --> DEBT_CFG
    Models --> OUTPUT_CFG
    Models --> LOG_CFG
    Models --> EXCEL_CFG
    Models --> WCR_CFG
    Models --> EXP_CFG
    Models --> DEP_CFG

    %% Styling
    classDef manager fill:#e3f2fd,stroke:#1565c0,stroke-width:3px
    classDef deprecated fill:#fce4ec,stroke:#c62828,stroke-width:2px,stroke-dasharray: 5 5
    classDef profiles fill:#fff3e0,stroke:#ef6c00,stroke-width:2px
    classDef modules fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    classDef presets fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    classDef models fill:#ffebee,stroke:#c62828,stroke-width:2px

    class CM manager
    class CL deprecated
    class MIGRATOR manager
    class DEFAULT,CONSERV,AGGRESS,CUSTOM profiles
    class MOD_INS,MOD_LOSS,MOD_STOCH,MOD_BUS modules
    class PRE_MARKET,PRE_LAYER,PRE_RISK presets
    class PROF_META,MANU_CFG,WC_CFG,INS_CFG,SIM_CFG,LOSS_CFG,GROWTH_CFG,DEBT_CFG,OUTPUT_CFG,LOG_CFG,EXCEL_CFG,WCR_CFG,EXP_CFG,DEP_CFG models
    

Configuration Architecture

        graph LR
    %% Tier 1: Profiles
    subgraph Tier1["Tier 1: Profiles"]
        P1["Complete Configuration Sets"]
        P2["Default Profile"]
        P3["Conservative Profile"]
        P4["Aggressive Profile"]
        P5["Custom Profiles"]
    end

    %% Tier 2: Modules
    subgraph Tier2["Tier 2: Modules"]
        M1["Reusable Components"]
        M2["Insurance Module"]
        M3["Loss Module"]
        M4["Stochastic Module"]
        M5["Business Module"]
    end

    %% Tier 3: Presets
    subgraph Tier3["Tier 3: Presets"]
        PR1["Quick-Apply Templates"]
        PR2["Market Conditions"]
        PR3["Layer Structures"]
        PR4["Risk Scenarios"]
    end

    %% Relationships
    P1 --> P2
    P1 --> P3
    P1 --> P4
    P1 --> P5

    P2 -.includes.-> M1
    P3 -.includes.-> M1
    P4 -.includes.-> M1

    M1 --> M2
    M1 --> M3
    M1 --> M4
    M1 --> M5

    P2 -.applies.-> PR1
    P3 -.applies.-> PR1
    P4 -.applies.-> PR1

    PR1 --> PR2
    PR1 --> PR3
    PR1 --> PR4

    %% Styling
    classDef tier1 fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
    classDef tier2 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    classDef tier3 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px

    class P1,P2,P3,P4,P5 tier1
    class M1,M2,M3,M4,M5 tier2
    class PR1,PR2,PR3,PR4 tier3
    

Config Loading Pipeline

The following sequence diagram shows the full lifecycle of a configuration load request, including cache check, inheritance resolution, module application, preset application, and runtime overrides.

        sequenceDiagram
    participant User
    participant CM as ConfigManager
    participant Cache as _cache (dict)
    participant FS as File System
    participant CFG as Config

    User->>CM: load_profile("conservative", use_cache=True, **overrides)
    CM->>CM: Compute cache_key (SHA-256 of profile + overrides)
    CM->>Cache: Check cache_key
    alt Cache hit
        Cache-->>CM: Return cached Config
        CM-->>User: Return Config
    else Cache miss
        CM->>FS: Find profiles/conservative.yaml
        CM->>CM: _load_with_inheritance(profile_path)
        CM->>FS: Read conservative.yaml
        Note over CM: profile.extends = "default"
        CM->>FS: Read profiles/default.yaml (parent)
        CM->>CM: _deep_merge(parent_data, child_data)
        CM->>CFG: Config(**merged_data)
        CFG-->>CM: Validated config instance

        loop For each module in profile.includes
            CM->>FS: Read modules/{module}.yaml
            CM->>CFG: apply_module(module_data)
        end

        loop For each preset in profile.presets
            CM->>FS: Read presets/{type}.yaml
            CM->>CFG: apply_preset(preset_name, preset_data)
        end

        alt Runtime overrides provided
            CM->>CFG: with_overrides(**overrides)
            CFG-->>CM: New Config with overrides
        end

        CM->>CFG: validate_completeness()
        CFG-->>CM: List of issues (warnings if any)

        CM->>Cache: Store result
        CM-->>User: Return Config
    end
    

Profile Inheritance Resolution

Profiles support single inheritance through the extends field. When a profile extends another, the parent is loaded first (recursively if it also extends a parent), and the child’s values are deep-merged on top.

        graph TD
    subgraph InheritanceChain["Inheritance Resolution"]
        CUSTOM_CLIENT["custom/client_abc.yaml<br/>extends: conservative"]
        CONSERVATIVE["conservative.yaml<br/>extends: default"]
        DEFAULT["default.yaml<br/>base profile"]
    end

    DEFAULT -->|"1. Load base values"| CONSERVATIVE
    CONSERVATIVE -->|"2. Deep merge child overrides"| CUSTOM_CLIENT
    CUSTOM_CLIENT -->|"3. Final merged config"| RESULT["Config Instance"]

    classDef base fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    classDef derived fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
    classDef result fill:#fff3e0,stroke:#ef6c00,stroke-width:3px

    class DEFAULT base
    class CONSERVATIVE,CUSTOM_CLIENT derived
    class RESULT result
    

Inheritance rules:

  1. The chain is resolved recursively from child up to the root profile.

  2. Parent profiles are loaded first; child values are deep-merged on top.

  3. Deep merge recurses into nested dictionaries; non-dict values are replaced entirely.

  4. Missing parent profiles emit a warning but do not raise an error.

  5. Circular inheritance is prevented by Python’s natural recursion limit.

# profiles/conservative.yaml
profile:
  name: conservative
  description: "Conservative risk parameters"
  extends: default
  version: "2.0.0"

# Only fields that differ from default need to be specified
manufacturer:
  base_operating_margin: 0.06
growth:
  annual_growth_rate: 0.03

Component Descriptions

ConfigManager

Source: ergodic_insurance/config_manager.py

The main interface for the 3-tier configuration system. Handles profile loading with inheritance, module composition, preset application, runtime overrides, caching, and validation.

class ConfigManager:
    """Manages configuration loading with profiles, modules, and presets."""

    def __init__(self, config_dir: Optional[Path] = None):
        """Initialize manager.

        Args:
            config_dir: Root config directory. Defaults to
                        ergodic_insurance/data/config.
        """

    # --- Primary API ---

    def load_profile(
        self,
        profile_name: str = "default",
        use_cache: bool = True,
        **overrides
    ) -> Config:
        """Load a configuration profile with optional overrides."""

    # --- Discovery ---

    def list_profiles(self) -> List[str]:
        """List all available profile names (including custom/)."""

    def list_modules(self) -> List[str]:
        """List all available module names."""

    def list_presets(self) -> Dict[str, List[str]]:
        """List presets grouped by type."""

    def get_profile_metadata(self, profile_name: str) -> Dict[str, Any]:
        """Get profile metadata without loading full config (LRU-cached)."""

    # --- Mutation ---

    def create_profile(
        self, name: str, description: str,
        base_profile: str = "default",
        custom: bool = True,
        **config_params
    ) -> Path:
        """Create and save a new profile YAML file."""

    def with_preset(
        self, config: Config,
        preset_type: str, preset_name: str
    ) -> Config:
        """Return a new Config with a preset applied."""

    def with_overrides(
        self, config: Config, **overrides
    ) -> Config:
        """Return a new Config with runtime overrides."""

    # --- Validation ---

    def validate(self, config: Config) -> List[str]:
        """Validate config for completeness and consistency."""

    # --- Cache ---

    def clear_cache(self) -> None:
        """Clear configuration and preset caches."""

Key Features:

  • Profile inheritance resolution – recursive parent loading with deep merge

  • Module composition – selectively include optional configuration modules

  • Preset application – apply quick-change templates from preset libraries

  • Runtime overrides – override any parameter at load time via **kwargs

  • SHA-256 cache keys – deterministic caching based on profile name + overrides

  • Validation at load time – Pydantic field validation plus business logic checks

  • Profile creation – programmatic creation of new profile YAML files

Config

Source: ergodic_insurance/config.py

The main Pydantic BaseModel that holds the entire configuration state. It composes required sub-models for core business parameters and optional sub-models for extended functionality.

class Config(BaseModel):
    """Unified configuration model for the configuration system."""

    # --- Required sections ---
    profile: ProfileMetadata
    manufacturer: ManufacturerConfig
    working_capital: WorkingCapitalConfig
    growth: GrowthConfig
    debt: DebtConfig
    simulation: SimulationConfig
    output: OutputConfig
    logging: LoggingConfig

    # --- Optional module sections ---
    insurance: Optional[InsuranceConfig] = None
    losses: Optional[LossDistributionConfig] = None
    excel_reporting: Optional[ExcelReportConfig] = None
    working_capital_ratios: Optional[WorkingCapitalRatiosConfig] = None
    expense_ratios: Optional[ExpenseRatioConfig] = None
    depreciation: Optional[DepreciationConfig] = None
    industry_config: Optional[IndustryConfig] = None

    # --- Extensibility ---
    custom_modules: Dict[str, ModuleConfig] = {}
    applied_presets: List[str] = []
    overrides: Dict[str, Any] = {}

Key Methods:

Method

Description

from_profile(profile_path)

Class method: load from a single YAML file

with_inheritance(profile_path, config_dir)

Class method: load with recursive inheritance

apply_module(module_path)

Apply a module YAML file to this config (in-place)

apply_preset(preset_name, preset_data)

Apply preset parameters (in-place)

with_overrides(overrides)

Return a new Config with overrides (supports "section.field" dot notation)

validate_completeness()

Return list of missing or inconsistent items

_deep_merge(base, override)

Static method: recursive dictionary merge

Sub-Model Reference

ProfileMetadata

Metadata attached to every configuration profile.

class ProfileMetadata(BaseModel):
    name: str               # Alphanumeric + hyphens/underscores
    description: str
    version: str = "2.0.0"  # Semantic version (validated via regex)
    extends: Optional[str] = None    # Parent profile name
    includes: List[str] = []         # Module names to include
    presets: Dict[str, str] = {}     # {preset_type: preset_name}
    author: Optional[str] = None
    created: Optional[datetime] = None
    tags: List[str] = []             # Discovery tags

ManufacturerConfig

Core financial parameters for the simulated business entity.

class ManufacturerConfig(BaseModel):
    initial_assets: float          # > 0, starting asset value in dollars
    asset_turnover_ratio: float    # > 0, <= 5
    base_operating_margin: float   # > -1, < 1 (warns if > 0.3 or < 0)
    tax_rate: float                # [0, 1]
    retention_ratio: float         # [0, 1]
    ppe_ratio: Optional[float]     # Auto-set based on margin if None
    insolvency_tolerance: float    # Default $10,000
    expense_ratios: Optional[ExpenseRatioConfig]  # COGS/SG&A breakdown

    # Mid-year liquidity (Issue #279)
    premium_payment_month: int     # 0-11, month of premium payment
    revenue_pattern: Literal["uniform", "seasonal", "back_loaded"]
    check_intra_period_liquidity: bool = True

Factory method: ManufacturerConfig.from_industry_config(industry_config, **kwargs) creates a config from an IndustryConfig instance.

InsuranceConfig

Enhanced insurance program configuration with layered structure.

class InsuranceConfig(BaseModel):
    enabled: bool = True
    layers: List[InsuranceLayerConfig] = []   # Validated: no overlaps, sorted by attachment
    deductible: float = 0
    coinsurance: float = 1.0       # (0, 1]
    waiting_period_days: int = 0
    claims_handling_cost: float = 0.05  # [0, 1]

Each layer is defined by InsuranceLayerConfig:

class InsuranceLayerConfig(BaseModel):
    name: str
    limit: float                # > 0
    attachment: float           # >= 0
    base_premium_rate: float    # (0, 1]
    reinstatements: int = 0
    aggregate_limit: Optional[float] = None
    limit_type: str = "per-occurrence"  # "per-occurrence", "aggregate", "hybrid"
    per_occurrence_limit: Optional[float] = None

SimulationConfig

Controls simulation execution parameters.

class SimulationConfig(BaseModel):
    time_resolution: Literal["annual", "monthly"] = "annual"
    time_horizon_years: int     # > 0, <= 1000
    max_horizon_years: int = 1000  # [100, 10000]
    random_seed: Optional[int] = None
    fiscal_year_end: int = 12   # [1, 12], month of fiscal year end

Cross-field validation ensures time_horizon_years <= max_horizon_years.

LossDistributionConfig

Configuration for actuarial loss modeling.

class LossDistributionConfig(BaseModel):
    frequency_distribution: str = "poisson"  # poisson | negative_binomial | binomial
    frequency_annual: float       # > 0
    severity_distribution: str = "lognormal"  # lognormal | gamma | pareto | weibull
    severity_mean: float          # > 0
    severity_std: float           # > 0
    correlation_factor: float = 0.0  # [-1, 1]
    tail_alpha: float = 2.0      # > 1

GrowthConfig

Growth model selection and parameters.

class GrowthConfig(BaseModel):
    type: Literal["deterministic", "stochastic"] = "deterministic"
    annual_growth_rate: float    # [-0.5, 1.0]
    volatility: float = 0.0     # [0, 1], must be > 0 when type="stochastic"

DebtConfig

Debt financing parameters.

class DebtConfig(BaseModel):
    interest_rate: float          # [0, 0.5]
    max_leverage_ratio: float     # [0, 10]
    minimum_cash_balance: float   # >= 0

WorkingCapitalConfig

Basic working capital as a fraction of sales.

class WorkingCapitalConfig(BaseModel):
    percent_of_sales: float      # [0, 1], raises ValueError if > 0.5

WorkingCapitalRatiosConfig (Optional)

Detailed working capital with standard financial ratios.

class WorkingCapitalRatiosConfig(BaseModel):
    days_sales_outstanding: float = 45     # [0, 365]
    days_inventory_outstanding: float = 60 # [0, 365]
    days_payable_outstanding: float = 30   # [0, 365]
    # Warns if cash conversion cycle < 0 or > 180 days

ExpenseRatioConfig (Optional)

COGS and SG&A breakdown ratios (Issue #255).

class ExpenseRatioConfig(BaseModel):
    gross_margin_ratio: float = 0.15         # (0, 1)
    sga_expense_ratio: float = 0.07          # (0, 1)
    manufacturing_depreciation_allocation: float = 0.7  # [0, 1]
    admin_depreciation_allocation: float = 0.3          # [0, 1]

    # COGS breakdown (must sum to 1.0)
    direct_materials_ratio: float = 0.4
    direct_labor_ratio: float = 0.3
    manufacturing_overhead_ratio: float = 0.3

    # SG&A breakdown (must sum to 1.0)
    selling_expense_ratio: float = 0.4
    general_admin_ratio: float = 0.6

    # Computed properties
    @property
    def cogs_ratio(self) -> float: ...
    @property
    def operating_margin_ratio(self) -> float: ...

DepreciationConfig (Optional)

Depreciation and amortization tracking.

class DepreciationConfig(BaseModel):
    ppe_useful_life_years: float = 10              # (0, 50]
    prepaid_insurance_amortization_months: int = 12 # (0, 24]
    initial_accumulated_depreciation: float = 0

    @property
    def annual_depreciation_rate(self) -> float: ...
    @property
    def monthly_insurance_amortization_rate(self) -> float: ...

OutputConfig and LoggingConfig

class OutputConfig(BaseModel):
    output_directory: str = "outputs"
    file_format: Literal["csv", "parquet", "json"] = "csv"
    checkpoint_frequency: int = 0     # 0 = disabled
    detailed_metrics: bool = True

    @property
    def output_path(self) -> Path: ...

class LoggingConfig(BaseModel):
    enabled: bool = True
    level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "INFO"
    log_file: Optional[str] = None
    console_output: bool = True
    format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"

ExcelReportConfig (Optional)

class ExcelReportConfig(BaseModel):
    enabled: bool = True
    output_path: str = "./reports"
    include_balance_sheet: bool = True
    include_income_statement: bool = True
    include_cash_flow: bool = True
    include_reconciliation: bool = True
    include_metrics_dashboard: bool = True
    include_pivot_data: bool = True
    engine: str = "auto"  # xlsxwriter | openpyxl | auto | pandas
    currency_format: str = "$#,##0"
    decimal_places: int = 0
    date_format: str = "yyyy-mm-dd"

ModuleConfig and PresetConfig

Base models for extensibility:

class ModuleConfig(BaseModel):
    module_name: str
    module_version: str = "2.0.0"
    dependencies: List[str] = []
    model_config = {"extra": "allow"}  # Allows additional fields

class PresetConfig(BaseModel):
    preset_name: str
    preset_type: str     # market | layers | risk | optimization | scenario
    description: str
    parameters: Dict[str, Any]

StochasticConfig

Source: ergodic_insurance/stochastic_processes.py

Standalone Pydantic model for stochastic process parameters, used by all stochastic process implementations (GBM, mean-reverting, jump-diffusion, etc.).

class StochasticConfig(BaseModel):
    """Configuration for stochastic processes."""

    volatility: float    # [0, 2], annual volatility (standard deviation)
    drift: float         # [-1, 1], annual drift rate
    random_seed: Optional[int] = None  # >= 0
    time_step: float = 1.0  # (0, 1], time step in years

This model is separate from the main Config hierarchy and is consumed directly by StochasticProcess subclasses.

Class Diagram

        classDiagram
    class ConfigManager {
        +config_dir: Path
        +profiles_dir: Path
        +modules_dir: Path
        +presets_dir: Path
        -_cache: Dict
        -_preset_libraries: Dict
        +load_profile(profile_name, use_cache, **overrides) Config
        +list_profiles() List~str~
        +list_modules() List~str~
        +list_presets() Dict
        +get_profile_metadata(profile_name) Dict
        +create_profile(name, description, ...) Path
        +with_preset(config, preset_type, preset_name) Config
        +with_overrides(config, **overrides) Config
        +validate(config) List~str~
        +clear_cache() void
        -_load_with_inheritance(profile_path) Config
        -_apply_module(config, module_name) void
        -_apply_preset(config, preset_type, preset_name) void
        -_deep_merge(base, override) Dict
        -_validate_structure() void
    }

    class Config {
        +profile: ProfileMetadata
        +manufacturer: ManufacturerConfig
        +working_capital: WorkingCapitalConfig
        +growth: GrowthConfig
        +debt: DebtConfig
        +simulation: SimulationConfig
        +output: OutputConfig
        +logging: LoggingConfig
        +insurance: Optional~InsuranceConfig~
        +losses: Optional~LossDistributionConfig~
        +excel_reporting: Optional~ExcelReportConfig~
        +working_capital_ratios: Optional~WorkingCapitalRatiosConfig~
        +expense_ratios: Optional~ExpenseRatioConfig~
        +depreciation: Optional~DepreciationConfig~
        +industry_config: Optional~IndustryConfig~
        +custom_modules: Dict
        +applied_presets: List~str~
        +overrides: Dict
        +from_profile(profile_path)$ Config
        +with_inheritance(profile_path, config_dir)$ Config
        +apply_module(module_path) void
        +apply_preset(preset_name, preset_data) void
        +with_overrides(**kwargs) Config
        +validate_completeness() List~str~
    }

    class ProfileMetadata {
        +name: str
        +description: str
        +version: str
        +extends: Optional~str~
        +includes: List~str~
        +presets: Dict
        +author: Optional~str~
        +created: Optional~datetime~
        +tags: List~str~
    }

    class ManufacturerConfig {
        +initial_assets: float
        +asset_turnover_ratio: float
        +base_operating_margin: float
        +tax_rate: float
        +retention_ratio: float
        +ppe_ratio: Optional~float~
        +insolvency_tolerance: float
        +expense_ratios: Optional~ExpenseRatioConfig~
        +premium_payment_month: int
        +revenue_pattern: str
        +check_intra_period_liquidity: bool
        +from_industry_config(industry_config, **kwargs)$ ManufacturerConfig
    }

    class InsuranceConfig {
        +enabled: bool
        +layers: List~InsuranceLayerConfig~
        +deductible: float
        +coinsurance: float
        +waiting_period_days: int
        +claims_handling_cost: float
    }

    class SimulationConfig {
        +time_resolution: str
        +time_horizon_years: int
        +max_horizon_years: int
        +random_seed: Optional~int~
        +fiscal_year_end: int
    }

    class StochasticConfig {
        +volatility: float
        +drift: float
        +random_seed: Optional~int~
        +time_step: float
    }

    class ConfigMigrator {
        +legacy_dir: Path
        +new_dir: Path
        +migration_report: List~str~
        +run_migration() bool
        +convert_baseline() Dict
        +convert_conservative() Dict
        +convert_optimistic() Dict
        +extract_modules() void
        +create_presets() void
        +validate_migration() bool
        +generate_migration_report() str
    }

    class ConfigLoader {
        <<deprecated>>
        +config_dir: Path
        +load(config_name, overrides, **kwargs) Config
        +load_scenario(scenario, overrides, **kwargs) Config
        +compare_configs(config1, config2) Dict
        +validate_config(config) bool
        +load_pricing_scenarios(scenario_file) PricingScenarioConfig
        +list_available_configs() List~str~
        +clear_cache() void
    }

    ConfigManager --> Config : creates
    Config *-- ProfileMetadata
    Config *-- ManufacturerConfig
    Config *-- InsuranceConfig
    Config *-- SimulationConfig
    ConfigLoader --> ConfigManager : delegates to
    ConfigMigrator ..> Config : produces YAML for
    

Backward Compatibility

ConfigLoader (Deprecated)

Source: ergodic_insurance/config_loader.py

The original configuration loader. Now delegates to ConfigManager internally. Emits a DeprecationWarning on first use.

class ConfigLoader:
    """Deprecated. Use ConfigManager instead."""

    def load(self, config_name="baseline", overrides=None, **kwargs) -> Config: ...
    def load_scenario(self, scenario, overrides=None, **kwargs) -> Config: ...
    def compare_configs(self, config1, config2) -> Dict: ...
    def validate_config(self, config) -> bool: ...
    def load_pricing_scenarios(self, scenario_file) -> PricingScenarioConfig: ...
    def list_available_configs(self) -> List[str]: ...
    def clear_cache(self) -> None: ...

Migration Path

ConfigMigrator

Source: ergodic_insurance/config_migrator.py

Automated migration tool that converts the legacy flat YAML files into the 3-tier directory structure.

class ConfigMigrator:
    """Migrates legacy configurations to new system."""

    def run_migration(self) -> bool:
        """Execute full migration pipeline:
        1. convert_baseline()     -> profiles/default.yaml
        2. convert_conservative() -> profiles/conservative.yaml
        3. convert_optimistic()   -> profiles/aggressive.yaml
        4. extract_modules()      -> modules/{insurance,losses,stochastic,business}.yaml
        5. create_presets()       -> presets/{market_conditions,layer_structures,risk_scenarios}.yaml
        6. validate_migration()   -> verify all expected files exist
        """

    def generate_migration_report(self) -> str:
        """Generate formatted migration report with status of each step."""

Module extraction:

Legacy Files

New Module

insurance.yaml, insurance_market.yaml, insurance_structures.yaml, insurance_pricing_scenarios.yaml

modules/insurance.yaml

losses.yaml, loss_distributions.yaml

modules/losses.yaml

stochastic.yaml

modules/stochastic.yaml

business_optimization.yaml

modules/business.yaml

Preset generation:

Preset Library

Presets

market_conditions.yaml

stable, volatile, growth, recession

layer_structures.yaml

basic, comprehensive, catastrophic

risk_scenarios.yaml

low_risk, moderate_risk, high_risk

Run the migrator from the command line:

python -m ergodic_insurance.config_migrator

Industry Configuration

The system includes industry-specific configuration templates via the IndustryConfig dataclass hierarchy:

Config Class

Industry

Gross Margin

PP&E Ratio

DSO

ManufacturingConfig

Manufacturing

35%

50%

45 days

ServiceConfig

Services

60%

20%

30 days

RetailConfig

Retail

30%

40%

5 days

These feed into ManufacturerConfig.from_industry_config() to create appropriately parameterized simulations.

Profile System

Profiles provide complete configuration sets:

# profiles/default.yaml
profile:
  name: default
  description: "Standard baseline configuration"
  version: "2.0.0"

modules:
  - insurance
  - losses

manufacturer:
  initial_assets: 10_000_000
  base_operating_margin: 0.10
  tax_rate: 0.25

Inheritance:

# profiles/conservative.yaml
profile:
  name: conservative
  description: "Conservative risk parameters"
  extends: default
  version: "2.0.0"

manufacturer:
  base_operating_margin: 0.06
growth:
  annual_growth_rate: 0.03

Module System

Modules are optional, reusable components:

# modules/insurance.yaml
insurance:
  enabled: true
  layers:
    - name: "Primary"
      attachment: 0
      limit: 5_000_000
      base_premium_rate: 0.015
    - name: "Excess"
      attachment: 5_000_000
      limit: 20_000_000
      base_premium_rate: 0.008

Benefits:

  • Selective inclusion via profile’s includes list

  • Reusability across profiles

  • Clean separation of concerns

  • Easy testing in isolation

Preset System

Presets are quick-apply templates:

# presets/market_conditions.yaml
stable:
  manufacturer:
    revenue_volatility: 0.10
    cost_volatility: 0.08
  simulation:
    stochastic_revenue: false

volatile:
  manufacturer:
    revenue_volatility: 0.25
    cost_volatility: 0.20
  simulation:
    stochastic_revenue: true
    stochastic_costs: true

Usage:

config = manager.load_profile(
    "default",
    presets=["hard_market", "high_volatility"]
)

Performance Considerations

Caching Strategy

The ConfigManager uses a dictionary-based cache keyed by a SHA-256 hash of the profile name and serialized overrides:

cache_key = f"{profile_name}_{hashlib.sha256(
    json.dumps(overrides, sort_keys=True, default=str).encode()
).hexdigest()[:16]}"

Profile metadata lookups are cached separately with @lru_cache(maxsize=32).

Optimization Techniques

  1. SHA-256 cache keys: Deterministic hashing of profile + overrides for fast lookup

  2. Lazy module loading: Modules loaded only when listed in profile.includes

  3. Deep merging: Efficient recursive dictionary merge for inheritance chains

  4. Preset library caching: Preset YAML files loaded once and reused

  5. Pydantic v2 validation: Compiled validators for fast model construction

  6. LRU caching: get_profile_metadata() avoids repeated file I/O

Best Practices

Creating Custom Profiles

# profiles/custom/client_abc.yaml
profile:
  name: client-abc
  description: "Custom configuration for Client ABC"
  extends: conservative
  version: "2.0.0"
  includes:
    - insurance
    - stochastic
  presets:
    market_conditions: stable

manufacturer:
  initial_assets: 50_000_000
simulation:
  time_horizon_years: 30

Runtime Overrides

# Override specific parameters at runtime
config = manager.load_profile(
    "default",
    manufacturer={"base_operating_margin": 0.12},
    simulation={"random_seed": 42}
)

# Dot-notation for nested overrides
config = config.with_overrides({
    "manufacturer.initial_assets": 20_000_000,
    "simulation.time_horizon_years": 100,
})

Testing Configurations

def test_profile_loads():
    """Test that all profiles load successfully."""
    manager = ConfigManager()

    for profile in manager.list_profiles():
        config = manager.load_profile(profile)
        assert isinstance(config, Config)
        assert config.manufacturer.initial_assets > 0

def test_validation():
    """Test configuration validation."""
    manager = ConfigManager()
    config = manager.load_profile("default")
    issues = manager.validate(config)
    assert len(issues) == 0

Migrating from Legacy Code

# Before (deprecated)
from ergodic_insurance.config_loader import ConfigLoader
loader = ConfigLoader()
config = loader.load("baseline", overrides={"manufacturer": {"initial_assets": 5e6}})

# After (recommended)
from ergodic_insurance.config_manager import ConfigManager
manager = ConfigManager()
config = manager.load_profile("default", manufacturer={"initial_assets": 5e6})

Security Considerations

  1. Path traversal protection: Profile names are resolved within the configured directory structure only

  2. YAML safe loading: All YAML files loaded with yaml.safe_load() (no arbitrary code execution)

  3. Pydantic validation: All inputs validated through Pydantic field constraints and model validators

  4. Access control: Custom profiles stored in a separate custom/ subdirectory

  5. YAML anchor filtering: Private anchors (keys starting with _) are stripped before parsing

Future Enhancements

Planned Features

  1. Hot reloading: Automatic reload on file changes during development

  2. Schema versioning: Automatic migration between configuration schema versions

  3. Remote configs: Load from S3/HTTP endpoints

  4. Config diffing: Visual comparison of configurations (partially implemented via ConfigLoader.compare_configs())

  5. A/B testing: Built-in experiment configuration support

API Stability

The configuration API is considered stable as of v2.0:

  • ConfigManager.load_profile()Stable

  • Config models – Stable

  • Profile YAML format – Stable

  • Module/Preset format – Stable

  • ConfigLoaderDeprecated (will be removed in v3.0.0)

  • ConfigV2Deprecated alias (use Config directly)

Conclusion

The configuration system provides a modern, flexible, and maintainable approach to managing complex simulation parameters. The architecture enables both simplicity for basic use cases and power for advanced scenarios. The ConfigManager serves as the single entry point, and ConfigMigrator offers automated tooling for transitioning legacy YAML files to the new structure.