Loan facility & component manager¶
| ID | MOD-162 |
| System | SD05 |
| Repo | bank-credit |
| Build status | Deployed |
| Deployed | Yes |
| Last commit | 19b0610 |
Purpose¶
Manages the parent facility entity and all component records for the Flexible Loan Facility (PRD-024). This module owns the core data model: one facility with one approved credit limit, and any number of components (fixed-rate or floating-rate) established within that limit. It enforces the limit constraint, maintains the principal-weighted effective rate, records all component lifecycle transitions immutably, and publishes events for downstream consumption.
Context¶
The Flexible Loan Facility requires a two-tier data model that existing single-loan modules do not support. A standard loan account (credit.loan_accounts in MOD-065) has one rate and one term; the FLF has a parent entity with a limit plus multiple children each with independent rates and terms. This module introduces two new tables — credit.loan_facilities (the parent) and credit.loan_facility_components (the children) — and the aggregation logic that collapses them into a customer-facing effective rate.
The synthetic swap book analogy from the product research document maps directly to this module's output: the treasury view of the aggregated fixed cash flows is simply a query across credit.loan_facility_components grouped by maturity bucket. No separate treasury module is needed to construct this view; it is derived from the component records.
Data model¶
credit.loan_facilities¶
Mutable parent entity. One row per facility.
| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| customer_id | uuid | NOT NULL |
| credit_decision_id | uuid | NOT NULL FK → credit.credit_decisions |
| facility_limit | numeric(18,2) | NOT NULL |
| currency | char(3) | NOT NULL |
| expiry_date | date | NOT NULL |
| jurisdiction | char(2) | NOT NULL CHECK ('NZ','AU') |
| effective_interest_rate | numeric(8,6) | NOT NULL — principal-weighted average, recomputed on component events |
| status | text | NOT NULL CHECK ('ACTIVE','EXPIRED','CANCELLED') |
| master_agreement_ref | text | NULL — document reference for the signed master agreement |
| trace_id | text | NULL |
| created_at | timestamptz | NOT NULL DEFAULT now() |
| last_updated | timestamptz | NOT NULL DEFAULT now() |
credit.loan_facility_components¶
Append-only. New rows are inserted on every component event; terminal-state rows are Cat 1 immutable via trg_loan_facility_components_immutable (reuses credit.fn_immutable_row()). A component exists as a sequence of rows — the latest row is the current state.
| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| facility_id | uuid | NOT NULL FK → credit.loan_facilities |
| component_seq | int | NOT NULL — ordinal within the facility (1, 2, …) |
| component_type | text | NOT NULL CHECK ('FIXED','FLOATING') |
| principal_amount | numeric(18,2) | NOT NULL CHECK (> 0) |
| interest_rate | numeric(8,6) | NOT NULL — contracted rate for FIXED; current effective rate for FLOATING |
| rate_benchmark | text | NULL — 'BKBM' or 'BBSY'; present only for FLOATING |
| benchmark_margin | numeric(8,6) | NULL — customer margin over benchmark; present only for FLOATING |
| term_months | int | NULL — NULL for FLOATING (no fixed term) |
| start_date | date | NOT NULL |
| maturity_date | date | NULL — NULL for FLOATING |
| amortisation_type | text | NULL CHECK ('INTEREST_ONLY','PRINCIPAL_AND_INTEREST') — NULL for FLOATING |
| status | text | NOT NULL CHECK ('PENDING','ACTIVE','MATURED','PREPAID','CANCELLED') |
| trigger_reason | text | NOT NULL CHECK ('INITIAL_CREATION','ROLLOVER','RATE_REPRICING','PARTIAL_PREPAYMENT','FULL_PREPAYMENT','FACILITY_EXPIRY','MANUAL_ADMIN') |
| previous_component_id | uuid | NULL FK → self — set on rollover or partial prepayment replacing an earlier row |
| model_version | text | NOT NULL DEFAULT 'v1.0.0' |
| trace_id | text | NULL |
| created_at | timestamptz | NOT NULL DEFAULT now() |
Immutability trigger fires on BEFORE UPDATE OR DELETE; throws an exception if the row's status is MATURED, PREPAID, or CANCELLED.
Index: (facility_id, status, maturity_date) for maturity sweep queries. (facility_id, component_seq, created_at DESC) for current-state lookups.
Handlers¶
create-facility — Invoked by MOD-029 via event or direct call after credit decision APPROVE. Creates the credit.loan_facilities row and the initial FLOATING component holding the full facility limit. Publishes bank.credit.facility_created.
create-component — Creates a new FIXED component by allocating principal from the floating residual. Validates: sum of all ACTIVE component principals ≤ facility_limit; component principal ≥ configured minimum. Calls MOD-112 to generate the amortisation schedule. Recomputes effective rate. Publishes bank.credit.component_created.
daily-maturity-sweep — EB Scheduler cron, runs at 06:00 NZST. Transitions ACTIVE components whose maturity_date = CURRENT_DATE to MATURED. Creates a new FLOATING component absorbing the matured principal if no rollover was elected. Publishes bank.credit.component_status_changed. DISABLED in non-prod.
reprice-floating — Consumes bank.core.rate_changed from MOD-006. Updates the effective rate on the FLOATING component. Recomputes facility effective rate. Publishes bank.credit.effective_rate_changed.
update-component-status — Called by MOD-163 after a binding break-cost acknowledgement is confirmed. Transitions a FIXED component to PREPAID or updates its principal on partial prepayment. Increases the floating residual. Recomputes effective rate.
Effective rate computation¶
On every component event, the effective interest rate is computed as:
effective_rate = Σ(component.principal_amount × component.interest_rate) / Σ(component.principal_amount)
where the sum is over all ACTIVE components for the facility. This is persisted to credit.loan_facilities.effective_interest_rate within the same transaction as the component write. It is used for disclosure (CON-004), for IFRS 9 EIR computation (MOD-031), and for customer-facing statements (MOD-113).
Events published¶
| Event | Bus | Trigger |
|---|---|---|
bank.credit.facility_created |
bank-credit | Facility established |
bank.credit.component_created |
bank-credit | New component added to facility |
bank.credit.component_status_changed |
bank-credit | Component transitions to MATURED, PREPAID, or CANCELLED |
bank.credit.effective_rate_changed |
bank-credit | Effective rate changes on any component event |
Consumers: MOD-030 (IFRS 9 stage allocation on facility_created), MOD-163 (component data for break-cost calculations), MOD-042 (CDC ingestion).
Implementation notes¶
The floating component is not a fixed-term obligation; it absorbs the unallocated portion of the facility limit at all times. Its principal changes whenever a fixed component is created (reduces floating), a fixed component matures or is prepaid (increases floating), or a partial prepayment of the floating leg itself occurs. Its interest rate changes with MOD-006 rate-change events.
The facility limit constraint must be enforced transactionally: the DB must have a row-level check or the handler must use a SELECT FOR UPDATE on the facility row before inserting a new component. Race conditions where two concurrent component-creation requests both pass the in-memory check are not acceptable given the credit limit is a regulatory obligation.
The model_version field on each component row records the version of the pricing and aggregation logic in effect when the row was written. This supports retrospective investigation of effective-rate discrepancies.
Module dependencies¶
Depends on¶
| Module | Title | Required? | Contract | Reason |
|---|---|---|---|---|
| MOD-029 | Pre-approval engine | Required | — | Facility creation requires an approved credit decision from the pre-approval engine; the facility limit and expiry are sourced from the credit decision record. |
| MOD-112 | Amortisation schedule engine | Required | — | Amortisation schedule engine computes the repayment schedule for each fixed-rate component at creation and recomputes after any principal or rate event. |
| MOD-005 | Daily accrual calculator | Required | — | Daily interest accrual runs per component at each component's contracted rate; the accrual module receives component-level rate and balance inputs from this module. |
| MOD-006 | Rate change propagation | Required | — | Rate change propagation updates the floating-rate component's effective rate when the bank adjusts its lending benchmark. |
| MOD-085 | Market rates ingestion & normalisation | Required | — | Floating component repricing requires the current benchmark rate (BKBM for NZ, BBSY for AU) from the market rates ingestion module. |
| MOD-103 | Neon database platform bootstrap | Required | — | Neon database and schema provisioned by MOD-103 must exist before this module can create credit.loan_facilities and credit.loan_facility_components. |
| MOD-104 | AWS shared infrastructure bootstrap | Required | — | AWS shared infrastructure provisioned by MOD-104 (EventBridge buses, SSM, KMS, Lambda execution role) is required before this module can be deployed. |
Required by¶
| Module | Title | As | Contract |
|---|---|---|---|
| MOD-163 | Break-cost calculator | Hard dependency | — |
| MOD-164 | Facility component self-service | Hard dependency | — |
| MOD-165 | Synthetic swap book aggregator | Hard dependency | — |
Policies satisfied¶
| Policy | Title | Mode | How |
|---|---|---|---|
| CRE-002 | Responsible Lending Policy | AUTO |
Component creation is blocked when the sum of component principals would exceed the approved facility limit — no code path creates a component outside the approved credit envelope; enforced at the handler layer and by a DB CHECK constraint. |
| CON-004 | Product Disclosure & Sales Practice Policy | AUTO |
Principal-weighted effective interest rate is recomputed and persisted to the facility record within 500 ms of every component event, making the current effective rate available for disclosure at all times. |
| REP-004 | Financial Statements Policy | AUTO |
Component-level cash flow data and stage transition events are published on every lifecycle change, feeding the IRRBB repricing gap view and ECL stage-allocation inputs automatically. |
Capabilities satisfied¶
(No capabilities mapped)
Part of SD05 — Credit Decisioning & Loan Platform
Compiled 2026-05-22 from source/entities/modules/MOD-162.yaml