Calculation catalog › Optimizer and comparisons

Optimizer schedule lifetime tax

The tax the conversion optimizer's readout charges the schedule its solver returned, summed over the plan's years in each year's nominal dollars: federal bracket tax and state tax on the solver's taxable ordinary income, the year's cumulative IRMAA surcharges, and the solver's single 15% rate on its realized gains. None when the solve has no solution. The solver's own figure, not the projection's lifetime taxes and penalties of the same schedule.

Field: Optimizer's own modeled lifetime tax Unit: US dollars Basis: nominal dollars Computed by: Engine Status: Complete chain

Appears in: mcp (run_optimizer.schedule.lifetimeTax (null when the solve has no solution))

Calculation records

  • Optimizer's own ending after-tax wealth and lifetime tax Model record, justified by derivation

    Purpose: What the conversion optimizer's solver reports as its schedule's ending after-tax wealth and lifetime tax: its own linear model's figures, which differ from what the projection of the same schedule shows.

    Statement: strategies/optimizer.ts#optimizeSchedule solves, with HiGHS, the multi-year conversion and withdrawal model #buildOptimizerModel writes from the input projection/optimizePlan.ts#buildOptimizerInput builds, reads HiGHS's raw solution (which HiGHS writes to 16 significant digits; the engine reads it through a wrapper around the highs package's solution writer, not the package's six-digit parse of the pretty print), and publishes two figures with the schedule; RetireGolden-MCP returns them (run_optimizer.schedule) as the solver computed them, not read from a projection of that schedule: when the schedule has conversions, projection/optimizePlan.ts#optimizePlan projects them to clean them (postProcessExactLedgerSchedule), and it always runs its tournament, which prices candidates on the full projection, but it publishes those results separately (RetireGolden-MCP's run_optimizer.tournament), never in these two figures. Both are published when the solve has a solution, that is when the raw solution's primal status is Feasible (an optimum, or the incumbent a node or time limit stopped at), and both then describe that solution; a solve with no solution (an infeasible model, or a limit reached before any incumbent) publishes both as null, with an empty schedule. endingAfterTax is the objective value in the raw solution, rounded to cents: c_D times the tax-free bucket (Roth, cash and HSA) and the taxable bucket at the end of the last year, plus c_DL times the traditional and inherited traditional buckets, plus 0.000001 times every year's conversion, where c_D is 1 / f, f the plan's general-inflation factor for its last year on the engine's dollar basis (projection/dollarBasis.ts#planDollarBasis: 1 + inflation compounded left to right over the n − 1 years from the first plan year to the last, so 1 for a one-year plan), written into the model text to at most eight decimal places (1 / 1.025 is written 0.97560976), and c_DL is the unrounded deflator 1 / f times one minus the heir rate L (the plan's heirTaxRatePct over 100 unless a caller passes liquidationRatePct), that product then written to at most eight decimal places, not the written c_D times one minus L. It is in today's dollars at the start of the plan on the engine's basis: the projection divides its last year's figures by the same f. lifetimeTax is, rounded to cents, the sum over the plan's years in each year's nominal dollars of the federal bracket tax on the year's published taxable ordinary income, that income times the year's flat state rate, the state bracket tax on it when the year has state brackets, the cumulative IRMAA surcharges of the year's published tier (each tier's Part B increase over the tier below and its Part D increase, times 12, summed up to the tier) and the published realized gain times the solver's gain rate (0.15 when the plan has a taxable balance). Both are the solver's own figures and differ from the projection of the same schedule's ending after-tax estate and lifetime taxes and penalties.

    Formula: with a solution: endingAfterTax = round2(c_D × (O_n + X_n) + c_DL × (T_n + I_n) + 0.000001 × Σ_t conv_t), c_D = fmt8(1 / f), c_DL = fmt8((1 / f) × (1 − L)), f = the product of (1 + i) over the n − 1 years from the first plan year to the last; lifetimeTax = round2(Σ_t [F_t(ti_t) + s_t × ti_t + S_t(ti_t) + Σ_{k ≤ tier_t} m_k + r × gain_t]); with no solution: endingAfterTax = lifetimeTax = null

    • O_n, X_n: The solver's tax-free bucket (Roth, cash and HSA) and taxable bucket at the end of the last plan year (unit: nominal USD) (domain: nonnegative; X_n is 0 when the plan has no taxable bucket)
    • T_n, I_n: The solver's traditional and inherited traditional buckets at the end of the last plan year (unit: nominal USD) (domain: nonnegative)
    • conv_t: The solver's conversion in year t (unit: nominal USD) (domain: nonnegative)
    • i: The plan's inflation rate (unit: fraction per year) (domain: finite)
    • n: Number of plan years in the solve (unit: years) (domain: positive integer)
    • f: The plan's general-inflation factor for its last year on the engine's dollar basis, the factor the projection publishes as that year's inflationScale (unit: none) (domain: positive; 1 when n is 1)
    • L: Heir tax rate the traditional buckets are haircut at: heirTaxRatePct, or liquidationRatePct when a caller passes it, over 100 (unit: fraction) (domain: 0..1)
    • fmt8: The model text's number format: a whole number as it is, anything else to eight decimal places (unit: none) (domain: finite)
    • ti_t: Year t's published taxable ordinary income: the raw solution's value in cents (unit: nominal USD) (domain: nonnegative)
    • F_t, S_t: Year t's federal bracket tax for the filing status, and its state bracket tax (0 when the year has no state brackets) (unit: nominal USD) (domain: nonnegative)
    • s_t: Year t's flat state and local rate (unit: fraction) (domain: 0..1)
    • m_k: IRMAA tier k's annual increase over tier k − 1: its Part B total minus the lower one's, plus its Part D amount minus the lower one's, times 12 (unit: nominal USD) (domain: nonnegative)
    • tier_t, gain_t: Year t's published IRMAA tier and realized gain (unit: tier index; nominal USD) (domain: 0..5; nonnegative)
    • r: The solver's single gain rate (unit: fraction) (domain: 0.15 with a taxable bucket, else 0)

    Timing: once per solve; the lifetime tax adds every plan year

    Rounding: Each figure to cents. The model's coefficients and constants are written to at most eight decimal places; the objective and the per-year values the lifetime tax is computed from are read from HiGHS's raw solution, to 16 significant digits, and the per-year values are rounded to cents before the lifetime tax is computed from them.

    Limits:

    • Not the projection of the same schedule: when the schedule has conversions, optimizePlan projects them to clean them, and it runs its tournament on the full projection, but these two figures are the solver's, and RetireGolden-MCP's run_optimizer publishes the tournament's results separately. The solver's buckets hold only the investable accounts (no property, debt, insurance, ladder, pension or annuity value; a property sale's net proceeds and a permanent-life death benefit reach it only as the year's cash, and a HECM draw not at all) and grow at one blended rate a year; its tax is linear in its variables, resting on the registered approximations irc-1-h-optimizer-flat-fifteen-percent-preferential-rate, irc-86-a-optimizer-taxable-social-security-linearization, usc-42-1395r-i-3-1395w-113-a-7-optimizer-beneficiary-month-exposure and usc-42-1395r-i-5-optimizer-uniform-threshold-indexing; and the heir rate falls flat on every traditional dollar, with no basis share, no spouse destination and no HSA heir tax. Measured on the library example bracket-fill-roth (2026-10-10, the solve optimal in milliseconds): endingAfterTax is $282,266.26, while the projection of the same schedule ends with an after-tax estate of $470,194.78 in 2049, $266,458.08 in today's dollars on the engine's basis, which is the solver's basis too; lifetimeTax is $181,976.47, against the projection's lifetime taxes and penalties of $251,819.64. Those dollars are measured, not derived; the evidence holds each of the five to the figure stated here, and a figure more than half a cent from the stated value fails it.
    • lifetimeTax counts the IRMAA surcharges the projection books as Medicare premiums, prices every realized gain at one rate, carries no penalty, and is computed from the taxable income and gain as published, in cents, not from the solver's own values; in the evidence's two-year case both give $47,663.94.
    • endingAfterTax includes the reward of one millionth of a dollar per converted dollar ($0.11 in the evidence's one-year case), and its weights are the eight-decimal forms of the deflator and the haircut (0.56669724 and 0.42502293 for bracket-fill-roth's 24-year plan, deflated over its 23 years).
    • A solve stops at HiGHS's node limit (options.maxNodes, 5,000 branch-and-bound nodes unless a caller sets one; status node-limit), whose stop does not depend on the machine's speed: HiGHS runs single-threaded in the one WebAssembly build of the pinned highs package, whose floating-point arithmetic WebAssembly specifies exactly, so the same input is expected to stop at the same incumbent on any machine (seen on repeated runs on one machine, not measured across machines), and both figures then describe that incumbent, not a proven optimum. Measured on rmd-irmaa's model, the only library example that reaches it (2026-10-10, highs 1.15.2 solving that model directly, not derived): the node limit stops at an objective of $744,755.92 in about 8 seconds, 20,000 nodes stop at the same incumbent, and the best objective found in five minutes was $744,815.16, never proven optimal, so the published incumbent is $59.24 (0.008%) below it. The time limit (options.timeLimitSec, 60 seconds unless a caller sets one; status timeout) remains as a guard against a hang; a machine too slow to reach the node limit within it stops there instead, and its figures then depend on that machine. No library example reaches it here.
    • A solve with no solution publishes both figures as null and an empty schedule, and the status tells an infeasible model from a limit reached before any incumbent. Run as RetireGolden-MCP runs them (measured 2026-10-10), 10 of the 29 library examples have no solution, all infeasible, and each is an example whose own projection depletes; projection/optimizePlan.ts#optimizePlan publishes that depletion year beside a first solve with no solution (OptimizedSchedule.projectionDepletionYear), read from the projection, not assumed from the solver.
    • Which solution HiGHS returns where more than one is optimal belongs to the highs package the engine pins (1.15.2), and so does the format of its raw solution: a writer that no longer prints it as the engine reads it makes every solve throw an error that says so, rather than publish; an upgrade that puts any figure the evidence compares more than half a cent from the value stated here fails it.
    • Evidence: two cases derived by hand from the library example rmd-irmaa's facts, entered as solver inputs (one year, and two years varied: an inherited IRA drawn $40,000 a year, a smaller traditional IRA, no cash, a $60,000 one-off cost, a 10% heir rate and IRMAA on the same year's income), two infeasible inputs, one with tier binaries and one without, and bracket-fill-roth run as RetireGolden-MCP runs it. The hand cases solve without the example's qualified charitable distribution ($15,000 a year from the IRA, which the plan-built input carries as an exclusion from the year's ordinary income and a diversion of its required distribution's cash, and which would change the one-year case's figures), the senior deduction's phase-out, the taxable Social Security phase-in and state tax, which the plan-built input adds where they apply, and with IRMAA on the same year's income where the plan-built input uses the income two years before; the readout is the same.

    Provenance: derived by claude, implemented by claude.

    Independent review: codex

    Gates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass

    Worksheet: optimizer-schedule-objective-and-lifetime-tax.md Mutation receipt: optimizer-schedule-objective-and-lifetime-tax.mutation.md

    Evidence tests: optimizer.schedule.evidence.test.ts

    Implemented in: optimizer.ts; optimizePlan.ts; dollarBasis.ts

    Is the number of: optimizer-schedule-ending-after-tax-objective, optimizer-schedule-lifetime-tax

