Calculation catalog › Accounts and growth

Display balance by category annual

Per year, the year-end balances of the plan's logical investable accounts (one per id) summed within each category (cash, taxable, equityComp, traditional, roth, hsa), with the unassigned cash the ledger holds outside every account published beside them.

Field: Balances by account type Unit: US dollars Basis: nominal dollars Computed by: UI transformation Status: Complete chain

Appears in: chart (Investable balances by account type stacked areas); report (chart-data block cash/taxable/equityComp/traditional/roth/hsa and chartDataCsv, and the printed report chart's stacked categories)

Calculation records

  • Balances by account type, one value per logical account Composition record, justified by derivation

    Purpose: How a year's account balances roll up into the six stacked categories of the balance chart, with the unassigned cash beside them.

    Statement: projection/yearFigures.ts#balancesByCategory publishes, for a ledger year, the sum of YearResult.balances[id] over the plan's selected logical balance accounts (one per id, model/plan.ts#selectedLogicalBalanceAccounts) by account type into cash, taxable, equityComp, traditional, roth and hsa, and refuses a plan in which an account id is also the id of a property, a debt or a permanent-life policy. projection/yearFigures.ts#unassignedCash reads YearResult.unassignedCash, the surplus the ledger holds outside every account, which is in investableTotal and in no category. Units: nominal USD at year end. Rounding: none.

    Formula: balance[c] = sum over logical balance accounts a of type c of balances[a.id]; investableTotal = sum over c of balance[c] + unassignedCash (up to binary association)

    • c: Category (unit: enum) (domain: cash | taxable | equityComp | traditional | roth | hsa)
    • balances[id]: The year's published aggregate balance of the logical account id (unit: usd) (domain: finite)
    • unassignedCash: Surplus with no cash or taxable account to land in (unit: usd) (domain: nonnegative; 0 in a plan with a cash or taxable account)

    Timing: annual, end of year after flows and growth

    Rounding: none

    Limits:

    • One value per logical account id: rows sharing an id are one account, whose aggregate the ledger publishes once. Owner decision R1 (2026-09-25): the pages used to add the aggregate once per physical row, so an account split across two 50,000 rows showed 200,000 instead of 100,000
    • Unassigned cash is in investableTotal and in no category; it is published beside the categories so the chart can draw it rather than stop short of investableTotal
    • Refuses, rather than reads, an id shared with a property, a debt or a permanent-life policy (decision D-CASH-PROPERTY-ALIAS): the plan checks refuse such plans and stored ones are repaired on load, so this fires only on a plan that did not go through them
    • endingByCategory in the projection summary is this roll-up read for the last row, without equity compensation

    Provenance: derived by claude, implemented by claude-subagent.

    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: display-balance-by-category-annual.md Mutation receipt: display-balance-by-category-annual.mutation.md

    Evidence tests: yearFigures.evidence.test.ts

    Implemented in: yearFigures.ts; plan.ts; result.ts

    Is the number of: display-balance-by-category-annual

Records that feed this number

  • Year-end balance map: one entry per id, written channel by channel Composition record, justified by derivation

    Purpose: The per-account year-end balances the ledger publishes for every projection year.

    Statement: YearResult.balances, materialized by projection/internal/annualSnapshot.ts#annualSnapshot, is the year-end map written in exactly this order: logical investable balances, then property values, then ordinary debt balances, then permanent-life cash values. Object.fromEntries keeps the later-channel write, so when two channels share an id the LAST channel written wins. Each value is that channel's own full-year figure and no netting across channels happens: a debt appears as its positive outstanding balance, not as a negative asset. Units: nominal dollars per id. Rounding: none.

    Formula: balances = fromEntries([...investable, ...property, ...debt, ...insuranceCashValue]) ; last write wins per id

    • investable: One published row per logical investable account id, at its year-end balance (unit: usd) (domain: finite)
    • property, debt: Year-end property values and ordinary debt balances, in insertion order (unit: usd) (domain: nonnegative)
    • insuranceCashValue: Permanent-life cash values, written last (unit: usd) (domain: nonnegative)

    Timing: once per projection year, at the end of the annual pass

    Rounding: none

    Limits:

    • Beyond the worksheet's inputs the evidence plan fixes: a single filer aged 51 in 2026 (no Medicare month, no required distribution), the plan's inflationPct at 3 (the property grows at general inflation, see below), zero return on the investable account, wage and recurring income sized to fund the stated contribution and the $4,000 debt service, no spending other than that debt service and the $15,000 withdrawal realized as a one-time goal, a single-year horizon, and an interest-free debt whose scheduled payment is exactly the $4,000 of principal the worksheet amortizes
    • The withdrawal is realized as an uninflated one-time goal, which is what makes the investable close opening + contributions - withdrawals at a zero return
    • The worksheet names 3% as the PROPERTY's appreciation; internal/propertyEventsAndGrowth.ts grows a property at GENERAL inflation and ignores the property account's own annualReturnPct, so the evidence sets the plan's inflationPct to 3 instead. In the start year every cumulative factor is still 1, so nothing else in the row moves
    • The collision case is asserted by calling annualSnapshot directly with four channels on one id, because no valid plan can reach the snapshot with two channels on one id: the plan checks refuse every pair of rows that would publish a value under a shared id (model/sharedIdCollisions.ts). The overwrite order is a property of the snapshot, not of any plan
    • The map holds no HECM line: HECM debt is published separately and only enters net worth under its non-recourse cap
    • Until decision D-CASH-PROPERTY-ALIAS (2026-09-25, extended the next day) the plan checks accepted several pairs on a single id: a cash account and a property, a property and a debt, two properties, two debts, an account and a permanent-life policy, and two permanent-life policies. The later value then overwrote the earlier one in this map and in every figure read from it (a $300,000 home published as cash), and two properties, two debts or two permanent-life policies under one id counted only one of them in net worth. The checks now refuse every such pair, and a stored plan that has one gets new ids for the colliding rows when it is loaded (an investable account keeps the id), so no plan reaches the snapshot with one

    Provenance: derived by codex, implemented by claude.

    Independent review: cursor

    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: accounts-balance-per-account-annual.md Mutation receipt: accounts-balance-per-account-annual.mutation.md

    Evidence tests: annualSnapshot.balances.evidence.test.ts

    Implemented in: annualSnapshot.ts; simulate.ts

    Is the number of: accounts-balance-per-account-annual

    Feeds: display-balance-by-category-annual

  • Today's dollars by the ledger's own inflation factor Formula record, justified by derivation

    Purpose: How a year's nominal amount becomes start-year dollars on every page, and how a start-year figure such as the FI target is placed in a year's dollars.

    Statement: projection/dollarBasis.ts converts a nominal amount x of projection year y to start-year dollars as x / f(y), and a start-year amount to year-y dollars as x * f(y), where f(y) is the ledger's published YearResult.inflationScale: the left-to-right product of (1 + inflationPct/100) taken y - startYear times from 1, exactly 1 in the start year. projectionDollarBasis reads the factors from a projection's rows; planDollarBasis runs the same recurrence for a page that holds no projection rows and is bit-identical to a deterministic run's factors. nominalForDisplay shows a nominal amount in the page's mode (converted in today's-dollar mode, unchanged in nominal mode) and todayForDisplay shows a start-year amount (unchanged in today's-dollar mode, times f(y) in nominal mode), so the FI target line is fiNumber itself in today's-dollar mode and fiNumber * f(y) in nominal mode. A year outside the projection, or a fractional year, is refused. Units: USD. Rounding: none.

    Formula: f(s) = 1; f(y) = f(y - 1) * (1 + i/100); today(y, x) = x / f(y); nominal(y, x) = x * f(y); FI target in year y = fiNumber (today mode) or fiNumber * f(y) (nominal mode)

    • i: General inflation rate, plan.assumptions.inflationPct (unit: percent per year) (domain: finite, above -100)
    • s: Projection start year, the first ledger row (unit: calendar year) (domain: integer)
    • y: Year of the amount (unit: calendar year) (domain: integer in [s, endYear])
    • x: Amount converted (unit: usd) (domain: finite)
    • f(y): Cumulative general-inflation factor, YearResult.inflationScale (unit: ratio) (domain: f(s) = 1)

    Timing: annual; one factor per calendar year, applied to every amount of that year

    Rounding: none

    Limits:

    • planDollarBasis is the plan's assumed rate and matches only a deterministic run: a Monte Carlo path grows by its own inflation series, so its rows are converted with projectionDollarBasis on that path's own result
    • Start-year dollars in whole-year steps: an amount paid in December and one paid in January of the same year carry the same factor
    • General inflation only: the healthcare inflation premium and the Social Security cost-of-living adjustment are separate assumptions and are never the basis
    • Years outside the projection are refused, never extrapolated; a projection whose horizon ends before its start year has an empty basis in which every year is refused
    • Owner decision R19 (2026-09-25): the planner pages used to compound the plan's rate with Math.pow from the page's start year. The ledger's factor is a product built one year at a time, which differs from the power in the last binary digit in some years (21 of 41 years at 2.5 percent, one unit each), so a whole-dollar figure can change only on an exact half-dollar tie; the FI target in today's-dollar mode becomes exactly fiNumber instead of fiNumber * f / f
    • The FI target is recorded here rather than in a record of its own: it is projection-summary-fi-number placed in a year through todayForDisplay
    • Since B2-P1 slice 2 the spending page converts nothing itself: each published withdrawal rule's ending estate is converted by that rule's own run (decisions/swrComparator.ts#compareSwrRules publishes endingAfterTaxEstateTodayDollars), and the solver's evidence estate by the answer run's own factors; planDollarBasis remains for the relocation page

    Provenance: derived by claude, implemented by claude-subagent.

    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: display-dollar-basis-conversion.md Mutation receipt: display-dollar-basis-conversion.mutation.md

    Evidence tests: swrComparator.evidence.test.ts; dollarBasis.evidence.test.ts

    Implemented in: dollarBasis.ts; detectorProjection.ts; result.ts; swrComparator.ts

    Is the number of: display-dollar-basis-conversion, display-fi-target-annual

    Feeds: display-balance-by-category-annual, display-net-care-cost-annual, display-tax-free-gains-room-annual, display-tax-plus-penalties-annual, display-total-spending-annual, display-upside-shortfall-annual, display-upside-spending-annual

Transformations

  • Sum YearResult.balances[id] once per logical account id, by the account's type (projection/yearFigures.ts#balancesByCategory, owner decision R1), refusing an id shared with a property, debt or permanent-life policy; read YearResult.unassignedCash beside the categories; then apply the selected dollar basis on the Results page.

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.