Skip to content

Break-cost calculator

ID MOD-163
System SD05
Repo bank-credit
Build status Deployed
Deployed Yes
Last commit 19b0610

Purpose

Calculates the break cost or benefit when a fixed-rate loan component is prepaid, rolled over before maturity, or otherwise terminated before its contracted end date. Serves two call paths: on-demand indicative quotes (available to the customer at any time without triggering account action) and binding calculations (required by MOD-050 before any component change proceeds). Every calculation is immutably logged.

Context

The break-cost calculation is the single most important conduct and risk management feature of the Flexible Loan Facility. It is the mechanism by which the bank recovers (or passes back) the mark-to-market movement on the interest-rate position it hedged when the customer locked a rate. Getting this wrong — whether through formula error, stale market rates, or inconsistency between indicative and binding figures — creates both regulatory exposure and the specific conduct risk evidenced by the UK Tailored Business Loans episode.

This module implements the formula once, in a single service that is called by all paths — indicative, binding, and any future batch re-valuation. There is no separate or simplified formula for any call path.

Formula

break_cost = Σ_t [ cash_flow_t × (contracted_rate − current_market_rate) / (1 + discount_rate)^t ]

In the simplified annuity form used for standard components:

break_cost = (contracted_rate − current_market_rate) × outstanding_principal × annuity_factor(discount_rate, remaining_months)

Where: - contracted_rate — the fixed rate locked at component establishment, from credit.loan_facility_components - current_market_rate — the mid-market swap rate for the residual tenor in months, sourced from MOD-085 at the moment of calculation - outstanding_principal — the current outstanding principal of the component - discount_rate — the current market rate for the component tenor (same as current_market_rate for a par-swap valuation) - remaining_months — months between calculation date and the component maturity date (floor: 0)

A positive result means the customer pays the bank (rates have fallen since fixing). A negative result is a break benefit; the bank pays the customer in full per CRE-009.

Formula version is stored on every calculation row as formula_version (e.g. v1.0.0). A formula change requires a version increment.

Data model

credit.break_cost_calculations

Cat 1 immutable via trg_break_cost_calculations_immutable (ADR-048, reuses credit.fn_immutable_row()). One row per calculation call.

Column Type Notes
id uuid PK
component_id uuid NOT NULL FK → credit.loan_facility_components
facility_id uuid NOT NULL FK → credit.loan_facilities
customer_id uuid NOT NULL
calculation_type text NOT NULL CHECK ('INDICATIVE','BINDING')
contracted_rate numeric(8,6) NOT NULL
current_market_rate numeric(8,6) NOT NULL — rate sourced from MOD-085
market_rate_tenor_months int NOT NULL
market_rate_source text NOT NULL — identifies the MOD-085 feed and instrument
market_rate_timestamp timestamptz NOT NULL — timestamp of the rate from MOD-085
outstanding_principal numeric(18,2) NOT NULL
remaining_months int NOT NULL
discount_rate numeric(8,6) NOT NULL
break_cost_amount numeric(18,2) NOT NULL — positive = customer pays; negative = bank pays benefit
currency char(3) NOT NULL
formula_version text NOT NULL DEFAULT 'v1.0.0'
acknowledgement_id text NULL — set for BINDING type once MOD-050 acknowledgement is confirmed
acknowledged_at timestamptz NULL
calculated_by text NOT NULL CHECK ('CUSTOMER','SYSTEM','ADMIN')
trace_id text NULL
calculated_at timestamptz NOT NULL DEFAULT now()

Index: (component_id, calculation_type, calculated_at DESC) for current binding calculation lookup.

Handlers

calculate-indicative — Synchronous API callable by MOD-164 (customer-facing) and any internal caller. Reads component data from MOD-162, fetches current market rate from MOD-085 for the residual tenor, runs the formula, inserts a calculation_type='INDICATIVE' row, and returns the result. No account action is triggered. Response p99 ≤ 500 ms (NFR-007 applies).

calculate-binding — Invoked when a customer confirms intent to terminate or roll a fixed-rate component early. Runs the same formula but inserts calculation_type='BINDING'. Returns the calculation_id. The caller (MOD-164 or MOD-132) passes calculation_id to MOD-050 to trigger the break-cost acknowledgement disclosure. The component cannot be changed until MOD-050 returns a confirmed acknowledgement ID.

