# Mortgage installments spec

This page is the live calculator; this markdown is the canonical math it runs against. Partners and reviewers can read it without booting the app.

All amounts are integer cents. All math is cents-exact: rounding happens at small, well-defined seams, and totals reconcile to the principal exactly.

## 1. Inputs

| Symbol | Type | Description |
| --- | --- | --- |
| `P` | cents | Loan principal (>= 0). |
| `r` | bps / 10_000 | Annual interest rate (>= 0). |
| `n` | months | Term in months. The page maps `termYears` -> `n = termYears * 12` and clamps `n >= 1`. |
| `E_annual` | cents | Annual escrow (taxes + insurance). The page takes this as the canonical input; the monthly escrow is `round(E_annual / 12)`. |

`r = 0` is a valid edge case (zero-interest note): the monthly payment collapses to `round(P / n)` and there is no interest paid.

## 2. Monthly principal-and-interest (`M`)

Standard amortizing fixed-rate formula:

```
M = P * ( r * (1 + r)^n ) / ( (1 + r)^n - 1 )
```

- `r = 0` shortcut: `M = round(P / n)`; entries' `interestPaidCents = 0`, `principalPaid = M` until balance hits 0 in month `n`.
- All compound exponents use integer `n` and rational `r` (basis points / 10_000). The implementation computes `(1 + r)^n` once as a `Number` and rounds only the per-month interest share.
- Last amortization row absorbs the rounding remainder so that the sum of `principalPaid` over `n` payments equals `P` exactly (balance hits 0 in month `n`).

## 3. Schedule rows (per month)

For each month `k` from 1 to `n`:

```
interest_k = round( balance_{k-1} * r )
principal_k = M - interest_k        (last month absorbs remainder so balance_n = 0)
balance_k   = balance_{k-1} - principal_k
```

Returned shape:

```
{ month, principalPaidCents, interestPaidCents, balanceCents }
```

- `month = k`
- `balance_0 = P`
- `totalInterestPaid = sum( interest_k )`

## 4. Monthly escrow

The page accepts **annual** escrow as the input; monthly escrow is derived:

```
E_monthly = round( E_annual / 12 )
```

This makes "the page takes 1/12 of annual escrow" reversible for partners who want to reason at a different scale.

## 5. Cadence breakdown (optional)

When a user toggles cadence on (`weekly` or `semi_monthly`), the page splits the **monthly P&I + monthly escrow** across per-installment rows and applies a per-row service fee.

| Cadence | Installments | Fee rate (bps) | Fee rate (%) | Source |
| --- | --- | --- | --- | --- |
| `weekly` | 4 rows over `startAt + {0, 7, 14, 21}` days | `WEEKLY_INSTALLMENT_FEE_BPS = 200` | 2.00% | `src/lib/brand-stripe-links.ts` |
| `semi_monthly` | 2 rows over `startAt + {0, 14}` days | `SEMI_MONTHLY_INSTALLMENT_FEE_BPS = 150` | 1.50% | `src/lib/brand-stripe-links.ts` |
| `none` | (toggle off) | `0` | 0.00% | — |

The single source of truth for the BPS rates is `src/lib/brand-stripe-links.ts`; this page does NOT redeclare them. Same numbers as `/pricing`.

### Per-row math

For each row:

```
amount_per_row = round( (M_monthly + E_monthly) / N ),  last row absorbs rounding remainder
fee_per_row    = round( amount_per_row * feeBps / 10_000 )
total_per_row  = amount_per_row + fee_per_row
```

`N = 4` for weekly, `N = 2` for semi-monthly. The cents-exact split of (P&I + escrow) mirrors `splitRent` so per-row amounts sum exactly to the shared monthly pool.

### Row shape

```
{ sequence, dueAt, amountCents, feeCents, totalCents }
```

## 6. Worked example (matches the page's defaults)

Inputs: `P = $400,000`, `r = 6.50%`, `n = 30 * 12 = 360`, `E_annual = $4,800`.

- `M ≈ $2,528.27 / month` (recursive amortization; balance reaches 0 in month 360).
- `E_monthly = round(4_800_00 / 12) = 400_00` ($400/mo).
- `Total monthly (P&I + escrow) ≈ $2,928.27`.
- Total interest over the life of the loan is the 360-month sum of `interest_k` (compute on the page; the spec doesn't pin it because rate×n exhibits the canonical mortgage constant).
- Weekly (2.00% per row): 4 rows of `round(2_528_27 + 400_00) / 4 ≈ 732.07`, each row's fee ≈ `$14.64`, total per row ≈ `$946.71`. Last row absorbs the cents remainder so the 4 rows sum to the full monthly pool exactly.
- Semi-monthly (1.50% per row): 2 rows of `round(2_528_27 + 400_00) / 2 ≈ 1,464.14`, each row's fee ≈ `$21.96`, total per row ≈ `$1,486.10`. Last row absorbs the cents remainder.

These are illustrative; the page renders the live number from the inputs it accepts.

## 7. Definitions

- **P&I** — principal + interest. The amortizing monthly payment on the loan.
- **Escrow** — yearly amount set aside for taxes + insurance. Divided into 12 monthly installments.
- **BPS** — basis points; 10_000 bps = 100%. Conversion to a decimal rate is `bps / 10_000`.
- **Cadence** — the per-installment frequency a customer chooses when split payments are enabled.

## 8. Where this lives in the codebase

- Math: `src/lib/business/mortgage/amortize.ts`
- Barrel: `src/lib/business/mortgage/index.ts`
- Client island: `src/components/custom/mortgage-calculator-island.tsx`
- Page (RSC): `src/app/mortgage/page.tsx`
- Served under: `/docs/mortgage-installments-spec.md`
