Calculation catalog › Spending and withdrawals

Portfolio need annual

The nonnegative annual amount that must be supplied by the portfolio after income.

Field: Net portfolio need Unit: US dollars Basis: nominal dollars Computed by: Engine Status: Complete chain

Appears in: results-table (Bucket lens need per year (BucketYearRow.need) and bucket sums); cash-flow-drilldown (needBasedPortfolioWithdrawal sizing)

Calculation records

  • Annual net portfolio need Composition record, justified by derivation

    Purpose: What the portfolio has to supply this year, floored so a surplus is never reported as a need.

    Statement: projection/internal/annualYearResultAssembly.ts#annualYearResultAssembly publishes projection/internal/types/result.ts#YearResult.netPortfolioNeed as Math.max(0, expenses.total + tax + penalties - incomes.total), computed from the committed ledger totals and the settled tax and penalty scalars at the end of the annual pass. It is what the year REQUIRED, not what the portfolio managed to supply: a year that cannot fund it still publishes the need and records the gap in shortfall. A year whose incomes cover every outflow publishes exactly 0. Units: nominal USD per year. Rounding: none.

    Formula: netPortfolioNeed = max(0, expenses.total + tax + penalties - incomes.total)

    • expenses.total: The year's net outflow (unit: usd/year) (domain: nonnegative)
    • tax, penalties: Settled tax and the penalty scalar, which is not inside tax (unit: usd/year) (domain: nonnegative)
    • incomes.total: The year's cash income (unit: usd/year) (domain: nonnegative)

    Timing: once per projection year, published last in the year-result assembly

    Rounding: none

    Limits:

    • Asserted at annualYearResultAssembly, the function that computes the field, with the worksheet's four year-row components, and separately on a real simulatePlan run where the published need equals its own row's published expenses, tax, penalties and incomes
    • The floor case the worksheet names (the same outflows against $120,000 of income) is asserted at the same function, because the published need must be exactly 0 rather than the negative surplus
    • Penalties are a separate term from tax and are not inside it; omitting them is the worksheet's first wrong reading

    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: portfolio-need-annual.md Mutation receipt: portfolio-need-annual.mutation.md

    Evidence tests: annualYearResultAssembly.evidence.test.ts

    Implemented in: annualYearResultAssembly.ts; result.ts

    Is the number of: portfolio-need-annual

    Feeds: spending-shortfall-annual

