MOD-033 — RWA & capital ratio engine
Purpose
Computes Basel III standardised Risk-Weighted Assets (RWA) and capital
adequacy ratios (CET1, Tier 1, Total Capital) for each jurisdiction
(NZ under RBNZ BS2A/BS2B; AU under APRA APS 110/112/113) daily and
within ≤5 min of any material ledger event. A Snowflake Alert raises
capital_ratio_breach to MOD-076 alarm-intake (SNS) when any ratio
breaches its regulatory minimum or internal management buffer.
Functional requirements
| Code |
Requirement |
| FR-205 |
Calculate RWA and all capital ratios (CET1, Tier 1, Total Capital) per RBNZ BS2A/BS2B + APRA APS 110/112/113 daily. |
| FR-206 |
Make ratio results available ≤5 minutes after the daily ledger snapshot — intraday capital monitoring. |
| FR-207 |
Alert CFO and CRO when any capital ratio falls below the internal management buffer, distinguishing between the management trigger and the regulatory minimum. |
| FR-208 |
Produce output in RBNZ BS / APRA ARS return formats, ready for MOD-036 ingestion — no manual reformatting. |
| NFR-006 |
(Cross-cutting) Interest accrual posted by 23:59 — informs the daily ledger snapshot cutover timing. |
| NFR-008 |
Capital/liquidity metrics available after material event ≤ 5 minutes — DT target_lag=1h + Alert poll=5min satisfies this. |
| NFR-010 |
Regulatory submissions produced without manual intervention ≥ 90% — FR-208 satisfies. |
| NFR-024 |
Audit log record mutability = 0 — CAPITAL_RUN_AUDIT has no UPDATE/DELETE/TRUNCATE grants. |
Policies satisfied
| Policy |
Mode |
How |
| CLQ-001 |
CALC |
Capital ratios computed from MOD-028's pre-computed basel_risk_weight × MOD-031's ECL stage overlay — no manual risk-weight application in MOD-033 (verified by tests/policy/CLQ-001-calc.test.ts). |
| REP-002 |
CALC |
RBNZ/APRA returns sourced from V_CAPITAL_CURRENT; MOD-036 reads the view directly with no derivation layer (verified by tests/policy/REP-002-calc.test.ts + tests/integration/fr-208-return-format.test.ts). |
| CLQ-006 |
CALC |
Pillar 3 surface (V_CAPITAL_BY_PORTFOLIO) sources from the same int_exposures_risk_weighted view that feeds CAPITAL_POSITIONS → V_CAPITAL_CURRENT — single source of truth (verified by tests/policy/CLQ-006-calc.test.ts + integration test that SUM(rwa_contribution) equals risk_weighted_assets). |
| GOV-002 |
ALERT |
ALERT_CAPITAL_RATIO_BREACH reads V_CAPITAL_CURRENT × CAPITAL_CONFIG every 5 minutes, publishes via BANK_SNS_INTEGRATION to MOD-076 alarm-intake when any of CET1, Tier 1, or Total Capital ratios breach (verified by tests/policy/GOV-002-alert.test.ts + tests/integration/snowflake-objects.test.ts). |
| NFR-024 |
LOG |
CAPITAL_RUN_AUDIT is append-only — no UPDATE/DELETE/TRUNCATE grants to any role (verified by tests/policy/NFR-024-log.test.ts + tests/integration/capital-positions-immutability.test.ts). |
Architecture
Snowflake-native, dbt + DCM only — no Lambdas. Per ADR-046 §1 (Snowflake
Tasks own orchestration) and §2 (dbt owns transformations), the full
pipeline lives inside Snowflake. MOD-076's alarm-intake SNS topic
receives the alert directly via the BANK_SNS_INTEGRATION primitive.
raw_cdc_credit (MOD-042) raw_cdc_core (MOD-042)
├── credit_scores ──┐ └── postings ─────┐
│ (basel_risk_ │ │
│ weight, AD-3, │ ▼
│ MOD-028) │ stg_postings (table)
├── loan_accounts ──┤ │
│ (EAD) │ ▼
└── ecl_provisions ─┘ int_capital_stack (view)
(stage, MOD-031)│ (CET1 + AT1 + T2)
│ │ │
▼ ▼ │
stg_credit_scores │
stg_loan_accounts │
stg_ecl_provisions (all materialized=table │
│ for VALUES-chain break) │
▼ │
int_exposures_risk_weighted (view) │
(EAD × risk_weight × ECL overlay) │
│ │
└────────────────┬───────────────────────────┘
▼
capital_positions (DT, 1h target_lag, INCREMENTAL)
│
┌────────────┼────────────┐
▼ ▼ ▼
V_CAPITAL_CURRENT V_CAPITAL_BY_PORTFOLIO CAPITAL_RUN_AUDIT
(REP-002 + (Pillar 3 — CLQ-006) (NFR-024 LOG)
GOV-002)
│
▼
ALERT_CAPITAL_RATIO_BREACH (5-min schedule)
│
▼ BANK_SNS_INTEGRATION
MOD-076 alarm-intake (SNS) → CFO + CRO (FR-207)
Snowflake objects owned
| Object |
Type |
Owner |
Notes |
BANK_{ENV}_RISK.RISK_CAPITAL |
schema |
DCM v2 (co-owned with MOD-032) |
define schema is idempotent — safe co-ownership |
RISK_CAPITAL.CAPITAL_CONFIG |
table |
DCM v2 |
Versioned regulatory minimums + internal buffers per (jurisdiction × ratio_type). ADR-046 §4 UPSERT pattern. |
RISK_CAPITAL.CAPITAL_RUN_AUDIT |
table |
DCM v2 |
NFR-024 append-only run log. Named distinct from MOD-032's MODEL_RUNS per ruling j-4. |
RISK_CAPITAL.CAPITAL_POSITIONS |
Dynamic Table |
dbt |
INCREMENTAL, target_lag 1h, cluster (position_date, jurisdiction). 16-column schema per SD06 data model. post_hook RESUME. |
RISK_CAPITAL.V_CAPITAL_CURRENT |
view |
dbt |
ADR-046 §3 published contract — current ratios per jurisdiction. |
RISK_CAPITAL.V_CAPITAL_BY_PORTFOLIO |
view |
dbt |
Pillar 3 disclosure surface — RWA breakdown. |
RISK_CAPITAL.ALERT_CAPITAL_RATIO_BREACH |
Alert |
DCM v2 (post-dbt) |
5-min schedule. FR-207 / GOV-002. |
RISK_CAPITAL.MOD_033_STREAMLIT_STAGE |
internal stage |
legacy infra/snowflake/ (apply-snowflake-ddl.ts) |
Holds streamlit_app.py. ADR-054 §"no Streamlit in DCM v2" forces legacy path. |
RISK_CAPITAL.STREAMLIT_CAPITAL_DASHBOARD |
Streamlit |
legacy infra/snowflake/ |
bank-wiki issue #35 / SD06 dashboard layer (2026-05-19). Reads V_CAPITAL_CURRENT, V_CAPITAL_BY_PORTFOLIO, CAPITAL_CONFIG, CAPITAL_POSITIONS. Authorised: BANK_DBT_ROLE immediately + RISK_INTELLIGENCE_ROLE via conditional EXECUTE IMMEDIATE grant that lands once MOD-102 provisions that role. Consumed by MOD-171. |
dbt models live in MOD-033-rwa-capital-ratio/models/MOD-033-rwa-capital-ratio/
tagged tag:mod-033 (per dbt_project.yml).
SSM contract
Reads (consumed)
| SSM path |
From |
Used for |
/bank/{env}/eventbridge/bank-risk-platform/arn |
MOD-104 |
(Not used in v1 — no Lambda) |
/bank/{env}/iam/lambda/bank-risk-platform/arn |
MOD-104 |
(Not used in v1) |
/bank/{env}/observability/adot-layer-arn |
MOD-076 |
(Not used in v1) |
/bank/{env}/sns/alarm-intake/arn |
MOD-076 |
Alert routing target via BANK_SNS_INTEGRATION |
/bank/{env}/snowflake/account-locator |
MOD-102 |
Snowflake connection (tests) |
/bank/{env}/snowflake/databases/risk |
MOD-102 |
DT + view materialisation target |
/bank/{env}/snowflake/databases/core |
MOD-102 |
CDC source for raw_cdc_core.postings |
/bank/{env}/snowflake/databases/credit |
MOD-102 |
CDC source for raw_cdc_credit.* |
/bank/{env}/snowflake/warehouses/etl |
MOD-102 |
(Not used in v1 — no Lambda; DT uses target.warehouse) |
/bank/{env}/snowflake/warehouses/dbt |
MOD-102 |
dbt build target warehouse |
/bank/{env}/snowflake/roles/domain-{nonprod,prod} |
MOD-102 |
Domain role template |
Writes (published)
| SSM path |
Value |
Consumed by |
/bank/{env}/risk-platform/capital/positions-table |
RISK_CAPITAL.CAPITAL_POSITIONS |
MOD-034, MOD-036 |
/bank/{env}/risk-platform/capital/current-view |
RISK_CAPITAL.V_CAPITAL_CURRENT |
MOD-036 (REP-002 + CLQ-006), MOD-058 (breach notification), regulatory dashboard |
/bank/{env}/risk-platform/capital/portfolio-view |
RISK_CAPITAL.V_CAPITAL_BY_PORTFOLIO |
MOD-036 Pillar 3, MOD-105 (when unblocked) |
/bank/{env}/risk-platform/capital/event-source-name |
bank.risk-platform |
MOD-076 (via SNS), MOD-058 (EB subscriber) |
/bank/{env}/risk-platform/rwa-capital-ratio/streamlit-url |
RISK_CAPITAL.STREAMLIT_CAPITAL_DASHBOARD |
MOD-171 Risk Intelligence Dashboard (capital drill-down) — bank-wiki issue #35 |
EventBridge contract
| Direction |
Bus |
Source |
Detail-type |
Schema |
| Publish |
bank-risk-platform-{env} |
bank.risk-platform |
capital_ratio_breach |
{ trace_id, run_id, ratio_type: "CET1"\|"TIER1"\|"TOTAL_CAPITAL", jurisdiction: "NZ"\|"AU", ratio_value, regulatory_minimum, internal_buffer, breach_severity: "regulatory_minimum"\|"internal_buffer", position_date, calculated_at } |
No subscriptions. CDC data is consumed via dbt adapter.get_relation
(bootstrap-resilient) per ADR-046 §2 — the market_rates_updated /
bank.credit.ecl_updated events are NOT subscribed.
Primary fan-out goes through MOD-076 alarm-intake SNS via the
BANK_SNS_INTEGRATION primitive — MOD-076 routes to CFO + CRO per
FR-207. MOD-058 (breach notification engine) subscribes via an EB rule
on the bank-risk-platform-{env} bus for downstream workflow.
The retracted capital_ratio_updated placeholder event (a Lambda-era
artefact) was removed per bank-wiki issue #26 ruling j-2.
Dependencies
| Module |
Why |
What's read |
| MOD-102 |
Snowflake account + roles + warehouses |
RISK_CAPITAL schema, BANK_DBT_ROLE / BANK_NONPROD_RISK_ROLE / BANK_PROD_RISK_ROLE, NONPROD_WH / PROD_WH / PROD_DBT_WH |
| MOD-104 |
AWS bootstrap |
(Pulumi role + AWS region only — no AWS-side Lambdas in v1) |
| MOD-042 |
CDC pipeline |
raw_cdc_credit.credit_scores, raw_cdc_credit.loan_accounts, raw_cdc_credit.ecl_provisions, raw_cdc_core.postings |
| MOD-028 |
Credit score & risk rating |
credit_scores.basel_risk_weight (CLQ-001 / AD-3 source) |
| MOD-031 (optional) |
ECL calculation |
ecl_provisions.ecl_stage for stage-3 overlay |
| MOD-076 |
Observability platform |
/bank/{env}/sns/alarm-intake/arn (alert routing target) |
| MOD-032 |
LCR/NSFR calculator |
Shares RISK_CAPITAL schema (idempotent co-ownership). Audit table names kept distinct (CAPITAL_RUN_AUDIT vs MODEL_RUNS). |
V1 limitations + future work
- Market-risk RWA: not computed in v1. Standardised approach for
credit risk only. Market-risk RWA component (interest-rate, FX,
equity) is V2 work and depends on MOD-035 IRRBB results landing in
the same DT. Will add columns to CAPITAL_POSITIONS without breaking
the existing schema (REP-002 columns are stable).
- Operational-risk RWA: not computed in v1. Standardised
Operational Risk Approach (BIA / TSA) is V2 — needs business
income data from MOD-080.
- AT1 / T2 eligibility tests: v1 maps balance-sheet account
classes (
CAPITAL_AT1, CAPITAL_T2) to the capital stack. Proper
RBNZ BS2A / APRA APS 111 going-concern / gone-concern absorbency
tests are V2.
- Per-refresh CAPITAL_RUN_AUDIT writes: v1 has the table but no
per-refresh writer (the DT refreshes autonomously; there's no
per-refresh hook in dbt-snowflake). Audit rows arrive at deploy
time via the dbt post_hook chain. V2 adds a Snowflake Task that
inserts per refresh.
- AppConfig threshold overrides: regulatory minimums + internal
buffers are seeded by
dcm/pre-dbt.sql and updated via UPSERT
to CAPITAL_CONFIG (ADR-046 §4). v2 may surface a Lambda or
Streamlit UI for treasury to apply changes without direct SQL
access.
CI / deploy notes
- Pipeline order: DCM plan →
pulumi up → pre-dbt SQL → dbt build
→ DCM deploy → tests. The pre-dbt step creates CAPITAL_CONFIG
CAPITAL_RUN_AUDIT idempotently with seed thresholds; dbt build
then materialises CAPITAL_POSITIONS + the two views; DCM deploy
finally reconciles + lands the ALERT_CAPITAL_RATIO_BREACH alert.
- Greenfield first deploy: stg_* models return zero rows; CAPITAL_POSITIONS
produces 2 rows (NZ + AU) with
risk_weighted_assets = 0 and ratio
= NULL (divide-by-zero guarded). Alert evaluates to FALSE (no breach
on NULL comparison). Integration tests assert structure, not numerics.
- No
pulumi destroy story beyond stack-level teardown — DCM v2's
declarative model means deleting MOD-033 also requires clearing
CAPITAL_CONFIG + CAPITAL_RUN_AUDIT rows or accepting ownership transfer
to MOD-032 (the shared-schema co-owner).