# Configuration Loading Flow ## Overview The Ergodic Insurance framework uses a **three-tier configuration architecture** to manage simulation parameters. This document provides detailed flow diagrams showing how configuration is loaded, resolved, validated, and delivered to consumers throughout the system. The three tiers are: | Tier | Component | Purpose | Location | |------|-----------|---------|----------| | **1** | Profiles | Complete configuration sets with inheritance | `data/config/profiles/` | | **2** | Modules | Reusable optional components | `data/config/modules/` | | **3** | Presets | Quick-apply templates | `data/config/presets/` | The primary entry point is `ConfigManager.load_profile()`, which orchestrates the entire loading pipeline: file resolution, inheritance, module composition, preset application, runtime overrides, Pydantic validation, and caching. --- ## 1. Configuration Loading Pipeline This sequence diagram shows the complete lifecycle of a `load_profile()` call, from the caller through `ConfigManager`, into the file system, and back through validation into a fully resolved `ConfigV2` object. ```{mermaid} sequenceDiagram participant Caller as Caller participant CM as ConfigManager participant Cache as LRU Cache participant FS as File System
(data/config/) participant CV2 as ConfigV2
(Pydantic) Caller->>CM: load_profile(profile_name, use_cache, **overrides) Note over CM: Generate cache key from
profile_name + SHA-256(overrides) CM->>Cache: Check cache_key alt Cache hit (use_cache=True) Cache-->>CM: Return cached ConfigV2 CM-->>Caller: ConfigV2 (cached) else Cache miss or use_cache=False Note over CM: Resolve profile file path CM->>FS: Read profiles/{profile_name}.yaml alt Profile not found CM->>FS: Read profiles/custom/{profile_name}.yaml alt Custom profile not found CM-->>Caller: Raise FileNotFoundError end end FS-->>CM: Raw YAML data (dict) Note over CM: _load_with_inheritance() CM->>CM: Check for "extends" field alt Has parent profile CM->>FS: Read profiles/{parent_name}.yaml FS-->>CM: Parent YAML data CM->>CM: _load_with_inheritance(parent)
(recursive) CM->>CM: _deep_merge(parent_data, child_data) end CM->>CV2: ConfigV2(**merged_data) Note over CV2: Pydantic validation
(type checks, constraints,
cross-field validators) CV2-->>CM: Validated ConfigV2 Note over CM: Apply modules from
profile.includes list loop For each module in profile.includes CM->>FS: Read modules/{module_name}.yaml FS-->>CM: Module YAML data CM->>CM: _apply_module(config, module_name) Note over CM: Deep merge module data
into ConfigV2 fields end Note over CM: Apply presets from
profile.presets dict loop For each (preset_type, preset_name) CM->>FS: Read presets/{preset_type}.yaml FS-->>CM: Preset library data CM->>CM: _apply_preset(config, type, name) Note over CM: config.apply_preset()
merges preset parameters end Note over CM: Apply runtime overrides alt Has **overrides CM->>CV2: config.with_overrides(**overrides) Note over CV2: Creates new ConfigV2
with merged overrides CV2-->>CM: New ConfigV2 end CM->>CV2: validate_completeness() CV2-->>CM: List of issues (warnings) CM->>Cache: Store config at cache_key CM-->>Caller: ConfigV2 (validated) end ``` **Key observations:** - The cache key is computed as `"{profile_name}_{sha256(overrides)[:16]}"` to ensure unique keys per parameter combination. - Inheritance resolution is **recursive** -- a profile can extend another profile which itself extends a third profile, forming an inheritance chain. - The `_deep_merge()` operation performs recursive dictionary merging, where child values override parent values at every nesting level. - Module application mutates the `ConfigV2` instance in-place using `setattr`, while `with_overrides()` creates a **new** `ConfigV2` instance. - Validation warnings (e.g., "Insurance enabled but no loss distribution configured") are emitted via Python `warnings.warn()` and do not block loading. --- ## 2. Profile Inheritance Resolution This flowchart shows how a profile request is resolved through the inheritance chain, module overlay, preset application, and runtime override stages. ```{mermaid} flowchart TD START([load_profile called]) --> RESOLVE_PATH subgraph Resolution["Phase 1: File Resolution"] RESOLVE_PATH[/"Resolve profile file path
profiles/{name}.yaml"/] RESOLVE_PATH --> CHECK_EXISTS{Profile file
exists?} CHECK_EXISTS -- Yes --> LOAD_YAML["Load YAML with
yaml.safe_load()"] CHECK_EXISTS -- No --> CHECK_CUSTOM{Custom profile
in custom/ dir?} CHECK_CUSTOM -- Yes --> LOAD_YAML CHECK_CUSTOM -- No --> ERROR_404["Raise FileNotFoundError
with available profiles"] end subgraph Inheritance["Phase 2: Inheritance Chain"] LOAD_YAML --> STRIP_ANCHORS["Strip YAML anchors
(keys starting with _)"] STRIP_ANCHORS --> HAS_EXTENDS{profile.extends
is set?} HAS_EXTENDS -- No --> CREATE_CV2 HAS_EXTENDS -- Yes --> LOAD_PARENT["Load parent profile
_load_with_inheritance(parent)"] LOAD_PARENT --> PARENT_HAS_EXTENDS{Parent also
has extends?} PARENT_HAS_EXTENDS -- Yes --> RECURSE["Recurse up
inheritance chain"] RECURSE --> MERGE_PARENT PARENT_HAS_EXTENDS -- No --> MERGE_PARENT["_deep_merge(parent, child)
Child overrides parent"] MERGE_PARENT --> CREATE_CV2["Create ConfigV2(**merged_data)
Pydantic validates all fields"] end subgraph Modules["Phase 3: Module Overlay"] CREATE_CV2 --> HAS_INCLUDES{profile.includes
is non-empty?} HAS_INCLUDES -- No --> CHECK_PRESETS HAS_INCLUDES -- Yes --> LOAD_MODULE["Load module YAML
modules/{name}.yaml"] LOAD_MODULE --> MODULE_EXISTS{Module file
exists?} MODULE_EXISTS -- No --> WARN_MODULE["Warn: module not found"] WARN_MODULE --> MORE_MODULES MODULE_EXISTS -- Yes --> APPLY_MODULE["For each key in module:
Deep merge into config fields"] APPLY_MODULE --> MORE_MODULES{More modules
to apply?} MORE_MODULES -- Yes --> LOAD_MODULE MORE_MODULES -- No --> CHECK_PRESETS end subgraph Presets["Phase 4: Preset Application"] CHECK_PRESETS{profile.presets
is non-empty?} CHECK_PRESETS -- No --> CHECK_OVERRIDES CHECK_PRESETS -- Yes --> LOAD_PRESET_LIB["Load preset library
presets/{type}.yaml"] LOAD_PRESET_LIB --> PRESET_EXISTS{Preset name found
in library?} PRESET_EXISTS -- No --> WARN_PRESET["Warn: preset not found"] WARN_PRESET --> MORE_PRESETS PRESET_EXISTS -- Yes --> APPLY_PRESET["config.apply_preset()
Merge preset parameters"] APPLY_PRESET --> TRACK_PRESET["Track in
applied_presets list"] TRACK_PRESET --> MORE_PRESETS{More presets
to apply?} MORE_PRESETS -- Yes --> LOAD_PRESET_LIB MORE_PRESETS -- No --> CHECK_OVERRIDES end subgraph Overrides["Phase 5: Runtime Overrides"] CHECK_OVERRIDES{Runtime
overrides
provided?} CHECK_OVERRIDES -- No --> VALIDATE CHECK_OVERRIDES -- Yes --> APPLY_OVERRIDES["config.with_overrides(**kwargs)
Creates NEW ConfigV2 instance"] APPLY_OVERRIDES --> VALIDATE end subgraph Validation["Phase 6: Final Validation"] VALIDATE["validate_completeness()
Check required sections
Check logical consistency"] VALIDATE --> HAS_ISSUES{Validation
issues?} HAS_ISSUES -- Yes --> EMIT_WARNINGS["Emit warnings via
warnings.warn()"] EMIT_WARNINGS --> CACHE_RESULT HAS_ISSUES -- No --> CACHE_RESULT end CACHE_RESULT["Cache result at
computed cache_key"] --> RETURN([Return ConfigV2]) style START fill:#e3f2fd,stroke:#1565c0,stroke-width:2px style RETURN fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style ERROR_404 fill:#ffebee,stroke:#c62828,stroke-width:2px ``` **Inheritance example:** A custom profile `custom/client_abc.yaml` with `extends: conservative` triggers the following chain: 1. Load `custom/client_abc.yaml` 2. See `extends: conservative`, load `conservative.yaml` 3. See `extends: default`, load `default.yaml` (base -- no parent) 4. Merge: `default` <-- `conservative` overrides <-- `client_abc` overrides 5. Result: client-specific values override conservative values, which override defaults --- ## 3. Migration Path from Legacy to v2 This flowchart documents how the legacy 12-file configuration system maps to the new 3-tier architecture, and shows both the automated migration tool (`ConfigMigrator`) and the runtime compatibility layer (`ConfigCompat` / `LegacyConfigAdapter`). ```{mermaid} flowchart TB subgraph Legacy["Legacy System (data/parameters/)"] B_YAML["baseline.yaml"] C_YAML["conservative.yaml"] O_YAML["optimistic.yaml"] INS_FILES["insurance.yaml
insurance_market.yaml
insurance_structures.yaml
insurance_pricing_scenarios.yaml"] LOSS_FILES["losses.yaml
loss_distributions.yaml"] STOCH_FILE["stochastic.yaml"] BIZ_FILE["business_optimization.yaml"] end subgraph MigrationTool["ConfigMigrator (Automated)"] direction TB RUN["run_migration()"] CONV_BASE["convert_baseline()
baseline -> default profile"] CONV_CONS["convert_conservative()
conservative -> conservative profile
(extends: default)"] CONV_OPT["convert_optimistic()
optimistic -> aggressive profile
(extends: default)"] EXTRACT["extract_modules()
Merge related YAML files
into single modules"] CREATE_PRE["create_presets()
Generate preset libraries"] VALIDATE_MIG["validate_migration()
Check all files exist"] REPORT["generate_migration_report()"] RUN --> CONV_BASE --> CONV_CONS --> CONV_OPT CONV_OPT --> EXTRACT --> CREATE_PRE CREATE_PRE --> VALIDATE_MIG --> REPORT end subgraph NewSystem["New 3-Tier System (data/config/)"] direction TB subgraph Profiles["profiles/"] DEFAULT_P["default.yaml"] CONSERV_P["conservative.yaml
(extends: default)"] AGGRESS_P["aggressive.yaml
(extends: default)"] CUSTOM_P["custom/*.yaml"] end subgraph ModulesDir["modules/"] INS_MOD["insurance.yaml"] LOSS_MOD["losses.yaml"] STOCH_MOD["stochastic.yaml"] BIZ_MOD["business.yaml"] end subgraph PresetsDir["presets/"] MARKET_PRE["market_conditions.yaml"] LAYER_PRE["layer_structures.yaml"] RISK_PRE["risk_scenarios.yaml"] end end B_YAML --> CONV_BASE C_YAML --> CONV_CONS O_YAML --> CONV_OPT INS_FILES --> EXTRACT LOSS_FILES --> EXTRACT STOCH_FILE --> EXTRACT BIZ_FILE --> EXTRACT CONV_BASE -.-> DEFAULT_P CONV_CONS -.-> CONSERV_P CONV_OPT -.-> AGGRESS_P EXTRACT -.-> INS_MOD EXTRACT -.-> LOSS_MOD EXTRACT -.-> STOCH_MOD EXTRACT -.-> BIZ_MOD CREATE_PRE -.-> MARKET_PRE CREATE_PRE -.-> LAYER_PRE CREATE_PRE -.-> RISK_PRE subgraph CompatLayer["Runtime Compatibility (config_compat.py)"] direction TB ADAPTER["LegacyConfigAdapter"] NAME_MAP["Name Mapping:
baseline -> default
optimistic -> aggressive
conservative -> conservative"] LOAD_LEGACY["load(config_name, overrides)"] MAP_PROFILE["Map legacy name to profile"] CALL_CM["ConfigManager.load_profile()"] CONVERT["_convert_to_legacy(ConfigV2)
Extract 7 core sections
into legacy Config"] FALLBACK["_load_legacy_direct()
Fall back to parameters/ dir"] ADAPTER --> LOAD_LEGACY LOAD_LEGACY --> MAP_PROFILE MAP_PROFILE --> NAME_MAP NAME_MAP --> CALL_CM CALL_CM --> CONVERT CALL_CM -- FileNotFoundError --> FALLBACK end subgraph Consumers["Consumer Code"] OLD_CODE["Legacy Code
ConfigLoader().load('baseline')"] NEW_CODE["New Code
ConfigManager().load_profile('default')"] end OLD_CODE --> ADAPTER NEW_CODE --> NewSystem style Legacy fill:#fff3e0,stroke:#ef6c00,stroke-width:2px style MigrationTool fill:#e3f2fd,stroke:#1565c0,stroke-width:2px style NewSystem fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style CompatLayer fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px style Consumers fill:#fce4ec,stroke:#c62828,stroke-width:2px ``` **Migration steps in detail:** 1. **Profile conversion**: Each legacy scenario file (`baseline.yaml`, `conservative.yaml`, `optimistic.yaml`) is wrapped with `ProfileMetadata` and saved under `profiles/`. The conservative and aggressive profiles declare `extends: default`. 2. **Module extraction**: Related legacy files are merged using `_deep_merge()` into consolidated modules. For example, four insurance-related YAML files are merged into a single `modules/insurance.yaml`. 3. **Preset generation**: The migrator creates preset libraries with predefined parameter combinations (market conditions, layer structures, risk scenarios). 4. **Validation**: `validate_migration()` checks that all expected files exist in the new directory structure. 5. **Runtime compatibility**: The `LegacyConfigAdapter` maps old config names to new profile names, delegates to `ConfigManager`, and converts `ConfigV2` back to the legacy `Config` format. If the new profile is not found, it falls back to loading directly from `data/parameters/`. --- ## 4. Configuration Model Hierarchy This class diagram shows the `ConfigV2` model and all of its sub-models. Required fields are shown with solid borders; optional modules have dashed borders. ```{mermaid} classDiagram class ConfigV2 { +ProfileMetadata profile +ManufacturerConfig manufacturer +WorkingCapitalConfig working_capital +GrowthConfig growth +DebtConfig debt +SimulationConfig simulation +OutputConfig output +LoggingConfig logging +InsuranceConfig insurance? +LossDistributionConfig losses? +ExcelReportConfig excel_reporting? +WorkingCapitalRatiosConfig working_capital_ratios? +ExpenseRatioConfig expense_ratios? +DepreciationConfig depreciation? +IndustryConfig industry_config? +Dict custom_modules +List applied_presets +Dict overrides +from_profile(Path) ConfigV2$ +with_inheritance(Path, Path) ConfigV2$ +apply_module(Path) void +apply_preset(str, Dict) void +with_overrides(**kwargs) ConfigV2 +validate_completeness() List~str~ } class ProfileMetadata { +str name +str description +str version +str extends? +List~str~ includes +Dict~str,str~ presets +str author? +datetime created? +List~str~ tags } class ManufacturerConfig { +float initial_assets +float asset_turnover_ratio +float base_operating_margin +float tax_rate +float retention_ratio +float ppe_ratio? +float insolvency_tolerance +ExpenseRatioConfig expense_ratios? +int premium_payment_month +str revenue_pattern +bool check_intra_period_liquidity +from_industry_config(IndustryConfig) ManufacturerConfig$ } class WorkingCapitalConfig { +float percent_of_sales } class GrowthConfig { +str type +float annual_growth_rate +float volatility } class DebtConfig { +float interest_rate +float max_leverage_ratio +float minimum_cash_balance } class SimulationConfig { +str time_resolution +int time_horizon_years +int max_horizon_years +int random_seed? +int fiscal_year_end } class OutputConfig { +str output_directory +str file_format +int checkpoint_frequency +bool detailed_metrics } class LoggingConfig { +bool enabled +str level +str log_file? +bool console_output +str format } class InsuranceConfig { +bool enabled +List~InsuranceLayerConfig~ layers +float deductible +float coinsurance +int waiting_period_days +float claims_handling_cost } class InsuranceLayerConfig { +str name +float limit +float attachment +float base_premium_rate +int reinstatements +float aggregate_limit? +str limit_type +float per_occurrence_limit? } class LossDistributionConfig { +str frequency_distribution +float frequency_annual +str severity_distribution +float severity_mean +float severity_std +float correlation_factor +float tail_alpha } class ExcelReportConfig { +bool enabled +str output_path +bool include_balance_sheet +bool include_income_statement +bool include_cash_flow +str engine } class ExpenseRatioConfig { +float gross_margin_ratio +float sga_expense_ratio +float manufacturing_depreciation_allocation +float admin_depreciation_allocation +float direct_materials_ratio +float direct_labor_ratio +float manufacturing_overhead_ratio +float selling_expense_ratio +float general_admin_ratio } class DepreciationConfig { +float ppe_useful_life_years +int prepaid_insurance_amortization_months +float initial_accumulated_depreciation } class WorkingCapitalRatiosConfig { +float days_sales_outstanding +float days_inventory_outstanding +float days_payable_outstanding } class ModuleConfig { +str module_name +str module_version +List~str~ dependencies } class PresetConfig { +str preset_name +str preset_type +str description +Dict parameters } class PresetLibrary { +str library_type +str description +Dict~str,PresetConfig~ presets +from_yaml(Path) PresetLibrary$ } ConfigV2 *-- ProfileMetadata : profile ConfigV2 *-- ManufacturerConfig : manufacturer ConfigV2 *-- WorkingCapitalConfig : working_capital ConfigV2 *-- GrowthConfig : growth ConfigV2 *-- DebtConfig : debt ConfigV2 *-- SimulationConfig : simulation ConfigV2 *-- OutputConfig : output ConfigV2 *-- LoggingConfig : logging ConfigV2 o-- InsuranceConfig : insurance (optional) ConfigV2 o-- LossDistributionConfig : losses (optional) ConfigV2 o-- ExcelReportConfig : excel_reporting (optional) ConfigV2 o-- WorkingCapitalRatiosConfig : working_capital_ratios (optional) ConfigV2 o-- ExpenseRatioConfig : expense_ratios (optional) ConfigV2 o-- DepreciationConfig : depreciation (optional) ConfigV2 o-- ModuleConfig : custom_modules (dict) InsuranceConfig *-- InsuranceLayerConfig : layers (list) ManufacturerConfig o-- ExpenseRatioConfig : expense_ratios (optional) PresetLibrary *-- PresetConfig : presets (dict) ``` **Design notes:** - **Composition over inheritance**: `ConfigV2` uses Pydantic `BaseModel` composition rather than class inheritance. Each sub-model is an independent, validated component. - **Required vs. optional**: The seven required sub-models (`profile`, `manufacturer`, `working_capital`, `growth`, `debt`, `simulation`, `output`, `logging`) define the minimum viable configuration. Optional modules (`insurance`, `losses`, etc.) are loaded only when declared in the profile's `includes` list. - **`with_overrides()` immutability**: Calling `with_overrides()` returns a **new** `ConfigV2` instance rather than mutating the existing one, enabling safe concurrent usage and cache correctness. - **`validate_completeness()`** checks for logical consistency beyond Pydantic's type validation, for example verifying that insurance is not enabled without a loss distribution. --- ## Orchestration Summary The following table summarizes the key classes and their responsibilities: | Class | Module | Responsibility | |-------|--------|----------------| | `ConfigManager` | `config_manager.py` | Main entry point; orchestrates loading, caching, inheritance, modules, presets, and overrides | | `ConfigLoader` | `config_loader.py` | **Deprecated.** Legacy YAML/JSON file loading; delegates to `LegacyConfigAdapter` internally | | `ConfigV2` | `config.py` | Pydantic-validated configuration container; holds all sub-models | | `Config` | `config.py` | Legacy configuration container (7 required sections, no optional modules) | | `LegacyConfigAdapter` | `config_compat.py` | Maps legacy `ConfigLoader` calls to `ConfigManager`; converts `ConfigV2` back to `Config` | | `ConfigTranslator` | `config_compat.py` | Bidirectional conversion between `Config` and `ConfigV2` formats | | `ConfigMigrator` | `config_migrator.py` | One-time migration tool: converts 12 legacy YAML files to 3-tier directory structure | | `PresetLibrary` | `config.py` | Collection of `PresetConfig` entries loaded from a single preset YAML file | --- ## File System Layout ``` ergodic_insurance/ data/ config/ # New 3-tier system root profiles/ default.yaml # Tier 1: Standard baseline conservative.yaml # Tier 1: Conservative (extends: default) aggressive.yaml # Tier 1: Aggressive (extends: default) custom/ # Tier 1: User-defined profiles *.yaml modules/ insurance.yaml # Tier 2: Insurance configuration losses.yaml # Tier 2: Loss distribution configuration stochastic.yaml # Tier 2: Stochastic process configuration business.yaml # Tier 2: Business model configuration presets/ market_conditions.yaml # Tier 3: Market scenario presets layer_structures.yaml # Tier 3: Insurance layer templates risk_scenarios.yaml # Tier 3: Risk scenario presets parameters/ # Legacy system (deprecated) baseline.yaml conservative.yaml optimistic.yaml ... ``` --- ## Related Documentation - [Configuration System v2.0 Architecture](configuration_v2.md) -- Detailed design specification and usage examples - [Module Overview](module_overview.md) -- How modules interact across the framework - [Data Models](class_diagrams/data_models.md) -- Broader data model class diagrams