confirm-acknowledgement — Called by MOD-050 when the customer acknowledges the binding disclosure. Updates the binding row with acknowledgement_id and acknowledged_at. Emits bank.credit.break_cost_acknowledged for MOD-162 to action the component change.

Market rate staleness

The MOD-085 rate feed has a configured maximum age. If the cached rate is older than the staleness threshold (default: 15 minutes), calculate-indicative returns a RATE_STALE warning alongside the result; calculate-binding returns a RATE_STALE error and does not produce a binding calculation until a fresh rate is available. Binding calculations on stale rates are prohibited — the customer could be shown an incorrect break cost and acknowledge a figure that does not reflect the actual unwind cost.

Break benefit

When break_cost_amount is negative, the result is a break benefit. CRE-009 requires the bank to pass this benefit to the customer in full. The binding calculation handler, when producing a break benefit, sets a benefit_payable flag in the bank.credit.break_cost_acknowledged event. MOD-001 is responsible for posting the benefit credit to the customer's account via the standard payments path. The break-cost calculator does not post to the general ledger directly.

Consistency with MOD-116

MOD-116 (Mortgage servicing engine) uses the same break-cost formula for simple fixed-rate mortgage components. Both modules source market rates from MOD-085 and use the same PV formula. In v1, each module maintains its own implementation. A shared calculation library (@bank/break-cost) should be extracted as a v2 improvement to eliminate the duplicate and ensure formula changes are applied atomically across both modules.

Implementation notes

The formula must be deterministic for any given set of inputs. Given the same contracted rate, market rate, principal, remaining term, and discount rate, the formula must return the same result regardless of when or by whom it is called. This is a testability requirement (pure function in the core calculation service) and a regulatory requirement (CRE-009 formula consistency).

The market_rate_tenor_months must match the component's actual remaining term to the nearest available tenor in the MOD-085 rate curve, not the original component term. If the remaining term falls between two available tenors on the curve, interpolate linearly. Document the interpolation method in the design doc.


Module dependencies

Depends on

Module Title Required? Contract Reason
MOD-162 Loan facility & component manager Required Component principal, contracted rate, term, start date, and maturity date are read from the loan facility component record to compute remaining term and outstanding principal.
MOD-085 Market rates ingestion & normalisation Required contract/events/ MOD-085 writes mid-market swap curve data into the credit.swap_rates_mirror Postgres table on each Snowflake refresh and emits bank.risk-platform.swap_curve_updated; MOD-163 reads the mirror table at calculation time — no inline Lambda call (ADR-046 compliance ruling 2026-05-18).
MOD-050 Disclosure enforcement module Required Binding break-cost disclosures are delivered and acknowledged via the disclosure enforcement module; the acknowledgement record ID must be verified before any component termination proceeds.
MOD-103 Neon database platform bootstrap Required Neon database and schema provisioned by MOD-103 must exist before this module can create credit.break_cost_calculations.
MOD-104 AWS shared infrastructure bootstrap Required AWS shared infrastructure provisioned by MOD-104 (SSM parameters, KMS, Lambda execution role) is required before this module can be deployed.

Required by

Module Title As Contract
MOD-132 Loan restructure and variation workflow Hard dependency
MOD-164 Facility component self-service Hard dependency

Policies satisfied

Policy Title Mode How
CRE-009 Fixed-Rate Component Break-Cost Methodology Policy CALC Break cost is computed using the documented present-value formula against live market rates sourced from MOD-085; every calculation — indicative and binding — is immutably logged with contracted rate, current market rate, source timestamp, remaining principal, remaining term, and the resulting amount; the formula is version-controlled and produces identical results to any customer-facing quotation.
CON-005 Fee & Pricing Transparency Policy GATE The binding break-cost calculation is delivered to the customer via MOD-050 for acknowledgement before any fixed-rate component early termination or pre-maturity rollover is processed; the component status handler verifies the acknowledgement record ID before proceeding; no code path bypasses this gate.

Capabilities satisfied

(No capabilities mapped)


Part of SD05 — Credit Decisioning & Loan Platform Compiled 2026-05-22 from source/entities/modules/MOD-163.yaml