> ## Documentation Index
> Fetch the complete documentation index at: https://docs.veydra.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Veydra Model Standard (VMS)

> Conventions for building system dynamics models that are executable and analyzable

## Overview

The Veydra Model Standard (VMS) is a set of conventions for building system dynamics models that are both executable and analyzable. VMS-compliant models enable automatic generation of stock-and-flow diagrams, causal loop diagrams, and other analytical visualizations through AST (Abstract Syntax Tree) parsing.

<CardGroup cols={2}>
  <Card title="Executable" icon="play">
    Models run in Python with numerical simulation via SciPy
  </Card>

  <Card title="Analyzable" icon="diagram-project">
    AST parsing extracts structure for automatic diagram generation
  </Card>

  <Card title="Modular" icon="cubes">
    Independent submodels can be composed into larger systems
  </Card>

  <Card title="Traceable" icon="route">
    Explicit dependencies enable causal loop analysis
  </Card>
</CardGroup>

## Core Architecture

VMS models consist of:

* **Submodels**: Independent modules that inherit from `Submodel` base class
* **VARIABLES Dictionary**: Module-level declaration of all model variables
* **SimulationContext**: Runtime context for accessing stocks, parameters, and intervention gating
* **Calculation Functions**: Module-level functions for complex computations
* **Multi-Scenario Execution**: `run_multi_scenario()` runs baseline + N scenarios in a single call
* **Output Helpers**: Summary tables, stacked tables, and time-window filtering

```python theme={null}
from veydra_model_standard import (
    Submodel,
    VeydraModelStandard,
    SimulationContext,
    META_KEYS,
    compute_summary_stats,
    build_summary_table,
    build_stacked_table,
    apply_time_window,
)
import numpy as np
```

## Submodel Structure

Every submodel follows this structure:

```python theme={null}
"""Module docstring describing the submodel's purpose."""

from veydra_model_standard import Submodel
import numpy as np

# 1. VMS Calculation Functions (optional, for complex logic)
def calc_something(input1, input2):
    """Docstring describing the calculation."""
    return input1 * input2

# 2. VARIABLES dictionary (REQUIRED - module level)
VARIABLES = {
    'namespace.s_stock_name': {...},
    'namespace.parameter_name': {...},
}

# 3. Submodel class
class MySubmodel(Submodel):
    """Class docstring."""
    
    def calculate_derivatives_and_flows(self, sim_context):
        # Implementation
        return derivatives, flows
```

<Warning>
  **Required Rules:**

  * Do NOT override `__init__` method
  * Do NOT override `get_variables()` method
  * VARIABLES dict MUST be at module level
  * Only implement `calculate_derivatives_and_flows()`
</Warning>

## VARIABLES Dictionary

The VARIABLES dictionary declares all model variables at module level.

### Stock Definition

```python theme={null}
'budget.s_government_budget': {
    'name': 'Government Budget',           # Display name
    'units': 'dollars',                    # Unit of measurement
    'description': 'Accumulated fund',     # Plain text description
    'default': 100000.0,                   # Initial value (THIS IS THE INITIAL VALUE)
    'min': -1000000.0,                     # Minimum allowed
    'max': 10000000.0,                     # Maximum allowed
    'type': 'slider',                      # UI control type
    'category': 'stock'                    # MUST be 'stock'
}
```

### Parameter Definition

```python theme={null}
'budget.tax_rate': {
    'name': 'Tax Rate',
    'units': 'dimensionless',
    'description': 'Fraction collected as tax',
    'default': 0.02,
    'min': 0.0,
    'max': 0.15,
    'step': 0.005,                         # Step size for UI
    'type': 'slider',
    'category': 'parameter'                # MUST be 'parameter'
}
```

### Naming Conventions

| Type      | Prefix/Suffix  | Example                      |
| --------- | -------------- | ---------------------------- |
| Stock     | `s_` prefix    | `budget.s_government_budget` |
| Parameter | none           | `budget.tax_rate`            |
| Flow      | `_flow` suffix | `budget.tax_revenue_flow`    |

