How the Planner Works

Reference architecture · PurpleTeam Financial Planner

One File, No Server

overview

The planner is a single HTML page. Everything it needs, its code, styles, tax tables, market history and lessons, is inside that one file. It makes no network requests: a household's numbers are entered, simulated and stored in the browser that has the page open, and they leave it only as a file the person chooses to download.

The page is assembled from about source parts by a small Python script, build.py, and tested by test suites before each version is published. Each version adds a section to the developer notes, HACKING.md, and a test of its own.

The same source also makes a website, with dist.py (versions 382 to 384): the page split into files named by their contents (assets/style-…css, engine-…js, data-…js, app-…js) that browsers may keep for good, a service worker so the planner opens without a connection, a web app manifest and icons so it can be installed, welcome, privacy and terms pages, a sitemap, and a one-file copy beside them. The website still sends nothing anywhere: its pages load only their own files.

The Build

build.py · precomp.js

There is no bundler and no package to install. build.py reads every folder under src/ in sorted order and joins the files; a file's three-digit prefix alone decides where it goes, and numbers go up in tens to leave room. Nothing is named in the script, so adding a file never means editing it.

  1. The page title from head.html, then one <style> block: app.css and every file in css/.
  2. The markup: shell.html (top bar, sidebar and an empty navigation that the tab registry fills), one file per tab in views/, then the dialogs.
  3. A first script with the engine, engine/*.js, as plain functions that the node tests can also load.
  4. A second script: the BUILD_ID (a hash of every source file), the data tables, and the app modules in app/, wrapped in one closure that 000-prelude.js opens and 260-start.js closes.

Data files become variables: data/NAME.json becomes var NAME, and data/NAME.csv becomes the string NAME_DATA, parsed at start-up. The same run writes two test pages beside the published one: view.html starts from a simple example plan, view-sample.html from the sample household.

The Precomputed Sample

A first visit shows the sample household's result at once, without waiting for the simulation. precomp.js opens the built page in headless Chromium, lets it run the sample at full size, and stores the result in data/precomp.json under a key made of the version, the BUILD_ID, the calendar year, the number of futures and a hash of the plan. The page uses it only when that key matches its own.

Order matters: build, run precomp.js, build again. A stale file does no harm; the page simply runs the simulation.

The Layers

src/app · src/engine

The runtime diagram groups the code by what it does. Each module is one file; the tables below list the main ones. The whole app shares one scope, so a module can use any function defined in another.

Your Data, in the Browser

PartWhat it doesFiles
The planOne object, cfg, built from defaults and the saved copy, every field cleaned on the way in. Saved on each change, with 40 steps of undo.000-prelude, 060-inherit-data, 150-rail
Plans and backupsUp to 20 named plans; a plan file in JSON; a whole-browser backup with a fingerprint, optionally encrypted (PBKDF2 and AES-GCM through Web Crypto).234-mod-plans, 249-mod-settings, 249-mod-sec
CSVAccount balances in; the whole plan, the results and progress snapshots in and out. Cells are guarded against spreadsheet formulas.030-csvimp, 235-mod-import, 246-mod-datacsv, 110-downloads
Reference tablesYearly tax and benefit figures, state rules, life tables, market history since 1871 and more. Each can be replaced on the Actual Data tab.006-data, 253-mod-data, engine 005-datayr

Tabs and Views

One registry, TABS in 005-tabs.js, lists every tab with its group, icon, search words and what runs when it opens. The navigation, keyboard order and search are all built from it. A view (Easy, Client, Enterprise, Planner or Learn) decides which tabs show and how much is said. The page opens in Easy, which runs every plan on the market's history with the guardrails and flat spending and keeps the plan's own choices for the other views; the others never change the plan or its result.

GroupTabs

The Engine

The engine lives apart in src/engine: plain functions with no page code, so the node tests load them directly. Most of the model is in 010-engine.js.

PieceHow it works
Timelinetimeline(c): the working years, then up to 60 years of retirement, capped at age 120.
MarketsmakeShocks draws correlated yearly shocks for stocks, bonds and cash from a seeded generator, so the same plan always gives the same answer. One year in about seven is turbulent (fat tails), with the overall volatility unchanged. Returns are lognormal with the mean and spread entered. Historical cycles since 1871 and five named crashes can replace the random futures.
Inflation and mixSteady, high for a stretch, or history replayed. The stock share can follow a glide path, rising, falling or a bond tent.
Spending rulesFourteen, from fixed spending (with an optional skipped raise after a losing year) to Guyton-Klinger guardrails (the default), risk-based guardrails, Vanguard's dynamic rule, variable percentage withdrawal, TIPS amortization (ARVA), Kitces' ratchet and the RMD method.
Two modelssimulate runs one portfolio. makeTaxSim runs the accounts as four tax pools (each person's pre-tax money, Roth and HSA, taxable), in a withdrawal order, and returns a job the page runs in slices, or splits over Web Workers: raw() and merge() carry each future's figures from a worker's range into the page's run (version 372).
A tax yearyearTax: federal brackets and deductions, capital-gains rates, each state's rules, the 3.8% investment tax, Social Security taxation, required distributions, the early-withdrawal penalty, Medicare surcharges with their two-year look-back, the marketplace subsidy, payroll tax and Roth conversions.
Three futuresrepPicks ranks every future (those that run out first by the year they do, then by what is left) and returns the 5th, 10th, 15th, 50th, 85th, 90th and 95th, year by year and by kind of account, for the Plan tab. pickShocks takes futures out of the full set so they can be run again alone, exactly as before; lastingScale uses that to find the spending that would carry one to the end, and repDetail to keep each retirement year of the futures shown in detail: taxes by kind, the tax on the next $1,000 of income, and health costs with the Medicare surcharge tier and the marketplace line, for the tax and health care dashboards. Each future also carries what it can spend a year (sp), for the spending card.
Everything elseSocial Security with spousal and survivor benefits and the earnings test; pensions, annuities, a TIPS ladder, rental and home sales; debts; 529 plans; inheritances; long-term care events; dependents.

The engine's other modules are small and focused: the Tools calculators (020-calc.js), Medicare, state rules, life tables, Social Security from an earnings record, insurance needs, debt, student aid and credits.

Results and the Cache

A run reduces each year of every future to five percentiles and adds the chance the money runs out, how often spending hits its floor or ceiling, the balance at the end and a read on the futures that fail. render draws the Plan tab from it, including three single futures side by side (102-mod-futures.js) and the chance of lasting year by year as bars in the same style, with its details beside In Plain Words on a wide screen (107-mod-lasting.js, versions 402 and 403); the result is kept under a key of the plan and the build, so reopening the page shows it again without a run.

Built on the Same Plan

PartWhat it doesFiles
Insights, Cash FlowRead the result, and re-run the plan on the same futures for withdrawal detail, what moves the result most, and the early years.210-insights-tab, 220-bridge, 236-mod-sens, 240-mod-flow
Tools231-mod-tools2a, 232-mod-*, 233-mod-tools2b
ReportsA PDF writer written for the planner (no library, vector charts), branded client reports, meeting packs, Current vs Proposed, Spanish client reports, CSV and a calendar file of key dates.120-pdf, 241-mod-rep, 251-mod-reports, 252-mod-cvp, 249-mod-dates
Lessons248-mod-learn, 248-mod-learnpdf, lessons_src.py
Planner GuideThe user guide's stories, copied from the published guide into data/guide.json by docs/guide2app.js, in every view; each tab or tool a story names opens from it (version 335).249-mod-uguide, guide.json
Finding Your WayHome, the plan at a glance when you come back, first under Results and with a picture on each tile (versions 404 and 405); Start Here, which narrows the menu and the tools to one goal; pinned and recent tools; a search that also runs actions, explains about fifty terms and finds close matches; the way back from a tool (versions 369 to 376).211-mod-home, 259-mod-nav, 249-mod-gloss, 250-search
Month by MonthEnvelopes and sinking funds from the budget; bills with a calendar, the cash they leave and a calendar file; bank files (CSV and OFX) with rules, repeats left out and recurring payments found; the path of the money in the futures that last (versions 377 to 380).202-mod-envelope, 202-mod-bills, 207-mod-bankx, 207-mod-progviz, 107-mod-lasting
Easy ViewA calmer first screen with one next step, the menu and first screen in Spanish (machine-translated), a reminder to back up, and installing the planner as an app (versions 373 to 386).258-mod-ezcalm, 249-mod-esui, 249-mod-bknag, 249-mod-pwa
SharingA read-only copy of the Plan Summary to send (version 387).112-mod-share
Accounts and Net WorthOutside Easy, Your Accounts in foldable groups by what each account is for, with a drawn picture for each kind; then what else is owned and what is owed, below zero, each row read from where it is entered (the Liabilities, Insights, Income and Statements tabs) and changed in place or there; Net Worth Today with the balance sheet's totals, and Net Worth Over Time from the Progress tab's snapshots (versions 393 to 396).153-mod-acgrp, 154-mod-acicons, 155-mod-acnet, 156-mod-nwhist
Budget, Accounts and Social SecurityPets in the budget: a dog's or a cat's yearly costs (ASPCA) as lines that end when it reaches the typical lifespan of its kind (version 406). Treasury notes in a bonds account, each with its term, dates, amount and rate, pasted the way TreasuryDirect lists them, with a ladder of the years they come due; card cash back and a crypto wallet as kinds of account (version 407). A Social Security benefit that starts from a statement and grows with the covered pay the plan still has, and jobs outside Social Security (version 408).046-pets-data, 203-mod-pets, 010-tsy-data, 157-mod-tsy, 095-ssearn
Spending Lines, Tables and RulesThe spending card's draggable lines are three: the first year, the floor and a ceiling. They cannot cross: the floor runs up to the first year, the ceiling down to it. Each has its own handle, side by side; where lines meet, a press on the shared line waits for the first move, and up takes the ceiling, then the first year, then the floor, whichever can still go that way. A switch puts the first year's health care on top of each line, the setting moving by the line less its data-off (version 410). A column's title sits on the side its figures do, checked on every table of every tab and tool (version 411). Compare the Spending Rules runs every rule in the model field's list on the plan and the same futures, one after another, and shows them in one table with Use and Save on each row; it is kept while the plan stays the same but for its rule (version 412).104-mod-dash, 105-mod-spend, 190-thalign, 131-mod-rules
Floors Over the YearsOn the spending card the middle future is a little wider than the other two (version 413). Made larger, it sets up to four floors, the plan's and three more, each from its year until the next (cfg.floorSteps). Both engines take the floor in force each year (floorByYear): the rules stop their cuts at it, the ceiling is never under it, and a floor above the spending raises the spending to it in its years. Without later floors the engines run as before. The floor line is drawn in stretches, each a slider (version 414).106-mod-floors, 105-mod-spend, 104-mod-dash, 010-engine
What IfOn the Scenarios tab, fifteen turns of life in three groups, each with its own numbers and published defaults named on its tile: a layoff, a child, a new car, a health care bill, retiring earlier, helping family, a home repair, long-term care, a partner's death, living longer, a crash, lower returns, higher inflation, a Social Security cut, a smaller or later inheritance. Each is a change to a copy of the plan (WI[k].apply), run on the plan's futures beside the plan; it can be saved as a scenario. A cost in a working year goes into cfg.saveAdj (versions 416 and 417).132-mod-whatif, 133-mod-whatif2, 010-engine
The Plan in One PictureVersion 451: one future followed age by age, in two charts on one age scale: the portfolio as a line, and under it each retirement year’s costs (spending, health care, other costs and taxes) stacked by what pays for them: pensions, each person’s Social Security, pay, other income and the portfolio for the rest. The middle future or the portfolio card’s hard or strong one, from the three run again in detail; the Retire line dragged or moved with the keys sets the retirement year. On the Plan tab in Enterprise and Planner and in the Client view’s Plan Summary.
Share This Plan ButtonVersion 450: a button at the foot of the menu, under Start a Blank Plan, opens one window, Share This Plan, with Share by Link, Copy for Your AI and Open a Plan Someone Sent. It shows in Easy, Enterprise, Planner and Client (without the start-over buttons there), not in Learn. The menu’s tightest rows now apply up to 895 pixels (800 before), so the whole menu still fits every window 768 to 940 pixels high; above that its rows stay 24 pixels or more (WCAG 2.5.8).
Loading CoverVersion 449: the page no longer flashes the Enterprise layout before the saved view. The first thing drawn is a cover in the top bar’s purple with the mark and “Loading the planner”; the saved view is set before anything is drawn, and the cover fades once the first tab is laid out (at once with reduced motion), lifting by itself after eight seconds if the code stopped short.
If Work StoppedVersion 447: a card on the Expenses tab runs the plan’s budget, accounts and income month by month as if a paycheck stopped, through the Between Jobs engine, which now takes the money in the order that keeps the 10% penalty away longest: cash, taxable accounts and Roth contributions, then a 457(b), a 401(k) under the rule of 55, 72(t) payments and everything after 59½, then the penalty, Roth growth last. Side income, a partner’s pay, a severance and health insurance count; a second run has a bad first year. On request the whole plan runs again with the What If tool’s layoff to show the chance of running out before and after.
Landing PageVersion 446: the website opens on a landing page (index.html, built by site_landing.py) and the planner is planner.html. The page says what the planner does, how a plan stays in the browser and is shared, lists guides and videos (landing-links.json: a YouTube link with a picture kept on the site, nothing loaded from YouTube) and links the Discord. Its figures are read from the source at build time. One inline script, named in the security policy by its hash, passes old share links, tab links and the installed app on to planner.html; #lessons opens the Learn view.
Locked LinksVersion 445: a locked share link names the rounds that locked it. The first byte 1 means 600,000 rounds, fixed, as versions 439 to 444 wrote it and as is still written while the setting stays there; 2 means the rounds follow in four bytes and are part of the additional data. The head is checked before the passphrase is asked for: rounds outside 100,000 to 5,000,000 are a damaged link, a later first byte a newer version’s. Raising the rounds later no longer turns older locked links into a wrong passphrase.
Merged CardsVersion 444: cards that showed the same thing twice are one card with views. Taxes Over the Retirement Years takes in Taxes and Penalties Each Year (Three Futures and All Futures, the typical year as a line); Year by Year on the Cash Flow tab takes in One Future, Year by Year (Typical and seven futures, Table or Chart); Net Worth Over Time on Progress (By Kind, Against the Plan); Fees on Plan Settings (the funds, the advisory fee and its cost); Spending by Year on Expenses; the Debt Calculator’s two what-ifs; the life spans on People. A moved card leaves one that opens it (151-mod-ptr.js: data-tab with data-card and data-then), and Insights keeps short summaries that link to the full cards.
Samples and BadgesVersion 442: thirteen sample households (059-samples.js), eight of them new: a widow at 74, new retirees, a single worker at 59 with little saved, an unmarried couple, a sandwich-generation couple, a self-employed electrician, a couple retiring at 42 and a family with a disabled son, each a plan with its published figures linked in SMP_SRC. Every sample has a stage, tags and a line of what it shows; the Settings tab groups them by age, filters them by tag (smpFilter) and reads each card’s ages, state, pay and savings from its own plan (smpFacts). License badges (249-mod-lic.js) now cover 60 tools (TOOL_LIC) and the tab cards that do licensed work (CARD_LIC, put first in the card by licCard), the Retirement Analysis heading (HEAD_LIC) and the Strategy tab’s decisions (GD_LIC).
Blocks of HistoryVersion 440: a fourth way to draw the markets. Each future strings together runs of ten years (1 to 20) from the record since 1871, as standard draws the engine turns into returns at the plan's own averages and swings (blockShocks); Treasury bills from 1928 to 2023 for cash in the historical cycles when the plan asks (c.histCash).
Share and AIVersion 439: the Social Security statement file (XML) read into Social Security From Earnings; the plan as Markdown and JSON for an AI assistant, names left out by default; a link with the plan inside it after #share=, compressed, sealed with AES-GCM when a passphrase is set, opened as a copy that saves nothing.
Quick StartVersion 438: five questions in a dialog (household, retirement age and state, savings, spending, Social Security from the statement or from pay) make a plan from the blank one, with one pre-tax account, the guardrails and health care as the samples have it; a card on the Plan tab lists what was assumed until it is hidden (cfg.quick).
Retirement PaycheckVersion 437: the first retired year's spending a month, with the portfolio values that would cut or raise it (the middle future's sweep, shared with the Withdrawals tab), a one-time cost against them, and across every future the share that cuts more than 10% and 25%, the deepest cut, years below the plan, goal completion and spending too little (cutStats in both engines); on Easy's first screen and on Home.
Other AssumptionsVersion 436: beside the headline, the plan run again on the 2026 forecasts, on the other way of drawing markets and with Social Security at 78% from 2033 (108-mod-other.js: lean runs, 2,000 simulated futures with the plan on the same ones, started when the card is in view); health costs 1.5 points faster than prices by default; the care tools on 2025 costs; the TIPS curve of 2 October 2026; SHA256SUMS.txt in the website package.
Find Realistic SpendingVersion 435: a button on What You Could Spend Each Year that sets the first year, the floor (the essentials) and the ceiling (110%, 125% or 150%) as high as the limits allow: on history at most 4% run out and the worst 5% keep the larger of $100,000 and 5% of the money invested today; on Monte Carlo at most 15% run out (spfRun, spfFirst, spfEval on the plan's own futures, 2,000 of them for the search when there are more, then checked on all).
Between JobsVersion 434: a tool, month by month from next month, for how long the cash lasts after a layoff, what the 401(k) and IRA add and cost taken early (the 10% with the $1,000 exception, the federal tax on top of benefits and new pay), what buys time and tax timing (rwRun, rwSteps, rwLow); Kimberly, a single parent between jobs, as a fifth sample; pre-tax money a working year's cost takes out grossed up for its tax and the 10% (OUTR in makeTaxSim); the Monthly Money Check counts money the plan takes from savings.
Withdrawals PreviewVersion 433: the Withdrawals tab's first card for any year of the median, worst-10% or best-10% future (wdNextPaint, a detail per future kept until the plan changes); the spending rule chosen on its card there; the sample on historical cycles with Guyton-Klinger at a 2% failure rate, with a German pension taxed like Social Security.
Inflation by FutureVersion 432: inflation that varies from one future to the next (inflPaths: a persistent surprise correlated with the year's returns, or a cycle's own CPI), scaling only what is fixed in dollars per future in simulate and runPath; the retirement month and an income's first and last month (incSpan's s and e, incShare); life insurance as an Expenses row whose benefit reaches the survivor through inheritModel (lifeCover); and a credit shelter trust against portability, with lifetime gifts, in the Estate and Heirs tool.
Months, Local Tax, HarvestVersion 431: Social Security claimed by the month (ssFactor in months, the claim year paid from the claim month); the claiming tool weighted by the life table (one run per death age, ssExp); a local income tax at a flat rate on income or pay (_loc, counted as state tax); gains harvested up to the 0% line, under the 3.8% tax and Medicare lines (hvY, raising the basis); and a year's tax return on the Withdrawals tab from new detail keys.
Orders by StageVersion 430: a withdrawal order for each stage of retirement (until Social Security, until required withdrawals, after), worked out per year in the tax engine (orderByYear); the Strategy tab searches Roth conversion lines and years on the whole plan, ranked on what heirs keep after tax, tests an income annuity at retirement, and tries the orders by stage.
Claiming Ages, EachVersion 429: the Strategy tab searches each spouse’s Social Security age on the whole plan, every pair of 62, 65, 67 and 70 not yet past, then the years around the best pair, one person at a time; the plan’s own pair takes its base run. The Capabilities tab is brought up to date, with what could still be added.
Spending in AllVersion 428: on What You Could Spend Each Year, Add other costs and taxes puts the Expenses tab’s other costs and the taxes on each future’s bars, from the futures’ own year-by-year detail, so a bar is all the year pays out, as the Withdrawals tab lists it. A future made larger can set the spending ceiling to the marketplace line less 2 points (398% of the poverty line), with a note on the years its marketplace income still passes the line.
Whole-Planner ReviewVersion 427: ten reviewers, one slice each (the engine twice, helpers and data, the run and results, the data tabs, the tools twice, the planning tabs, security, UI and text), 150 findings each reproduced, each fixed by its finder in a copy of the site with a patch script and a suite of its own (v427Atest.js to v427Utest.js). The tax now converges each year and nothing unpaid is dropped; unmarried partners have their own fill, conversion and marketplace lines; the Roth basis is kept in dollars; an income fixed, capped or rising by a set percentage is its first payment in the dollars of its year; a home sale in a working year that costs money is charged; the kept result follows the data table in use; downloads wait for the run; the site lists script hashes instead of unsafe-inline; no text is under 12px.
WithdrawalsA tab under Planning, not in Easy (237-mod-wdplan.js). The first year the accounts pay for the plan, read from the middle future run again alone with its detail (repDetail): each kind of account split over the accounts by today’s balances, what it pays for, the required withdrawal, the conversion, what is put back; month by month with the four estimated tax payments. Marketplace years with the hold under the line: cfg.acaHold makes makeTaxSim redo a marketplace year the order would take over the line, taxable money first (only its gain counts), pre-tax in the room left, then HSA money for medical bills and Roth money; a year that still cannot fit goes as the order says. The result carries acaLife, the typical lifetime premiums, and the detail held and wdM; the Strategy tab ranks the hold as a decision of its own. The spending rule is explained with a ladder the engine works out: the middle future again with its first retirement year’s stock return moved, next year’s spending against next year’s portfolio, each step found by halving the gap twelve times. The 2027 figures published so far (HSA and HDHP, Rev. Proc. 2026-24; the marketplace percentages, Rev. Proc. 2026-26; the marketplace out-of-pocket limits) are in the table; refAuto never takes a year later than the calendar year, so they take over on January 1, 2027, and limFor(y) checks a contribution against its own year’s limits. A budget is read one calendar year at a time (budYearTotals): the pay a lifestyle needs had added up every line, whatever its years.
Review FixesA review of versions 410 to 424, every finding tried before it was fixed: a later floor raises its own years only and the spending rule keeps its path; a working-year cost comes out of the year's savings in both engines; Solve keeps the later floors it tested; Save with no name, the rules table after Undo, names with % and :, and a what-if never taken for the sample plan; the tax guess files unmarried partners apart with the plan's deductions; a statement loaded on the Statements tab is not counted twice in net worth; a snapshot applied back adds nothing twice (version 425).010-engine, 080-solve, 130-compare, 132/133-mod-whatif, 171-mod-inctax, 155-mod-acnet, 207-mod-prog
One Figure EverywhereNet worth today is one balance sheet (nwBS): the start of the plan's first year, the accounts whenever there are any, every line kept on the Statements tab, nothing that arrives later. Home, Insights, the Overview's first year, Net Worth Today and Over Time, a snapshot taken from the plan, the reports and the Planner view all read it. The main run keeps each working year's typical portfolio, so the Overview's working years are filled in, and retired years split the typical portfolio so the kinds add up to it. Emergency months (emFig), the savings rate and the chance the money lasts (lastsPct) are each worked out once (version 424).155-mod-acnet, 249-mod-first, 243-mod-map, 090-run, 100-render
Cards, Sources and Family LoansEvery tool has the plain-words card the Easy view's tools had, in every view: the question, what you enter, what you get, the words to know and a picture drawn from six templates, every word of it measured inside its shape (version 421). On the spending card, a switch colours each future's bars by what pays the year (income, each pre-tax pool, Roth and HSA, taxable and cash), from the three futures run again with their detail; a future made larger lists the amounts year by year, a withdrawal plan (version 422). Family Loan sets up money lent by family as a real loan: the AFR for its length, the schedule against a bank, what each side gains, the below-market rules of section 7872 and the gift on the day, and forgiving some each year against the annual exclusion (version 423).236-mod-toolez1, 236-mod-toolez2, 106-mod-spsrc, 232-mod-famloan, 236-mod-toolez3
Lists and GuessesThe table of every spending rule draws up to five ticked rules year by year on the Scenarios tab's chart, typical spending or the typical portfolio (version 418). Each job and self-employment row on Your Income opens the taxes its pay may cost this year, a guess from the household's pay and profits that year: federal and state shared by the row's share, Social Security and Medicare its own (version 419). Your Income sorts by type or year and Your Accounts by balance, name, owner or type, a one-time reorder with Undo (version 420).131-mod-rulestl, 171-mod-inctax, 172-mod-sort
GoalsOutside Easy, a Goals tab under Your Data, moved from a small card on Insights: goals the plan checks (retire on time, money lasts, leave an amount) and savings goals with a target, a month to reach it, what is saved (typed in or read from accounts), a monthly amount and a rate; each with a progress bar, what remains and a badge (Ahead, On track, At risk, Behind, Past due, Reached); suggestions such as an emergency fund from the plan's own numbers; and a chart of how each savings goal grows (versions 398 to 401). Kept in the plan as goals.013-goals-data, 214-mod-goals, 215-mod-golsave, 216-mod-golsug, 217-mod-golproj

What Happens When You Change a Number

090-run.js

The runtime diagram's numbered flow, in more detail.

  1. A field's handler calls setFromUI, which writes cfg and brings every control into line (syncAll).
  2. changed() cancels anything running, marks the derived results stale, saves the plan, keeps an undo step, and schedules a run 160 ms later, so fast typing starts one run, not many.
  3. run() calls ensureSH: the yearly figures and any law changes are applied, and the market futures are drawn with seed 11.
  4. If a kept result or the precomputed sample has the plan's key, it is shown at once.
  5. Otherwise the engine runs. With 8,000 futures or more, the accounts model runs in Web Workers, one for each core (2 to 8), each the engine script itself made into a Blob, with its own range of the futures; the page merges what they hand back and works out the result, the same to the last digit (091-workers.js, version 372). The engine's settings reach them as the page last set them (001-engrec.js). Meanwhile the page works out 2,000 of the futures itself and shows that first estimate, faded (version 370). Without workers (a policy that blocks them, an error) it runs on the page in slices of about 35 ms, handed back through a MessageChannel, so the page stays responsive and a progress bar moves.
  6. The result comes back as percentiles and summaries; render updates the headline, charts and tables, and the result is stored.
  7. If another change arrived meanwhile, it runs again; otherwise the open tabs refresh their live parts. Insights, Cash Flow and Strategy run their own jobs on the same futures when opened.
  8. Downloads go through the host page's downloads capability; nothing else leaves the page.

How a Tool Is Built

the TT registry

Every calculator on the Tools tab is one object in a registry, TT, described rather than coded by hand. The framework builds the form, checks the numbers, saves them, and draws the result, chart and tables.

KeyWhat it holds
def, FThe default values, and the fields: key, label, prefix or unit, limits, whole numbers, a list of options, or a condition for showing.
calc(S)The arithmetic: takes the field values and returns the result, or a message about what is missing.
res, chart, table, extraThe words, the chart, the table and an optional last card, each drawn from the result.
acts, fillButtons such as "Use My Plan's Numbers", and what they do: read the plan in, or write a choice back into it.

Adding one is a new 232-mod-name.js, its id and name in 233-mod-tools2b.js, a place in one of the ten groups, a row in the Capabilities list, and a test.

Security and Privacy

249-mod-sec.js
  • No network: no external scripts, styles or fonts, and no calls out. Settings lists what the page has contacted, from the browser's own record.
  • Every piece of text written to the page is escaped; links are allowed only to http and https addresses.
  • Every value read from storage or a file is cleaned against the fields it may hold, with numbers clamped to their limits.
  • A content security policy in the page blocks plugins, base-URL changes and form posts; there is no eval. On the website it also allows scripts, styles, the manifest and workers only from the site itself.
  • The service worker (website only) keeps the site's own files for use offline and asks the network first for the page, so a new version arrives when there is a connection.
  • A read-only copy to share (version 387) is the Plan Summary saved as a page of its own, with no script, under its own content security policy, and only when the person asks for it.
  • Data in the browser is not encrypted unless the person makes an encrypted backup, and the code has not had an outside security audit.

The Tests

run.js · suites.js

  1. Before any suite, a regression check runs the default plan at full size with seed 11; the chance of running out must match the stored baseline to five decimal places.
  2. Suites run three at a time, slowest first. A suite fails on an error, a line marked x, or its own check.
  3. A suite that fails is run again alone at the end; if it passes, it is reported as flaky rather than failed. Each version's full run must pass before it is published.
  4. Accessibility has a checker of its own, wcag.js, run inside the page against WCAG 2.2 AA: text contrast, names for controls and images, charts as images with a text alternative, focus order, duplicate ids and target size (version 385; the axe-core package could not be installed here).
  5. Seven scripted journeys count each step and second a person takes for common tasks, against a budget, so a change that adds a step fails (version 388). usability-script.md is a five-task think-aloud session for real people.

A full run takes about 29 minutes on two cores (versions 296 and 297, from about 32). The runner starts one browser for the whole run and the suites connect to it (L.browser()); suites whose checks need no exact figure run 10,000 futures instead of 30,000 (L.fast(ctx), window.PT_SIM), and the sample's result at that size ships precomputed; steps whose redraw is quick wait for the page to be still (L.settle) instead of a fixed sleep; and a stale precomputed result is remade before the suites start. The CPU, not waiting, is now the limit.

The published sample changed in version 409, but the suites were written around the household before it, which has the debts, the child and the home the new one lacks. So the test pages (and only they) carry that household as a fixture with its own precomputed result, and the new sample has suites of its own on a page without it.

A file's own var lines run when the browser reaches them, but its functions can be called before: a saved plan is read (sanitizeExtras, from 060-inherit-data.js) before 106-mod-floors.js has run, so a constant set there is still undefined then. Version 414's first draft took a later floor's amount as NaN that way. A function used while a plan is read keeps its numbers inside it.

The app is one function, so a name declared at the top of two files is one name, and the later file wins without a word. Version 412's first draft took RL and rlKey from the Spanish reports this way and broke them; v412test.js now reads every app file and fails on a name declared in two.

Every test file names the source files it covers in a /* covers: */ line, and a quick mode runs only the suites whose files changed. The structure suite guards the layout itself: file naming, a view for every tab and a tab for every view, a build script that names no file, and, in a copy of the source, that adding a tab takes one view file and one registry entry.

