Skip to content

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