<Note>
  **Critical Rule**: The `default` field in a stock definition **IS** the initial value. Never create separate `initial_*` parameters.
</Note>

```python theme={null}
# ❌ WRONG - Don't do this
'population.initial_population': {'default': 1000000, ...}  # FORBIDDEN

# ✅ CORRECT - Stock default IS the initial value
'population.s_population': {'default': 1000000, ...}  # default = initial value
```

## Stock-and-Flow Modeling

### Separate Flows by Direction

Each parameter affecting a stock must have its own distinct flow. Never collapse flows.

<Tabs>
  <Tab title="✅ Correct">
    ```python theme={null}
    # Separate flows preserve directionality
    deposit_flow = periodic_deposit_amount
    withdrawal_flow = periodic_withdrawal_amount
    interest_flow = balance * interest_rate

    d_balance_dt = deposit_flow - withdrawal_flow + interest_flow

    flows = {
        'account.deposit_flow': deposit_flow,
        'account.withdrawal_flow': withdrawal_flow,
        'account.interest_flow': interest_flow
    }
    ```
  </Tab>

  <Tab title="❌ Wrong">
    ```python theme={null}
    # Flow collapsing loses directionality
    net_flow = deposit_amount - withdrawal_amount  # Lost individual visibility!
    d_balance_dt = net_flow + interest_flow

    flows = {
        'account.net_flow': net_flow  # Can't trace individual impacts
    }
    ```
  </Tab>
</Tabs>

### Why This Matters

* Individual flows enable proper stock-and-flow diagram generation
* Collapsed flows obscure causal relationships
* AST parser cannot trace through intermediate "net" variables

## Direct Flow Reference Rule

**Derivatives MUST directly reference the same variable names that appear in the flows dictionary.**

This enables the AST parser to trace stock-flow relationships for automatic diagram generation.

```python theme={null}
# Step 1: Define flow variables
government_tax_revenue_flow = tax_revenue
government_spending_flow = spending

# Step 2: Derivative DIRECTLY references flow variable names
d_government_budget_dt = government_tax_revenue_flow - government_spending_flow

# Step 3: flows dict uses the SAME variable names
flows = {
    'budget.government_tax_revenue_flow': government_tax_revenue_flow,  # ✅ Same name
    'budget.government_spending_flow': government_spending_flow         # ✅ Same name
}
```

<Warning>
  **Common Mistakes:**

  * Using different variable names in derivative vs flows dict
  * Using intermediate "net" variables that hide individual flows
</Warning>

## Calculation Function Convention

For complex intermediate calculations (conditionals, aggregations, multi-step computations), define module-level calculation functions where **ALL dependencies are explicit in the function signature**.

### Why Use Calculation Functions

The AST parser extracts all function call arguments as dependencies. This makes complex logic fully traceable.

```python theme={null}
# AST parser sees: pool_frac_ill depends on all 5 arguments
pool_frac_ill = calc_pool_frac_ill(insured_pool, insured_healthy, frac_ill_healthy, insured_sickly, frac_ill_sickly)
```

### Pattern

```python theme={null}
# Module-level calculation function with explicit dependencies
def calc_pool_frac_ill(insured_pool, insured_healthy, frac_ill_healthy, insured_sickly, frac_ill_sickly):
    """Calculate weighted average fraction ill across insured pool."""
    if insured_pool > 1.0:
        return (insured_healthy * frac_ill_healthy + insured_sickly * frac_ill_sickly) / insured_pool
    return frac_ill_healthy

# In calculate_derivatives_and_flows:
pool_frac_ill = calc_pool_frac_ill(insured_pool, insured_healthy, frac_ill_healthy, 
                                    insured_sickly, frac_ill_sickly)
```

### Function Naming Convention

* Use `calc_` prefix: `calc_pool_frac_ill`, `calc_total_income`, `calc_tax_revenue`
* Name should describe what is being calculated
* Keep signatures to 6 or fewer parameters when possible