Records that feed this number

  • Optimizer's per-year solution Model record, justified by derivation

    Purpose: What the conversion optimizer's solver plans for each year: its conversion, its withdrawal from each bucket, the gain and taxable income it books, and its year-end balances.

    Statement: strategies/optimizer.ts#optimizeSchedule publishes OptimizedSchedule.schedule, one OptimizedYear per plan year in order, from the solution HiGHS returns for the model #buildOptimizerModel writes, when the solve has one (the raw solution's primal status Feasible: an optimum, or the incumbent a node or time limit stopped at), and no rows when it has none (an infeasible model, or a limit reached before any incumbent). conversion, withdrawTraditional (the required-distribution floor included), withdrawInheritedTraditional, withdrawOther (from the tax-free bucket of Roth, cash and HSA), withdrawTaxable and taxableOrdinary are the solution's values of the year's conversion, withdrawal, sale and taxable-income columns; endTrad, endInheritedTrad, endOther and endTaxable are the four buckets' balances at the start of the next year, after the year's flows and growth, endTaxable 0 when the plan has no taxable bucket; taxableGainRealized is the published withdrawTaxable times the gain fraction min(1, max(0, 1 − the taxable basis ratio)); irmaaTier is the highest tier whose binary is above one half, 0 for none. The engine reads each value from HiGHS's raw solution, which HiGHS writes to 16 significant digits, through a wrapper around the highs package's solution writer (not the package's six-digit parse of the pretty print); each is then rounded to cents, and a column the solution does not carry reads 0. In the model, taxable ordinary income is at least the year's ordinary income base plus the taxable part of its conversion and its traditional and inherited withdrawals, less the standard deduction with the age-65 addition (and the senior deduction net of its phase-out when the solve prices it), and at least 0; minimizing tax holds it there. The tax-free withdrawal enters the model only as a difference with the surplus the year saves, which is not published, so a solution sets one of the two to 0; and a reward of one millionth of a dollar per converted dollar reports traditional drainage as a conversion, leaving withdrawTraditional at its floor unless the year needs the cash. OptimizedSchedule.conversions lists each year whose conversion is above $0.50, with the same amount, and is empty with no solution. Units: each year's nominal dollars.

    Formula: with a solution: v_t = round2(column_t); taxableGainRealized_t = round2(withdrawTaxable_t × gf), gf = min(1, max(0, 1 − basisRatio)); irmaaTier_t = max{k : b_{t,k} > 0.5}, else 0; endBucket_t = bucket_{t+1}; conversions = [(t, conversion_t) : conversion_t > 0.5]; with no solution: schedule = [], conversions = []

    • column_t: The raw solution's value of year t's conversion, withdrawal, sale, taxable-income or next-year balance column, as HiGHS writes it (16 significant digits) (unit: nominal USD) (domain: nonnegative; 0 when the solution carries none)
    • basisRatio: The opening taxable balance's cost basis over the balance, one ratio for the whole plan (unit: fraction) (domain: 0..1; 1 when not given)
    • b_{t,k}: The solution's value of year t's IRMAA tier k binary (unit: none) (domain: 0 or 1)

    Timing: every plan year of one solve

    Rounding: cents of the raw solution's value

    Limits:

    • Cents of the solution: a planned conversion of $107,028.8775 is published as $107,028.88, in schedule[] and in schedule.conversions[] alike. Which solution HiGHS returns where more than one is optimal belongs to the highs package the engine pins (1.15.2), and so does the format of its raw solution: a solve whose raw solution the engine cannot read throws an error rather than publish; an upgrade that puts any figure the evidence compares more than half a cent from the value stated here fails it.
    • A node-limit or timeout status publishes the rows of the incumbent HiGHS held when it stopped, which is not a proven optimum (optimizer-schedule-objective-and-lifetime-tax states the two limits); a solve with no solution publishes no rows, and the status says whether the model was infeasible or a limit came first.
    • The solver's plan, not what the projection of the same conversions does. The projection executes each conversion only up to what the owners' accounts hold (the cleaned schedule), funds spending in the plan's own withdrawal order and taxes each account by its own basis and rules, so its withdrawals, realized gains, taxable income and balances (withdrawals-by-category-annual, tax-realized-gains-annual, accounts-balance-per-account-annual) can differ from these in every year. The solver's buckets grow at one blended rate a year and its sale realizes one gain fraction for the whole plan.
    • withdrawOther is the net draw on the tax-free bucket when the year needs one and 0 when the year saves; the saving itself is not published and shows only in the next year-end balance (in the evidence's two-year case, $7,057.25 saved in 2027 ends as $7,410.11).
    • irmaaTier is a classification, a census exclusion; its surcharges enter the solver's lifetime tax.
    • Evidence: the two cases of optimizer-schedule-objective-and-lifetime-tax, derived by hand from the library example rmd-irmaa's facts without its qualified charitable distribution: the one-year case's conversion through the 24% bracket, required distribution, tax-free draw and year-end balances, and the two-year case's required distributions, inherited draws, sale and its gain, two tiers and saved surplus; and its two infeasible inputs, which publish no rows. A year with both a conversion and a sale, and an inherited distribution set by a divisor, are not exercised; they follow the same readout.

    Provenance: derived by claude, implemented by claude.

    Independent review: codex

    Gates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass

    Worksheet: optimizer-schedule-year-solution.md Mutation receipt: optimizer-schedule-year-solution.mutation.md

    Evidence tests: optimizer.schedule.evidence.test.ts

    Implemented in: optimizer.ts

    Is the number of: optimizer-recommended-conversion-annual, optimizer-schedule-withdrawal-by-bucket-annual, optimizer-schedule-taxable-gain-realized-annual, optimizer-schedule-taxable-ordinary-income-annual, optimizer-schedule-ending-balance-by-bucket-annual

    Feeds: optimizer-schedule-lifetime-tax, optimizer-schedule-ending-after-tax-objective, optimizer-schedule-conversion-total

Engine source: engine/src/strategies/optimizer.ts (OptimizedSchedule.lifetimeTax)

The evidence links on this page (each record's worksheet, mutation receipt, evidence tests and implementation, and the source links) are pinned to engine commit 2624dbdba9fd, committed 2026-10-11. The live calculation index and output-family census links point at the repository's main branch, so you can check this snapshot against them.