Optimizer schedule conversion total
Total dollars in a conversion schedule, summed across years in nominal dollars: the raw solver request, the cleaned schedule, the displayed recommendation, the tournament winner, a promotion's schedules, or the installed optimized schedule.
Field: Optimizer conversion schedule total Unit: US dollars Basis: nominal dollars Computed by: UI transformation Status: Complete chain
Appears in: optimize-page ("$X of conversions across N years", "Raw optimizer request", "Cleaned executable schedule"); optimize-page (Incumbent card "Your current schedule ($X of conversions across N years)"); optimize-page (Repriced promotion note: named and aggregate schedule totals); optimize-page (Strategy section callout "This N-year schedule ($X total) was produced by the Optimize tab"); assumptions-card (Strategy group "optimized schedule (N years, $X total)"); mcp (run_optimizer.schedule.conversionTotal)
Calculation records
-
Conversion schedule total Formula record, justified by derivation
Purpose: The one sum of a Roth conversion schedule, as every surface that prints a schedule total shows it.
Statement: strategies/conversionScheduleTotal.ts#conversionScheduleTotal (re-exported by strategies/optimizer.ts) adds a conversion schedule's amounts left to right from 0, the order every surface used, so one schedule always gives one double (floating-point addition is not associative), and refuses a non-finite amount with a RangeError. OptimizedSchedule.conversionTotal publishes it on the raw solve (optimizeSchedule, whatever its status) and on each cleaned schedule (projection/optimizePlan.ts#scheduleWithConversions recomputes it rather than copying it), ExactLedgerTournament.winnerConversionTotal publishes it for the recommended schedule, the engine's own cleaned-schedule gates read it, and decisions/evaluateCandidate.ts#buildConversionExecution computes the requested total with it. Amounts of $10,000.25, $20,000.50 and $30,000.75 total $60,001.50; 0.1, 0.2 and 0.3 total 0.6000000000000001 left to right, where the other association gives 0.6. Units: nominal dollars of different years added. Rounding: none beyond the amounts' own cents.
Formula:
total = ((0 + a_1) + a_2) + … + a_n-
a_i: The i-th listed conversion amount of the schedule (unit: nominal USD of its year) (domain: finite)
Timing: once per schedule, where the schedule is made
Rounding: none beyond the amounts' own cents
Limits:
- The total adds nominal dollars of different years, undiscounted; the Optimize page's schedule chart says nominal dollars, while the hero and the Strategy callout do not.
- A total says nothing about which years carry it; the page prints the count of entries beside it, and every raw entry is above $0.50.
- A cleaned schedule carries the raw solve's endingAfterTax and lifetimeTax, which describe the raw schedule; nothing reads them from a cleaned schedule.
Provenance: derived by
claude, implemented byclaude-subagent.Independent review:
codexGates (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-conversion-total.md Mutation receipt: optimizer-schedule-conversion-total.mutation.md
Evidence tests: optimizer.conversionTotal.evidence.test.ts
Implemented in: conversionScheduleTotal.ts; optimizer.ts; optimizePlan.ts; evaluateCandidate.ts
Is the number of: optimizer-schedule-conversion-total
-
Records that feed this number
-
Expected return for an account: allocation blend, else account rate, else plan default Composition record, justified by derivation
Purpose: Which expected nominal return applies to an account this year, by precedence.
Statement: For an account, the plan assumptions and a year: when the account is taxable, traditional, roth or hsa and carries an allocation policy (accountAllocation), the return is blendedReturnPct(targetWeightsAt(policy, year), resolveAssetClassParams(assumptions.assetClassParams)) and the account's own annualReturnPct is ignored; otherwise the account's annualReturnPct when the account type carries that field and it is not null; otherwise assumptions.defaultReturnPct. Units: percent per year, nominal. Rounding: none.
Formula:
r = blend(targetWeightsAt(policy, year), classParams) if the account has an allocation; else annualReturnPct if not null; else defaultReturnPct-
policy: The account's opt-in allocation policy, if any (unit: policy) (domain: taxable, traditional, roth or hsa accounts only) -
annualReturnPct: The account's own scalar expected return (unit: percent/year) (domain: number or null) -
defaultReturnPct: The plan-wide default expected return (unit: percent/year) (domain: finite) -
r: The expected return selected for the account (unit: percent/year) (domain: finite)
Timing: annual; the blend uses the year's target weights
Rounding: none
Limits:
- Only taxable, traditional, Roth and HSA accounts can carry an allocation; every other type falls to the scalar precedence
- The blend uses the year's target weights, not the drifted weights the ledger holds under rebalancing "none"; the two coincide at the start year and under annual rebalancing
- DUPLICATION: the ledger does not call this function. annualPostSolveAccountGrowth.ts applies the same precedence inline (the class blend over the account's tracked weights when a track exists, else state.account.annualReturnPct ?? defaultReturnPct); the production callers are optimizePlan.ts#blendedGrowth in the engine and assumptionsExport.ts in planner-ui, which formats the Assumptions card and has no census family. The evidence drives the ledger phase with the worksheet's allocated account and requires it to ignore the 9% scalar the same way
- The worksheet names the per-account balance, investable-total and net-worth families upstream; the ledger's inline precedence produces them, so this record feeds the optimizer schedule families its caller reaches
Provenance: derived by
codex, implemented byclaude-subagent.Independent review:
cursorGates (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: allocation-account-expected-return.md Mutation receipt: allocation-account-expected-return.mutation.md
Evidence tests: assetClasses.evidence.test.ts
Implemented in: assetClasses.ts
Feeds: optimizer-recommended-conversion-annual, optimizer-schedule-conversion-total
-
-
Blended expected nominal return of a weight vector Formula record, justified by derivation
Purpose: The one-period weighted mean of the class expected returns for a given allocation.
Statement: For a weight vector w in ASSET_CLASS_IDS order and resolved class parameters, r = sum over i of w_i x returnPct_i, with a missing w_i read as 0. Units: percent per year, nominal; the class returns are percents and the weights fractions, so the result is in percent. Rounding: none.
Formula:
r = sum_i w_i r_i-
w_i: Fraction of the account in class i (unit: 1) (domain: w_i >= 0, sum 1 for a normalized vector) -
r_i: Expected nominal total return of class i (unit: percent/year) (domain: finite) -
r: Blended expected nominal return (unit: percent/year) (domain: between min and max r_i for a normalized vector)
Timing: one period (a year)
Rounding: none
Limits:
- Weights are taken as given; the function neither normalizes nor validates them, so a vector not summing to 1 returns a scaled blend
- Arithmetic one-period blend, not geometric: the expected return of a mix over one year is the weighted mean of the class returns
- DUPLICATION: packages/engine/src/projection/internal/annualPostSolveAccountGrowth.ts forms the same sum of w_i r_i inline, with the year's class shock added to each r_i, to grow an allocated account; the ledger never calls blendedReturnPct, which reaches production only through expectedAccountReturnPct (the optimizer's balance-weighted growth rate in optimizePlan.ts#blendedGrowth) and the planner-ui AllocationPanel preview. The evidence drives annualPostSolveAccountGrowth at zero shock and requires its growth to equal blendedReturnPct on the worksheet's inputs
- The worksheet names the per-account balance and investable-total families upstream; the ledger's inline copy produces them, so this record feeds the optimizer schedule families its own callers reach and pins parity with the copy instead of claiming the balance families
Provenance: derived by
codex, implemented byclaude-subagent.Independent review:
cursorGates (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: allocation-blended-expected-return.md Mutation receipt: allocation-blended-expected-return.mutation.md
Evidence tests: assetClasses.evidence.test.ts
Implemented in: assetClasses.ts
Feeds: optimizer-recommended-conversion-annual, optimizer-schedule-conversion-total
-
-
Conversion coordinate descent search Model record, justified by derivation
Purpose: Refine an annual conversion schedule through deterministic local search.
Statement: decisions/search.ts#refineConversionSchedule deterministically searches annual Roth-conversion dollars by fixed-order coordinate descent, first coarse then fine steps, retaining only hard-constraint-feasible moves whose full-projection primary metric improves by more than the minimum, subject to simulation and sweep caps.
Formula:
for coarse then fine step, for each year coordinate try amount +/- step; retain a feasible candidate only when score > incumbent + minimumImprovement-
schedule: Annual conversion amounts (unit: nominal USD) (domain: nonnegative) -
step: Coarse/fine move size (unit: USD) (domain: positive) -
score: Objective metric (unit: objective dollars) (domain: finite)
Timing: deterministic annual-coordinate order under simulation and sweep budgets
Rounding: schedule cents; no statistical tolerance
Limits:
- Local search only; no global-optimum claim. The worksheet score table is injected at the evaluation seam with a four-evaluation budget covering exactly its four entries; any unspecified probe is refused rather than guessed. This proves search selection only. The returned aggregate schedule still passes downstream legal-owner/action readiness and publication gates, so it feeds rather than publishes optimizer recommendations.
Provenance: derived by
codex, implemented bycodex.Independent review:
cursorGates (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: conversion-coordinate-descent-search.md Mutation receipt: conversion-coordinate-descent-search.mutation.md
Evidence tests: search.evidence.test.ts
Implemented in: search.ts
Feeds: optimizer-recommended-conversion-annual, optimizer-schedule-conversion-total
-
-
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 byclaude.Independent review:
codexGates (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
-
Transformations
- strategies/conversionScheduleTotal.ts#conversionScheduleTotal (amounts added left to right from 0), published as OptimizedSchedule.conversionTotal on the raw and cleaned schedules and ExactLedgerTournament.winnerConversionTotal; the displayed list, the promotion note, the Strategy callout and the assumptions export call the helper.
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.