Skip to content

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

  1. Variable rate change (MOD-006 event received) — new instalment amount calculated from outstanding balance, remaining term, new rate; schedule regenerated from next payment date.
  2. 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.
  3. Interest-only period expiry — schedule transitions from interest-only payments to principal and interest; instalment amount recalculates over remaining P&I term.
  4. Hardship restructure (from MOD-065) — new schedule generated from restructured terms; original schedule retained for audit; both stored as separate schedule versions.
  5. Loan variation (from MOD-132) — new schedule generated from confirmed variation terms (new rate, term, frequency, rate type); prior is_current=true schedule superseded. See POST /internal/v1/loans/{account_id}/recalc-variation below.

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:

min_repayment = max(configured_floor_amount, outstanding_balance × configured_percentage)

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