MOD-035 — IRRBB / EVE / NII Model
Status: Built (pending CI)
Repo: bank-risk-platform
System: SD06
Phase: 2 (depends on MOD-085 — Built+Deployed)
ADRs: ADR-002 (Snowflake analytics), ADR-039 (per-domain schemas), ADR-046 (data-product architecture), ADR-054 (DCM v2)
A Snowflake-native IRRBB model. Daily computation of Economic Value of
Equity (EVE) and Net Interest Income (NII) sensitivity to interest-rate
shocks across the six BCBS 239 standard scenarios (parallel up/down,
steepener, flattener, short-rate up/down). Reads rate-sensitive balance-
sheet positions from CDC and yield curves from MOD-085 market views.
Snowflake Alert detects EVE-sensitivity breaches above the 15% Tier 1
BCBS supervisory outlier threshold and routes via SNS → Lambda →
EventBridge irrbb_outlier_breach (full envelope: event_id, event_time,
schema_version, idempotency_key) to MOD-076 for ALCO/CRO escalation.
Functional requirements
| ID |
Requirement |
How satisfied |
| FR-213 |
EVE + NII sensitivity for 6 BCBS 239 scenarios daily |
irrbb_sensitivities DT, INCREMENTAL, target_lag=1h, one row per (jurisdiction × scenario × shock_basis) |
| FR-214 |
Results within 5 min of daily rate-curve update |
DT target_lag well below NFR-008's 5-min ceiling; incremental on rate change is seconds |
| FR-215 |
Alert when EVE sensitivity exceeds 15% of Tier 1 capital |
eve_outlier column drives V_IRRBB_OUTLIER_BREACHES filter view → Snowflake Alert (5-min schedule) → SNS → alert-publish Lambda → EventBridge irrbb_outlier_breach event with full envelope |
| FR-216 |
Output in RBNZ/APRA prudential return format; 7-year retention |
RISK_CAPITAL.IRRBB_RUNS append-only audit log; immutability enforced by absent UPDATE/DELETE/TRUNCATE grants on consumer roles (NFR-024) |
| NFR-006 |
Interest accrual posted by 23:59 daily (transitive — drives EOD repricing snapshot) |
Out-of-scope upstream (MOD-006/MOD-007) |
| NFR-008 |
Capital/liquidity metrics ≤ 5 min after material event |
DT 1h target_lag is the worst-case bound; incremental on actual rate change is seconds |
| NFR-024 |
Audit log mutability = 0 modifications |
IRRBB_RUNS has no UPDATE/DELETE/TRUNCATE grants on BANK_DBT_ROLE; verified by tests/integration/fr-216-immutability.test.ts (uses MOD-039 / MOD-032 pattern of SHOW GRANTS TO ROLE to exclude OWNER's intrinsic privileges) |
Policies satisfied
| Policy |
Mode |
Test |
| CLQ-004 |
CALC |
tests/policy/CLQ-004-calc.test.ts — DT materialization, INCREMENTAL refresh, 1h target_lag, RESUME post-hook, target.warehouse |
| REP-002 |
CALC |
tests/policy/REP-002-calc.test.ts — single DT feeds internal monitor (v_irrbb_current) + regulatory return path (MOD-036 reads IRRBB_SENSITIVITIES via SSM-published table reference) |
| GOV-002 |
ALERT |
tests/policy/GOV-002-alert.test.ts (structural) + integration tests — Snowflake Alert + alert-publish Lambda emit irrbb_outlier_breach with the catalogue-required envelope (event_id, event_time, schema_version, idempotency_key) and decimal-string financial values |
Snowflake objects
| Object |
Type |
Owner |
Notes |
BANK_{ENV}_RISK.RISK_CAPITAL |
schema |
DCM v2 (shared with MOD-032 + future MOD-033) |
define schema is idempotent |
RISK_CAPITAL.IRRBB_RUNS |
table |
DCM v2 |
FR-216 audit; INSERT-only grant; immutability test |
RISK_CAPITAL.IRRBB_SHOCK_SCENARIOS |
table |
DCM v2 + pre-dbt seed |
BCBS / RBNZ / APRA shock magnitudes per (jurisdiction × scenario × basis); plus Tier 1 fixture for V1 fallback |
RISK_CAPITAL.IRRBB_SENSITIVITIES |
Dynamic Table |
dbt |
INCREMENTAL, target_lag 1h, cluster (position_date, jurisdiction, scenario_code), post_hook RESUME, target.warehouse |
RISK_CAPITAL.V_IRRBB_CURRENT |
view |
dbt |
ADR-046 §3 published contract — current row per (jurisdiction × scenario × basis) |
RISK_CAPITAL.V_IRRBB_OUTLIER_BREACHES |
view |
dbt |
ADR-046 §3 — filter view (eve_outlier = TRUE); drives Snowflake Alert |
RISK_CAPITAL.IRRBB_OUTLIER_BREACH_ALERT |
Alert |
DCM v2 (post-dbt) |
5-min schedule; references V_IRRBB_OUTLIER_BREACHES; publishes via BANK_SNS_INTEGRATION |
RISK_CAPITAL.IRRBB_SENSITIVITIES schema
The SD06 wiki data model does not yet have an irrbb_sensitivities
entry — MOD-035-complete.handoff.md requests its addition. Pending
that wiki update, the authoritative shape is:
| Column |
Type |
Description |
| position_id |
VARCHAR NOT NULL |
Surrogate key — SHA2 of (jurisdiction, position_date, scenario_code, shock_basis) |
| position_date |
DATE NOT NULL |
Business date of the calculation |
| jurisdiction |
VARCHAR(2) NOT NULL |
NZ | AU |
| scenario_code |
VARCHAR NOT NULL |
PARALLEL_UP | PARALLEL_DOWN | STEEPENER | FLATTENER | SHORT_UP | SHORT_DOWN |
| shock_basis |
VARCHAR NOT NULL |
BCBS | RBNZ | APRA |
| eve_baseline |
NUMBER(18,2) |
EVE at current rates |
| eve_shocked |
NUMBER(18,2) |
EVE under scenario shock |
| eve_change |
NUMBER(18,2) |
shocked − baseline |
| tier1_capital |
NUMBER(18,2) |
Snapshot used for outlier-pct calc — from MOD-033 capital_positions when present, else IRRBB_SHOCK_SCENARIOS.tier1_capital_fixture |
| eve_change_pct_tier1 |
FLOAT |
|eve_change| / tier1_capital |
| nii_baseline |
NUMBER(18,2) |
12-month NII at current rates |
| nii_shocked |
NUMBER(18,2) |
12-month NII under shock |
| nii_change |
NUMBER(18,2) |
shocked − baseline |
| nii_change_pct |
FLOAT |
|nii_change| / |nii_baseline| |
| eve_outlier |
BOOLEAN NOT NULL |
TRUE when eve_change_pct_tier1 > 0.15 (BCBS supervisory threshold; FR-215) |
| model_run_id |
VARCHAR NOT NULL |
FR-216 audit correlation |
| tier1_source |
VARCHAR |
capital_positions | fixture — which Tier 1 source the row used |
| calculated_at |
TIMESTAMP_LTZ NOT NULL |
|
SSM contract
Reads (consumed)
| SSM path |
From |
/bank/{env}/eventbridge/bank-risk-platform/arn |
MOD-104 |
/bank/{env}/eventbridge/bank-risk-platform/dlq-arn |
MOD-104 |
/bank/{env}/iam/lambda/bank-risk-platform/arn |
MOD-104 |
/bank/{env}/observability/adot-layer-arn |
MOD-076 |
/bank/{env}/sns/alerts/arn |
MOD-104 |
/bank/{env}/snowflake/account-locator |
MOD-102 |
/bank/{env}/snowflake/databases/risk |
MOD-102 |
/bank/{env}/snowflake/warehouses/etl |
MOD-102 |
/bank/{env}/snowflake/roles/domain-{nonprod,prod} |
MOD-102 (template — {DOMAIN} → RISK) |
/bank/{env}/risk-platform/market/swap-curve-table |
MOD-085 |
/bank/{env}/risk-platform/market/ois-curve-table |
MOD-085 |
Writes (published)
| SSM path |
Value |
Consumed by |
/bank/{env}/risk-platform/irrbb/sensitivities-table |
RISK_CAPITAL.IRRBB_SENSITIVITIES |
MOD-034 stress, MOD-036 prudential builder |
/bank/{env}/risk-platform/irrbb/current-view |
RISK_CAPITAL.V_IRRBB_CURRENT |
MOD-034, MOD-036, MOD-076 dashboards |
/bank/{env}/risk-platform/irrbb/outlier-breach-view |
RISK_CAPITAL.V_IRRBB_OUTLIER_BREACHES |
MOD-076 |
/bank/{env}/risk-platform/irrbb/event-source-name |
bank.risk-platform |
SD04 EB rules; MOD-076 |
/bank/{env}/risk-platform/irrbb/alert-publish-arn |
Lambda ARN |
Ops |
/bank/{env}/risk-platform/irrbb/alert-topic-arn |
SNS topic ARN |
MOD-102 SNS integration setup |
EventBridge contract
| Direction |
Bus |
Source |
Detail-type |
Schema |
| Publish |
bank-risk-platform-{env} |
bank.risk-platform |
irrbb_outlier_breach |
per wiki event-catalogue entry |
Payload (full envelope; financial values as decimal strings per catalogue convention):
{
"event_id": "11111111-2222-3333-4444-555555555555",
"event_time": "2026-05-06T10:00:00.000Z",
"schema_version": "1.0.0",
"idempotency_key": "<sha256(run_id|jurisdiction|scenario|basis)>",
"trace_id": "trace-abc",
"run_id": "mod-035-20260506100000",
"jurisdiction": "NZ",
"scenario_code": "PARALLEL_UP",
"shock_basis": "BCBS",
"eve_change": "-450000000.00",
"eve_change_pct_tier1": "0.180000",
"tier1_capital": "2500000000.00",
"supervisory_threshold": "0.1500",
"position_date": "2026-05-06",
"calculated_at": "2026-05-06T10:00:00.000Z"
}
No subscriptions. Market data (MOD-085) consumed via dbt source() per
ADR-046 §2.
Dependencies
| Module |
Why |
What's read |
| MOD-042 |
CDC pipeline lands RAW_CDC_CORE.REPRICING_SCHEDULE |
dbt adapter.get_relation lazy-resolved (bootstrap-resilient — empty stub when CDC absent) |
| MOD-085 |
Market curves for shock scenarios |
dbt source() against MARKET.V_SWAP_CURVE, MARKET.V_OIS_CURVE |
| MOD-102 |
Snowflake account, RISK database, domain roles, BANK_SNS_INTEGRATION |
SSM reads |
| MOD-104 |
AWS bootstrap (EB bus, Lambda IAM role, ADOT, SNS, KMS) |
SSM reads |
| MOD-033 (optional) |
risk_capital.capital_positions.tier1_capital |
stg_capital_tier1.sql lazy-resolves via adapter.get_relation; falls back to IRRBB_SHOCK_SCENARIOS.tier1_capital_fixture (V1) |
V1 limitations + future work
- Tier 1 capital fixture, not live. MOD-033 is blocked on
MOD-028/MOD-031. V1 seeds NZ=$2.5B and AU=$3.2B in
IRRBB_SHOCK_SCENARIOS.tier1_capital_fixture — chosen so a 16–18% EVE
shock fires the FR-215 outlier alert in dev. When MOD-033 deploys,
stg_capital_tier1.sql's lazy-resolution branch picks up live Tier 1
automatically; no model changes needed across the transition.
- Repricing schedule lazy-resolved. MOD-031 (interest-rate repricing)
is Not started; MOD-042 hasn't materialised
RAW_CDC_CORE.REPRICING_SCHEDULE
in dev. The bootstrap stub returns zero rows; the DT computes
EVE_BASELINE = EVE_SHOCKED = 0 until upstream lands.
- NII calc is bullet-form approximation. Per Basel III IRRBB §III
acceptable simplification — extends to interest-and-principal cash flows
in a v2 revision.
- Shock parameterisation is linear (short ≤12m / long >12m). Same
Basel III simplified form. A v2 can layer a smoother twist function.
- RBNZ + APRA shock_basis seeded as
BCBS only in V1. Treasury
override via UPSERT on IRRBB_SHOCK_SCENARIOS with new shock magnitudes
- new
effective_from. Schema ready; values not seeded.
V1 numerical reference
For the integration test (planned out-of-scope this initial commit):
Inputs (NZ jurisdiction, fixture portfolio):
Asset: 1y zero coupon, $5B notional, 4.5% current rate
Liability: 6m zero coupon, $4B notional, 4.0% current rate
Tier 1 fixture: $2.5B (NZ)
PARALLEL_UP scenario (+200 bp short and long):
Base EVE = $5B/(1.045) - $4B/(1.040^0.5) ≈ $4.785B - $3.923B = $862M
Shocked EVE = $5B/(1.065) - $4B/(1.060^0.5) ≈ $4.695B - $3.886B = $809M
EVE change = $809M - $862M = -$53M
EVE change pct = $53M / $2.5B = 2.12% → no outlier (under 15%)
To deliberately trigger outlier in dev test fixture, configure higher
notional/duration mismatch so EVE change exceeds 15% × $2.5B = $375M.