Retirement calculators are where legacy math hides longest — the answers are decades out, so nobody notices drift. This is the general migration pattern made concrete: one legacy tool, one verified model, every mapping decision shown.
The companion piece on backfilling a legacy calculator gives the general pattern: shadow mode, a diff harness, a defensible cutover. This guide walks it through the most common concrete case — a homegrown retirement projection tool being replaced by the FIRE model — because retirement migrations concentrate all three classic hazards: unit ambiguity, unbounded legacy domains, and answers too far in the future for users to have ever flagged as wrong.
Step one: the field mapping, in writing
The FIRE model takes seven inputs, every one required — no defaults, no optional fields. That property forces the migration's most valuable artifact into existence: a written mapping in which every model input names the legacy field it comes from and the transformation applied. Retirement tools are where the transformations bite.
| Model input | Domain | The classic mapping hazard |
|---|---|---|
| currentAge | 18 to 65 integer | Legacy tools accepted any age — decide now what happens to the 70-year-old user |
| currentSavings | 0 to 1000000 | Legacy field may include home equity — the model means investable savings |
| annualIncome | 30000 to 500000 | Monthly versus annual is the oldest bug in finance forms |
| annualExpenses | 20000 to 300000 | Often derived in legacy code as income minus savings, silently |
| savingsRate | 0.05 to 0.8 decimal | Legacy stores 15 for fifteen percent — the model wants 0.15 |
| expectedReturn | 0.02 to 0.12 decimal | Is the legacy return nominal or real — the answer changes everything |
| withdrawalRate | 0.02 to 0.06 decimal | Legacy tools hard-coded 0.04 — the model makes it an explicit choice |
Two of these deserve their own paragraph. The percent-versus-decimal convention is mechanical once noticed — the rejection surface notices for you, because a savings rate of 15 is outside the 0.05-to-0.8 domain and comes back as a structured error rather than a silently absurd projection. The nominal-versus-real return question is not mechanical: if the legacy tool compounded a nominal 10 percent while displaying inflation-adjusted-sounding language, its answers were systematically rosier than a real-return framing. No API can decide which your product meant. The mapping document must, in a sentence a reviewer can read.
Step two: shadow the traffic and respect the rejections
Run both engines side by side on live traffic, storing legacy answer, model envelope, and the mapping version that produced the call. The model's domain edges will reject some of your historical population — the 17-year-old, the seven-figure portfolio, the 90 percent savings rate. Do not widen anything by clamping; count the rejections instead. That count is a product decision surfacing: the users your legacy tool answered with unstudied math are the users the new tool will decline, and product should decide what those users see before cutover, not discover the cohort after.
Step three: the diff, with retirement-specific classes
The general three-way classification — legacy bug, convention difference, mapping error — holds, but retirement diffs cluster in two predictable places. Compounding conventions first: annual versus monthly compounding, contributions at period start versus end, return applied before versus after the year's savings — each defensible alone, each producing diffs that grow with horizon. The spec states the model's convention; your log states the legacy one; the diff is then explained, not mysterious. Sentinel semantics second: the model returns retirementAge 100 as an annotated sentinel meaning FIRE is not reached by age 100. Legacy tools extrapolate — a projection that cheerfully reported retirement at 112 was not more capable, it was reporting an answer from outside any studied range. Every legacy answer past the sentinel boundary lands in the diff as a disagreement in which the model's refusal is the defensible side.
Step four: cutover with the retirement-specific disclosure
Everything from the general pattern applies: classified diff log, CI fixture pinned the same day, envelopes stored from the first request. The retirement-specific addition is the disclosure decision, because projection deltas here are emotionally loaded — a user whose projected retirement moves from 54 to 58 experiences a product change as a life change. The honest framing, backed by the diff log, is that the tool's math changed deliberately: the new engine computes within published domains, states its assumptions, and refuses ranges it has not studied, and figures may differ from earlier versions. The envelope makes the sentence provable — every post-cutover answer carries its spec version, its inputs, and its assumptions, which is precisely what the legacy tool could not offer the user whose number just moved.
- Write the mapping with a transformation note per field — decimals, annual figures, investable savings.
- Resolve nominal-versus-real in writing before shadowing.
- Count domain rejections as a product cohort, never clamp them through.
- Sort diffs by horizon as well as by dollars.
- Treat legacy answers past the sentinel boundary as the legacy bug class.
- Disclose the change with the envelope as the receipt.
What this buys beyond the swap
The migration ends with the retirement tool on maintained math — the seven domains, the sentinel, and the spec are the platform's to keep current, tracked in a public changelog your fixture watches. But the durable gain is the artifact set: a mapping document that finally states the units, a diff log that is the first accurate description of what the old tool did, and envelopes on every answer going forward. The next question about a projection — from a user, a reviewer, or the team's own future — gets an answer with provenance instead of an archaeology project.
Sources
- [1] Worthune FIRE model documentation. https://worthune.com/docs/models/fire
- [2] Worthune writing: Backfilling a Legacy Calculator With a Verified Engine. https://worthune.com/writing/backfilling-legacy-calculator
- [3] Worthune writing: Handling Out-of-Range Errors Gracefully. https://worthune.com/writing/out-of-range-errors