Records that feed this number

  • 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

  • Annual net expense total Composition record, justified by derivation

    Purpose: The year's actual net outflow, and which expense members compose it.

    Statement: projection/internal/annualExpenseSummary.ts#annualExpenseSummary publishes projection/internal/types/yearLedger.ts#YearExpenses.total as baseSpending + oneTimeGoals + debtService + propertyCosts + healthcare + insurancePremiums + careCost - ltcBenefit, kept in that left-to-right association because regrouping the LTC pair can move the last bit. careCost is gross and ltcBenefit offsets it. guardrailFactor is descriptive: the cut is already inside baseSpending and is never multiplied in again. requiredSpending, targetSpending, idealSpending, excessSpending and intendedSpending are overlapping layer summaries, not additional members, and intendedSpending is the no-cut request and need not equal this total. Units: nominal USD per year. Rounding: none.

    Formula: total = baseSpending + oneTimeGoals + debtService + propertyCosts + healthcare + insurancePremiums + careCost - ltcBenefit

    • baseSpending: Recurring lifestyle spending already net of the guardrail cut (unit: usd/year) (domain: nonnegative)
    • oneTimeGoals: One-time goals funded this year (unit: usd/year) (domain: nonnegative)
    • careCost, ltcBenefit: Gross care-episode cost and the policy benefit offsetting it (unit: usd/year) (domain: nonnegative; the benefit is subtracted)
    • guardrailFactor: Discretionary multiplier already applied inside baseSpending (unit: 1) (domain: descriptive only)

    Timing: once per projection year, at the expense summary

    Rounding: none

    Limits:

    • Asserted at the annualExpenseSummary seam with the worksheet's eight member amounts, because the worksheet states them as published year-row components: a single real plan cannot present a chosen base spending, one-time goal, debt service, property cost, healthcare charge, insurance premium, gross care cost and LTC benefit at those exact values simultaneously
    • Cross-checked on a real simulatePlan run: the published total of that year row equals the same row's own eight published members in the same association
    • The guardrail factor and intended spending are asserted NOT to enter: the test drives the seam with a factor of 0.90 and an intended request of $100,000 and shows neither changes the total

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

    Evidence tests: annualExpenseSummary.evidence.test.ts

    Implemented in: annualExpenseSummary.ts; yearLedger.ts

    Is the number of: spending-total-annual

    Feeds: portfolio-need-annual, display-total-spending-annual

  • Annual penalties: early withdrawal plus the IRC 4974 excise Composition record, justified by derivation

    Purpose: The two penalty channels a year composes, and why neither is part of tax.

    Statement: projection/internal/types/result.ts#YearResult.penalties is composed by projection/internal/annualFundingCandidateEvaluation.ts#annualFundingCandidateEvaluation as projection/internal/annualFundingWithdrawalEffects.ts#annualFundingWithdrawalEffects reporting penaltyExcludingRmdShortfallExcise plus the IRC 4974 excise from rmd/rmdShortfallExcise.ts#computeRmdShortfallExcise. The early-withdrawal rate is 10 percent on pre-age-59-and-a-half taxable traditional withdrawals, inherited distributions are never subject to it, and the excise prices max(0, required - distributed by deadline) at the stated rate, whose post-SECURE-2 default is 25 percent, once for each obligation. In the calendar year a surviving spouse's election to treat an inherited IRA as her own takes effect, after the year of the owner's death, that IRA owes only its owner RMD (Treas. Reg. 1.408-8(c)(3)) and leaves the beneficiary's same-decedent group ((e)(2)(i)), so projection/internal/annualInheritedIraDistributions.ts#annualInheritedIraDistributions puts it in no beneficiary obligation. Penalties stay outside tax, AGI and MAGI. Units: nominal USD per year. Rounding: none.

    Formula: penalties = penaltyExcludingRmdShortfallExcise + rmdShortfallExciseTax; early = 0.10 x penalizable traditional; excise = rate x max(0, required - distributed)

    • penalizable traditional: Taxable pre-59.5 traditional withdrawal (unit: usd) (domain: nonnegative; 0 for inherited accounts)
    • required: Required minimum for the obligation (unit: usd) (domain: nonnegative)
    • distributed: Distributed by the statutory deadline (unit: usd) (domain: nonnegative)
    • rate: IRC 4974 rate (unit: 1) (domain: 0.25 default from 2023)

    Timing: once per accepted funding evaluation in the projection year

    Rounding: none

    Limits:

    • Asserted twice. At the two component producers: annualFundingWithdrawalEffects reports 2,000 of penaltyExcludingRmdShortfallExcise on the worksheet's 20,000 pre-59.5 taxable traditional withdrawal, and computeRmdShortfallExcise prices the worksheet's 12,000 required against 4,000 distributed as an 8,000 shortfall and a 2,000 excise at the default 25-percent rate with no relief elected. And as the published penalties of a real simulatePlan 2026 row that carries both channels at once: a 50-year-old whose only portfolio is a traditional IRA and whose 16,000 of required lifestyle plus the 2,000 excise drive a need-based withdrawal of exactly 20,000 under a zero-rate test calculator, alongside an inherited Roth account whose completed five-year deadline observation (opening benefit 12,000, 4,000 distributed by the 2026 deadline) prices the excise without replaying any cash
    • Plan assumptions beyond the worksheet's inputs for that ledger year: filing single in KY at a zero state rate with a zero-rate test tax calculator, so tax is 0 and the whole need is spending plus penalties; the test asserts the 10-percent rate relation W = S + excise + 0.10 W closes at exactly 20,000
    • The worksheet's fourth wrong reading is asserted as a rule: the same withdrawal-effects call on an inherited traditional account reports a zero penalty
    • Restated 2026-09-30 and reviewed by Grok the same day: in an election year the elected IRA enters no beneficiary obligation. Asserted in simulate.spousalElectionYearOwnerRmd.test.ts on a 75-year-old spouse whose mid-2026 election takes effect: the owner RMD of 4,065.04 (100,000 / 24.6) is paid by 1,000 before the election and 3,065.04 after and is the one obligation, and the beneficiary figure of 6,689.19 (99,000 / 14.8) stays on the account's evidence as the trigger only, so the excise is 0 and not the 1,672.30 a second, unpaid obligation would charge

    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: tax-penalties-annual.md Mutation receipt: tax-penalties-annual.mutation.md

    Evidence tests: annualFundingCandidateEvaluation.evidence.test.ts

    Implemented in: result.ts; annualFundingCandidateEvaluation.ts; annualFundingWithdrawalEffects.ts; rmdShortfallExcise.ts; annualInheritedIraDistributions.ts

    Is the number of: tax-penalties-annual

    Feeds: portfolio-need-annual, display-total-spending-annual, display-tax-plus-penalties-annual, scenario-lifetime-penalties

  • Annual composed tax Composition record, justified by derivation

    Purpose: What the year's published tax figure is composed of, and what it excludes.

    Statement: projection/internal/types/result.ts#YearResult.tax is the amount produced by the composed calculator built by tax/federalTax.ts#combineTaxCalculators: the federal total of regular income tax plus AMT plus NIIT, plus the state calculator amount, plus any further composed calculator amounts. Penalties are excluded from it, from AGI and from MAGI. Units: nominal USD per year. Rounding: none; the composition is an ordered fold.

    Formula: tax = federalTotal + stateAmount + sum of further calculator amounts

    • federalTotal: Regular income tax + AMT + NIIT (unit: usd/year) (domain: nonnegative)
    • stateAmount: State calculator amount (unit: usd/year) (domain: nonnegative)
    • penalties: Early-withdrawal and IRC 4974 amounts (unit: usd/year) (domain: never a member of tax)

    Timing: once per accepted tax evaluation in the projection year

    Rounding: none

    Limits:

    • The worksheet treats the federal and state amounts as outputs independently evidenced by their own worksheets and checks only the annual composition boundary, so the test composes two calculators that report the worksheet's stated 12,000 federal total and 3,000 state amount and asserts the composed 15,000 through both compute and computeResult. Those two calculators are test doubles standing in for the evidenced federal and state records; this record makes no claim about federal or state tax arithmetic
    • The 500 of penalties is asserted as excluded by composing the same calculators and showing the amount is unchanged: penalties never enter the calculator chain at all

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

    Evidence tests: federalTax.evidence.test.ts

    Implemented in: result.ts; federalTax.ts

    Is the number of: tax-total-annual

    Feeds: portfolio-need-annual, surplus-invested-annual, display-tax-plus-penalties-annual, display-total-spending-annual

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

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.