Calculation catalog › Cash flow and summary

Projection summary savings rate pct annual

Each projection year's savings (employee contributions plus employer match plus surplus invested) as a percentage of that year's gross income, clamped to 0 through 100, and 0 in a year with no gross income. Its pre-retirement average is projection-summary-average-pre-retirement-savings-rate-pct.

Field: Savings rate by year Unit: percent Basis: no dollar basis Computed by: Engine Status: Complete chain

Appears in: mcp (run_projection.summary.savingsRates[].ratePct); mcp (compare_scenarios.a.savingsRates[].ratePct); mcp (compare_scenarios.b.savingsRates[].ratePct)

Calculation records

  • Savings rate by year Formula record, justified by derivation

    Purpose: What share of each year's gross income the household saves, as a percentage.

    Statement: projection/compare.ts#summarizeProjection publishes ProjectionSummary.savingsRates, one entry per ledger row in ledger order. Its year is the row's year, and ratePct = min(100, max(0, S / I × 100)) when I > 0, else 0, where S = contributions + employerMatch + surplusInvested and I = incomes.total, all four from that row in that year's nominal dollars. The rate is in percentage points (20 means 20%). Rounding: none.

    Formula: ratePct_y = I_y > 0 ? min(100, max(0, (C_y + M_y + U_y) / I_y × 100)) : 0

    • C_y: Contributions made in year y (YearResult.contributions) (unit: nominal USD) (domain: nonnegative)
    • M_y: Employer match made in year y (YearResult.employerMatch) (unit: nominal USD) (domain: nonnegative)
    • U_y: Surplus cash flow invested in year y (YearResult.surplusInvested) (unit: nominal USD) (domain: nonnegative)
    • I_y: Gross cash income of year y (YearResult.incomes.total) (unit: nominal USD) (domain: nonnegative)

    Timing: every projection year

    Rounding: none

    Limits:

    • The denominator is the year's gross cash income as the ledger totals it (wages, Social Security, pensions, annuities, ladder income, recurring and one-time income, taxable yield and tax-exempt interest), not wages alone, so a retired year whose surplus is reinvested shows a positive rate with no wages.
    • Savings count the employer match and the surplus the ledger invests as well as the household's own contributions. A year whose savings exceed its gross income is clamped to 100: for example a Social Security year whose surplus, reinvested from a required distribution larger than its spending, exceeds that year's income. The distribution is a withdrawal, not income (incomes.total), so it adds to the savings and not to the denominator. The floor at 0 binds only on a negative savings amount, which the ledger does not publish. A year with no gross income publishes 0 whatever it saves.
    • Evidence: summarizeProjection on four rows of the early-career-match example (wages of $65,000 with $9,000 contributed and a $2,600 match, a retired year with no income, and two Social Security years with reinvested surpluses), entered as ledger rows; the rate reads only the row, so how a projection reaches those amounts does not enter it.

    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: projection-summary-savings-rate-annual.md Mutation receipt: projection-summary-savings-rate-annual.mutation.md

    Evidence tests: compareSummary.breakdowns.evidence.test.ts

    Implemented in: compare.ts

    Is the number of: projection-summary-savings-rate-pct-annual

    Feeds: projection-summary-average-pre-retirement-savings-rate-pct

Engine source: engine/src/projection/compare.ts (ProjectionSummary.savingsRates[].ratePct)

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.