What-If Analysis¶
Gaspatchio supports a declarative config format that lets you define scenario shocks without writing Python code. This is designed for:
- Actuaries: Ask natural language questions about assumption changes
- LLMs: Generate executable scenario configs from user questions
- Audit: Clear, JSON-serializable shock specifications
For deeper context on why configs matter (and why you don’t need to regenerate assumption tables) plus more realistic actuarial prompts, see Natural Language → Executable Configs.
The Concept¶
Instead of creating separate assumption files for each scenario or writing Python shock classes, you specify simple JSON configs:
{"table": "mortality", "multiply": 1.2}
The framework parses this into shock objects and applies them to your existing tables automatically. Your base assumptions stay untouched - shocks create modified copies at runtime.
Asking What-If Questions¶
Here are example questions and the configs they translate to:
"What if mortality increases by 20%?"¶
[
{"id": "BASE"},
{"id": "MORT_UP_20", "shocks": [{"table": "mortality", "multiply": 1.2}]}
]
"What happens if lapse rates drop by half?"¶
[
{"id": "BASE"},
{"id": "LAPSE_DOWN_50", "shocks": [{"table": "lapse", "multiply": 0.5}]}
]
"Show me the impact of a flat 5% discount rate"¶
[
{"id": "BASE"},
{"id": "DISC_FLAT_5PCT", "shocks": [{"table": "disc_rates", "set": 0.05}]}
]
"What if interest rates increase by 100 basis points?"¶
[
{"id": "BASE"},
{"id": "RATES_UP_100BPS", "shocks": [{"table": "disc_rates", "add": 0.01}]}
]
"What's the worst case if mortality is 20% higher AND lapses drop 10%?"¶
[
{"id": "BASE"},
{"id": "ADVERSE", "shocks": [
{"table": "mortality", "multiply": 1.2},
{"table": "lapse", "multiply": 0.9}
]}
]
Config Format Reference¶
Scenario Config Structure¶
A scenario config is a list where each element defines a scenario:
[
{"id": "SCENARIO_NAME"},
{"id": "SCENARIO_NAME", "shocks": [shock, ...]}
]
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Unique scenario identifier |
shocks |
list | No | List of shock configs to apply |
Shock Config Structure¶
Each shock specifies which table to shock and how to shock it:
{
"table": "table_name",
"column": "column_name",
"multiply": 1.2
}
| Field | Type | Required | Description |
|---|---|---|---|
table |
string | Yes | Target assumption table name |
column |
string | No | Specific column (defaults to value column) |
| Operation | float | Yes | One of: multiply, add, or set |
Shock Operations¶
| Operation | Effect | Example | Result |
|---|---|---|---|
multiply |
Scale by factor | {"table": "mortality", "multiply": 1.2} |
value * 1.2 |
add |
Add constant | {"table": "rates", "add": 0.01} |
value + 0.01 |
set |
Replace with value | {"table": "lapse", "set": 0.0} |
value = 0.0 |
Rules:
- Exactly one operation per shock (
multiply,add, orset) - Multiple shocks can target the same table (applied sequentially)
- Different shocks can target different tables in the same scenario
Realistic Scenario Examples¶
Interest Rate Sensitivity¶
Question: "Show me PV impact of rates moving +/-50bps and +/-100bps"
[
{"id": "BASE"},
{"id": "RATES_DOWN_100BPS", "shocks": [{"table": "disc_rates", "add": -0.01}]},
{"id": "RATES_DOWN_50BPS", "shocks": [{"table": "disc_rates", "add": -0.005}]},
{"id": "RATES_UP_50BPS", "shocks": [{"table": "disc_rates", "add": 0.005}]},
{"id": "RATES_UP_100BPS", "shocks": [{"table": "disc_rates", "add": 0.01}]}
]
Mortality Sensitivity¶
Question: "What's the impact of mortality being 10%, 20%, or 30% higher than expected?"
[
{"id": "BASE"},
{"id": "MORT_UP_10", "shocks": [{"table": "mortality", "multiply": 1.1}]},
{"id": "MORT_UP_20", "shocks": [{"table": "mortality", "multiply": 1.2}]},
{"id": "MORT_UP_30", "shocks": [{"table": "mortality", "multiply": 1.3}]}
]
Combined Stress Scenarios¶
Question: "Show me best case, base case, and worst case scenarios"
[
{"id": "BEST_CASE", "shocks": [
{"table": "mortality", "multiply": 0.9},
{"table": "lapse", "multiply": 1.1},
{"table": "disc_rates", "add": 0.005}
]},
{"id": "BASE"},
{"id": "WORST_CASE", "shocks": [
{"table": "mortality", "multiply": 1.2},
{"table": "lapse", "multiply": 0.8},
{"table": "disc_rates", "add": -0.01}
]}
]
Regulatory Stress Test¶
Question: "Run the standard regulatory stress scenarios"
[
{"id": "BASE"},
{"id": "EQUITY_DOWN_40", "shocks": [{"table": "equity_returns", "multiply": 0.6}]},
{"id": "RATES_DOWN_200BPS", "shocks": [{"table": "disc_rates", "add": -0.02}]},
{"id": "MORT_PANDEMIC", "shocks": [{"table": "mortality", "multiply": 1.5}]},
{"id": "COMBINED_STRESS", "shocks": [
{"table": "equity_returns", "multiply": 0.8},
{"table": "disc_rates", "add": -0.01},
{"table": "mortality", "multiply": 1.2},
{"table": "lapse", "multiply": 1.3}
]}
]
Flat Assumption Override¶
Question: "What if we assume zero lapses and mortality?"
[
{"id": "BASE"},
{"id": "NO_DECREMENTS", "shocks": [
{"table": "mortality", "set": 0.0},
{"table": "lapse", "set": 0.0}
]}
]
Common Table Names¶
When generating configs, use these standard table names:
| Table Name | Description | Typical Shocks |
|---|---|---|
mortality |
Death rates by age/duration | multiply (+/-10-50%) |
lapse |
Withdrawal/surrender rates | multiply (+/-20-50%) |
disc_rates |
Discount/interest rates | add (+/-50-200bps) |
expense |
Per-policy expenses | multiply (+/-10-30%) |
inflation |
Expense inflation | add (+/-1-3%) |
equity_returns |
Fund returns | multiply (stress), add (drift) |
premium_rates |
Premium loading | multiply (rare) |
Shock Magnitude Guidelines¶
Typical ranges for sensitivity analysis:
| Assumption | Typical Range | Notes |
|---|---|---|
| Mortality | +/-10% to +/-50% | Higher for pandemic stress |
| Lapse | +/-20% to +/-50% | Direction depends on product |
| Interest rates | +/-50bps to +/-200bps | Use add not multiply |
| Expenses | +/-10% to +/-30% | Often paired with inflation |
| Equity | -20% to -40% | Stress scenarios |
Naming Conventions¶
Use clear, descriptive scenario IDs:
| Pattern | Example | Use Case |
|---|---|---|
{TABLE}_{DIRECTION}_{AMOUNT} |
MORT_UP_20 |
Single assumption shock |
{DIRECTION}_{AMOUNT}BPS |
RATES_DOWN_100BPS |
Interest rate moves |
| Descriptive | ADVERSE, BEST_CASE |
Combined scenarios |
| Regulatory | SFCR_STRESS_1 |
Standard tests |
Best Practices¶
Always Include BASE¶
Every config should include an unshocked BASE scenario for comparison:
[
{"id": "BASE"},
{"id": "STRESS_1", "shocks": [...]},
{"id": "STRESS_2", "shocks": [...]}
]
Validation Errors¶
The parser validates configs and provides clear error messages:
| Error | Cause |
|---|---|
| "Shock config must include 'table' key" | Missing table field |
| "Shock config must include exactly one operation" | Missing or multiple operations |
| "Duplicate scenario ID" | Same id used twice |
LLM Integration¶
The config format is designed for LLM generation. An LLM can translate natural language to executable configs:
| User Question | Generated Config |
|---|---|
| "What if mortality is 20% higher?" | {"table": "mortality", "multiply": 1.2} |
| "Add 50bps to discount rates" | {"table": "disc_rates", "add": 0.005} |
| "Set lapse rates to zero" | {"table": "lapse", "set": 0.0} |
| "What's the worst case?" | Multiple shocks combined |
This enables actuaries to ask questions in plain English and get executable scenario analysis without writing code.
See Also¶
- Shock Operations - Advanced shocks: clip, pipeline, filters, regulatory scenarios
- Table Sensitivities - Python API for applying shocks
- Scenarios Overview - High-level scenario concepts
- Performance - Optimizing large scenario runs