Calculation catalog › Cash flow and summary

Income total annual

All modeled income received in a projection year before expenses and taxes.

Field: Total income Unit: US dollars Basis: nominal dollars Computed by: Engine Status: Complete chain

Appears in: chart (income); csv (totalIncome); report (yearLedger); results-table (income total); results-table (Income column); report (year-ledger block income and ReportPage year table); scenarios-page (Annual "Gross income" and lifetime "Total gross income"); results-headline (Depletion narrative "Income doesn't stop: about $X" (deflated))

Calculation records

  • Annual cash-income total Composition record, justified by derivation

    Purpose: The year row's single spendable-income figure, and which published members compose it.

    Statement: projection/internal/types/yearLedger.ts#YearIncomes.total is the year's cash income: wages + socialSecurity + pension + annuity + tipsLadder + recurring + oneTime + taxableYield + taxExemptInterest, folded in that operand order by projection/simulate.ts#simulatePlan after every income pass has committed. The separately published taxableInterest, ordinaryDividends and qualifiedDividends characterize taxableYield for tax purposes and never join the sum again. Units: nominal USD per year. Rounding: none; the sum is a binary float.

    Formula: total = wages + socialSecurity + pension + annuity + tipsLadder + recurring + oneTime + taxableYield + taxExemptInterest

    • wages, socialSecurity, pension, annuity: Person-owned stream payments this year (unit: usd/year) (domain: nonnegative)
    • tipsLadder: TIPS-ladder coupons and maturing principal (unit: usd/year) (domain: nonnegative; 0 without ladders)
    • recurring, oneTime: Household streams inside their year window (unit: usd/year) (domain: nonnegative)
    • taxableYield: Taxable interest plus all dividends distributed by taxable accounts (unit: usd/year) (domain: nonnegative)
    • taxExemptInterest: Federally exempt municipal interest, cash-real but never ordinary income (unit: usd/year) (domain: nonnegative)

    Timing: once per projection year, after the three income passes and the ladder pass

    Rounding: none

    Limits:

    • Asserted on a real simulatePlan run whose single year realizes all nine members at the worksheet's amounts. Plan assumptions beyond the worksheet's inputs: zero general inflation, zero account returns, a single 68-year-old filing single in KY with a zero state rate, the Social Security stream claimed at that cohort's full retirement age so the claim factor is exactly 1 and the earnings test withholds nothing, and four separate taxable accounts so the interest, wholly qualified dividend, wholly ordinary dividend and municipal sleeves each carry one worksheet figure exactly
    • Reinvestment is switched off on those taxable accounts so the generated yield is visible as cash; the published total counts taxableYield either way, because the field sums characterized yield rather than yield that reached the household cash hub
    • The composition is the published members only. The year also carries realized gains, Roth conversions, forced distributions and loan proceeds, none of which are cash income members: they reach the ledger through withdrawals, transfers and the cash-flow report instead

    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: income-total-annual.md Mutation receipt: income-total-annual.mutation.md

    Evidence tests: simulate.evidence.test.ts

    Implemented in: yearLedger.ts; simulate.ts

    Is the number of: income-total-annual

    Feeds: portfolio-need-annual