### When to Use Calculation Functions

| Use Case                | Example                                 |
| ----------------------- | --------------------------------------- |
| Conditional logic       | `if pool > 0: ... else: ...`            |
| Weighted averages       | `(a * weight_a + b * weight_b) / total` |
| Multi-step computations | Intermediate results needed             |
| Complex aggregations    | Summations with conditions              |

## Derivative Patterns

### Dictionary Format (Required)

Always return derivatives as a dictionary mapping stock names to their rates of change:

```python theme={null}
derivatives = {
    'budget.s_government_budget': d_government_budget_dt,
    'budget.s_private_budget': d_private_budget_dt
}
```

<Warning>
  Never use array format - it's unclear which stock is which:

  ```python theme={null}
  # ❌ WRONG
  derivatives = [d_government_budget_dt, d_private_budget_dt]
  ```
</Warning>

### Derivative Expression Pattern

Build derivatives from individual flows with explicit addition/subtraction:

```python theme={null}
# ✅ CORRECT - Explicit inflow/outflow pattern
d_stock_dt = inflow1 + inflow2 - outflow1 - outflow2
```

### Accessing Stocks and Parameters

```python theme={null}
# ✅ CORRECT - Use getter methods
stock_value = sim_context.get_stock('namespace.s_stock_name', default_value)
param_value = sim_context.get_param('namespace.param_name', default_value)

# ❌ WRONG - Direct attribute access
stock_value = sim_context.stocks['namespace.s_stock_name']  # Don't do this
param_value = sim_context.params['namespace.param_name']    # Don't do this
```

## AST Traceability

The VMS conventions enable automatic AST parsing for:

<Steps>
  <Step title="Stock-Flow Diagram Generation">
    Automatically creates visual diagrams showing stocks, flows, and their connections
  </Step>

  <Step title="Causal Loop Analysis">
    Traces parameter influences through the model to identify feedback loops
  </Step>

  <Step title="Dependency Extraction">
    Identifies all variable dependencies for each calculation
  </Step>
</Steps>

### How AST Parsing Works

1. **Shallow Extraction**: Parser extracts direct variable references from expressions
2. **Function Arguments**: All arguments to `calc_*` functions are extracted as dependencies
3. **Flow Dictionary Keys**: Flow names from the flows dict are matched to derivative expressions

| Convention            | Enables                               |
| --------------------- | ------------------------------------- |
| Direct Flow Reference | Stock-to-flow connections in diagrams |
| Calculation Functions | Complete dependency graphs            |
| Separate Flows        | Individual causal links               |
| VARIABLES dict        | Parameter metadata extraction         |

## Complete Example

Here's a complete VMS-compliant submodel:

