Amortisation schedule engine¶
| ID | MOD-112 |
| System | SD01 |
| Repo | bank-core |
| Build status | Deployed |
| Deployed | Yes |
| Last commit | 35402a8a7d9c6f1e2b5c8d0e4f7a3b6c9d2e5f8a |
What it does¶
MOD-112 generates and maintains the amortisation schedule for all instalment loan products. It computes the initial schedule at origination, serves the schedule to the customer, and recalculates it whenever a schedule-changing event occurs — variable rate change, extra repayment, interest-only period expiry, or hardship restructure.
Schedule generation¶
At loan origination, the module produces a full schedule of payment dates, payment amounts, principal components, interest components, and running outstanding balance. The schedule is generated using the declining balance method. For variable rate loans, the initial schedule uses the origination rate with a note that instalments will adjust when the rate changes. For fixed rate loans, the schedule is deterministic for the full term.
Repayment amount formula:
P = L × [r(1+r)^n] / [(1+r)^n − 1]
Where:
P = periodic repayment amount
L = loan principal
r = periodic interest rate (annual rate / payment frequency periods per year)
n = total number of payment periods
Schedule events that trigger recalculation¶
- Variable rate change (MOD-006 event received) — new instalment amount calculated from outstanding balance, remaining term, new rate; schedule regenerated from next payment date.
- Extra repayment — customer makes a payment above the scheduled amount; module offers two options: reduce term (same instalment, shorter loan) or reduce instalment (same term, lower payment). Customer selects via app; new schedule generated and delivered.
- Interest-only period expiry — schedule transitions from interest-only payments to principal and interest; instalment amount recalculates over remaining P&I term.
- Hardship restructure (from MOD-065) — new schedule generated from restructured terms; original schedule retained for audit; both stored as separate schedule versions.
- Loan variation (from MOD-132) — new schedule generated from confirmed variation terms (new rate, term, frequency, rate type); prior
is_current=trueschedule superseded. SeePOST /internal/v1/loans/{account_id}/recalc-variationbelow.
HTTP surface¶
| Endpoint | Purpose |
|---|---|
POST /internal/v1/loans/{account_id}/originate-schedule |
Initial schedule at loan origination — origination only |
POST /internal/v1/loans/{account_id}/recalc-rate |
Recalculate from new variable rate |
POST /internal/v1/loans/{account_id}/extra-repayment |
Stage an extra-repayment recalculation |
POST /internal/v1/loans/{account_id}/extra-repayment/accept |
Confirm extra-repayment option |
POST /internal/v1/loans/{account_id}/recalc-variation |
Recalculate from confirmed loan variation (MOD-132, FR-590) |
GET /internal/v1/loans/{account_id}/schedule |
Retrieve current schedule |
GET /internal/v1/loans/{account_id}/total-cost |
Total cost of credit |
recalc-variation request body (RecalcVariationRequestV1 — @bank-core/mod-112-contracts@1.1.0)¶
{
"new_annual_rate": "0.0625",
"new_term_months": 84,
"new_payment_frequency": "MONTHLY | FORTNIGHTLY | WEEKLY",
"new_rate_type": "FIXED | VARIABLE",
"variation_id": "uuid (MOD-132 correlation)",
"effective_date": "YYYY-MM-DD",
"idempotency_key": "≥ 8 chars"
}
Produces a new credit.amortisation_schedules row with generated_by = 'restructure', version = current.version + 1, is_current = true; supersedes the prior current schedule. Caller-supplied idempotency_key — replay returns null (no double-recalc). Publishes bank.core.amortisation_schedule_recalculated with generated_by = 'restructure' and variation_id in metadata.
SSM paths¶
| Path | Value |
|---|---|
/bank/{stage}/mod-112/api/base-url |
Internal API base URL |
/bank/{stage}/mod-112/variation-recalc-api |
Full URL for recalc-variation endpoint (MOD-132 reads this directly) |
/bank/{stage}/mod-112/lambdas/recalc-variation/arn |
Lambda ARN for the recalc-variation handler |
Minimum repayment (revolving credit)¶
For revolving credit facilities (PRD-008), the minimum monthly repayment is:
Both values are tenant-configurable in the fee schedule. The minimum repayment is posted automatically on the due date if no payment has been received; if a payment has been received but is below the minimum, the shortfall triggers a late payment fee assessment via MOD-110.
Data model¶
Edge case coverage — Fineract LoanScheduleGenerator comparison¶
As part of the Fineract design reference review (see fineract-design-reference.md), MOD-112 was compared against Fineract's LoanScheduleGenerator and RepaymentScheduleInstallment logic. Fineract's implementation is the most battle-tested open-source reference for these patterns, having been exercised across portfolios of millions of accounts in 80+ countries. The comparison identified the following coverage map and v2 scope items.
Covered in v1¶
| Scenario | MOD-112 v1 status |
|---|---|
| Standard P&I declining balance schedule | ✓ Full |
| Variable rate change — recalculate from outstanding balance | ✓ Full |
| Fixed rate — deterministic schedule for full term | ✓ Full |
| Extra repayment: reduce-term option | ✓ Full |
| Extra repayment: reduce-instalment option | ✓ Full |
| Interest-only period transitioning to P&I | ✓ Full |
| Hardship restructure (new terms, versioned schedule) | ✓ Full |
| Revolving minimum repayment calculation | ✓ Full |
Not covered in v1 — v2 scope items¶
Balloon payment structures. Some mortgage and business loan products have a large final payment (balloon) that is materially larger than the regular instalment. The current formula produces equal instalments; it does not support a product parameter that sets a balloon amount and back-calculates the regular instalments accordingly. Fineract handles this via loanProductData.amortizationType = EQUAL_INSTALLMENTS vs BALLOON. Flag as v2 when a product requiring a balloon structure is launched.
Payment holiday (deferral period). A customer may be granted a payment holiday — a period during which no instalment is due but interest continues to accrue and is capitalised onto the principal. The v1 implementation does not model this; hardship restructure is the closest equivalent but requires new terms rather than a deferral window. Fineract's isNpa and payment-defer logic is the reference. Flag as v2 when hardship deferral (as distinct from full restructure) is introduced.
Capitalisation of arrears balance. In some products, unpaid interest in arrears is capitalised onto principal (added to the outstanding balance) at a configurable frequency rather than being tracked as a separate overdue amount. v1 tracks arrears separately via MOD-065. Fineract supports compoundingMethod = INTEREST with periodic capitalisation. Flag as v2 if a product requiring arrears capitalisation is launched.
Day-count precision at boundary conditions. v1 uses actual/365 throughout. Fineract supports actual/actual, actual/360, 30/360, and actual/365. For NZ mortgage products, CCCFA disclosure requirements specify the exact day-count convention, and some products may require actual/actual (which produces slightly different amortisation in leap years and at month boundaries). Review required before a product with a non-actual/365 day-count convention is launched.
Early full settlement — interest rebate calculation. When a customer repays a fixed-rate loan in full before the maturity date, a Rule of 78 or actuarial rebate of prepaid interest may apply depending on the product terms. v1 closes the schedule via MOD-065 but does not compute an interest rebate. Fineract's InterestRefundService is the reference implementation. Flag as v2 when a fixed-rate product with early settlement rebate is required.
-- credit.amortisation_schedules (Postgres)
CREATE TABLE credit.amortisation_schedules (
schedule_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
account_id uuid NOT NULL,
version int NOT NULL DEFAULT 1,
schedule_type text NOT NULL CHECK (schedule_type IN ('PI','IO','REVOLVING')),
generated_at timestamptz NOT NULL DEFAULT now(),
generated_by text NOT NULL, -- 'origination' | 'rate_change' | 'extra_repayment' | 'restructure'
rate_at_generation numeric(8,6) NOT NULL,
is_current boolean NOT NULL DEFAULT true
);
CREATE TABLE credit.schedule_instalments (
instalment_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
schedule_id uuid NOT NULL REFERENCES credit.amortisation_schedules(schedule_id),
payment_number int NOT NULL,
due_date date NOT NULL,
payment_amount numeric(18,2) NOT NULL,
principal_amount numeric(18,2) NOT NULL,
interest_amount numeric(18,2) NOT NULL,
opening_balance numeric(18,2) NOT NULL,
closing_balance numeric(18,2) NOT NULL,
status text NOT NULL DEFAULT 'PENDING' -- PENDING | PAID | MISSED | PARTIAL
);
Module dependencies¶
Depends on¶
| Module | Title | Required? | Contract | Reason |
|---|---|---|---|---|
| MOD-001 | Double-entry posting engine | Required | — | Repayment postings (principal reduction + interest) are executed as ledger entries through the posting engine. |
| MOD-005 | Daily accrual calculator | Optional | — | Loan accrual is out of MOD-005 v1 scope. The amortisation schedule engine computes interest components using the declining-balance formula from the schedule rate — it does not read accounts.accruals_daily in v1. This dependency activates when MOD-005 adds loan accrual support. |
| MOD-006 | Rate change propagation | Required | — | Variable rate changes from the rate change propagation module trigger schedule recalculation. |
| MOD-110 | Fee engine | Required | — | Early repayment fees and break costs on fixed-rate loans are assessed and posted 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¶
| Module | Title | As | Contract |
|---|---|---|---|
| MOD-116 | Mortgage servicing engine | Optional enhancement | — |
| MOD-121 | Construction loan drawdown engine | Hard dependency | — |
| MOD-132 | Loan restructure and variation workflow | Hard dependency | — |
| MOD-139 | Financial hardship formal variation workflow | Hard dependency | — |
| MOD-162 | Loan facility & component manager | Hard dependency | — |
Policies satisfied¶
| Policy | Title | Mode | How |
|---|---|---|---|
| CRE-002 | Responsible Lending Policy | AUTO |
Full amortisation schedule is generated and provided to the customer at loan origination — disclosure of total cost of credit, total interest payable, and repayment schedule is automated with no manual step. |
| CON-004 | Product Disclosure & Sales Practice Policy | AUTO |
Any event that changes the repayment schedule (rate change, extra repayment, hardship restructure) triggers an automatic updated schedule delivery to the customer within 24 hours. |
| CON-005 | Fee & Pricing Transparency Policy | CALC |
Total interest payable and effective annual rate are computed from the schedule and displayed on the product dashboard at all times. |
Capabilities satisfied¶
(No capabilities mapped)
Part of SD01 — Core Banking Platform
Compiled 2026-05-22 from source/entities/modules/MOD-112.yaml