Skip to content

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