```python theme={null}
"""Budget submodel: government and private sector funding dynamics."""

from veydra_model_standard import Submodel
import numpy as np


# ═══════════════════════════════════════════════════════════════════════════════
# VMS CALCULATION FUNCTIONS - explicit dependency signatures for AST traceability
# ═══════════════════════════════════════════════════════════════════════════════

def calc_tax_revenue(total_income, tax_rate):
    """Calculate government tax revenue from total income."""
    return total_income * tax_rate


def calc_spending(investment_spending, split_fraction):
    """Calculate spending portion based on split."""
    return investment_spending * split_fraction


# ═══════════════════════════════════════════════════════════════════════════════
# VARIABLES - module-level declaration (stock defaults ARE initial values)
# ═══════════════════════════════════════════════════════════════════════════════

VARIABLES = {
    # Stocks
    'budget.s_government_budget': {
        'name': 'Government Budget',
        'units': 'dollars',
        'description': 'Accumulated government fund',
        'default': 100000.0,  # THIS IS THE INITIAL VALUE
        'min': -1000000.0,
        'max': 10000000.0,
        'type': 'slider',
        'category': 'stock'
    },
    
    # Parameters
    'budget.tax_rate': {
        'name': 'Tax Rate',
        'units': 'dimensionless',
        'description': 'Fraction collected as healthcare tax',
        'default': 0.02,
        'min': 0.0,
        'max': 0.15,
        'step': 0.005,
        'type': 'slider',
        'category': 'parameter'
    }
}


class BudgetSubmodel(Submodel):
    """Government budget dynamics with tax revenue and spending."""

    def calculate_derivatives_and_flows(self, sim_context):
        # ── Read stocks ───────────────────────────────────────────────────
        government_budget = sim_context.get_stock('budget.s_government_budget', 100000.0)

        # ── Read parameters ───────────────────────────────────────────────
        tax_rate = sim_context.get_param('budget.tax_rate', 0.02)
        
        # ── Read cross-submodel values ────────────────────────────────────
        total_income = sim_context.get_param('economy.total_income', 1000000.0)
        investment_spending = sim_context.get_param('capacity.investment_spending', 50000.0)

        # ── VMS Calculation Functions ─────────────────────────────────────
        tax_revenue = calc_tax_revenue(total_income, tax_rate)
        government_spending = calc_spending(investment_spending, 0.5)

        # ── Define flow variables (for direct reference rule) ─────────────
        tax_revenue_flow = tax_revenue
        spending_flow = government_spending

        # ── Derivative (directly references flow variable names) ──────────
        d_government_budget_dt = tax_revenue_flow - spending_flow

        # ── Return dictionaries ───────────────────────────────────────────
        derivatives = {
            'budget.s_government_budget': d_government_budget_dt
        }

        flows = {
            'budget.tax_revenue_flow': tax_revenue_flow,    # Inflow
            'budget.spending_flow': spending_flow           # Outflow
        }

        return derivatives, flows
```

## Multi-Scenario Execution

`VeydraModelStandard.run_multi_scenario()` runs one or many scenarios in a single call with shared simulation parameters and calibration.

### Input Config Shape

```python theme={null}
config = {
    # ── Control ──────────────────────────────────────────────
    "include_baseline": True,           # Run a pure-defaults baseline (default: False)

    # ── Shared simulation params ─────────────────────────────
    "simulation.duration": 104,
    "simulation.intervention_start_time": 52,

    # ── Meta-keys (stripped before parameter resolution) ─────
    "__calibration_params__": {          # Pre-intervention overrides
        "retail.order_quantity": 600
    },
    "__initial_state__": {               # Stock overrides at t=0 (game-mode warm-start)
        "retail.s_inventory_on_hand": 500
    },
    "__time_window__": {                 # Filter output to a time range
        "start": 10, "end": 80
    },
    "__output_format__": "full",         # "full" | "summary" | "stacked" | "all"
    "__summary_stats__": ["final_value", "peak", "mean"],
    "__summary_variable__": "retail.s_inventory_on_hand",

    # ── Scenario list ────────────────────────────────────────
    "scenarios": [
        {"id": "high_qty", "params": {"retail.order_quantity": 1000}},
        {"id": "low_qty",  "params": {"retail.order_quantity": 200}},
    ]
}

result = model.run_multi_scenario(config)
```

### How It Works

<Steps>
  <Step title="Extract meta-keys">
    `include_baseline`, `__calibration_params__`, `__initial_state__`, `__time_window__`, `__output_format__`, `__summary_stats__`, `__summary_variable__`, and `scenarios` are pulled out of the config before parameter resolution. They never reach the ODE solver.
  </Step>

  <Step title="Run baseline (if requested)">
    When `include_baseline` is `true`, a run is executed with only the shared simulation params plus calibration values. No intervention gating is applied (there are no scenario overrides to gate).
  </Step>

  <Step title="Run each scenario">
    For every entry in `scenarios`, the simulation params are merged with that scenario's `params`. If `simulation.intervention_start_time` is set, the scenario override keys are gated so they only take effect after that time (see Intervention Gating below).
  </Step>

  <Step title="Format output">
    Results are assembled in the requested `__output_format__` and returned.
  </Step>
</Steps>