Records that feed this number

  • Annual annuity income under the joint-survivor payout form Model record, justified by derivation

    Purpose: What an annuity contract pays in a projection year once the payout form is applied.

    Statement: projection/internal/annualPensionAndAnnuityIncome.ts#annualPensionAndAnnuityIncome starts an annuity at its owner's startAge (the first annuitant the plan names, required since schema v7; guaranteed-income-owner), compounds monthlyAmount x 12 by its annual COLA over the years since that start age, applies the selected payout form, and publishes the result as projection/internal/types/result.ts#YearResult.incomes.annuity. Under payoutForm.kind "jointSurvivor" the payment continues after the owner's death to the other household member at survivorPct/100 of the full amount; "lifeOnly" stops at the owner's death and "periodCertain" continues only inside its guarantee window. Units: nominal USD per year. Rounding: none.

    Formula: full = monthlyAmount * 12 * (1 + colaPct/100)^(age - startAge); jointSurvivor pays full * survivorPct/100 after the owner dies

    • monthlyAmount: Monthly payment at the start age (unit: usd/month) (domain: nonnegative)
    • colaPct: Annual cost-of-living adjustment on the contract (unit: percent/year) (domain: 0 is a level contract)
    • survivorPct: Percent continuing to the surviving joint annuitant (unit: percent) (domain: 1..100; requires a two-person household)

    Timing: income pass 2, once per projection year

    Rounding: none

    Limits:

    • Asserted on a real simulatePlan run over 2028-2029. Plan assumptions beyond the worksheet's inputs: a two-person household filing jointly in KY with a zero state rate, zero inflation and zero account returns, the owner's planning age set to 65 so the run makes them dead from 2029, the joint annuitant's planning age set to 95, and an already-owned contract with no purchase event so no exclusion ratio is derived
    • taxablePct is the contract's tax character, not its cash amount; the published income field is the same under any character
    • Period-certain and life-only are the other two forms and are not this record's claim
    • Restated 2026-09-28 under decision D-2027-ROLLOVER. A PURCHASE DATED BEFORE THE START, a registered limit of the convention and not corrected: a contract whose purchase year is before the projection start year is treated as already paid, so the projection pays its annuity and takes no premium from the funding account, whose entered balance is read as already net of it. That is right when the household entered the balance after buying; when the balance was entered before the purchase and not updated (a plan saved in the purchase year and reopened the next), the premium is counted twice, once in the balance and once as the contract it bought. On the derivation's U1 household (a $100,000 non-qualified contract bought in 2026 from the brokerage, $550 a month from 67), as pinned below: at a 2026 start the purchase lowers ending net worth by $151,691.59; at a 2027 start it raises it by $455,159.49. History: the derivation itself measured $147,623 and $454,837, and the changes the next limit lists have moved both since. The independent check measured every tax qualification the same way. The projection cannot tell which case it has, so it does not deduct the premium (which would double-deduct it for every household that did update the balance); it names each such purchase in a warning with the premium and the funding account, and says to lower that balance by the premium if it still includes it (projection/preStartEvents.ts#preStartEvents)
    • The U1 sizes are pinned to the cent in a repository test (2026-09-29, review L7): packages/planner-ui/src/planner/preStartEvents.figures.test.ts builds the household from the example couple and asserts -$151,691.59 from a 2026 start and +$455,159.49 from a 2027 start (-$147,622.51 and +$455,165.79 before the 2026-09-29 change to CMS's published IRMAA amounts, -$147,623.51 from 2026 before Kentucky's part-year slice took its whole standard deduction on 2026-10-07, and -$147,615.81 before the slice received the characterized retirement rows on 2026-10-08, when Kentucky's pension exclusion began to meet the in-plan Roth conversion received while resident, up to the whole $31,110 of 2025 Schedule P: the household are Kentucky residents who move to Florida in November; DOCS/calculations/taxes/scripts/part-year-split-years.mjs lists the split year's slices and rows). The review measured +$454,836.90 from 2027 before main's #761 (D-EXAMPLE-SOURCE-SWITCH) priced a saved example's 2027 premium tax credit from a 2027 start; the same test on main at 9676392f gives +$455,165.79

    Provenance: derived by codex, implemented by claude.

    Independent review: grok

    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: income-annuity-annual.md Mutation receipt: income-annuity-annual.mutation.md

    Evidence tests: annualPensionAndAnnuityIncome.evidence.test.ts

    Implemented in: annualPensionAndAnnuityIncome.ts; result.ts; preStartEvents.ts

    Is the number of: income-annuity-annual

    Feeds: income-total-annual

  • Annual one-time income Formula record, justified by derivation

    Purpose: What a one-time income stream pays, and only in its named year.

    Statement: projection/internal/otherIncomeStreams.ts#otherIncomeStreams pays a one-time stream only in the calendar year it names, and only while any household member is alive, as amount x inflFactor when inflationAdjusted is true and amount alone when it is false; annualIncomeSetup folds the row into the published projection/internal/types/result.ts#YearResult.incomes.oneTime. Every other year publishes 0 for that stream. Tax treatment routes the income for tax and never changes the cash amount. Units: nominal USD in the payment year. Rounding: none.

    Formula: oneTime = amount * (inflationAdjusted ? inflFactor : 1) when year equals the stream's year and any household member is alive, else 0

    • amount: Payment in today's dollars (unit: usd) (domain: nonnegative)
    • inflFactor: Cumulative general-inflation factor from the projection start year (unit: 1) (domain: positive)
    • year: The single calendar year the stream names (unit: calendar year) (domain: integer)

    Timing: income pass 2, once per projection year

    Rounding: none

    Limits:

    • Asserted on a real simulatePlan run over 2028-2032. Plan assumptions beyond the worksheet's inputs: a single person filing single in KY with a zero state rate, no accounts, and the worksheet's 1.12 cumulative inflation factor supplied as a per-year inflation path of 12 percent in the first projection year and 0 afterwards, so that every year from 2029 on carries exactly that factor
    • The neighbouring years are asserted too, because "pays only in its year" is half the claim
    • taxTreatment "capitalGain" is the row's character, not an exclusion from cash income
    • Restated 2026-09-28 under decision D-2027-ROLLOVER: a stream whose year is before the projection start year pays nothing in the projection, as before, and the projection now names it in a warning (projection/preStartEvents.ts#preStartEvents): the income is dated before the plan starts, so it is not counted, and if it has not arrived the household moves it to the start year or later. From a 2027 start the derivation's U1 household's $50,000 inheritance dated 2026 dropped out with only an editor note to say so

    Provenance: derived by codex, implemented by claude.

    Independent review: grok

    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: income-one-time-annual.md Mutation receipt: income-one-time-annual.mutation.md

    Evidence tests: otherIncomeStreams.evidence.test.ts

    Implemented in: otherIncomeStreams.ts; result.ts; preStartEvents.ts

    Is the number of: income-one-time-annual

    Feeds: income-total-annual

  • Annual pension income, with COLA and survivor continuation Model record, justified by derivation

    Purpose: What a pension pays in a projection year, including the share a surviving spouse keeps.

    Statement: projection/internal/annualPensionAndAnnuityIncome.ts#annualPensionAndAnnuityIncome pays a pension from the owner's startAge onward (the owner the plan names, required on every pension since schema v7; guaranteed-income-owner) as monthlyAmount x 12 compounded by its annual COLA over the years since the start age, and publishes it as projection/internal/types/result.ts#YearResult.incomes.pension. While the owner is alive the full amount pays; after the owner dies it continues to a surviving household member at survivorPct/100 of that full amount, and stops when no survivor is alive. A pension commuted by a lump-sum election pays nothing. Units: nominal USD per year. Rounding: none.

    Formula: full = monthlyAmount * 12 * (1 + colaPct/100)^(age - startAge); paid = full while the owner lives, full * survivorPct/100 to a survivor after the owner dies

    • monthlyAmount: Monthly pension at the start age (unit: usd/month) (domain: nonnegative)
    • colaPct: Annual cost-of-living adjustment (unit: percent/year) (domain: 0 is a fixed-nominal pension)
    • age - startAge: COLA years elapsed since payments began (unit: years) (domain: integer at or above 0)
    • survivorPct: Percent of the benefit continuing to a surviving spouse (unit: percent) (domain: 0..100)

    Timing: income pass 2, once per projection year

    Rounding: none

    Limits:

    • Asserted on a real simulatePlan run over 2028-2030. Plan assumptions beyond the worksheet's inputs: a two-person household filing jointly in KY with a zero state rate, zero inflation and zero account returns, the owner's planning age set to 65 so the run's own longevity makes them dead from 2029 on, and the survivor's planning age set to 95 so the survivor continuation is the only thing being read
    • The COLA count is driven by the owner's would-be attained age, not by whether they are alive: the survivor keeps a benefit that has continued to compound
    • A lump-sum election is a separate path this record does not price; the test’s pension carries no election

    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: income-pension-annual.md Mutation receipt: income-pension-annual.mutation.md

    Evidence tests: annualPensionAndAnnuityIncome.evidence.test.ts

    Implemented in: annualPensionAndAnnuityIncome.ts; result.ts

    Is the number of: income-pension-annual

    Feeds: income-total-annual

  • Annual recurring household income Formula record, justified by derivation

    Purpose: What a recurring income stream pays inside its year window.

    Statement: projection/internal/otherIncomeStreams.ts#otherIncomeStreams pays a recurring stream in every year inside its inclusive startYear..endYear window while any household member is alive, as annualAmount x inflFactor when inflationAdjusted is true and annualAmount alone when it is false; annualIncomeSetup folds the rows into the published projection/internal/types/result.ts#YearResult.incomes.recurring. A null startYear or endYear leaves that side of the window open. Tax treatment routes the income for tax and never changes the cash amount. Units: nominal USD per year. Rounding: none.

    Formula: recurring = annualAmount * (inflationAdjusted ? inflFactor : 1), paid when startYear <= year <= endYear and any household member is alive

    • annualAmount: Stream amount in today's dollars (unit: usd/year) (domain: nonnegative)
    • inflFactor: Cumulative general-inflation factor from the projection start year (unit: 1) (domain: positive)
    • startYear, endYear: Inclusive calendar-year window; null leaves that side open (unit: calendar year) (domain: integer or null)

    Timing: income pass 2, once per projection year

    Rounding: none

    Limits:

    • Asserted on a real simulatePlan run over 2028-2030. Plan assumptions beyond the worksheet's inputs: a single person filing single in KY with a zero state rate, no accounts, and the worksheet's 1.08 cumulative inflation factor supplied as a per-year inflation path of 8 percent in the first projection year and 0 afterwards, because a single constant rate cannot give a two-year cumulative factor of exactly 1.08
    • The gate is household-wide, not person-owned: a recurring stream has no owner and stops only after the last household death
    • taxTreatment "none" is a tax-free row that still joins cash income; the field is cash, not taxable income

    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: income-recurring-annual.md Mutation receipt: income-recurring-annual.mutation.md

    Evidence tests: otherIncomeStreams.evidence.test.ts

    Implemented in: otherIncomeStreams.ts; result.ts

    Is the number of: income-recurring-annual

    Feeds: income-total-annual

  • Annual taxable yield distributed by a taxable account Formula record, justified by derivation

    Purpose: Interest plus all dividends, the single taxable-yield figure the income total counts.

    Statement: projection/internal/distributedTaxableYieldRows.ts#distributedTaxableYieldRows computes, for each taxable account whose start-of-year balance is strictly positive, taxableGross = interest + dividends, and projection/internal/annualIncomeSetup.ts#annualIncomeSetup folds those rows into the published projection/internal/types/result.ts#YearResult.incomes.taxableYield. Qualified and ordinary dividends partition dividends and are not added again; federally tax-exempt interest is computed on the same balance from taxExemptInterestYieldPct but is a separate published member and never joins taxable yield. Units: nominal USD per year. Rounding: none.

    Formula: taxableYield = startBalance * interestYieldPct / 100 + startBalance * dividendYieldPct / 100

    • startBalance: Start-of-year balance, the prior year's close (unit: usd) (domain: strictly positive, else no row)
    • interestYieldPct, dividendYieldPct: Account yields in percent of the start balance (unit: percent/year) (domain: nonnegative)
    • taxExemptInterestYieldPct: Municipal sleeve yield, published separately (unit: percent/year) (domain: nonnegative; never in taxable yield)

    Timing: once per taxable account per projection year, before the wage pass

    Rounding: none

    Limits:

    • Asserted on two real simulatePlan runs of the projection's first year, where the start-of-year balance is the plan balance. Plan assumptions beyond the worksheet's inputs: a single 51-year-old filing single in KY with a zero state rate, zero inflation, zero account return and cost basis equal to balance; the account carries no allocation, so the defaults case exercises the no-blend fallback
    • The dividend partition is asserted alongside the total, because the worksheet's first wrong reading is adding the partition back on top of its own parent
    • Taxable yield joins the income total whether or not it is reinvested; reinvestment only decides whether the same dollars reach the household cash hub

    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: income-taxable-yield-annual.md Mutation receipt: income-taxable-yield-annual.mutation.md

    Evidence tests: distributedTaxableYieldRows.evidence.test.ts

    Implemented in: distributedTaxableYieldRows.ts; annualIncomeSetup.ts; result.ts

    Is the number of: income-taxable-yield-annual

    Feeds: income-total-annual

  • Annual wage income for one stream Formula record, justified by derivation

    Purpose: What a wage stream pays in a projection year, after real raises and inflation.

    Statement: projection/internal/wageIncomeStreams.ts#wageIncomeStreams emits one row per wage stream that pays this year, in plan.incomes order, with amount = annualGross x (1 + realGrowthPct/100)^(year - startYear) x inflFactor, computed in that operand order; annualIncomeSetup folds those rows into the published projection/internal/types/result.ts#YearResult.incomes.wages. A stream is skipped when its owner is not alive, or when the owner has attained the stream's stop age: endAge when the stream names one, else the person's retirementAge, and neither when both are null. Wages run before Social Security because pass 3's earnings test reads the per-person wage totals. Units: nominal USD per year. Rounding: none.

    Formula: wages = annualGross * (1 + realGrowthPct/100)^(year - startYear) * inflFactor

    • annualGross: Gross wage at the projection start year (unit: usd/year) (domain: nonnegative)
    • realGrowthPct: Annual real raise, in percent (unit: percent/year) (domain: absent reads as 0)
    • year - startYear: Elapsed projection years, computed operand for operand at the phase (unit: years) (domain: integer at or above 0)
    • inflFactor: Cumulative general-inflation factor from the projection start year (unit: 1) (domain: positive)
    • stopAge: endAge when present, else the person's retirementAge (unit: attained age) (domain: either may be null; attaining it stops the row)

    Timing: income pass 1, once per projection year, before Social Security

    Rounding: none

    Limits:

    • Asserted on a real simulatePlan run over 2028-2030. Plan assumptions beyond the worksheet's inputs: a single person filing single in KY with a zero state rate, no accounts, and the worksheet's 1.08 cumulative inflation factor supplied as a per-year inflation path of 8 percent in the first projection year and 0 afterwards, because a single constant rate cannot give a two-year cumulative factor of exactly 1.08
    • The stop age is attained-age based: a stream with endAge 65 pays at attained age 64 and stops at 65, so the worksheet's paying year is the last one before the stop
    • The engine preserves plan order across wage rows rather than pre-summing them, so the last binary digit of a multi-row wage total depends on that order; the test asserts a single row

    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: income-wages-annual.md Mutation receipt: income-wages-annual.mutation.md

    Evidence tests: wageIncomeStreams.evidence.test.ts

    Implemented in: wageIncomeStreams.ts; result.ts

    Is the number of: income-wages-annual

    Feeds: income-total-annual, social-security-benefit-annual

  • Annual tax-exempt interest, and the ACA-year maximum Formula record, justified by derivation

    Purpose: Municipal interest the year really generates, raised to an attested household total in an ACA year.

    Statement: projection/internal/types/result.ts#YearResult.taxExemptInterest is the account-generated federally tax-exempt interest total, except in a year with an active ACA contract whose taxExemptInterest state is known, when it is max(attested household total, plan-generated subset). Generated account interest is the stated taxable-account balance times taxExemptInterestYieldPct/100. Cash and balances always follow the generated figure. Units: nominal USD per year. Rounding: none.

    Formula: generated = B x q; published = known ACA contract ? max(attested, generated) : generated

    • B: Taxable-account start-of-year balance (unit: usd) (domain: nonnegative)
    • q: Tax-exempt interest yield (unit: 1) (domain: taxExemptInterestYieldPct / 100)
    • attested: Household tax-exempt interest on a known ACA contract (unit: usd/year) (domain: nonnegative)

    Timing: once per projection year, after the account yield pass

    Rounding: none

    Limits:

    • Both cases asserted as the published field of a real simulatePlan 2026 row: one municipal sleeve at the worksheet's 200,000 balance and 2-percent tax-exempt yield, zero account return and zero inflation. Plan assumptions beyond the worksheet's inputs: a 60-year-old filing single in KY at a zero state rate, the sleeve does not reinvest its yield, and Case B adds a 2026 ACA contract at a 500-per-month enrollment premium whose taxExemptInterest state is known at the worksheet's 6,000; the enrollment premium is what makes the contract active and is not read by this field
    • The ACA maximum characterizes income only; the test also asserts that the generated 4,000 remains the cash-real income member in Case A

    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: year-result-tax-exempt-interest.md Mutation receipt: year-result-tax-exempt-interest.mutation.md

    Evidence tests: simulate.evidence.test.ts

    Implemented in: result.ts; annualAggregateRothConversionPhase.ts

    Is the number of: year-result-tax-exempt-interest

    Feeds: income-total-annual, tax-total-annual

Engine source: engine/src/projection/internal/types/result.ts (YearResult.incomes.total)

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.