Finding a Bug in a Plan

112-mod-techrep.js

The tests cover the plans they were written for; a plan someone brings may find what they missed. The Plan tab's Technical Report (MD) and Technical Data (CSV), in the Enterprise and Planner views, hold everything needed to find it, for a person or a program.

  1. One list of tables. trData(r) builds the report once: where it came from (version, build, view, futures, returns, stress test, yearly figures, data tables replaced), every field of the plan with its key, stored value and whether it is in use, each list with every field of every row, every other key the plan holds, every number of the result with its key, the year-by-year percentiles, the seven futures ranked both ways, and the other series the result holds.
  2. Two files from it. buildTechReport writes Markdown tables and the plan as JSON at the end; buildTechCSV writes one line per value, section,row,column,value, so the two always hold the same numbers.
  3. Checks first. trChecks(r) puts section 0 at the top: every figure a number, a row for each year, percentiles in order, nothing below zero, chances between 0 and 1, the run-out count adding up, the futures in their ranked order and running out when their place says they should, and the rows of the plan the engine leaves out. Each is "flag" (cannot be right), "look" (may be fine) or "ok".

The test for version 281 breaks a result on purpose, one fault at a time, and each check must flag it; and it runs every retirement path of version 273 through the page, where none may.

Settings You Can Drag