### Implicit Current Scenario

If no `scenarios` list is provided but there are non-simulation override keys in the config, they are automatically bundled into a single scenario with id `"current"`:

```python theme={null}
# These two calls are equivalent:
model.run_multi_scenario({"retail.order_quantity": 800})
model.run_multi_scenario({"scenarios": [{"id": "current", "params": {"retail.order_quantity": 800}}]})
```

### Auto-Detection in run\_interactive\_simulation

`run_interactive_simulation()` automatically routes to `run_multi_scenario()` when any of these keys are present: `scenarios`, `include_baseline`, or `__output_format__`. Otherwise it falls through to a plain `run_simulation()` call for full backward compatibility.

```python theme={null}
# Single run (backward compatible)
run_interactive_simulation(model, {"retail.order_quantity": 800})

# Multi-scenario (auto-detected)
run_interactive_simulation(model, {
    "include_baseline": True,
    "scenarios": [{"id": "s1", "params": {"retail.order_quantity": 800}}]
})
```

## Intervention Gating

`SimulationContext.get_param()` natively supports intervention gating. When a scenario is run with `simulation.intervention_start_time` set, scenario override parameters return their **default (or calibration) value** before the intervention time, and the **scenario value** after it.

This eliminates the need for any external monkey-patching or runtime code injection.

### Priority Order

`get_param(key)` resolves values in this order:

1. **Intervention gating** - if `current_time < intervention_start_time` and `key` is a scenario override, return calibration value (or model default)
2. **Dynamic parameters** - if the value is a `__dynamic__` spec, resolve it to a scalar at `current_time`
3. **Plain scalar** - return the value from `all_params` directly

```python theme={null}
# SimulationContext is constructed with gating context automatically
# by the orchestrator during run_multi_scenario(). Submodels don't need
# to do anything special — sim_context.get_param() handles it.

# Before intervention_start_time:
sim_context.get_param('retail.order_quantity')  # → calibration value or default

# After intervention_start_time:
sim_context.get_param('retail.order_quantity')  # → scenario override value
```

### SimulationContext Constructor

```python theme={null}
SimulationContext(
    current_time=t,
    current_datetime=current_datetime,
    all_params=params,
    all_stocks=stocks,
    intervention_active=False,
    # Optional gating fields (set automatically by run_multi_scenario):
    intervention_start_time=52.0,
    scenario_param_keys={'retail.order_quantity'},
    calibration_params={'retail.order_quantity': 600},
    param_defaults={'retail.order_quantity': 500},
)
```

<Note>
  Submodel code does not change at all. The gating is handled entirely inside `get_param()`, so existing `sim_context.get_param('retail.order_quantity')` calls work with or without gating enabled.
</Note>

## Dynamic (Time-Varying) Parameters

Parameters can be time-varying instead of scalar. Dynamic parameters are dicts with `__dynamic__: true` and are resolved to a scalar at `current_time` by `SimulationContext.get_param()`.

### Modes

| Mode       | Description                                   | Required Fields                    |
| ---------- | --------------------------------------------- | ---------------------------------- |
| `function` | Evaluates a math expression at each time step | `expression` (string)              |
| `array`    | Linearly interpolates a lookup table          | `points` (list of `[time, value]`) |
| `drawn`    | Interpolates user-drawn curve points          | `points` (list of `[time, value]`) |

### Example

```python theme={null}
params = {
    # Static parameter
    'retail.order_quantity': 500,

    # Dynamic parameter — function mode
    'retail.demand_rate': {
        '__dynamic__': True,
        'mode': 'function',
        'expression': 'sin(t * 0.1) * 50 + 200',
        'defaultValue': 200.0
    },

    # Dynamic parameter — array mode
    'retail.seasonal_factor': {
        '__dynamic__': True,
        'mode': 'array',
        'points': [[0, 1.0], [26, 1.5], [52, 1.0], [78, 0.8], [104, 1.0]],
        'defaultValue': 1.0
    }
}
```

