Calculation catalog › Optimizer and comparisons
Claim age co optimization combinations evaluated
Number of household Social Security claim-age combinations evaluated by optimization.
Field: combinationsEvaluated Unit: count Basis: no dollar basis Computed by: Engine Status: Complete chain
Appears in: optimize-page (claim combinations evaluated); report (modeled-findings claim-age "SS claim combinations optimized")
Calculation records
-
Claim age co-optimization Model record, justified by derivation
Purpose: Count the Social Security claim combinations priced and publish the current-claim and joint exact estates, or say why no claim age was searched.
Statement: projection/optimizePlan.ts#optimizePlanCoOptimizingClaimAge publishes ClaimAgeCoOptimization.outcome: already-claimed when every one of the first two Social Security streams was claimed before the start year (socialSecurity/openClaims.ts#isClaimAlreadyMade: birth year plus claim years before the start year), aca-unpriced when the plan as entered has a year whose premium tax credit cannot be priced (each year published with its blocking support codes), no-claims with no stream, no-age-left when a claim is open but the generator offers no candidate (every canonical age is the current claim or before the start year), and searched otherwise; only a searched outcome prices candidates. combinationsEvaluated is one plus the claim candidates decisions/generators.ts#socialSecurityClaimGenerator produced (for each of the first two streams whose claim is not already made, the three canonical claim ages 62y0m, the person's own full retirement age computed from the birth year and labelled FRA, and 70y0m, skipping the stream's own current claim age and any age whose claim year is before the start year), currentClaimExactEstate is the exact after-tax estate of the optimum at the current claim ages, and jointExactEstate is the exact after-tax estate of the best evaluated claim-and-schedule pair, which stays the current-claim estate unless a candidate beats it by more than DEFAULT_CLAIM_SWITCH_MARGIN_DOLLARS, which is 1000 dollars. The claims held fixed are published as alreadyClaimed.
Formula:
combinationsEvaluated = 1 + generatedClaimCandidates (0 candidates unless the outcome is searched); jointExactEstate = the best pair estate that clears currentClaimExactEstate + 1000, else currentClaimExactEstate; winningClaimLabel and winningClaimPatch are null exactly when no pair clears it-
generatedClaimCandidates: Claim candidates from socialSecurityClaimGenerator: for each open stream (at most 2), the canonical ages that differ from its current claim and are not before the start year (unit: count) (domain: integer 0 to 6) -
currentClaimExactEstate: Exact after-tax estate of the optimum at the plan's current claim ages (unit: nominal USD at horizon) (domain: finite) -
jointExactEstate: Exact after-tax estate of the winning claim-and-schedule pair (unit: nominal USD at horizon) (domain: finite) -
claimSwitchMarginDollars: DEFAULT_CLAIM_SWITCH_MARGIN_DOLLARS (unit: nominal USD) (domain: 1000)
Timing: one full conversion optimum per claim combination, the current claim included
Rounding: none; the count is an integer and the estates carry binary floating-point representation
Limits:
- The count includes the current claim and excludes the stream's own current age as a candidate: an open stream claiming at 70y0m generates two candidates and so evaluates three combinations, and a plan with no stream, or whose claims are all made, still evaluates one. The middle point is the person's own full retirement age from socialSecurity/nra.ts#fraForBirthYear. The two estates are RUN-PINNED, not derived: the worksheet states no dollar figure for either, so the test's example carries the value one execution of the co-optimizer produced on the stated plan, and it is evidence of that execution rather than of an independent derivation. What is derived is their equality: with no traditional balance every schedule is empty and no claim clears the 1000-dollar margin, so the joint estate IS the current-claim estate and both claim outputs are null. The strictness of that margin at exactly 1000 dollars was NOT constructed: it needs a candidate whose exact estate sits one margin above the current-claim optimum, which no plan input the worksheet states can produce
- The two estate dollars (1,130,409.20 in the current-claim-wins case) stay pinned from one engine run. Nothing outside the engine derives them, so the test shows only that the run has not changed, not that the figure is right; the independent review of 2026-09-29 asked for an outside derivation, and none is made
- It differs from the Social Security page's whole-plan sweep (social-security-claim-age-sweep): it re-optimizes conversions for each candidate but tries only 62, full retirement age and 70, one stream at a time, compares on the estate alone under every objective, and needs a 1000-dollar margin; the sweep holds the plan's conversions and tries every whole year. Adding the sweep's winner as one more candidate is a later change
- A claim already made, and a canonical age whose claim year is before the start year, is not offered: an early claim cannot be paid for months before its application (20 CFR 404.621(a)(3)), and a later one only six months back ((a)(2)); withdrawal within 12 months and voluntary suspension from full retirement age are not modeled
- Where the premium tax credit cannot be priced in a year of the plan as entered, no claim candidate is priced, since a claim age moves the income that credit depends on and the ledger cannot say which way; the conversion side keeps its own refusal (the tournament's ACA veto)
- Its streams are the first two Social Security streams, where the page's sweep takes the first two with a benefit (socialSecurity/openClaims.ts#claimAgeStreams: a positive entered PIA or an earnings history). A plan with a zero-PIA stream among its first two, on which the ledger can still pay a spouse benefit, is searched differently on the two pages, and a past claim on such a stream is named on the Optimize card only; no example plan has one
Provenance: derived by
claude, implemented byclaude.Independent review:
codexGates (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: claim-age-co-optimization.md Mutation receipt: claim-age-co-optimization.mutation.md
Evidence tests: optimizePlan.evidence.test.ts
Implemented in: optimizePlan.ts; generators.ts; openClaims.ts
Is the number of: claim-age-co-optimization-combinations-evaluated, claim-age-co-optimization-current-claim-exact-estate, claim-age-co-optimization-joint-exact-estate
-
Records that feed this number
-
Social Security claim already made Model record, justified by derivation
Purpose: Decide, for every claim-age search, whether a claim was already made before the plan starts, so it is held rather than re-made.
Statement: socialSecurity/openClaims.ts#isClaimAlreadyMade says a claim at a claim age is already made when its claim year (#claimYearOf: the calendar year of the date of birth plus the claim age's whole years, the months ignored) is before the plan's start year, and open otherwise, the ledger's annual convention. It is the one test every claim-age search applies: #openClaims for the Social Security page's sweep and its month refinement, socialSecurity/analysis/expectedValue.ts#benefitsOnlyRanking, decisions/generators.ts#socialSecurityClaimGenerator for a stream and for each canonical age, projection/optimizePlan.ts#optimizePlanCoOptimizingClaimAge, and the Scenarios page's claim-age lever. A claim already made is held at its own claim age; a claim age whose claim year is before the start year is not offered. Units: calendar years. Rounding: none.
Formula:
claimYear = birthYear + claimAge.years; alreadyMade = claimYear < startYear-
birthYear: The calendar year of the person's date of birth as entered (no birth-month or January-1 adjustment) (unit: calendar year) (domain: integer) -
claimAge.years: The whole years of the stream's claim age; its months are ignored (unit: years) (domain: integer 62 to 70) -
startYear: The plan's first projection year (unit: calendar year) (domain: integer)
Timing: once per claim, against the start year
Rounding: none
Limits:
- The birth-month convention: the test reads years only, so a claim whose month has already passed in the start year is still open, and a claim age's months never move the claim year (born 1964-06-15, a claim at 62y6m falls in 2026, open in 2026 and made from 2027)
- A claim at or after full retirement age that was never filed can still be paid up to six months back (20 CFR 404.621(a)(2)); a claim year before the start year cannot be known to fall within those six months, so the test treats it as made, the conservative side
- Withdrawing an application within 12 months of the first month of entitlement, repaying every benefit (20 CFR 404.640(b)(3), (b)(4); a withdrawn application is treated as never filed, 404.640(d)), is not modeled: a claim made stays made
- Voluntary suspension from full retirement age to 70 (42 U.S.C. 402(z)) is not modeled, so a claim made cannot earn delayed credits here
Provenance: derived by
claude, implemented byclaude.Independent review:
codexGates (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: social-security-claim-already-made.md Mutation receipt: social-security-claim-already-made.mutation.md
Evidence tests: openClaims.evidence.test.ts
Implemented in: openClaims.ts
Feeds: social-security-claiming-sweep-objective, claim-age-co-optimization-combinations-evaluated
-
Engine source: engine/src/projection/optimizePlan.ts
(ClaimAgeCoOptimization.combinationsEvaluated)
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.