Skip to content

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.