Available math functions in expressions: `sin`, `cos`, `tan`, `exp`, `log`, `sqrt`, `abs`, `min`, `max`, `pow`, `floor`, `ceil`, `pi`, `e`. The variable `t` represents `current_time`.

<Warning>
  Dynamic parameter specs are preserved through `_clean_params()` and `_resolve_parameters()` — they bypass scalar validation (min/max clamping) since their value changes over time.
</Warning>

## Output Formats

The `__output_format__` meta-key controls how `run_multi_scenario()` returns results.

### `"full"` (default)

Returns the complete time-series for every scenario:

```python theme={null}
{
    "success": True,
    "scenarios": {
        "baseline": {"success": True, "time": [...], "stocks": {...}, "flows": {...}},
        "high_qty": {"success": True, "time": [...], "stocks": {...}, "flows": {...}}
    }
}
```

### `"summary"`

Returns a compact table with one row per scenario and configurable summary statistics:

```python theme={null}
{
    "success": True,
    "summary": {
        "columns": ["scenario_id", "final_value", "peak", "min", "mean", "growth_pct"],
        "rows": [
            ["baseline", 450.2, 520.1, 380.0, 455.3, 12.5],
            ["high_qty", 680.9, 710.0, 400.0, 590.1, 36.2]
        ],
        "primary_stock": "retail.s_inventory_on_hand"
    }
}
```

Customize with `__summary_stats__` (list of stat names) and `__summary_variable__` (which stock to summarize).

**Available stats:** `final_value`, `peak`, `min`, `mean`, `growth_pct`, `variance`, `time_to_peak`

### `"stacked"`

Returns a long-format table suitable for charting in Google Sheets:

```python theme={null}
{
    "success": True,
    "stacked": {
        "columns": ["scenario_id", "time", "variable", "value"],
        "rows": [
            ["baseline", 0, "s_inventory_on_hand", 500.0],
            ["baseline", 0, "s_inventory_on_order", 0.0],
            ["baseline", 1, "s_inventory_on_hand", 485.3],
            ...
        ]
    }
}
```

Variable names use shorthand (last segment after `.`) for readability.

### `"all"`

Returns all three formats in a single response: `scenarios`, `summary`, and `stacked`.

## Meta-Keys Reference

Meta-keys are special config keys that control execution behavior. They are stripped from the parameter dict before the ODE solver sees them.

| Key                      | Type     | Default     | Description                                                                   |
| ------------------------ | -------- | ----------- | ----------------------------------------------------------------------------- |
| `include_baseline`       | `bool`   | `false`     | Run a pure-defaults baseline alongside scenarios                              |
| `scenarios`              | `list`   | `[]`        | List of `{id, params}` scenario definitions                                   |
| `__calibration_params__` | `dict`   | `{}`        | Parameter overrides applied to baseline and used as pre-intervention defaults |
| `__initial_state__`      | `dict`   | `{}`        | Stock value overrides at t=0 (warm-start / game-mode)                         |
| `__time_window__`        | `dict`   | `null`      | `{start, end}` to filter output time range                                    |
| `__output_format__`      | `string` | `"full"`    | `"full"`, `"summary"`, `"stacked"`, or `"all"`                                |
| `__summary_stats__`      | `list`   | default set | Which statistics to compute in summary mode                                   |
| `__summary_variable__`   | `string` | first stock | Which stock to summarize                                                      |

<Warning>
  Meta-keys must never collide with actual model parameter names. They are defined in `META_KEYS` frozenset and automatically excluded from `_clean_params()`.
</Warning>

## Initial State and Game-Mode Stepping

The `__initial_state__` meta-key overrides stock values at t=0, enabling two use cases:

### Warm-Start

Resume a simulation from a previously saved state:

```python theme={null}
result = model.run_simulation({
    'simulation.duration': 52,
    '__initial_state__': {
        'retail.s_inventory_on_hand': 450.0,
        'retail.s_inventory_on_order': 120.0,
    }
})
```