105-mod-spend.js · 104-mod-dash.js

The spending card's charts carry three sliders (versions 285 and 286): the Retire line, across the years, and the first-year and floor lines, up and down. Each is an SVG group with role slider and the chart's scale in data attributes, so the card's handlers turn a pointer or a key into a year or a dollar amount without the chart's code. While it moves, the line moves on all three charts; when it is let go (or 700 ms after the last key) it sets the plan's field through setFromUI, the way typing in Plan Settings does, so the plan runs again and Undo takes it back.

Conventions

HACKING.md
To addWhat it takes
A tabA view file in src/views and one entry in TABS.
A toolA TT spec, its id, name and group, a Capabilities row, a test. A field that needs explaining takes tip, the "?" beside its label (version 338).
The Easy view's lookRules in 167-easy.css, every one under html[data-mode="easy"] and every colour a theme token, so the other views keep their look and Easy follows the device's light or dark setting: the numbered tabs to fill in, the Plan tab's jump bar (ezJump) and a next-step bar at the foot of each data tab (EZ_NEXT) (versions 343 to 345).
A tool's cardA card in TOOL_EASY and a picture in TZ_PIC, from a file that loads after 236-mod-toolez.js (the objects do not exist before it): the question, what you enter, what you get and the words to know, each sentence at grade 12 or below, the picture made with the templates in 236-mod-toolez1.js and its words at 12px or more and inside their shapes. Every tool has one since version 421; checkez.js checks a file of cards before it goes in.
A lessonAn entry in lessons_src.py, which writes the JSON. Every figure in its text must be what its calculator shows at the defaults, and the tests check it.
A settingA default, a field entry and, if it is not a plain number, a line in the cleaner.
A spending ruleBoth engine models, the settings, and a test pinned to the rule's published example.
A data tableA file in src/data; a table people may replace also needs a year column and an entry on the Actual Data tab.
Words in the Easy viewShort, plain sentences, none past grade 12 by Flesch-Kincaid (tests measure every Easy tab). Code writes ez(full, plain); a view's HTML writes both halves, .ez-f and .ez-e; a note that only explains the model carries .ez-hide.
A card out of the Easy viewAn id on the card and a line in 139-modes.css under html[data-mode="easy"]. A setting Easy decides for the household (the market history, the guardrails, flat spending, no stress test) goes in EASY_RUN, which keeps the plan's own in cfg.easyWas for the other views.
A versionThe version and date in 000-prelude.js, a section in HACKING.md, and a test named after it.

Headings, buttons and field labels are in Title Case (AP style), options and hints in sentence case, and a test enforces it. All money colours come from shared tokens, and destructive buttons share one style.

The Formulas

checked against published sources

This part lists the formulas the planner uses, in the order a plan runs: the markets, a year in the portfolio, the spending rules and the results, then taxes, health costs, Social Security, retirement accounts, lifetimes, pensions, credits and the Tools calculators. The last part lists what the check found. Each one gives the formula as the code computes it, where it lives, and what it was checked against.

All amounts are in today's dollars and all returns are real (after inflation), unless a formula says otherwise. t is the year on the plan's timeline, P the portfolio at the start of a year, s the spending level the rule sets.

Matches the published rule or standard formula, checked on the web. Adapted follows a published rule with the differences stated. Planner's model is an assumption of this planner, with no outside rule to match. Differs does not yet match the law or the source; the card says how, and it is on the list to fix.

Formulas: Markets

010-engine.js · makeShocks · lnParams · histSeries

Random Numbers

Planner's modelmakeShocks · mulberry32
u = mulberry32(seed) → a uniform number in [0, 1)

A small seeded generator. The main run uses seed 11 and 30,000 futures (10,000 in the faster setting), so the same plan always gives the same answer, and every plan is compared on the same futures.

Normal Shocks

MatchesmakeShocks · goldZ
z = √(−2 ln u₁) × cos(2π u₂)

The Box–Muller transform, cosine branch. Box–Muller transform.

Correlated Shocks

Planner's modelmakeShocks · goldZ
z_stocks = e₁ z_bonds = 0.1 e₁ + √0.99 e₂ z_cash = (0.3 ÷ √0.99) e₂ + √(1 − 0.09 ÷ 0.99) e₃ z_gold = a e₂ + √(1 − a²) e₄, a = 0.1 ÷ √0.99

e₁…e₄ are independent normal shocks; the weights are the Cholesky factor of the correlations stocks–bonds 0.1, bonds–cash 0.3, stocks–cash 0, and gold–bonds 0.1. The factorisation is standard; the correlations are the planner's assumptions.

A Year's Return

MatcheslnParams · zFor
σ_L² = ln(1 + σ² ÷ (1 + μ)²) μ_L = ln(1 + μ) − σ_L² ÷ 2 r = exp(μ_L + σ_L z) − 1

Lognormal returns matched to the average μ and volatility σ entered, so the mean of r is exactly μ and its standard deviation exactly σ, and no year can lose more than everything. Defaults: stocks 7% and 18% (a median year of 5.52%), bonds 1.8% and 7%, cash 0.8% and 2.5%, gold 2.8% and 20%. Log-normal parameters from mean and variance.

Fat Tails

Planner's modelTURB_P · TURB_M · CALM_M
turbulent year (probability 0.15): σ × √3 ≈ 1.73 σ calm year (probability 0.85): σ × √(0.55 ÷ 0.85) ≈ 0.80 σ check: 0.15 × 3 + 0.85 × (0.55 ÷ 0.85) = 1

With fat tails on, about one year in seven is turbulent for every asset at once. The mixture keeps each asset's average and its overall variance, so only the shape changes: more calm years and more large moves.

Fees

Planner's modelFEE
r_net = r − (fund fee + advice fee)

Taken from every asset's real return every year, including historical and stress-test years. Default 0.1% and 0%.

Stock Returns From History

MatcheshistSeries
π = CPI₁ ÷ CPI₀ − 1 r_s = (1 + (P₁ − P₀ + D₀) ÷ P₀) ÷ (1 + π) − 1

Price change plus dividends, made real with the Fisher relation, from one January to the next. The data is Robert Shiller's S&P Composite, dividend, CPI and ten-year Treasury series since 1871. Fisher equation.

Bond Returns From History

MatchesbondYear
ä = (1 − (1 + y₁)^−q) ÷ y₁ B = y₀ × ä + (1 + y₁)^−q + y₀ − 1, q = 9 (long bonds: 19) r_b = (1 + B) ÷ (1 + π) − 1

A ten-year par bond bought at the January yield y₀, priced a year later as a nine-year bond at the new yield y₁, plus its coupon: the standard price of a bond from its yield. Bond valuation.

Historical Cycles

Planner's modelhistShocks
future i, year t → the historical year min(i + t − 1, last)

Each future starts in a different year since 1871 and follows the record in order. The file has no Treasury bill series, so cash keeps its lognormal median (about 0.77% at the defaults) in these futures.

Inflation

Planner's modelinflModel · dfl
price level L₀ = 1, L_t = L_(t−1) × (1 + π_t) dfl(a, b) = L(b) ÷ L(a)

π is steady (2.5% by default), high for a stretch then steady, or a replay of the CPI from a chosen year. Because returns are real, inflation matters only for amounts fixed in dollars (divided by dfl), for Guyton-Klinger's inflation rule, and for tax on nominal interest.

Stock Share Over Time

Planner's modelglidePath
saving years: G_t = now + (a − now) × t ÷ (d + 1) retirement year k: G = a + (b − a) × k ÷ n (then b)

A glide path or bond tent moves the stock share in straight lines. d is the number of saving years, a and b the start and end shares, n the years it takes.

Compound Return Shown in Hints

Matchescmpd
geometric ≈ μ − σ² ÷ 2

The usual approximation of the compound return, shown next to the settings only; the simulation itself uses the exact lognormal above. Geometric and arithmetic means.

Formulas: A Year in the Portfolio

simulate · makeTaxSim

Mid-Year Money

Planner's modelev
V′ = (V − ½w)(1 + r) − ½w = V(1 + r) − w(1 + r ÷ 2)

Half of a year's withdrawal w leaves at the start and half at the end, which stands in for money taken through the year. A saving is a negative w. Money that arrives in month m counts as (1 + (1 − e) r) ÷ (1 + r) of its amount at the start of the year, e = (m − 1) ÷ 12, so it earns only the part of the year it is invested.

Saving Years

Planner's modelsaveByYear
saving_t = saving × (1 + g)^(t − 1) V_t = (V_(t−1) + ½ saving_t)(1 + r_mix) + ½ saving_t

r_mix is the stock share times the stock return plus the rest times the bond return (and gold, if held). In the accounts model the saving is split among pre-tax, Roth and taxable by the shares set.

Rebalancing

Planner's modelsimulate
reset: cash = min(target, P); stocks = G_(t+1) × P; bonds = the rest refill: cash is topped up from whichever of stocks, bonds or gold rose

The cash target is a number of years of withdrawals or a share of the portfolio. Withdrawals come from cash, then bonds, then gold, then stocks.

Withdrawal Order

Planner's modelplan · takeFree
1 required distributions 2 pre-tax up to the fill line: F = max(0, line − ordinary income − RMDs) 3 taxable 4 the rest of pre-tax (penalty-free) 5 Roth contributions and HSA for medical costs, then Roth growth, then HSA 6 pre-tax money still under the 10% penalty

The default, tax-smart order. "Taxable first" skips step 2, and "proportional" takes from every pool in proportion. The fill line is the top of the 12% bracket plus the standard deduction unless another is chosen.

Gains Realised in a Taxable Account

Planner's modelmakeTaxSim
gains = w × (stocks ÷ account) × max(0, 1 − basis ÷ stocks) basis′ = basis × (1 − share sold) + dividends reinvested

Only stocks carry gains, and all gains are long-term. Dividends are 1.6% of the stocks a year, all qualified; bond and cash interest is taxed at its nominal yield.

What Must Be Raised

Planner's modelmakeTaxSim
need = s × mult_t + health + expense rows + care − income raise = need + Medicare surcharge + marketplace premium + tax

Tax depends on what is raised, so each year is solved by repeating it three times (five with a marketplace plan or a conversion) until it changes by less than 50 cents. A year that cannot be met ends the future: it has run out.

Formulas: Spending Rules

simulate · makeTaxSim · vpwRate · capeRate · rbgTable

Every rule sets a spending level s each year after the first. What is spent is s × mult_t, where mult_t carries the fall after a death and the change with age. Every rule except fixed stays between the floor and the ceiling.

The Rate the Rules Watch

Adaptedbase1 · base2 · XR
rate_t = max(s + XR_t − ref_t, 0) ÷ P base = (S₀ + XR_first) ÷ V before every income has started base = max(S₀ + XR_last − ref, 0) ÷ V after ref = the average yearly income once every stream pays (rounded down to $1,000)

The withdrawal rate is the share of the portfolio that spending needs once income is counted. XR is the Expenses tab's regular rows, certain long-term care and pre-Medicare health costs; one-time, education, charity from an IRA, marketplace and uncertain care rows are left out. V is that future's portfolio at retirement.

Money In or Out While Working

Planner's modelsaveAdjByYear · ADJ
sv_t = savings_t + inheritance_t + employer_t + ADJ_t (one portfolio) ADJ_t < 0, accounts: taken from the taxable pool, then the pretax pools, then Roth, in that order, by shares ADJ_t > 0, accounts: added with the savings, split as they are

