Credit repayment engine — the math (Task 10.2)
Status: ✅ Complete
Plan reference: Task 10.2 in the Credit module implementation plan (operations workspace root)
The credit module has two repayment engines:
- A declining-balance engine shared by Diminishing Musharakah and Ijarah — rent accrues on the shrinking outstanding balance.
- A fixed-profit engine for Murabaha — total profit is fixed at signing and recognised straight-line as the customer pays.
Both write credit_schedule_lines and both close the final-period residual so the totals tie out to the penny.
Declining-balance engine (DM / Ijarah)
Inputs
facilityAmount (= assetCost − downPayment), annual rate, number of periods N (termPeriods), periods-per-year.
Per-period formulas
- Principal (buy-out), straight-line:
principalPortion = facilityAmount / Nfor every period; the final period absorbs the rounding residual soΣ principalPortion === facilityAmountexactly. - Rent on the declining balance:
profitPortion = beginningBalance × periodRate, whereperiodRate = rate / periodsPerYear. Because the balance shrinks each period, the rent declines every period. totalPayment = principalPortion + profitPortion.endingBalance = beginningBalance − principalPortion.
Ownership
Each period the customer's owned share rises by principalPortion / facilityAmount; Ethica's falls by the same. Written to credit_ownership_ledger and onto each schedule line.
Worked example (the DM / Ijarah golden case)
AssetCost ₦100,000,000, downPayment ₦0, rate 8% p.a., 15 years × 1/yr → 15 periods.
facilityAmount = 100,000,000.principalPortion = 100,000,000 / 15 = ₦6,666,666.67per period (straight-line).- Period 1:
beginningBalance = 100,000,000; rent= 100,000,000 × 8% = ₦8,000,000; total= 6,666,666.67 + 8,000,000 = ₦14,666,666.67; ending= ₦93,333,333.33. - Period 2: rent
= 93,333,333.33 × 8% = ₦7,466,666.67— lower than period 1, because the balance shrank. And so on: rent declines every period. - Totals:
Σ rent ≈ ₦64,000,000;Σ principal = ₦100,000,000exactly (final-period residual closed).
| Period | Beginning balance | Principal (buy-out) | Rent (8% of beginning) | Total | Customer owned |
|---|---|---|---|---|---|
| 1 | 100,000,000.00 | 6,666,666.67 | 8,000,000.00 | 14,666,666.67 | 6.67% |
| 2 | 93,333,333.33 | 6,666,666.67 | 7,466,666.67 | 14,133,333.34 | 13.33% |
| … | … | … | … | … | … |
| 15 | ≈6,666,666.67 | 6,666,666.67* | ≈533,333.33 | ≈7,200,000.00 | 100% |
| Σ | 100,000,000.00 | ≈64,000,000.00 | ≈164,000,000.00 |
* Period 15's principal is the residual that closes Σ principal to exactly 100,000,000.
Fixed-profit engine (Murabaha)
The anti-riba rule
Murabaha total payable is assetCost + fixedTotalProfit, where fixedTotalProfit is locked at signing and supplied on the facility — it is never derived from the rate. This is the core AAOIFI no-riba guarantee for Murabaha: the profit is a sale margin agreed up front, not a time-value-of-money charge.
Per-period formulas
- Principal, straight-line:
principalPortion = assetCost / N, with the final period closing the residual soΣ principalPortion === assetCostexactly. - Profit, straight-line:
profitPortion = fixedTotalProfit / N, with the final period closing the residual soΣ profitPortion === fixedTotalProfitexactly. totalPayment = principalPortion + profitPortion.
Pace-invariance guarantee
Because both principal and profit are scheduled straight-line from amounts fixed at signing, the total profit the customer pays is invariant to the pace of payment. Paying early, late, or in lumps changes when profit is recognised, never how much. On early settlement Murabaha recognises all remaining deferred profit at once (there is no ibra' rebate yet — see open items). Contrast DM/Ijarah, where early settlement waives future rent because rent is a function of time-on-balance.
Residual closing (both engines)
Naive total / N leaves a sub-naira remainder after N periods because of rounding to 4dp. Both engines compute periods 1..N−1 at the rounded straight-line figure and set period N = the exact remaining balance, guaranteeing:
- DM/Ijarah:
Σ principalPortion === facilityAmount. - Murabaha:
Σ principalPortion === assetCostandΣ profitPortion === fixedTotalProfit.
This is asserted in the engine's unit tests against the golden example above.
Files
- Engine —
ethica-api/src/credit/engine/*.ts(declining-balance + fixed-profit schedulers) - Tests — credit engine specs covering the golden DM/Ijarah case and Murabaha pace-invariance
Related
- Credit data model —
credit_schedule_linesand the ownership ledger. - Credit API —
GET /credit/facilities/:ref/schedulereturns this output. - Credit architecture — AAOIFI rationale and the posting flow.