### Game-Mode Stepping

Run the simulation one step at a time (e.g., one week per turn). Each step feeds the final state of the previous step as `__initial_state__` for the next:

```python theme={null}
state = {}  # Start with defaults
step_size = 1  # 1 week per turn

for week in range(52):
    result = model.run_simulation({
        'simulation.duration': step_size,
        '__initial_state__': state,
        # Player can change params each turn
        'retail.order_quantity': player_decision,
    })

    # Capture final stock values as next initial state
    state = {
        stock: values[-1]
        for stock, values in result['stocks'].items()
    }
```

<Note>
  In the frontend, game-mode stepping runs client-side via Pyodide (not server-side). The `__initial_state__` dict is passed in each step call.
</Note>

## Calibration Parameters

`__calibration_params__` are shared across all scenarios in a `run_multi_scenario()` call. They serve two purposes:

1. **Baseline modification**: Applied directly to the baseline run as regular parameter overrides
2. **Pre-intervention defaults**: For scenario runs with intervention gating, calibration values are used as the pre-intervention value (taking priority over model defaults)

```python theme={null}
result = model.run_multi_scenario({
    'include_baseline': True,
    'simulation.duration': 104,
    'simulation.intervention_start_time': 52,
    '__calibration_params__': {
        'retail.order_quantity': 600,  # Calibrated to real-world data
    },
    'scenarios': [
        {'id': 'high', 'params': {'retail.order_quantity': 1000}},
    ]
})

# baseline: order_quantity = 600 for the entire run
# high scenario:
#   t < 52: order_quantity = 600 (calibration value, via intervention gating)
#   t >= 52: order_quantity = 1000 (scenario override kicks in)
```

## Helper Functions

### compute\_summary\_stats

Compute named statistics for a single time-series:

```python theme={null}
from veydra_model_standard import compute_summary_stats

stats = compute_summary_stats([10, 20, 30, 40, 50], ['final_value', 'mean', 'growth_pct'])
# {'final_value': 50.0, 'mean': 30.0, 'growth_pct': 400.0}
```

### build\_summary\_table

One row per scenario with configurable stats:

```python theme={null}
from veydra_model_standard import build_summary_table

table = build_summary_table(scenario_results, 'retail.s_inventory_on_hand')
# {'columns': ['scenario_id', 'final_value', ...], 'rows': [...], 'primary_stock': '...'}
```

### build\_stacked\_table

Long-format table for all scenarios (suitable for Google Sheets charting):

```python theme={null}
from veydra_model_standard import build_stacked_table

table = build_stacked_table(scenario_results)
# {'columns': ['scenario_id', 'time', 'variable', 'value'], 'rows': [...]}
```

### apply\_time\_window

Filter a single-run result dict to a time range:

```python theme={null}
from veydra_model_standard import apply_time_window

filtered = apply_time_window(result, {'start': 10, 'end': 80})
```

## Quick Reference Checklist

When writing VMS-compliant models, verify:

<CheckGroup>
  <Check>VARIABLES dict is at module level</Check>
  <Check>Stock names use `s_` prefix</Check>
  <Check>Stock `default` is used for initial value (no separate `initial_*` params)</Check>
  <Check>All `sim_context.get_param()` calls match VARIABLES entries</Check>
  <Check>Flows are separate (not collapsed into "net" flows)</Check>
  <Check>Derivative expressions directly reference flow variable names</Check>
  <Check>Complex calculations use `calc_*` functions with explicit arguments</Check>
  <Check>`calculate_derivatives_and_flows()` returns `(derivatives_dict, flows_dict)`</Check>
  <Check>No custom `__init__` or `get_variables()` methods</Check>
  <Check>Descriptions use plain text (no apostrophes or special characters)</Check>
  <Check>Meta-keys (`include_baseline`, `__calibration_params__`, etc.) never collide with model parameter names</Check>
  <Check>Dynamic parameters use `__dynamic__: true` with `mode`, `expression` or `points`</Check>
</CheckGroup>
