Skip to content

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_POSITIONSV_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).