Before retirement the planner adds the savings and pays no costs: pay is taken to cover them. A what-if's cost in a working year is therefore a change to that year's savings (cfg.saveAdj, today's dollars), and only what is more than the savings comes out of the portfolio; no tax or penalty on it is counted. Years in retirement are left out: a cost then is an expense row (version 416).

The Floor in Force

Planner's modelfloorByYear · FLY
floor₀ = min(the plan's floor, S₀) ceiling₀ = max(the plan's ceiling, S₀) floor_t = the amount of the last later floor whose year ≤ the year of t, else floor₀ ceiling_t = max(ceiling₀, floor_t) with later floors, each year before the rule: s = max(s, floor_t)

Up to three later floors, each from its year on (version 414). They are in the units of s, before mult_t, as the plan's floor is. Only the plan's floor is held at or under the starting spending; a later one may be above it, and then the spending rises to it in its years. Without later floors FLY is null and nothing changes.

Guyton-Klinger (Default)

Adaptedmodel 'gk'
1 after a losing year, if rate > base: no inflation raise (s ÷ (1 + π)) 2 if rate > 1.2 × base and more than 15 years are left: s × 0.9 3 else if rate < 0.8 × base: s × 1.1, only if the new rate stays ≤ 1.2 × base

Guyton and Klinger's withdrawal, capital preservation and prosperity rules with their published 20% guardrails and 10% steps. The planner's changes: the rate counts the expense rows and income as above, the planned fall with age is taken out before comparing, the floor and ceiling bind, and a raise is skipped if it would trigger a cut the next year. The original decision rules.

Portfolio Guardrails

Planner's modelmodel 'gkp'
if P ÷ P_retire < 0.8: s × 0.9 ÷ (1 + π) if P ÷ P_retire > 1.2: s × 1.1 ÷ (1 + π)

A simpler variant several online tools use: the test is the portfolio against its value at retirement, and a step replaces that year's inflation raise.

Risk-Based Guardrails

Adaptedmodel 'rbg' · rbgTable · rbgRate
R(n, q) = the highest constant rate that lasts n more years in a share q of futures if rate ≤ R(n, 100%): s = R(n, 80%) × P + ref if rate > R(n, 25%): s = R(n, 45%) × P + ref

Start at an 80% chance of success, raise back to 80% when it reaches 100%, cut back to 45% when it falls to 25%: the settings in Kitces's 2024 comparison. R is read from a table solved once on the plan's own futures (90 rates from 0.2% to 20%, a stock and bond mix, 2,000 futures), with n the years left. Why Guyton-Klinger guardrails are too risky.

Vanguard Dynamic Spending

Matchesmodel 'vg'
target = base × P d = min(max(target, d_prev × (1 − 2.5%)), d_prev × (1 + 5%))

A share of this year's portfolio, held within a 5% ceiling and a 2.5% floor on last year's real spending. Vanguard's dynamic spending strategy (AAII).

Endowment Smoothing

Adaptedmodel 'endow'
d = 0.7 × d_prev + 0.3 × base × P

The Yale rule's blend of last year's spending and a share of the portfolio. Yale weights them 80% and 20% with a 5.25% target on the value two years earlier; the planner uses 70% and 30% on this year's value, and both weights can be changed. The Yale spending rule.

Variable Spending Plan

Planner's modelmodel 'vz'
s = s_prev × (1 + z × (P ÷ P_prev − 1)), z = 50%

Spending follows part of the portfolio's change from one year to the next.

Share of the Portfolio

Matchesmodel 'pct'
s = base × P + ref

The constant-percentage method, at the plan's own starting rate.

CAPE-Based Rate

AdaptedcapeRate · capeNext
rate = a + b ÷ CAPE, a = 1.0%, b = 0.5 CAPE′ = CAPE × (1 + r_stocks − 1.6%) ÷ (1 + 2.6%)

Early Retirement Now's rule. ERN's current recommendation is a = 1.75% with his adjusted CAPE; the planner defaults to 1.0% with Shiller's plain CAPE (40.9 in August 2026), and both can be changed. The CAPE moves with each future's price change against real earnings growth of 2.6% a year (AQR, 1950–2023). ERN, part 54.

Variable Percentage Withdrawal

MatchesvpwRate
r = w × 5.0% + (1 − w) × 1.9%, n = 100 − age rate = 1 ÷ (1 + (1 − (1 + r)^−(n − 1)) ÷ r)

One over an annuity-due factor to age 100, at VPW's own growth rates for stocks and bonds (from the Credit Suisse yearbook), with w the stock share. At 65 with half in stocks the rate is 4.80%. Bogleheads VPW; VPW's return assumptions.

Hebeler Autopilot

Adaptedmodel 'heb' · singleLife
s = 0.5 × s_prev + 0.5 × P ÷ LE(age)

A blend of last year's real spending and an RMD-style amount, with LE from the IRS Single Life Table; both weights can be changed. This is the Autopilot as blended spending and RMD tools describe it; Hebeler's later Autopilot II uses 75% of last year and 25% of an annuity payment at 5%. cFIREsim methods; Autopilot II.

Kitces' Ratchet

Matchesmodel 'ratchet' · rtTrig · rtUp · rtGap
if P > (1 + 50%) × P₀ and at least 3 years since the last raise: s = s × (1 + 10%)

Fixed spending that is raised by 10% whenever the portfolio, in today's dollars, is half again what it was when spending started, at most every three years, and never cut. All three numbers can be changed (version 366). Kitces, the ratcheting safe withdrawal rate.

The RMD Method

Adaptedmodel 'rmd' · singleLife
s = P ÷ LE(age)

The Hebeler code with all the weight on the life-expectancy share, as a rule of its own: each year the portfolio over the IRS Single Life Table's expectancy at the person's age (version 366). IRS Publication 590-B.

A Skipped Raise After a Loss

AdaptedfixSkip · negY
after a year the portfolio lost money: s_real = s_real ÷ (1 + inflation)

An option on Fixed spending: the year after a loss the dollar amount is not raised for inflation, so its real value falls by that year's inflation; one of the flexible ways Morningstar's retirement income research tests. Vanguard's rule may now rise without a limit, which with a 5% fall is Clyatt's 95% rule (version 366). Morningstar: flexibility pays.

TIPS Amortization (ARVA)

Matchesmodel 'arva' · arvaFactor · arvaY · arvaRate
s = P ÷ Σk = 0 … n−1 (1 + yk)−k, with n the years left in the plan

Waring and Siegel's annually recalculated virtual annuity, the Bogleheads' amortization-based withdrawal: each January the portfolio buys, on paper, a ladder of TIPS paying a level real amount in every year left, and spends that amount. yk is the TIPS yield for k years from the yearly figures, or one real rate set by hand, when the sum makes s Excel's PMT(r, n, −P, 0, 1); the test checks it against that formula in both engines (version 389). Waring and Siegel, Financial Analysts Journal, 2015; Bogleheads: amortization based withdrawal.

Spending as You Age

MatchesageStep · AGE_SMILE
Δ = 0.00008 × age² − 0.0125 × age − 0.0066 × ln(spending) + 0.546 mult_t = Π (1 + Δ), Δ held within ±5%, from 60, age capped at 95

Blanchett's "retirement spending smile" regression for the yearly change in real spending, applied each year after retirement at the older person's age. The steady option instead falls 1% a year after 75. Blanchett, Journal of Financial Planning, 2014.

After a Death

Planner's modelspendMult
mult = 1 − drop (drop = 25% by default)

One person's share of spending stops when they die; a move can raise or lower spending from its year.

Formulas: Results

simulate · pct · LIFE.weighted · repPicks

Chance of Running Out

Planner's modeldep
chance = futures that run out ÷ all futures

Shown as "less than 0.1%" below 0.05%. The words run from very low (under 1%) to very high (15% or more).

Percentiles

Matchespct · quantiles
h = (n − 1) q; Q = x₍⌊h⌋₎ + (h − ⌊h⌋)(x₍⌈h⌉₎ − x₍⌊h⌋₎)

Linear interpolation between sorted values, the method of Excel's PERCENTILE.INC. The bands use the 5th, 25th, 50th, 75th and 95th. Percentile.

Running Out in Your Lifetimes

MatchesLIFE.weighted · LIFE.plan
S₀ = 1, S_k = S_(k−1) × (1 − q(age + k − 1)) alive_t = 1 − (1 − S_you)(1 − S_partner) chance = (1 ÷ n) × Σ over futures that run out of alive at that year

Each future that runs out counts by the chance that one of you is still alive that year, with q from the Social Security period life table (details with Social Security, below). The two lives are treated as independent.

The Chance of Lasting, Year by Year

Planner's modellastingCounts · lyDetails
run out by year k = (Σ_(t ≤ d) depBy_t + Σ_(j ≤ k) depBy_(d+j)) ÷ n (d: years before retirement) still with money_k = 1 − run out by year k riskiest five years = the window k … k + 4 with the largest Σ depBy half of them by = the first year the run-outs so far reach half of all that run out

depBy counts the futures by the year they first run out, so a future that still has money after the last of them keeps it to the end. The strip under the bars puts each year's share on the same scale as the headline.

Worst, Median and Best Futures

Planner's modelrepPicks
score = end balance, or (year it runs out − 10,000,000) pick = the future at rank round(q × (n − 1)), q = 5%, 10%, 15%, 50%, 85%, 90%, 95%

Futures that run out rank below every one that lasts, earliest first.

Left at the End

Planner's modelendA
after tax = pre-tax × (1 − heirs' rate) + Roth + taxable

The heirs' rate is 15% by default. The chance of leaving a goal is the share of futures ending at or above it.

Spending That Lasts

Planner's modellastingScale · tuneRun
one future: the largest k in [0.2, 1] with spending × k lasting (12 halvings) the plan: the most spending with chance ≤ target (1%), in $500 steps

Both search by halving; the floor and ceiling keep their ratio to spending.

Formulas: Federal Income Tax

010-engine.js · yearTax · makeTaxSim · TAXBOOK

The accounts model works out a tax year for every future and year. Tax-year figures are the 2026 law unless the yearly figures on the Actual Data tab give another year's. Amounts the law indexes stay constant in today's dollars; amounts the law fixes in dollars (the Social Security thresholds, the investment tax, the SALT cap, the senior deduction and others) shrink each year by inflation, as they do in real terms.

Filing Status

Planner's modelmakeTaxSim · SOLO · TBy
both alive → married filing jointly two years after a death, with a dependent at home → surviving spouse (joint rates) then, with a dependent that year → head of household otherwise → single two partners not married (cfg.married false): two single returns while both are alive, never a surviving spouse

The investment tax, senior deduction and Medicare surcharge use the single thresholds whenever the return is not joint.

Two Partners Not Married

Planner's modeltax2 · twoBook · niit2 · aca2 · inhRmd
each partner's return: own pay, pension, Social Security, pre-tax withdrawals and share of a conversion + ½ × (taxable account income, rental income, an inheritance); ½ of each itemized cost tax = yearTax(partner 1, single) + yearTax(partner 2, single or head of household for the one with more income) 3.8% tax = 3.8% × Σ min(½ × investment income, MAGI_p − $200,000)⁺; 0.9% on each one's pay above $200,000 IRMAA: each on Medicare pays the single step of their own MAGI two years before lines drawn on household income (the bracket to fill, a conversion's line) → the single book doubled after a death: inherited pre-tax money → life expectancy (survivor ≤ 10 years younger) or empty by year 10 home sale: $250,000 per owner while both are alive, then $250,000; marketplace: two households

No spousal or survivor's Social Security for a partner (versions 358 and 359). The equal split of shared income assumes a jointly owned taxable account. IRS: required minimum distributions.

Bracket Tax

MatchesordTax · TAXBOOK
tax(x) = Σ rate_k × (min(x, top_k) − top_(k−1)) for x > top_(k−1) rates 10, 12, 22, 24, 32, 35, 37% joint 24,800 100,800 211,400 403,550 512,450 768,700 single 12,400 50,400 105,700 201,775 256,225 640,600 head 17,700 67,450 105,700 201,750 256,200 640,600

The tops of each bracket for 2026. IRS 2026 inflation adjustments; head-of-household brackets.

Standard Deduction

MatchesDED
deduction = standard + extra65 × (people 65 or older by year end) standard: 32,200 joint · 16,100 single · 24,150 head extra65: 1,650 each, married · 2,050 unmarried

Tax Foundation, 2026 brackets and deductions.

Taxable Social Security

MatchesyearTax · CALC.ssTaxable
PI = other income + ½ × benefits if k1 < PI ≤ k2: taxable = min(½ (PI − k1), ½ × benefits) if PI > k2: taxable = min(0.85 × benefits, 0.85 (PI − k2) + min(½ × benefits, k3)) joint k1 = 32,000 k2 = 44,000 k3 = 6,000 · single 25,000 34,000 4,500

The IRS worksheet in two lines. The thresholds are fixed in law and never indexed. Tax-exempt interest, which the law adds to PI, is not modelled. How Social Security is taxed.

Income

Planner's modelmakeTaxSim · pass
ordinary = wages and pensions − ½ SE tax + interest + pre-tax withdrawals + Roth conversion + taxable Roth or HSA money qualified = stock dividends (1.6% of stocks) + realised gains AGI = ordinary + qualified + taxable Social Security

Interest is the nominal yield (real return plus inflation) on the taxable account's bonds and cash. Every dividend is qualified and every gain long-term.

Senior Deduction

MatchesyearTax · SENIOR_*
each person 65+: max(0, 6,000 − 6% × max(0, MAGI − 75,000 | 150,000 joint))

2025 through 2028, whether or not the return itemizes; each spouse's $6,000 shrinks by 6% of the excess, so a couple's is gone at $250,000. IRS, deduction for seniors; Schedule 1-A steps.

SALT Cap

MatchessaltAt · SALT_*
cap = max(10,000, cap_Y − 30% × max(0, AGI − line_Y)) cap_Y = 40,400 × 1.01^(Y − 2026) through 2029, then 10,000 line_Y = 505,000 × 1.01^(Y − 2026) through 2029, then none

Both the cap and the income line where it starts to shrink rise 1% a year through 2029 (fixed in version 242; the line had been held at $505,000). SALT changes in the 2025 law.

Itemized Deductions

AdaptedyearTax · ITEM_HAIRCUT
items = mortgage interest + min(state tax + property tax, cap) + max(0, charity − 0.5% × AGI) + max(0, medical − 7.5% × AGI) items −= 2/37 × min(items, max(0, AGI − start of 37% bracket)) deduction = the larger of items and (standard + non-itemizer charity)

The 0.5% charity floor and the 2/37 limit that holds itemized deductions to 35 cents on the dollar start in 2026. The planner's changes: mortgage interest is the loan balance times its rate on up to $750,000, AGI stands in for taxable income plus itemized deductions, and the 60%-of-AGI charity limit is not applied. Charitable changes in the 2025 law.

Charity Without Itemizing

MatchescharStdAt
deduction += min(charity, (1,000 | 2,000 joint) ÷ dfl)

From 2026, fixed in dollars, so worth a little less each year in today's dollars (fixed in version 242). Tax Foundation.

Tips, Overtime and Car Loan Interest

MatchesworkDed · WD_*
tips = max(0, min(25,000, tips) − cut) overtime = max(0, min(12,500 | 25,000 joint, overtime) − cut) cut = $100 × ⌊(MAGI − 150,000 | 300,000) ÷ 1,000⌋ car = max(0, min(10,000, interest) − $200 × ⌈(MAGI − 100,000 | 200,000) ÷ 1,000⌉)

2025 through 2028, as Schedule 1-A works them: whole thousands for tips and overtime, a thousand or part of one for car loan interest (fixed in version 242; tips and overtime had shrunk by 10% of the excess). IRS, tips and overtime; Schedule 1-A steps.

Qualified Business Income

AdaptedyearTax · QBI_*
f = 1 below T, 1 − (income − T) ÷ R in the range, 0 above QBI deduction = min(20% × QBI × f, 20% × taxable income) if QBI ≥ 1,000: at least min(400, taxable income) T = 403,500 joint | 201,750 · R = 150,000 | 75,000

The 2026 thresholds and the 2025 law's wider ranges and $400 minimum. The planner treats every business as a service business with no wage or property limit, so the deduction is gone above the range. Rev. Proc. 2025-32; QBI under the 2025 law.

Capital Gains and Qualified Dividends

MatchesyearTax
top = ordinary taxable + gains tax = ordinary tax + 15% × max(0, min(top, L15) − max(ordinary taxable, L0)) + 20% × max(0, top − max(ordinary taxable, L15)) L0 = 98,900 | 49,450 | 66,200 L15 = 613,700 | 545,500 | 579,600

Gains stack on top of ordinary income. A deduction larger than ordinary income spills onto the gains. 2026 capital gains thresholds.

Child Tax Credit

MatcheschildCredit
credit = 2,200 × children under 17 + 500 × other dependents credit −= 50 × ⌈(AGI − 400,000 | 200,000) ÷ 1,000⌉ refund = min(credit left after tax, 1,700 × children, 15% × (earnings − 2,500))

2026 amounts. The alternative refund for three or more children is not modelled. Tax Foundation, 2026.

No State Chosen

Planner's modelyearTax · stRateOf
state tax = flat rate × federal taxable income (before the QBI deduction)

Default 0%. One-time sales use the state's top rate, or this rate.

A Year's Tax and the Next $1,000

Planner's modelmakeTaxSim · mrg · brkRate
tax = federal + state + payroll + penalties + investment tax marginal = (tax with $1,000 more ordinary income − tax) ÷ 1,000

The marginal rate on the tax dashboard includes every knock-on effect: more Social Security taxed, a lost senior deduction, the investment tax.

Formulas: Other Taxes

payrollTax · stateTaxOf · NIIT

Net Investment Income Tax

AdaptedmakeTaxSim · NIIT_*
NIIT = 3.8% × max(0, min(investment income, MAGI − 250,000 | 200,000))

The thresholds are fixed in law. The planner counts the taxable account's interest, dividends and gains; the law also counts rental income and the taxable part of a home sale, which the planner leaves out. IRS Topic 559.

Payroll and Self-Employment Tax

MatchespayrollTax
employee: 6.2% × min(wages, 184,500) + 1.45% × wages self-employed (net × 92.35% ≥ 400): 12.4% × min(net × 92.35%, 184,500 − wages) + 2.9% × net × 92.35% extra: 0.9% × max(0, earnings − 250,000 | 200,000)

Half the self-employment tax is deducted from income. The 2026 wage base is $184,500. SSA 2026 figures.

State Income Tax

AdaptedstateTaxOf · STATES · states.csv
base = AGI (or federal taxable income, where the state starts there) − the state's gains exclusion − Social Security, if exempt (or below the state's income limit) − retirement income exclusion × its phase-out share − the state's standard deduction tax = brackets(base) + any separate gains tax

One row per state in states.csv, with its brackets, Social Security rule, retirement exclusion, age and limits, and gains rule (Arizona's 25% exclusion, Hawaii's 7.25% cap, Montana's gains rates, Washington's gains tax, and others). Brackets stay constant in today's dollars; local income taxes are not modelled.

Early Withdrawal Penalties

AdaptedmakeTaxSim · earlyPre · seppLevel
pre-tax before the penalty age: 10% × amount not covered by an exception Roth growth before the penalty age: 10% HSA money not for medical costs before 65: 20%

Exceptions: the rule of 55 (50 for public safety), 457(b) plans and equal payments (SEPP). The penalty age is 60 by default, standing in for 59½ with whole years. IRS, exceptions to the 10% tax.

Selling a Home

AdaptedhomeSale
gain = price − selling costs − basis tax = max(0, gain − 250,000 | 500,000) × (15% + state rate)

The exclusion is fixed in law. A surviving spouse keeps $500,000 for two years after the death. The planner uses 15% throughout; a very large gain could reach 20% and the 3.8% tax.

Rental Property

AdaptedrentalModel
taxable = rent − costs − interest − (price × (1 − land share)) ÷ 27.5 at sale: 25% × depreciation taken + 15% × the rest of the gain (+ state)

Residential property is depreciated over 27.5 years and depreciation is taxed at up to 25% on sale. Losses carry forward; the $25,000 allowance for active owners is not modelled. IRS Publication 527.

Heirs' Tax

Planner's modelpreRate · endA
left after tax = pre-tax × (1 − 15%) + Roth + taxable

A flat rate for the income tax heirs pay on inherited pre-tax money.

Formulas: Health Costs

expenseModel · irmaaExtra · acaNet · 070-medicare.js

Medicare Premiums in the Plan

Planner's modelexpenseModel · HEALTH_KIND
premium_t = amount × (1 + row rate + health growth)^(years)

An Expenses row per person from 65, $5,300 a year by default. Before Medicare, the pre-Medicare health amount on the Expenses tab is paid each year until the younger person reaches 65.

Medicare Surcharge (IRMAA)

MatchesirmaaExtra · irmaaTier
tier = number of lines MAGI(two years earlier) is above extra = per person a year: 0 · 1,148.40 · 2,884.80 · 4,620 · 6,355.20 · 6,936 joint lines 218,000 274,000 342,000 410,000 750,000 single lines 109,000 137,000 171,000 205,000 500,000

The Part B and Part D amounts for 2026 ($81.20 to $487.00 and $14.50 to $91.00 a month), charged for each person on Medicare, on income from two years before. CMS 2026 premiums; Kiplinger IRMAA tiers.

Medicare Calculator

MatchesMEDICARE.premiums · outOfPocket
monthly = Part A + Part B (202.90) + surcharge + late penalties + drug plan + Medigap or Advantage plan Part B costs = min(use, 283) + 20% × max(0, use − 283) hospital = stays × 1,736 drugs = min(2,100, min(cost, 615) + 25% × max(0, cost − 615))

2026 figures: Part A $311 or $565 a month without 40 quarters, Part B $202.90 with a $283 deductible, $1,736 a hospital stay, and the drug plan's $615 deductible and $2,100 cap. Late penalties are 10% of Part B for each full year late and 1% of the base drug premium for each month. CMS 2026 premiums and deductibles.

Marketplace Premium Credit

MatchesacaNet · acaPct · ACA_BANDS
r = MAGI ÷ poverty line (last year's guideline for the household size) you pay = min(benchmark premium, applicable % (r) × MAGI) r < 1.38 (expansion state) → Medicaid, $0; r < 1 (non-expansion) → no credit r > 4 → no credit (the cliff) 2026 %: 2.10 · 3.14–4.19 · 4.19–6.60 · 6.60–8.44 · 8.44–9.96 · 9.96

The percentage rises in straight lines within each band. MAGI here is AGI plus the Social Security that is not taxed. The enhanced credits of 2021–2025 expired, so the 400% cliff is back. The planner assumes the plan bought is the benchmark and leaves out cost-sharing reductions. 2026 and 2027 applicable percentages; status of the enhanced credits.

Marketplace Percentages for 2027

Differsrefdata.csv · aca.b*
2027 %: 2.15 · 3.23–4.30 · 4.30–6.78 · 6.78–8.66 · 8.66–10.22 · 10.22

Rev. Proc. 2026-26 raises every band for 2027. The planner has only the 2026 table and carries it forward, so a 2027 marketplace year shows the credit a little too high. Adding these alone would make 2027 the year in use while every other figure is 2026's, so they go in with the rest of 2027's yearly figures when the IRS and SSA publish them (October and November). Rev. Proc. 2026-26.

Keeping Income Under a Line

Planner's modelACAL · FILLT
conversion ≤ line × poverty line − 1,000 − MAGI before it

In marketplace years, Roth conversions (and a "fill to the marketplace line" order) stop $1,000 under the chosen line: 150%, 200%, 250% or the 400% cliff.

Long-Term Care

Planner's modelexpenseModel · ltcU
cost_t = max(0, amount × (1 + g)^(Y − now) − insurance benefit) paid in every future if the chance is 100%, otherwise in a share of futures equal to the chance

Default from 82 for three years. Certain care counts in the guardrails; uncertain care does not.

Formulas: Social Security

010-engine.js · ssFactor · streamsByYear · 095-ssearn.js · 020-calc.js

Benefits are entered as the monthly amount at full retirement age (the PIA) in today's dollars, and the plan assumes cost-of-living raises equal inflation, so a benefit is constant in today's dollars.

Full Retirement Age

MatchesFRA_BY_BIRTH · fraMonths
born 1937 or before → 65 1943–1954 → 66 1938–1942 → 65 + 2 months a year 1955–1959 → 66 + 2 months a year 1960 or later → 67

The Social Security Administration's table. People born on January 1 use the year before, which the planner does not model. SSA, retirement age and benefit reduction.

Claiming Early or Late

MatchesssFac · ssFactor
m = months claimed after full retirement age (negative if before) m ≥ 0: factor = 1 + m × 2/3% (8% a year, to 70) m < 0: factor = 1 − 5/9% × min(−m, 36) − 5/12% × max(0, −m − 36) benefit = PIA × 12 × factor

Claiming at 62 with full retirement at 67 gives 70%, at 70 gives 124%. The planner claims at whole ages and pays the whole first calendar year. SSA Handbook §724.

Spousal Benefit

MatchesstreamsByYear · spouseFactor
top-up = max(0, ½ × PIA_partner − PIA_own) × 12 × factor factor = 1 − 25/36% × min(m, 36) − 5/12% × max(0, m − 36), m = months before FRA paid from when both have claimed, while both are alive

No delayed credits on the top-up; at 62 with full retirement at 67 it is 65%. Spousal benefits.

Survivor Benefit

MatchessurvBase · survFactor
full = the deceased's benefit (with delayed credits), or their PIA if they claimed early limit = max(the deceased's reduced benefit, 82.5% × PIA) if they claimed early at age a: full × (1 − 28.5% × months before survivor FRA ÷ months from 60 to it) paid = max(own benefit, min(that, limit))

71.5% at 60, rising evenly to 100% at the survivor's full retirement age (the table two birth years later). The survivor takes the reduced amount early only when it beats their own benefit, otherwise the full amount from their full retirement age. The widow(er)'s limit; SSA Handbook §724.

The Earnings Test

AdaptedetHold · RET_LO · RET_HI
before the FRA year: withheld = min(benefit, ½ × max(0, earnings − 24,480)) in the FRA year: withheld = min(benefit × months ÷ 12, ⅓ × max(0, earnings before FRA − 65,160)) at FRA: the reduction is recomputed as if the withheld months had not been claimed

$1 for every $2 and $1 for every $3 over the 2026 limits ($2,040 and $5,430 a month). The spousal part is not recomputed at full retirement age. 2026 determinations; SSA, retirement earnings test.

PIA From Earnings

MatchesSSEARN.compute · bends
indexed_y = min(earnings_y, max taxable_y) × AWI(age 60) ÷ AWI(y) (years before 60) AIME = ⌊ top 35 indexed years ÷ 420 ⌋ PIA = 90% × min(AIME, b1) + 32% × (min(AIME, b2) − b1)⁺ + 15% × (AIME − b2)⁺ b1, b2 = 180 and 1,085 × AWI(age 60) ÷ 9,779.44 (2026: 1,286 and 7,749) then each cost-of-living raise from 62, rounded down to the dime

The formula for people turning 62 in 2026 and the wage index through 2024 ($69,846.57). 2026 determinations.

A Statement's Benefit, Grown by More Work

AdaptedSSE.grow · ssGrowInfo
AIME₀ = the statement's PIA worked back through b1 and b2 (newest bend points) sum = 420 × AIME₀ each later year e = covered pay (jobs inside Social Security, 92.35% of self-employment), ≤ the wage cap: sum += e while fewer than 35 years have earnings sum += max(0, e − sum₀ ÷ 35) otherwise (in place of a year at the record's average) PIA = the formula above on ⌊sum ÷ 420⌋, never below the statement's

The statement's benefit stands for the record; the years the plan still works are added in today's wages, as Social Security's own statement shows them. Without the record itself the lowest year is not known, so a year that replaces one is taken against the average, which understates the gain a little. SSA: retirement benefit calculation examples.

Trust Fund Shortfall

AdaptedssCutOf · ssPay
benefit × pay share (every year) benefit × pay share from a year (the "from a year" choice)

The 2026 Trustees Report projects the retirement fund to run dry in late 2032, after which income pays 78% of benefits (83% for the combined funds in 2034). The planner pays 100% by default; the setting applies a cut to every year or from a chosen year. 2026 Trustees Report.

Claiming Tool

MatchesCALC.ssCouple · ssFactor · spouseFactor · survFactor
total = Σ benefit × discount over the years both might live break-even = the first age the later claim's total catches up

The calculator takes the year you were born and uses that full retirement age and its factors, as the plan does: a 1959 birth gets 70.83% at 62 (fixed in version 242; it had assumed 67 for everyone).

Formulas: Retirement Accounts

RMDV · seppLevel · run529 · convOf

Required Minimum Distributions

MatchesRMDV · RMD_DIV · rmdAgeOf
RMD = balance at the start of the year ÷ divisor(age) start: 72 if born 1950 or before · 73 if born 1951–1959 · 75 if born 1960 or later divisor: 26.5 at 73 · 24.6 at 75 · 20.2 at 80 · 16.0 at 85 · 12.2 at 90 · 6.4 at 100

The IRS Uniform Lifetime Table and the SECURE 2.0 ages. A survivor takes over the other's pre-tax money on their own age. The joint table for a much younger spouse is not modelled. RMDs under SECURE 2.0.

Gifts From an IRA

AdaptedQCD_LIMIT · QCD_AGE
QCD = min(gift, 111,000) from 71; RMD −= QCD

Qualified charitable distributions count toward the RMD and are not income. The law allows them from 70½; the planner starts at 71. 2026 QCD limit.

Equal Payments Before 59½ (SEPP)

MatchesseppLevel · SEPP_ULT
n = Uniform Lifetime divisor at the starting age (48.5 at 50) payment = balance × i ÷ (1 − (1 + i)^−n), i = 5% by default paid every year until the later of five years and the penalty age

The amortization method under IRS Notice 2022-6, which allows a rate up to the greater of 5% and 120% of the federal mid-term rate. $1,000,000 at 50 pays $55,177 a year. Substantially equal periodic payments.

Roth Conversions

Planner's modelconvOf · CVT
fill: convert = max(0, bracket top + deduction − other ordinary income − taxable Social Security) amount: convert = the amount set both: no more than the pre-tax money left, and under the marketplace line in those years

From the retirement year to the year before required distributions, unless set. Brackets to 10, 12, 22, 24 or 32%, or the first two Medicare surcharge lines.

529 Plan to Roth

Matchesrun529 · R529_CAP
from the later of: 15 years after opening, the last school year each year = min(balance − the last 5 years' contributions and their growth, IRA limit (7,500 in 2026, +1,100 from 50) − other IRA contributions, a dependent's pay from work, 35,000 − already moved) money put in during the 5 years before the plan: a fifth becomes movable each year

The SECURE 2.0 rollover. The $35,000 lifetime limit is fixed in dollars. An adult beneficiary must still be working; a dependent's moves are grown to their 65 for the row (version 249). 529 to Roth rules.

Portfolio Analysis

MatchespaSplit · paFromCurrent · paByAcct
for each account: stocks = balance × st, bonds = balance × bd, cash and gold likewise international stocks = stocks × ist, U.S. stocks = stocks × (1 − ist); bonds likewise with ibd no ist (or ibd) given: that account's stocks (or bonds) are unclassified; gold counts as alternatives share of a class = its dollars ÷ all accounts to reach the target = target share × all accounts − the class's dollars (plus buy, minus sell) where a class sits = its dollars in one tax kind ÷ its dollars in all

The split is for the card only: the plan still runs on each account's stocks, bonds, gold and cash, so changing it runs nothing (versions 287 to 289).

Contributions to Each Account

Planner's modelctbByYear
share of pay: money a year = a.cv × the pay from the jobs of that person, that year amount: money a year = a.cv, in today's dollars in the saving years only: before the plan's retirement, the person's own retirement and their passing goes where the account does: pre-tax by owner, Roth (as contributions), HSA, taxable

On top of the plan's savings a year and an employer's contributions. The row's hint warns above the 2026 limits: $24,500 for a 401(k), 403(b) or 457(b) ($32,500 from 50), $7,500 for IRAs ($8,600 from 50), $4,400 or $8,750 for an HSA (version 301).

one person's 401(k), 403(b) and TSP rows added up in the row's first year; a governmental 457(b) on its own limit at that age = $24,500 + $8,000 from 50, or + $11,250 from 60 to 63 over it, but within $72,000: the rest may be after-tax money converted to Roth (the mega backdoor) pre-tax rows above $24,500 at 50 or over with job pay over $150,000: the catch-up must be Roth a traditional IRA with self-employment profit: + SEP, about 20% of the profit, at most $72,000 employer match = share × min(pay, $360,000) (401(a)(17), lim.comp)

The rows add up per person (version 322); the match counts pay only up to the limit (version 325).

Long-Term Care Stays

Planner's modelltcSpans · ltcLast
from an age: first year = birth year + start age; years = dur; needed at the chance set last years: last year = min(the year of passing, the plan's last year); first = last − dur + 1; certain both of you: one stay each (the second at amtW for durW years); uncertain stays are drawn apart cost in year Y = amount × (1 + rise + health care inflation)^(Y − today) − insurance benefit when the last stay starts (last years only): living spending × 0 from then; the first home sale moves to that year

The cost estimates are the 2025 national medians of CareScout's Cost of Care Survey (March 2026): assisted living $74,400, a nursing home $114,975 semi-private and $129,575 private, care at home 44 hours a week $80,080, adult day care $24,700 (versions 302 and 303).

The Advisory Fee

Planner's modelaumRateOn · aumFee · aumShares · aumCompare
tiered: rate on M = Σ (the part of M in level i) × rate_i ÷ M flat: the rate of the level M falls in billed k times a year (4, 12 or 1): fee = M × (1 − (1 − rate ÷ k)^k) M = the managed share of each tax group at the start × that group's value at the year's end taken from each group in that proportion, before the mix is rebalanced; not deducted from taxable income opportunity cost in a future = its portfolio without the fee − with it, on the same markets

The accounts marked managed pay it; the schedule defaults to 1% up to $1 million and 0.75% above, billed quarterly. The Expenses tab's card runs the drawn futures again with no account managed, and the whole plan once more for the chance with and without (versions 309 and 310). Its typical amount each year, the median across the futures (aumYears), shows on Expenses by Year, in the cash flow table and on the one-year flow, paid from the managed accounts (version 334); Accounts shows the managed and self-managed shares in a bar under the overall mix (version 336).

The Retirement Analysis Tab

Planner's modelraVars · raCfg · raRun · raSolve
proposed plan = a copy of the plan with each change set (goals, pay, savings, strategies) run as the plan is run, in slices, on the same market futures: a difference comes from the change, not the draw probability of success = 1 − the chance the money runs out; median ending portfolio, today's or the end year's dollars confidence: the probability against the target range (70% to 90% unless set) savings: the extra saved each year until retirement that reaches the target, by halving on 2,000 futures

Nothing changes until Apply to the Plan, which Undo takes back; each proposed plan's result is kept by its key, so a slider moved back is instant (versions 304 to 308, and the advisory fee among the strategies in 311).

Formulas: Lifetimes

090-life.js · lifetable.csv

The Chance of Dying in a Year

MatchesLIFE.q · lifetable.csv
q(age) = min(1, q_SSA(sex, age) × longevity × group factor); q(120) = 1 longevity: average 1 · long 0.7 · short 1.4

The Social Security Administration's 2023 period life table from the 2026 Trustees Report. At the average setting the planner's life expectancy at 65 is 18.12 years for men and 20.66 for women, the table's own figures. SSA actuarial life table.

Survival and Life Expectancy

MatchesLIFE.alive · expect · ageAt
S₀ = 1, S_k = S_(k−1) × (1 − q(age + k − 1)) expectancy = Σ S_k + ½ "1 in 10 live past" = the first age where S < 10%

The standard life-table calculation, counting half of the year of death.

Two Lives

Planner's modelLIFE.plan · alone
either alive = 1 − (1 − S_you)(1 − S_partner) both alive = S_you × S_partner

The two lives are treated as independent. A death age set on the People tab replaces the table for that person. The simulation itself assumes each person lives to the plan's end or that set age; the table is used to weight results.

Group Adjustment

Planner's modelLIFE.raceK · RACE_E
factor for ages 65–74, 75–84, 85+ chosen so that expectancy(age) = table expectancy × group's ÷ everyone's (NCHS 2024)

Each factor is found by halving between 0.1 and 6.

Formulas: Pensions, Annuities and Bonds

incValue · survShare · pnCalc · govCalc · tipsLadder

Pension Raises

MatchesincValue
real: a (raises equal inflation) fixed: a ÷ dfl(now, Y) (no raises) cola: a × (1 + g)^(Y − start) ÷ dfl(start, Y) cap: raises of min(inflation, cap) FERS: from 62: CPI ≤ 2% → CPI · 2–3% → 2% · above 3% → CPI − 1

The FERS "diet" COLA and its age-62 rule for regular retirees. The FERS COLA.

Joint and Survivor

Planner's modelsurvShare
after the owner's death: amount × 100 · 75 · 55 · 50 · 25 · 0%

The survivor share chosen on the Income tab, paid while the survivor lives.

Lump Sum or Monthly

MatchespnCalc · pnSpia
pay_k = 12 × monthly × (1 + cola)^k × (S1_k + share × S2_k × (1 − S1_k)) PV(r) = Σ pay_k ÷ (1 + r)^k break-even rate: PV(r) = lump sum (by halving between −5% and 30%)

The expected present value of the payments over the table's survival chances, compared with the lump sum; also the cost of the same income from an immediate annuity.

Federal and Military Pensions

MatchesgovCalc · govMRA
FERS: high-3 × years × (1.1% at 62 with 20 years, else 1%) MRA+10: −5% for each year under 62; survivor 50% costs 10%, 25% costs 5% supplement ≈ Social Security at 62 × years ÷ 40, until 62 CSRS: 1.5% × first 5 years + 1.75% × next 5 + 2% × the rest (max 80%) survivor costs 2.5% of the first $3,600 + 10% of the rest Military: 2.5% (High-3) or 2.0% (BRS) × years × base; SBP pays 55%, costs 6.5%

OPM, FERS computation; OPM, CSRS computation; Military retirement.

Immediate Annuities

Planner's modelannuity.json · annBuys
income = premium × payout rate(age, men | women | couple)

Payout rates per $100,000 at 60, 65, 70, 75 and 80, in straight lines between (Annuity.org and Cannex, April 2026). A fixed payment loses value with inflation; a cost-of-living payment keeps it.

TIPS Ladder

MatchestipsLadder · tipsYield · TIPS_CURVE
cost = Σ payment ÷ (1 + y_k)^k, k = years from the purchase y_k: 2.50% to 5 years, then in straight lines to 3.02% at 30 years

The present value of each year's real payment at the real yield for its maturity (the curve of 21 September 2026). Present value.

Loan Payments

MatchesloanLevel · DEBTS.level · rfPay
payment = balance × i ÷ (1 − (1 + i)^−n) (balance ÷ n when i = 0) monthly: interest = balance × i; balance −= payment − interest

The standard level payment, with i the monthly rate and n the months. The plan adds up each year's twelve payments; student loans can be forgiven after a set number of years. Mortgage calculator.

Formulas: Credits

130-credits.js · CRED

The credits tools on the Tools tab, for tax year 2026 unless noted. They work out the credits from income, filing status and family; the plan itself models only the child tax credit (above).

Earned Income Tax Credit

MatchesCRED.eitc
credit = max(0, min(rate × earnings, max) − out rate × max(0, max(earnings, AGI) − start)) children 0 1 2 3+ rate 7.65% 34% 40% 45% max 664 4,427 7,316 8,231 out rate 7.65% 15.98% 21.06% 21.06% start 10,860 23,890 23,890 23,890 (joint +7,280) none if investment income > 12,200

The IRS table works in $50 steps; the formula is continuous, so it can differ from the table by a few dollars. The credits end at $19,540, $51,593, $58,629 and $62,974 single. Tax Foundation, 2026; Rev. Proc. 2025-32.

Saver's Credit

MatchesCRED.saver
rate = 50% · 20% · 10% · 0 by AGI joint 48,500 · 52,500 · 80,500 head 36,375 · 39,375 · 60,375 single 24,250 · 26,250 · 40,250 credit = min(rate × min(2,000, saved) per person, tax)

Withdrawals in the look-back period, which the law subtracts, are not modelled. IRS 2026 limits; Saver's Credit guide.

Saver's Match (2027)

MatchesCRED.match
rate = 50% below lo, falling in a straight line to 0 at hi lo–hi: joint 41,000–71,000 · head 30,750–53,250 · single 20,500–35,500 match = rate × min(2,000, saved) per person, paid into the account

SECURE 2.0's replacement for the Saver's Credit, from 2027, before any inflation update. Saver's Match.

College Credits

MatchesCRED.college
f = 1 up to 80,000 | 160,000 joint, falling to 0 at 90,000 | 180,000 American Opportunity = students × (min(cost, 2,000) + 25% × min(cost − 2,000, 2,000)) × f 40% of it refundable Lifetime Learning = 20% × min(10,000, cost) × f

American Opportunity credit.

Adoption Credit

MatchesCRED.adopt
f = 1 − (MAGI − 265,080) ÷ 40,000, between 0 and 1 per child = min(costs, 17,670 − already claimed) × f (special needs: the full amount) refundable = min(per child, 5,120); the rest carries forward five years

Rev. Proc. 2025-32.

Credits Together

AdaptedCRED.all
tax = brackets(AGI − standard deduction − tips and overtime) order: Saver's Credit, child tax credit, then the refundable parts and the EITC

A quick tax for the credits tool: ordinary rates only, without capital-gains rates or the senior deduction.

Formulas: The Tools

232-mod-*.js · 020-calc.js · 100-insure.js · 110-debt.js · 120-fafsa.js

The calculators on the Tools tab, grouped. Most are standard finance; the rules of thumb are named as such.

Growth, Present Value and Rates

Matches230-mod-tools · goalPlan · viIrr
yearly factor = (1 + r ÷ m)^m, or e^r when continuous saving needed = (goal − today × G) ÷ H (G, H: what $1 now and $1 a period grow to) need at retirement = Σ_k withdrawal ÷ (1 + r)^k IRR: Σ flow × (1 + r)^(years to the end) = value (by halving)

Rates that have no closed form are found by halving between limits. Time value of money.

Savings Goals

Matches215-mod-golsave · golProject · golNeed · golSaveEval
each month: balance = balance × (1 + r ÷ 12) + monthly saving (n: the first month it reaches the target) monthly saving needed by month T = (target − saved × (1 + i)^T) × i ÷ ((1 + i)^T − 1) (i = r ÷ 12; ÷ T when r = 0) months to spare = T − n: 3 or more Ahead · 0 to 2 On track · −1 to −6 At risk · less Behind progress = saved ÷ target (the bar stops at 100%)

Saved is what the user types in, or the balance of the accounts the goal draws on. The chart's second line is the same balance in today's dollars, at the plan's inflation; the dashed line is what was put in. Compound interest (Investor.gov).

Paying Off Debts

MatchesDEBTS.run · minPay · compare
each month: balance += balance × rate ÷ 12, then the scheduled payment extra and freed payments go to: highest rate first (avalanche) or smallest balance first (snowball) card minimum = max(floor, 1% × balance + interest) ($40 floor) transfer: balance × (1 + fee), promo rate for its months, then the regular rate

The standard month-by-month schedule; the card minimum follows the common "1% plus interest" rule.

Student Loans

MatchesslRun · slRapM · slFpl
standard (from July 2026): 10 / 15 / 20 / 25 years for under 25k / 50k / 100k / more IBR: min(standard, 10% | 15% × (AGI − 150% × poverty line) ÷ 12); forgiven at 20 | 25 years RAP: 1% to 10% of AGI (one point per $10,000 over $10,000), ÷ 12, − $50 per dependent, at least $10 unpaid interest waived; principal falls at least $50; forgiven after 360 payments PSLF: forgiven tax-free after 120 payments

IBR; RAP; tiered standard plan.

Home, Car and Refinance

MatcheshafCalc · carCalc · rfCalc
housing = loan payment + property tax + insurance + HOA front ratio = housing ÷ monthly pay ≤ 28%; back ratio = (housing + debts) ÷ pay ≤ 36% car: 20% down, 4 years or less, car costs ≤ 10% of pay; underwater while loan > value refinance break-even = ⌈ closing costs ÷ monthly saving ⌉ months

The usual lender and personal-finance guidelines (28/36 and 20/4/10), stated as guidelines.

Business Value and Sale

MatchesebCalc · bzYear · 232-mod-bizsale
EBITDA = net income + interest + taxes + depreciation and amortization seller's discretionary earnings = adjusted EBITDA + one owner's market salary value = earnings × multiple; equity = value − debt + cash C corporation asset sale: 21% (+ state) inside, then gains tax on what is paid out

An installment sale taxes each year's principal in the ratio of gain to price, and its interest as income.

A Buyer's Letter to Cash

Planner's modelloiCalc
letter = EBITDA × multiple; price = (EBITDA − add-backs struck) × multiple after diligence − debt cash at closing = price × (1 − rollover − escrow − broker) − legal gain now = price × (1 − rollover) − fees − basis × (1 − rollover), a capital gain on the other income (bzYear) later = escrow when released + rollover × its growth at the buyer's sale

Put This in My Plan adds the part sold as a business sale in the year of closing and the rollover as a second sale (versions 332 and 333).

Qualified Small Business Stock

MatchesqsPct · qsFed · bizSale
share excluded: before July 5, 2025, 50%, 75% or 100% by the issue date, after more than five years after July 4, 2025: 50% at three years, 75% at four, 100% at five limit per company = max($10 million less earlier exclusions ($15 million for the new shares), 10 × basis) within it: excluded = limit's gain × share; the rest of it is 28% rate gain, taxed at the ordinary rates up to 28% above it: taxed as usual, stacked on the other income and the 28% part; the 3.8% on what is taxed state: follows; half (Hawaii); all of it (Alabama, California, Mississippi, Pennsylvania; Illinois and Oregon from 2026)

The tool checks the five conditions; the plan's business sale uses the same limit, the $10 million fixed in dollars and so lower in today's the later the sale (versions 323 and 324). 26 U.S.C. 1202.

Equity Pay

MatcheseqAmt · eqPayroll · eqRange
RSU and NSO income = shares × (price − strike); ISO spread counts for AMT only AMT exemption = 140,200 | 90,100 − 50% × (AMTI − 1,000,000 | 500,000)⁺ AMT = max(0, 26% × first 244,500 + 28% above − regular tax) ESPP: buy at (1 − discount) × lower of the two prices; $25,000 of stock a year withholding on bonus pay: 22%, 37% above $1,000,000

The 2026 AMT figures with the 2025 law's faster 50% phase-out. The price range uses lognormal percentiles. 2026 AMT; IRS 2026.

Roth, Backdoor and NUA

MatchesrtCalc · brCalc · nuRun
Roth or traditional: traditional × (1 − later rate) against Roth, the saving invested backdoor untaxed part = amount × (basis + amount) ÷ (pre-tax IRAs + basis + amount) direct Roth limit phases out 153,000–168,000 | 242,000–252,000; IRA limit 7,500 (+1,100 at 50) mega backdoor = min(72,000 − deferrals − employer, after-tax room) NUA: shares × growth − gain × gains rate − cost basis × income rate

The pro-rata rule of Form 8606 and the 2026 limits. IRS 2026 limits; Notice 2025-67.

HSA: Spend or Invest

MatcheshsaCalc
spend it: HSA pays the bills; the cash saved grows at r − drag invest it: HSA grows at r; the receipts are kept tax-free up to receipts + later medical costs (185,500), the rest taxed as income

The tax drag is points off the return a year, 0.5 by default (fixed in version 242; it had been taken as a share of the return). Fidelity's 2026 estimate for one 65-year-old is $185,500. Fidelity 2026 estimate.

After-Tax Income Goal

MatchesngTax · ngCalc
find income g with g − tax(g) = the income wanted (by halving) portfolio = (g − Social Security) ÷ withdrawal rate

With pay from work, there is no Social Security and no tax-free share, whatever the hidden fields hold (fixed in version 242).

Income a Lifestyle Needs

Planner's model204-mod-need · budTotals
find gross pay G with take-home(G) ≥ spending + costs + saving (by halving) take-home = G − pre-tax saving − income tax − payroll tax − state tax 50/30/20: needs, wants and saving as shares of take-home

The 50/30/20 split is a rule of thumb.

Student Aid Index

MatchesFAFSA.sai
parents' available income = income − taxes − protection allowance − employment allowance + 12% × net worth protection: 30,300 (2) · 37,720 (3) · 46,590 (4) · 54,970 (5) · 64,290 (6) · +7,260 each employment allowance = min(35% × earnings, 5,200) contribution: 22% up to 22,600, rising in steps to 47% student: 50% × (income − taxes − 12,220) + 20% × assets SAI = max(−1,500, parents + student)

The 2027–28 formula for a dependent student. Federal Register, 2027–28 methodology.

Insurance Needs

MatchesINSURE.need · pvDue
life: PV(income replaced) + PV(household work) + mortgage + debts + college + final costs − PV(Social Security survivors) − savings − cover owned DIME = debts + income × years + mortgage + education; rule of thumb 10 × income disability gap = target × pay − (group benefit − SSDI offset)

The needs method and the DIME rule, both standard ways to size cover.

Annuity Types

Matches232-mod-annuity
fixed index credit = max(0, min(cap, index return × participation − spread)) buffered loss = min(0, return + buffer); floored loss = max(return, −floor) variable: value × (1 + g) − charges; QLAC limit 210,000, income by 85

How the crediting methods work, and the 2026 QLAC limit. Notice 2025-67.

Loan Offers

MatchesofrRun · ofrIrr
payment = B × i ÷ (1 − (1 + i)^−n), i = rate ÷ 12 APR = 12 × j, where Σ_{t=1..n} payment ÷ (1 + j)^t = B × (1 − fee) (by halving) paid off in month k: cost = k × payment + balance_k × (1 + penalty) − B + fee 20/10 rule of thumb: debt payments ≤ 10% of take-home a month, debt ≤ 20% of a year's

The fee is a prepaid finance charge, so the APR counts it and the rate does not; paid off early, the APR comes from the cash flows actually paid. 12 CFR 1026.22; the 20/10 rule.

Start Early

MatchesstartRun · startCalc
starter k begins at a₀ + k × gap with m^k × the first amount a month each month: value = value × (1 + r)^(1/12) + amount; the amount rises g% once a year break-even return: value₀(r) = value₁(r) (by halving between 0.01% and 30%)

Monthly compounding of a yearly return, as Investment Growth does. With $100 from 20, doubling each decade to 65, the break-even is 6.6%.

Paycheck Plan: the Debt Share

MatchesppDebt
each month of the repeated pattern: holding += pay in − pay out; extra = (holding − cushion)⁺ debt: balance × (1 + rate ÷ 12) − payment − share × extra compared with the payment alone: months sooner, interest saved

The standard amortization, with the big-check rule's share for the debt added whenever pay rises above the cushion (version 246).

Leftover 529

Matchesl529Calc
cash out: growth × (tax rate + 10%), the 10% waived on withdrawals up to a scholarship move to the Roth IRA from 15 years after the later of opening and naming the beneficiary: each year = min(IRA limit (+1,100 from 50) − other IRA, pay from work, 35,000 − moved, balance − recent money not yet 5 years in) Roth at 65 = Σ move × (1 + g)^(years to 65)

Each move takes what was put in and growth in proportion. The IRS has not said whether a new beneficiary restarts the 15 years; the cautious reading is used (version 248). IRS Publication 970; 529 to Roth rules.

Spending Month by Month

MatchesprBank · prMonths · renderPrMon
each purchase in the 12 months before the snapshot → its month (YYYY-MM) and budget group monthly share = budget ÷ 12 (or the plan's spending ÷ 12) average month = the full months only; the first and last are part-months unless the date ends a month

Transfers, card payments and money coming in are left out, as for the year's totals (version 247).

Income Floor

MatchesfloorCover
essentials(t) = spending floor + health, Medicare, long-term care, medical and debt costs guaranteed(t) = Social Security + pensions + other streams (annuities, TIPS ladder, reverse mortgage) covered = guaranteed ÷ essentials; gap = (essentials − guaranteed)⁺ annuity to close it = average gap ÷ published payout at that age (joint for a couple)

The safety-first view of the RICP program: are the essentials paid by income that does not depend on the markets? Before tax, today's dollars (version 253).

Retirement Income Style

MatchesstyScore
safety-first = Σ 4 answers (0 to 3) ÷ 12; commitment = Σ 4 answers ÷ 12 probability + optionality → total return; safety + commitment → income protection probability + commitment → risk wrap; safety + optionality → time segmentation

The two preferences and four strategies taught in the RICP program, from Pfau and Murguía's research. The questions are the planner's own, not the trademarked RISA® profile (version 254). Kitces: the RISA framework.

Lifetime Withdrawal Benefit

MatchesgwRun
before income: base × (1 + roll-up), for its years; account = (account − fee) × (1 + g) income = base at the start × payout rate, level for life each year: account = (account − fee − income)⁺ × (1 + g); fee = rider % × base what the account cannot pay, the insurer does

The risk wrap, run at the rate entered, at 0%, and without the rider. Typical 2026 rider fees 0.95% to 1.25% of the base, roll-ups 5% to 8% (version 255). Annuity.org.

Reverse Mortgage Line of Credit

MatcheshecmRun
each start year since 1871, the same spending a year, three ways: portfolio alone; line when the portfolio is empty; line in the year after the mix fell line and loan grow at (loan rate + 0.5% MIP), after inflation; no repayment left = portfolio + (home − loan)⁺

The Sacks rule for a coordinated line, as tested in the research the AAII summarizes; the 2026 HECM limit is $1,249,125 (version 256). AAII.

Deferred Compensation Payout

MatchesnqRun
installment = balance × g ÷ (1 − (1 + g)^−n); lump sum when n = 1 federal = tax(other income + payment) − tax(other income) state rate = max(earned-in, live-in) for n < 10; live-in only for n ≥ 10 (4 U.S.C. 114) ranked by Σ after-tax payments ÷ (1 + g)^year

Section 409A payouts, taxed as ordinary income when paid (version 257). Fidelity: NQDC and state taxes.

A Life Policy in Retirement

MatcheslpCalc
keep = Σ alive_k × q_k × benefit ÷ (1 + d)^(k+½) − Σ alive_k × premium ÷ (1 + d)^k surrender = cash − charge − tax × (cash − premiums paid)⁺ sale = offer − income tax on (min(gain, cash − premiums))⁺ − gains tax on the rest exchange = Σ alive_k × annuity payment ÷ (1 + d)^k

Death rates from Social Security's life table; the sale's split follows Rev. Rul. 2020-5 (version 258). Rev. Rul. 2020-5.

Investment Policy Statement

MatchesrsIps
mix = accounts' stocks, bonds, cash, gold ÷ total (or stock share after the cash reserve) spending rate = spending ÷ portfolio when retirement starts fall of a quarter in stocks ≈ stock share × 25% off the portfolio cash years = cash ÷ spending

The CFA Institute's elements of a policy statement, written from the plan's own figures, with lines to sign (version 260). CFA Institute.

Retirement Transition

MatchestrCalc
readiness = Σ 10 answers (0 to 3), of 30; each area = its 2 answers, of 6 work on first = the lowest area trial run = (spending now − retirement budget)⁺ × months part-time = pay a month × 12 × years

The CRPC program's emotional and financial transition; the phases are Robert Atchley's (version 261).

Capital Needs Analysis

MatchescnCalc
shortfall₀ = (pay × ratio or budget) − Social Security − pensions shortfall₁ = shortfall₀ × (1 + i)^n; r* = (1 + r) ÷ (1 + i) − 1 annuity A = shortfall₁ × (1 − (1 + r*)^−N) ÷ r* × (1 + r*) capital preservation = A + A ÷ (1 + r)^N; purchasing power = A + A ÷ (1 + r*)^N saving = (capital − savings × (1 + r)^n) × r ÷ ((1 + r)^n − 1); serial at r*, × (1 + i)

The CFP retirement needs analysis (topic F.44); the test reproduces a published worked example to the cent, $6,987,586.22 and $19,954.85 a year (version 263). CentPotential.

Retirement Plan for a Small Business

MatchesbpCalc
self-employed pay = profit − ½ × (12.4% × min(0.9235 × profit, wage base) + 2.9% × 0.9235 × profit) employer share = 20% of that pay (25% of a salary), at most $72,000 401(k) = min(deferral left, pay − employer share) + employer share ≤ $72,000, + catch-up SIMPLE = min($17,000 or $18,100, deferral left) + catch-up + 3% of net earnings employees: SEP and profit sharing at the owner's rate; safe harbor 3%; SIMPLE up to 3% pension ≈ min($290,000, pay) × min(1, years ÷ 10) × annuity-due factor, funded level

IRS Publication 560's reduced rate and the 2026 limits of Notice 2025-67 (version 264). IRS Publication 560.

Special Needs and ABLE

MatchesabRun
year 1: own cash above $2,000 in first, then the family's gift, to $20,000 + ABLE to Work: min(own pay put in, pay, $15,650) when not in a 401(k), 403(b) or 457(b) ABLE = (ABLE + in − spending) × (1 + r); trust = (trust + gift beyond the limit) × (1 + r) SSI suspended while ABLE > $100,000; spending that keeps it below: found by halving

The 2026 ABLE limit (Rev. Proc. 2025-32), SSI's resource rules, and the age of onset before 46 (version 265). SSA: SSI resources.

Inherited IRA

MatchesihRun
divisor(t) = max(table(heir's age in year 1) − (t − 1), table(owner's age at death) − t) (a spouse: table(age in year t), looked up each year) required(t) = balance at the end of year t − 1 ÷ divisor(t); empty by year 10 (a minor child: ten years after the year of 21; others eligible: over life) tax(t) = tax(other + withdrawal) − tax(other) + state rate × withdrawal ranked by Σ after-tax ÷ (1 + g)^t (a Roth: by the total)

The SECURE Act beneficiary rules and the 2024 final regulations, with the IRS Single Life Table of 26 CFR 1.401(a)(9)-9 (version 266). IRS: beneficiary.

An ESOP Sale

MatchesTT.bz.res
C corporation: tax put off = the share sale's whole tax (section 1042) when the ESOP ends with ≥ 30%, the shares were held 3 years, the price is reinvested S corporation: up to 10% of the price, from 2028 (SECURE 2.0)

Business succession in Selling a Business (version 267). NCEO.

Paying for Long-Term Care

MatcheslfCalc
cost(y) = cost today × (1 + g)^(start − age + y) each year: income first, then savings, then what a payer covers policy pool = pool × (1 + growth)^years, after the elimination period; premiums' future value off savings Medicaid: savings spent to $2,000 + (spouse at home: ½ savings within $32,532–$162,660) then income − $50 − spouse's shortfall below $2,643.75 a month goes to the home look-back delay = gifts ÷ (cost a year ÷ 12) months

CLTC and CSA: the CMS 2026 spousal impoverishment standards and the AALTCI 2026 prices (version 268). CMS.

Dividing Assets in a Divorce

MatchesdvWorth
pre-tax × (1 − the receiver's rate); Roth and cash in full taxable − gain share × gains rate; home equity − (gain − $250,000)⁺ × gains rate evening payment = (after-tax A − after-tax B) ÷ 2; from a 401(k) by QDRO: ÷ (1 − receiver's rate)

CDFA: section 1041 carryover basis and the QDRO penalty exception (version 269). IRS Publication 504.

Social Security for Your Family

MatchessfMax, sfRun
family max = 150% × PIA to $1,643 + 272% to $2,371 + 134% to $3,093 + 175% above (2026) each child < 18 (19 in high school), spouse with a child < 16: 50% of PIA (75% as survivors) cut in proportion to fit (max − PIA), or the whole max for survivors

NSSA, and retiring with children at home (version 270). SSA: family maximum.

Roth Conversion Ladder

MatchesrlCalc
convert = set amount, or standard deduction + top of the 12% bracket − other income tax = tax(other + convert) − tax(other) + state rate × convert spending + tax from: contributions → conversions ≥ 5 years old → taxable bridge → short

Retiring before 59½, with Publication 590-B's ordering rules (version 271). IRS Publication 590-B.

Married or Not

MatchesmnCalc
tax both ways: single(A) + single(B) against joint(A + B), each with their own Social Security spouse's top-up = (½ higher PIA − lower PIA)⁺; survivor's gain = higher PIA − lower PIA

Partners who are not married, which the plan itself treats as a married couple (version 272). IRS: filing status.

What Each Future's Headline Shows

MatchesspHead
typical = median of the years the money lasts; over retirement = Σ every year, income-only years too highest = the largest year while the money lasts; Easy and Client views: typical only

A planner can show another measure than the typical year (version 274).

The Spending Card's Futures

MatchesrepPicksBy
score(i) = Σ spending(i, year) over retirement, income-only years included futures at the 5th, 10th, 15th, 50th, 85th, 90th and 95th place by score the portfolio, tax and health cards: ranked by run-out year, then what is left at the end

Ranked by what is left, a future whose early markets raised its spending and whose late ones drained the portfolio can land among the hard ones while spending more than the middle one (version 278).

A Continuing Care Community

MatchesccCalc
life care: entrance fee + monthly fee × 12 × (1 + g)^y, every year fee for service: entrance fee + monthly fee while independent, then care median × (1 − discount) × (1 + g)^y at home: (running cost × 12 + home care or nursing care) × (1 + g)^y worth today: Σ cost ÷ (1 + r)^(y + ½) − refund ÷ (1 + r)^N

CSA housing choices; the break-even is the years of nursing care at which life care costs no more (version 276).

Section 351 ETF Exchange

Matchess351Div · s351Calc
tax-free only if: largest issuer ≤ 25% and five largest ≤ 50% of the value (funds counted through, cash left out) and the fund does not pass the securities to authorized participants as part of the plan (Rev. Rul. 2026-20) tax now if not = long-term gain × long-term rate + short-term gain × short-term rate sold: (value − tax now) × (1 + g − fee_low)^t, basis = value − tax now exchanged: value × (1 + g − fee_ETF)^t, basis = the securities' basis (sections 358, 1223) left if sold in year t = value_t − (value_t − basis) × long-term rate; heirs: no tax (stepped-up basis)

The year selling pulls ahead is where the extra expense ratio outgrows what the deferred tax earns (version 298). Rev. Rul. 2026-20, 26 U.S.C. 368(a)(2)(F).

Bills and Reachable Money

Planner's modelrchRun · rchCalc
reachable_t = reachable_(t−1) × (1 + r) + left over − contribution × (1 − tax rate) − bills_t 401(k)_t = 401(k)_(t−1) × (1 + g) + contribution + match × min(1, contribution ÷ contribution for the whole match) short = the most any year falls below the cushion; from the 401(k) instead: short ÷ (1 − tax rate − 10%) the most that still covers them: halving between the match's contribution and today's

From a story: a retiree whose money is locked in the TSP until 59½ while this year's bills are due (version 299).

Medicare Sign-Up Dates

MatchesmtCalc · mtSign
month of 65 = birth month + 65 years (a month earlier for a birthday on the 1st) initial: that month ± 3; before it → covered from it, in or after it → the month after special: to 8 months after work coverage ends → the month after; general: January to March → the month after Part B: +10% for each full 12 months from the initial period's close to the general period's close, less work coverage months Part D: +1% of the base premium for each month without creditable coverage (none for 63 days or less) premium with the penalty rounded to the nearest 10 cents

Medicare.gov's rules; the premiums come from the yearly figures (2026: $202.90 and $38.99) (version 300). When coverage starts.

Safe and Perpetual Withdrawal Rates

MatchesswrYears · swrAt
G_k = (1 + x_k) … (1 + x_(N−1)): a dollar's growth from year k to the end, x the mix's real return left per dollar = G_0 − w × (G_0 + … + G_(N−1)) (withdrawals at the start of each year) safe rate = G_0 ÷ Σ G_k perpetual rate = (G_0 − 1) ÷ Σ G_k lasting in p% of start years = the rate p% of the start years' own rates reach

A balance below zero never recovers, so the closed form is exact; the test checks it against a year-by-year run found by halving. Over 1871 to 2025, a 60/40 mix's worst 30-year start is 1966 (version 312). Bengen (1994).

Sequence of Returns Risk

MatchesseqPath · seqCorr
the same N returns as they came, worst first, best first; spending taken at the start of each year with nothing taken out all three end at start × Π (1 + x): the order alone changes nothing explained = r², r = Pearson's correlation of the safe rate with the first 10 years' return (or all N years')

Kitces found r = 0.79 for the first ten years' real return and a 60/40 mix's safe rate (version 313).

Rebalancing Sensitivity

MatchesrbRun · rbDue
each part grows by its own return; at each year's end a rule may trade back to the target rules: never; every 1, 2 or 5 years; a part 5, 10 or 20 points off; a part a quarter of its target off turnover = Σ |trade| ÷ 2 ÷ value; volatility = sample standard deviation of the yearly returns

Vanguard's study found yearly checks with 5-point bands about as good at controlling risk as frequent trading, with far fewer trades (version 314).

Lump Sum or Spread It Out

Planner's modeldcaSim · dcaHist
monthly lognormal from each year's average μ and swing σ: v = ln(1 + σ² ÷ (1 + μ)²), a = ln(1 + μ) − v ÷ 2, a ÷ 12 and v ÷ 12 a month lump sum = A × P_0; spread over m months = Σ_(j

5,000 futures from a fixed seed; on the yearly record, spread over 2, 3 or 5 years. Vanguard (2012): a lump sum won in about two thirds of rolling 10-year periods (version 315).

Efficient Frontier

MatchesefStats · efCalc
every mix of stocks, bonds and gold in 5-point steps, rebalanced each January risk: volatility, the largest fall from a high, or the Ulcer Index = √(mean of (value ÷ running high − 1)²) frontier: the mixes no other beats on both return and risk (Pareto) knee: the frontier point farthest above the line between its ends, both axes scaled to their range

Markowitz (1952) with history in place of forecasts (version 316).

Portfolio Comparison

MatchespcRun · pcIrr
return a year = (end ÷ start)^(1/n) − 1; with withdrawals, the rate that turns the start and the withdrawals into the end Sortino = mean yearly return ÷ √(mean of the squared returns below zero) Sharpe (cash at 0% after inflation) = mean yearly return ÷ volatility largest fall with the years of its high, its low and the next high; the Ulcer Index as above

Up to three mixes, with the 30-year safe rate of each over the whole record (version 317).

Withdrawal Decision Map

AdaptedwdmSolve · wdmPick · wdmPolicy
V_T(W) = 0 if W ≥ floor, −λ below; run out in year t when W < floor + the smallest withdrawal Q_t(Y, m) = mean over the record's years k of V_(t+1)(Y × (1 + s_m × stocks_k + (1 − s_m) × bonds_k − fee)) V_t(W) = max over withdrawals a ≤ W − floor and stock shares m of a ÷ a_max + Q_t(W − a, m) spend the most: the smallest λ whose chance of lasting from today reaches the target (halving on ln λ) W on 110 levels evenly spaced in ln W; a year's return shifts every level alike, so the record groups by shift

Stochastic dynamic programming (the Bellman equation) over the returns since 1871, each year of the record equally likely, as Forsyth, Vetzal and Westmacott do with expected withdrawals against shortfall. The map is then run on 2,000 futures drawn a year at a time, 2,000 in five-year runs, and every start year in order, against every fixed withdrawal and stock share (version 337). Forsyth, Vetzal and Westmacott (2021).

Monte Carlo Mix Finder

AdaptedmcoCalc · mcoDraw · mcoChol
each draw: growth g_i, swing s_i and cash r from their ranges (triangle, flat or bell curve), ρ_ij evenly in its bounds growth and swing linked by a Gaussian copula: z₂ = c z₁ + √(1 − c²) e, u = Φ(z) ρ softened 5% at a time until its Cholesky factor exists m = Σ w_i (g_i + s_i² ÷ 2) + (1 − Σ w_i) r; σ² = Σ Σ w_i w_j ρ_ij s_i s_j; growth = m − σ² ÷ 2; Sharpe = (m − r) ÷ σ best = the mix (10-point steps, 5 with three investments or fewer) with the best percentile across the draws

Resampling the inputs instead of trusting one set of guesses, after Michaud; the growth is the compound rate, with the volatility drag (version 338). Michaud and Michaud.

Life-Cycle Monte Carlo

AdaptedlcCalc · lcBlocks · lcNext
a life = phases in order; each year: V += amount (fixed dollars ÷ the life's own inflation so far) then V ×= 1 + Σ w_i r_i − cost (rebalanced yearly) or each holding ×= 1 + r_i − cost (reset at each phase) years: blocks of consecutive years of the record, length L ~ even on [shortest, longest], start ~ even, wrapping from the last year to the first; without replacement no year twice until all are used V ≤ 0 after an amount → the money ran out (stays at 0) results: 10th, 25th, 50th, 75th, 90th percentile of V each year; share of lives not run out

A circular block bootstrap of the real returns since 1871 (stocks, 20-year bonds, gold, after inflation), with mulberry32 random numbers from a seed (version 360). Block bootstrap.

Workplace Benefits Together

Planner's modelwbCalc
match = share × min(pay + bonus, $360,000) deferral: pre-tax when the bracket after it + state > the rate expected later + state; catch-up Roth above $150,000 after-tax room = $72,000 − $24,500 − match (the catch-up is outside it) ESPP: price = (1 − discount) × min(first, last) with a lookback; most set aside = $25,000 × price ÷ first price deferred pay: N (1 + match) (1 + g)^n (1 − rate later) against (N − tax saved now)((1 + g)^n − 0.15((1 + g)^n − 1))

Company stock, with ESPP shares kept, is set against the 10% FINRA mentions (version 326).

Roth Catch-Up or Taxable

Planner's modelrcuRun
working years: Roth = C × ((1 + g)^n − 1) ÷ g × (1 + g); account the same at g − yield × dividend tax to 65, both drawn evenly: 1 ÷ (years left) of the balance at the start of each year Roth: contributions first; growth before 59½ pays the tax on other income + 10% account: (realized gain + dividends) × rate, and credit lost = acaNet(premium, income + them) − acaNet(premium, income)

With nothing taxed and no premium the two are equal, which the test checks (version 327).

Exercising Stock Options

Planner's modeloxYear · oxRun · oxCalc
each year: shares = options ÷ years (the last takes the rest); out = shares × strike + tax tax: NSO = eqTax(other, spread) + state × spread + eqPayroll; ISO = eqAmt(other, spread).amt spread = shares × (value now − strike), the value held the same over the years cash_t = cash_(t−1) + saving (from next year) − out_t − goal (in its year) at a sale: gain = options × (price − strike), taxed as a long-term gain (NSO: price − value at exercise), 3.8%, state

Equity Pay spreads an NSO exercise the same way (version 329); Put This in My Plan makes each year's out a one-time cost and the shares a business sale with the 3.8% (versions 330 and 331).

What the Check Found

version 242

Every formula above was checked against the law, the agency or the author that published it. Most matched. Seven did not; version 242 fixed six, and the seventh is data that comes with the next year's figures.

FormulaWhat was differentNow
SALT capThe $505,000 phase-out line was held flat; the law raises it 1% a year through 2029.Fixed in 242.
Tips and overtimeCut by 10% of the excess income; Schedule 1-A cuts $100 per whole $1,000.Fixed in 242.
Charity without itemizing$1,000 / $2,000 held in today's dollars; the law fixes it in dollars.Fixed in 242.
Claiming toolAssumed full retirement at 67 for everyone.Fixed in 242: from the birth year.
HSA toolThe tax drag was taken as a share of the return, not in points.Fixed in 242.
After-tax income toolHidden Social Security and tax-free fields still counted with "pay from work".Fixed in 242.
Marketplace 2027The 2026 percentages are carried into 2027; Rev. Proc. 2026-26 raises them to 2.15%–10.22%.With the 2027 yearly figures.