Loan lifecycle
A loan moves through a small set of states, and everything that happens to it is recorded as append-only, value-dated facts rather than mutable balances.
States
Draft ──activate──▶ Active ──┬─ natural run-off ─▶ PaidUp
├─ pay up (early) ─▶ Settled
├─ default ─▶ Defaulted
└─ write off ─▶ WrittenOff
- Draft — priced and persisted, but no money has moved.
- Active — funded and open for servicing; the schedule's due dates drive collection.
- PaidUp / Settled / Defaulted / WrittenOff — terminal outcomes.
Calculations are versioned
A loan's terms live in a calculation — the priced schedule, rate, APR, and totals. Re-terms and prepayments don't mutate the loan; they append a new calculation and bump the loan's sequence. The current calculation is the one whose sequence matches the loan — there's no stored "current" pointer, it's derived. Superseded schedules stay on record (the console's History tab).
The value-dated ledger
Money is tracked as a signed, append-only sub-ledger: disbursement adds principal, repayments reduce it, and the outstanding balance is the running sum of the deltas — derived, never stored. Because entries are append-only and value-dated, the balance is reproducible as of any date, and a late payment that arrives after a re-term still lands correctly on the current schedule.
Servicing runs in the background
The admin host runs scheduled sweeps (in the worker, once per cluster): charging due installments, aging into delinquency buckets, applying late fees, computing IFRS-9 provisions, posting the sub-ledger to the general ledger, and dispatching disbursements. The partner host runs none of these — it only serves requests against the same database.