Term deposit maturity engine¶
| ID | MOD-111 |
| System | SD01 |
| Repo | bank-core |
| Build status | Deployed |
| Deployed | Yes |
| Last commit | 2b41724 |
What it does¶
MOD-111 manages the full maturity lifecycle of term deposit accounts — from pre-maturity notification through to maturity proceeds disbursement or early exit. It handles standing instructions capture, auto-rollover execution, break cost calculation, and the disclosure gate that prevents early withdrawal without explicit cost acceptance.
Pre-maturity notification¶
A daily batch identifies term deposits maturing within 30, 14, and 7 calendar days. Notification events are published to MOD-063 (notification orchestration) at each threshold, with: product name, maturity date, current balance, projected maturity proceeds (principal + accrued interest), current rollover rate for the same term (to assist the customer's decision). The first notification at 30 days includes the standing instruction form — the customer can set: rollover to same term, rollover to different term, withdraw all to nominated account, partial rollover + partial withdrawal.
Maturity instructions¶
Instructions are stored against the account with a confirmation timestamp. If no instruction is received by 23:59 two business days before maturity, the system auto-applies the account's default instruction (configured at account opening — typically auto-rollover to same term at prevailing rate). Customers can change their instruction up to one business day before maturity.
Auto-rollover execution¶
On maturity date, a batch job: (1) calculates final accrued interest via MOD-005, (2) posts interest credit via MOD-001, (3) if rollover — re-fixes the balance at the new term and rate, updates the maturity date, posts a rollover event; if withdrawal — disburses proceeds to the nominated account via MOD-020/MOD-001. All steps are idempotent — replayable without double-posting.
Break cost calculation¶
Break cost = (Contract Rate − Current Reinvestment Rate) × Outstanding Balance × (Days Remaining / 365)
Where:
Contract Rate = the fixed rate at which the deposit was opened
Current Reinvestment Rate = prevailing rate for the remaining term (sourced from MOD-006 rate register)
If (Contract Rate − Current Reinvestment Rate) ≤ 0: break cost = 0 (rate has risen; no penalty to customer)
Break cost is never negative — it is a cost to the customer if rates have fallen, zero if rates have risen. The calculation is disclosed to the customer before any early withdrawal proceeds. The customer must explicitly accept via app confirmation or agent confirmation before funds are released.
Data model¶
-- core.term_deposit_instructions (Postgres)
CREATE TABLE core.term_deposit_instructions (
instruction_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
account_id uuid NOT NULL,
party_id uuid NOT NULL,
instruction_type text NOT NULL CHECK (instruction_type IN ('ROLLOVER_SAME','ROLLOVER_DIFFERENT','WITHDRAW_ALL','PARTIAL_ROLLOVER')),
rollover_term_days int, -- null unless ROLLOVER_DIFFERENT or PARTIAL_ROLLOVER
withdrawal_amount numeric(18,2), -- null unless PARTIAL_ROLLOVER
nominated_account uuid, -- destination for withdrawal proceeds
captured_at timestamptz NOT NULL DEFAULT now(),
source text NOT NULL -- 'customer_app' | 'agent' | 'auto_default'
);
-- core.break_cost_disclosures (append-only — consent trail)
CREATE TABLE core.break_cost_disclosures (
disclosure_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
account_id uuid NOT NULL,
party_id uuid NOT NULL,
break_cost_amount numeric(18,2) NOT NULL,
contract_rate numeric(8,6) NOT NULL,
reinvestment_rate numeric(8,6) NOT NULL,
days_remaining int NOT NULL,
disclosed_at timestamptz NOT NULL DEFAULT now(),
accepted_at timestamptz, -- null until customer accepts
accepted_via text -- 'app' | 'agent'
);
Module dependencies¶
Depends on¶
| Module | Title | Required? | Contract | Reason |
|---|---|---|---|---|
| MOD-001 | Double-entry posting engine | Required | — | Maturity proceeds disbursement and break cost posting are executed as ledger entries through the posting engine. |
| MOD-005 | Daily accrual calculator | Required | — | Daily interest accrual on term deposits is computed by the accrual calculator; the maturity engine reads the accrued interest balance to calculate final proceeds. |
| MOD-007 | Account state machine | Required | — | Term deposit accounts transition through state machine states (Active → Maturing → Matured / Broken); state transitions are enforced by MOD-007. |
| MOD-110 | Fee engine | Required | — | Break cost and early exit fees are posted as fee events through the fee engine. |
| MOD-104 | AWS shared infrastructure bootstrap | Required | — | AWS shared infrastructure required before this module can be deployed. |
| MOD-103 | Neon database platform bootstrap | Required | — | Neon database must exist before this module can read or write Postgres. |
Required by¶
(No modules in this wiki currently declare a dependency on this module.)
Policies satisfied¶
| Policy | Title | Mode | How |
|---|---|---|---|
| CON-004 | Product Disclosure & Sales Practice Policy | AUTO |
Pre-maturity notifications are sent automatically at 30, 14, and 7 days before maturity — no manual trigger required, no customer is missed. |
| CON-005 | Fee & Pricing Transparency Policy | GATE |
Early withdrawal is blocked until the break cost is calculated, disclosed to the customer, and explicitly accepted — no funds are released until acceptance is recorded. |
| PAY-001 | Payment Operations Policy | AUTO |
Maturity proceeds are disbursed automatically on the maturity date per the customer's standing instruction, with no manual intervention required. |
Capabilities satisfied¶
(No capabilities mapped)
Part of SD01 — Core Banking Platform
Compiled 2026-05-22 from source/entities/modules/MOD-111.yaml