Calculation catalog › Cash flow and summary

Income annuity annual

Annuity payments received in a projection year.

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

Appears in: chart (annuity); csv (annuity); scenarios-page (Lifetime income "Annuity"); cash-flow-drilldown (annuityPayment source line)

Calculation records

  • 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

Records that feed this number

  • Whose age and life a pension or annuity is paid on Model record, justified by derivation

    Purpose: Every pension and annuity names its owner, and the income is timed and ended on that person, never on whoever is listed first.

    Statement: Every pension and annuity names its owner (schema v7, decision D-PEOPLE-ORDER, rule R2): a pension's participant, an annuity's (first) annuitant. model/planCrossFieldChecks.ts refuses one with none, a qualified annuity purchase named for anyone but the owner of the traditional account that paid for it (except the surviving spouse once that owner's planning age has ended by the purchase year, IRC 402(c)(9) and 408(d)(3)(C)(ii)(II)), an annuity bought for a person whose planning age has ended by its purchase year (it would never pay), and a pension lump sum rolled into another person's account. projection/internal/annualPensionAndAnnuityIncome.ts#annualPensionAndAnnuityIncome reads that owner through model/plan.ts#guaranteedIncomeOwnerId (which refuses an owner-less account rather than reading the first person): payments start in the owner's birth year plus startAge (not before a purchase year), a pension pays in full while the owner lives and survivorPct/100 to the other household member after, and an annuity pays by its payout form on the owner's life and the other member's (the second annuitant). The pre-start contract value (simulate.ts), the runtime source-series check (internal/ownedNonRothIraRuntimeSourceSeries.ts), the late-start warning, the pension election and the Scenarios levers read the same owner. Plans saved before v7 are given the funding account's owner for a qualified purchase (the surviving spouse after that owner's death), else the person then listed first, never a person dead at the purchase, and the load says so. Every stored plan still opens (the independent review's N1): a purchase of any kind whose annuitant has died by its purchase year is given the other person when that person is alive then, the only one who could have bought it, and is removed, its premium kept where it was, when nobody is; the load says the figures change. Units: calendar years; nominal USD per year. Rounding: none.

    Formula: owner = account.ownerPersonId (required); firstYear = max(birthYear(owner) + startAge, purchase.year); pension: full while owner alive, full * survivorPct/100 to the other after; annuity: full * payoutFraction(form, ownerAlive, otherAlive)

    • owner: The participant or first annuitant the plan names (unit: person id) (domain: a household person)
    • startAge: The owner's age at the first payment (unit: years) (domain: integer)

    Timing: income pass 2, once per projection year

    Rounding: none

    Limits:

    • The joint-and-survivor form reduces on the owner's death only (the contingent form); a form that reduces on the first death of either would be a new payout form, not an owner value
    • Statute and regulation name the lives (irc-72-c-3-A-annuity-measured-on-named-lives); the rule that a qualified contract belongs to the funding account's owner is the IRA's exclusive benefit (IRC 408(a), 408(b)) and the QLAC's employer-or-IRA-owner purchase (Treas. Reg. 1.401(a)(9)-6(q)(1), 1.408-8(a)(3))
    • The annuitization insight, the Monte Carlo annuity sweep and the SPIA candidates name no owner in the plan, so they are written on the person model/peopleOrder.ts#canonicalFirstPerson puts first (the older; between two people born the same day, the sex order female, male, average and then the smaller id by ordinal comparison decide, so for two people with the same birth date and sex renaming the ids can move the annuitant and every figure on that life), whoever is listed first, and each names that person

    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: guaranteed-income-owner.md Mutation receipt: guaranteed-income-owner.mutation.md

    Evidence tests: peopleNamed.evidence.test.ts

    Implemented in: plan.ts; planCrossFieldChecks.ts; annualPensionAndAnnuityIncome.ts

    Feeds: income-pension-annual, income-annuity-annual

  • Joint last-survivor life expectancy of two independent lives Formula record, justified by derivation

    Purpose: The expected years until both members of a couple are dead, used to size joint-and-survivor annuity forms.

    Statement: For lives A and B at ages a and b, each with its own sex, e = 0.5 + sum over t = 1..120 of [1 - (1 - S_A(t))(1 - S_B(t))], where S_A(t) is A's probability of being alive t years on, read from the survival curve from A's floored age (survival-probability-product): for a man or a woman the product of (1 - q) over the t ages, for 'average' the mean of the man's and the woman's; a negative age survives with certainty until it reaches 0. S_B likewise. Lifetimes are independent. Because the table's last row is closed (q = 1 at 119), a life past the table contributes S = 0 and later terms add only the other life's survival. Units: years. Rounding: none.

    Formula: e = 0.5 + sum_{t=1..120} [1 - (1 - S_A(t))(1 - S_B(t))]; S(t) = the survival curve from the floored age at power 1 (for average, the mean of the male and female curves)

    • a, b: Current ages of the two lives, floored (unit: years) (domain: real)
    • S_A(t), S_B(t): Probability each life survives t more years (survival-probability-product) (unit: 1) (domain: [0, 1], nonincreasing in t)
    • e: Expected years until the second death (unit: years) (domain: e >= 0.5)

    Timing: annual survival steps; the 0.5 is the within-year death convention

    Rounding: none

    Limits:

    • Independent lifetimes; shared household hazards are not modeled and can be understated
    • Curtate expectation plus one half; no hazard adjustment is applied to either life
    • Ages are floored, and a negative age survives with certainty until it reaches 0; the caller is expected to pass ages of at least 0
    • Two 'average' lives are each a man or a woman with probability 1/2, independently of each other, so the expectancy is the average over the four equally likely sex pairings (it is bilinear in the two curves); it is not the reading "an opposite-sex couple, order unknown", which averages only the two mixed pairings (70 and 67: 21.5271 against 21.5732 years)
    • The census exposes no joint-expectancy output; income-annuity-annual is named as the nearest family because the joint-and-survivor exclusion multiple shapes that income's taxable portion
    • Every projection of a plan holding a joint-and-survivor annuity whose exclusion ratio is fixed reads it (projection/annuityForms.ts#annuityExclusionMultiple), so such a plan's taxable annuity income and taxes move when the table changes: annuity-purchases-estate with its annuity made 50 percent joint-and-survivor pays lifetime taxes of $349,346 on the 2022 table and $349,238 on 2023

    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: mortality-joint-last-survivor-expectancy.md Mutation receipt: mortality-joint-last-survivor-expectancy.mutation.md

    Evidence tests: mortality.evidence.test.ts

    Implemented in: mortality.ts

    Feeds: income-annuity-annual

  • One-year death probability: SSA's published q(x) Formula record, justified by derivation

    Purpose: The annual death probability of a man or a woman that every survival curve and stochastic-longevity draw reads, straight from SSA's published table.

    Statement: montecarlo/deathProbability.ts#annualMortality(age, sex) (re-exported by montecarlo/mortality.ts), for a man or a woman of age a with x = floor(a): the probability of dying within one year is the death probability SSA publishes for that sex and exact age x in Table 4C6 (ssa-period-life-table), for x from 0 to 118. At 119 and above it is 1: the table's last row is closed, so a life at 119 dies during that year, although SSA prints 0.926604 there. Below 0 it is 0. A person whose sex is 'average' has no single death probability, and the function refuses 'average' with a RangeError; the survival curve builds 'average' as the mixture of the two sexes (survival-probability-product). Units: probability. Rounding: none; the published six decimals.

    Formula: q(a) = q_SSA(sex, floor(a)) for 0 <= floor(a) <= 118; q(a) = 1 for floor(a) >= 119; q(a) = 0 for floor(a) < 0

    • a: Age as passed (unit: years) (domain: real)
    • q_SSA(sex, x): SSA's published probability of dying within one year at exact age x, male or female column (ssa-period-life-table) (unit: 1) (domain: [0, 1], six decimals)
    • q(a): Probability of dying between ages floor(a) and floor(a) + 1 (unit: 1) (domain: 0 <= q(a) <= 1)

    Timing: one-year transition between consecutive integer ages

    Rounding: none

    Limits:

    • The table end: SSA prints q(119) = 0.926604 for both sexes, and e(119) = 0.58, which counts life past 120; the engine closes the table at 119 instead, as the Monte Carlo horizon and the expected-value loops assume. Reading the printed q(119) and closing the table at 120 would change the chance of being alive at 120 from 0 to 6.8e-12 for a man and 2.3e-11 for a woman of 65, 2.8e-7 at 110, 0.00138 at 117 and 0.0734 for someone already 119, and would change no survival probability to an age below 120
    • The period table is applied unchanged to the person's whole remaining life: it describes 2023's death rates, not an individual's risk or future mortality improvement
    • No interpolation within a year of age: a fractional age reads the row of its whole years
    • A negative age returns 0 and is not flagged

    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: mortality-published-death-probability.md Mutation receipt: mortality-published-death-probability.mutation.md

    Evidence tests: mortality.evidence.test.ts

    Implemented in: deathProbability.ts

    Feeds: longevity-survival-percentile-age, monte-carlo-success-rate, monte-carlo-ending-investable-histogram, social-security-expected-present-value, social-security-survivor-switch-pv, social-security-fica-return-ratio, income-annuity-annual, spending-base-annual

  • SSA period life table, 2023 (2026 Trustees Report) Data record, justified by derivation

    Purpose: The death probabilities and life expectancies by age and sex that every survival figure and the longevity questionnaire read.

    Statement: longevity/ssaPeriodLifeTable.ts#SSA_PERIOD_LIFE_TABLE carries SSA's Table 4C6 period life table for 2023, as used in the 2026 Trustees Report: the probability of dying within one year, q(x), and the period life expectancy, e(x), at each exact age x from 0 to 119, for men and for women, as printed (q to six decimals, e to two), read from SSA's page on 2026-09-27. Its source record names the page, the table's caption, the period and Trustees Report years, the read date, an Internet Archive capture of the page and the SHA-256 of the four columns in a canonical text, which the evidence rebuilds from the numbers. #LAST_TABLE_AGE is the last row, 119. #baselineRemainingYears reads e: the printed e(x) at a whole age, linear between the two rows at a fractional one, and for 'average' the mean of the male and female values; the longevity questionnaire prints it as its baseline. #CURRENT_LIFE_TABLE_EDITION names the edition a percentile pick or a saved questionnaire result records, and #storedLifeTableEdition reads a stored figure that names none as made on the 2022 period table of the 2025 Trustees Report, the table the engine carried before. Units: probability; years. Rounding: none; the printed figures.

    Limits:

    • The number-of-lives columns SSA also prints are not carried; nothing reads them
    • A period table: 2023's death rates at every age, applied to a person's whole remaining life, not a cohort projection with future mortality improvement
    • When SSA publishes the next edition, the embedded table trails it until a reviewed data change replaces it; the tax-rule record for the table's edition (SSA Table 4C6) names it and is re-verified yearly (annuallyIndexed, the rules:due queue and verify:quotes, whose quoted introduction and row 65 stop matching the page when the edition changes)
    • The last row is closed: the engine reads q(119) as 1 (mortality-published-death-probability) although the table prints 0.926604 there for both sexes; the printed value is carried, not read
    • Which projections read it. The year-by-year projection reads the table in two cases only: when a plan's amortization-based spending is set to amortize to the age reached with a 25 or 10 percent chance (projection/simulate.ts, through survival-percentile-age and joint-survival-percentile-age, worked out again on every projection), and when a plan holds a joint-and-survivor annuity whose exclusion ratio is fixed (projection/annuityForms.ts, through mortality-joint-last-survivor-expectancy). Such a plan gets new year-by-year figures when the table changes: under-saved-single with its spending amortized to the 25 percent age ends with an after-tax estate of $9,840 on the 2022 table and $212,538 on the 2023 table. The Social Security analysis page, the percentile picker, the questionnaire and Monte Carlo with longevity modelled read it on their own pages; a percentile pick is stored once with its edition and not recomputed

    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: ssa-period-life-table.md Mutation receipt: ssa-period-life-table.mutation.md

    Evidence tests: ssaPeriodLifeTable.evidence.test.ts

    Implemented in: ssaPeriodLifeTable.ts

    Feeds: longevity-survival-percentile-age, monte-carlo-success-rate, monte-carlo-ending-investable-histogram, social-security-expected-present-value, social-security-survivor-switch-pv, social-security-fica-return-ratio, income-annuity-annual, spending-base-annual

  • The survival curve: product of hazard-adjusted one-year survivals, and the 50/50 mixture for 'average' Formula record, justified by derivation

    Purpose: The probability someone alive today is still alive at a later integer age, on SSA's published death probabilities with an optional health hazard.

    Statement: montecarlo/survival.ts#survivalCurve is the engine's one survival curve, for a whole starting age a (at least 0), sex and hazard power h (a positive finite number, default 1). For a man or a woman, survivalTo(t) = product over x = a..a+t-1 of (1 - q(x))^h (mortality-published-death-probability), multiplied left to right, where the factor is 0 when q(x) >= 1; 1 for t <= 0, and 0 once the running product reaches 0; deathProbabilityInYear(t) = survivalTo(t) x (1 - (1 - q(a + t))^h); and deathProbabilityGivenAlive(t) = 1 - (1 - q(a + t))^h, which at h = 1 is q(a + t) itself. For 'average', survivalTo(t) is the mean of the man's and the woman's survivalTo(t) from a, each under the power h: the chance for someone equally likely to be either; deathProbabilityInYear(t) = survivalTo(t) - survivalTo(t + 1); and deathProbabilityGivenAlive(t) = 1 - survivalTo(t + 1)/survivalTo(t), or 1 once survivalTo(t) is 0. Both death probabilities are 0 for t < 0. A fractional age or an invalid hazard is refused. #survivalProbabilityTo(c, sex, g, h) is a view of it: with from = floor(max(c, 0)) and to = floor(g), it returns 1 when to <= from and otherwise the curve from `from` at t = to - from, the same product in the same order. The percentile ages, the hazard solver, the death-age draw, the joint expectancy and the Social Security analysis models read the curve. Units: probability. Rounding: none.

    Formula: S(t) = prod_{x=a}^{a+t-1} (1 - q(x))^h for a man or a woman; S_average(t) = (S_male(t) + S_female(t))/2; S = 1 when t <= 0

    • a, t: Whole starting age and whole years elapsed (unit: years) (domain: integer, a >= 0)
    • q(x): SSA's published one-year death probability at x for the sex (mortality-published-death-probability) (unit: 1) (domain: [0, 1])
    • h: Proportional-hazards power (q' = 1 - (1 - q)^h; h > 1 is worse health), applied to each sex before mixing (unit: 1) (domain: h > 0)
    • S: Probability of being alive t years on (unit: 1) (domain: [0, 1])

    Timing: annual; one factor per integer age from the current age up to, not including, the target

    Rounding: none

    Limits:

    • Ages are floored; a fractional current or target age is not interpolated
    • A target at or below the current age returns exactly 1 without consulting the table
    • survivalProbabilityTo does not validate the hazard power (a non-positive h is used as passed); survivalCurve refuses one
    • annualSurvival is module-private; survivalCurve and survivalProbabilityTo read it, and the evidence reaches it through both
    • 'average' is the 50/50 mixture of the two sexes' curves from the person's current age, not a q(x) table: its one-year chance of dying given alive depends on the starting age, because the survivors of a mixed group become more female with age (at 90: 0.14086 for a mixture formed at 22, 0.14155 at 65 and 0.14327 at 85, against the mean q 0.144317). Every survival probability, the life expectancy (0.5 plus the sum of the survivals) and every expected value linear in the curve are the means of the male and female ones; ages read off the curve and hazard powers solved on it are not (at 65 the ages reached with 50, 25 and 10 percent chance are 85, 91 and 95, against 83, 89 and 94 for a man and 86, 92 and 96 for a woman)
    • The hazard power applies to each sex's curve before mixing: the person is one sex or the other, with that sex's death rates raised to h, and one power serves both halves
    • Two 'average' people are independent mixtures: each is a man or a woman with probability 1/2, independently of the partner, so a couple's expected value, joint expectancy or either-alive probability is the average over the four equally likely sex pairings (man-man, man-woman, woman-man, woman-woman). It is not the reading "an opposite-sex couple, order unknown", which averages only the two mixed pairings; that reading differs by up to 0.135 percent of a benefits-only row on the examples' two couples of 'average' people (all-401k-no-bridge and brokerage-bridge-401k) and changes no top claim age at 0, 2 or 4 percent

    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: survival-probability-product.md Mutation receipt: survival-probability-product.mutation.md

    Evidence tests: survival.evidence.test.ts

    Implemented in: survival.ts

    Feeds: longevity-survival-percentile-age, social-security-expected-present-value, social-security-survivor-switch-pv, social-security-fica-return-ratio, income-annuity-annual, spending-base-annual

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

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.