Build Mortgage Coach

KINDAback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A loan comparison builder whose entire value is that the numbers are correct and their assumptions are visible: amortisation computed exactly, every scenario stating its inputs, and an output that cannot be mistaken for the regulated disclosure it is not.

estimated effort multi-daythe files for this build are in the project pack

ArithmeticExact decimal maths with a documented rounding policyAmortisationA generated schedule per scenario, retainedAssumptionsStored per scenario and rendered on the outputRatesEntered by hand with the date and source recordedOutputA document clearly distinct from any regulated disclosure

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the calculation engine and the document renderer.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. installfree

    Why Three hundred and sixty periods of floating point produces a closing balance that is not zero, and a borrower checking your numbers will find it.

    Get it Any well-tested arbitrary precision decimal library for your runtime.

    Verify A thirty year schedule closes at exactly zero

  3. have readyfree

    Why Your own calculation cannot verify itself, and the whole value of this tool is that the numbers hold up.

    Get it Take a published schedule from a regulator or a bank for a stated principal, rate and term, and use it as a test fixture.

    Verify Your schedule matches the published one to the cent

  4. decidefree to decide; compliance advice if this is commercial

    Why Using a regulated term for a figure computed differently is the most serious thing this build can get wrong, and the decision belongs before the first calculation.

    Get it Decide whether you will implement the regulation's own calculation exactly. If not, choose different names for your figures and write down what each includes. open ↗

    Verify A written note names every displayed figure and states exactly what it includes

  5. have readyfree

    Why Phase 5 requires the output to be visually distinct from them, which you cannot check without having them side by side.

    Get it Download the official sample forms from the regulator.

    Verify Your output and the official form are distinguishable at a glance

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- scenarios(id, comparison_id, label, principal_minor, rate_bps, term_months, product_kind, points_bps, fees_json, escrow_json)
- assumptions(scenario_id, key, value, source) · tax rate, insurance, appreciation, inflation, anything that moves a number
- schedules(scenario_id, period, payment_minor, interest_minor, principal_minor, balance_minor) · generated, retained, checkable
- rate_quotes(id, scenario_id, rate_bps, quoted_on, source, expires_on) · a rate has a date or it is not a rate
- comparisons(id, borrower_ref, created_by, created_at, published_at, disclaimer_version)
- outputs(id, comparison_id, path, rendered_at, inputs_hash) · so a document can be reproduced exactly from its inputs
- Every displayed figure is derived from a retained schedule, so there is no summary number that cannot be traced to a row.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_URLsecretrequiredpostgres://localhost:5432/comparisonsBorrower scenarios and figures. Financial data about identified people.
ROUNDING_POLICYrequiredhalf_up_centStated on every output, because a borrower comparing against a bank needs to know.
RATE_EXPIRY_DAYSrequired7How long a quoted rate is presented as current. Short, because rates move.
APR_TERM_ALLOWEDrequiredfalseWhether the regulated term may be used. False unless the regulation's own calculation is implemented exactly.
DISCLAIMER_VERSIONrequired2026-09Recorded per comparison, so you know what a past document said.
COMMON_HORIZON_MONTHSrequired120The default comparison horizon. Same across scenarios or the comparison is meaningless.

The build, in order

  1. Amortisation that survives being checked

    Your schedule matches an independent calculation to the cent.

    1. Three hundred and sixty periods of floating point is a visible error.

      Files src/money.ts

    2. An independent source, in the test suite. Your own maths is not a check.

      Files tests/amortisation.test.ts

    3. And assert the closing balance is exactly zero.

      Files src/amortise.ts

    done when · tick each as it passes
    watch out
    • Floating point arithmetic over three hundred and sixty periods accumulates error that shows up as a final balance that is not zero, and a borrower who checks your figures against a bank's will find the discrepancy and stop trusting the whole document. Use exact decimal types, state the rounding policy, and assert the final balance is exactly zero.
  2. Products that are not simple loans

    Adjustable rates, interest-only periods and buydowns compute correctly.

    1. Both. Applying only one produces a worst case that is wrong.

      Files src/arm.tssrc/arm.test.ts

    2. Not as an option. Suppressing it is the misleading default.

      Files src/scenarios.ts

    3. The end of an interest-only or buydown period is what surprises people.

      Files src/steps.ts

    done when · tick each as it passes
    watch out
    • Showing an adjustable rate loan at its initial rate for the whole term makes it look permanently cheaper than a fixed one, which is the single most misleading thing this category of tool can do. Compute and display the fully indexed and cap-limited worst case beside the initial period, always, not as an option.
  3. Comparing scenarios on the same assumptions

    Two loans are compared fairly or the comparison is refused.

    1. Different horizons produce any answer you like.

      Files src/compare.ts

    2. At the top, not in a footnote. This is where comparisons mislead.

      Files src/compare.ts

    3. Points against rate is the actual question. Answer it with a number.

      Files src/breakeven.tssrc/breakeven.test.ts

    done when · tick each as it passes
    watch out
    • Comparing two loans over different horizons, or with different assumed tax and insurance figures, produces whichever answer the person building it wanted, and it looks entirely legitimate. Require assumptions to match, and where they genuinely differ, show the difference at the top rather than in a footnote.
  4. Rates have dates

    No figure is presented as current when it is not.

    1. A rate without a date is not information.

      Files src/rates.ts

    2. In the document, not only in the editor.

      Files src/rates.ts

    3. Possible, deliberate and recorded.

      Files src/publish.ts

    done when · tick each as it passes
    watch out
    • A saved comparison reopened three weeks later shows rates that are no longer available as though they were, and it is the most likely way this tool misleads someone without anyone intending to. Attach a date to every rate, expire it, and make a stale comparison visibly stale rather than quietly wrong.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more finance & accounting.