Repository navigation
Add configurable Social Security COLA means test - #10486
Merged
Merged
Conversation
# Conflicts: # policyengine_us/tests/code_health/test_rest_python_groups.py
Main's #10401 (ee6c1c5) added the ss-calculator-engine and ss-calculator-differential Rest groups beside this branch's social-security-inputs group. The Makefile keeps all three groups, their file lists and recipes, and lists all three files in REST_LISTED_TESTS so the core and remaining groups don't run them twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain E3 changes in ten overlapping files; upstream versions match the original stacked base. Remove the released benefit-lever changelog fragment.
MaxGhenis
marked this pull request as ready for review
October 29, 2026 11:33
Contributor
Author
|
US + core hub merge audit, us#10486 at
|
This was referenced Nov 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds an opt-in Social Security COLA means test shared by reported benefits and the supplied-record household calculator. It uses prior-year MAGI excluding Social Security, configurable indexed income lines, cohort or all-beneficiary scope, and statutory or chained CPI-U COLAs. The reform defaults off.
Stack and main merge
Both prerequisites, #10400 and #10401, are merged. The original stack and subsequent main merges remain in this branch's history; no history was rewritten. The earlier main merge retained this reform's changes in the overlapping benefit-lever files and brought in #10484. This round merges main
cbe322b51c6c9f791a318a32f0b093abee683fabin merge commit7f3724a092b7ad4301fc595e12487738be61b5d7. It brings upstream California CAPI/SSI and state LIHEAP changes, SSA YAML routing toother-shard-3, and behavioral-test speedups. The CAPI and SSI changes do not touch the COLA route.The earlier continuation corrected the calculated-base fixture, preserved the disabled-policy helper path, restored the calculator's legacy exact-year channel, and separated automatic decisions from per-person overrides. This round fixes the overrides' interactions with income-history inference and dataset extension and strengthens the existing isolation and builder coverage.
Policy mechanics and choices
OACT A9, 2026 Trustees Report basis, directs: “Use prior tax year income data for this determination.” A9 withholds COLAs above CPI-U-indexed lines, pays chained CPI-U below them, and uses chained CPI-U before receipt. The annual TaxUnit variable
social_security_cola_means_test_magiexcludes taxable Social Security and retains applicable deductions and IRMAA addbacks, following the linked memorandum, provision 5 and H.R. 6489 section 103(d). January payment yearyreads income and filing status from tax yeary−1.The configurable lever defaults to statutory COLAs below the line and first-eligible cohorts from 2026. Chained CPI-U and all beneficiaries are selectable. A separate pre-receipt index defaults to following the below-line choice; pre-receipt events use that index without an income test. The
a9preset selects all beneficiaries from the December 2028 COLA, chained CPI-U below the line and before receipt, and $85,000/$170,000 lines in 2018 dollars indexed by CPI-U. Its first affected checks are January 2029.Effective year/month, cohort start, filing groups, lines, dollar base year, CPI-U lag and rounding are parameters. The 2026 first-eligibility boundary is a configurable scenario choice, as documented by
first_eligible_year.yaml. Evaluated prior-year decisions require positive scenario lines; the defaults are neutral zero placeholders. Never/always sensitivities and pre-receipt index-only events do not read those lines.Only income strictly above a line withholds the COLA; equality is paid. Single, head-of-household and separate filers use the single line; joint filers use joint. Surviving spouses default to joint through a configurable mapping because A9 does not name that status. Numeric parameter selectors preserve compatibility with Core's parameter engine.
Each exposed event uses one joint numerator:
1if withheld, otherwise1 + adjusted_COLA, divided by1 + statutory_COLA. An active A5 alternative rate overrides the selected index; the percentage-point change is then added with a zero floor. Withholding overrides the entire increase. It does not reset the index high-water base or create a catch-up COLA. Neither withholding nor a downward adjustment reduces the nominal benefit level. Disabling the policy later preserves earlier effects.Calculated public routes bypass population factors already applied in the calculator's ledger. Reported routes retain the stable multiplication order. Across-the-board and payable factors apply once, before the calculator's earnings test. Receipt starts at the first check month, and payment outputs avoid unnecessary next-January income reads.
Override behavior and review fixes
The annual Person inputs
social_security_retirement_cola_override,social_security_disability_cola_override,social_security_survivors_cola_override,social_security_dependents_cola_overrideandss_calculator_cola_overrideuse-1=automatic(default),0=paid, and1=withheld. Each component'ssocial_security_X_cola_means_test_withheldvariable resolves its automatic decision plus its override. With the reform enabled, the calculator uses its own override, then the component override, then the automatic decision. The legacy boolean inputs remain the A5 channel when the reform is disabled; when it is enabled, automatic/new override decisions take precedence over those booleans.These controls are exact-year decisions. Integers outside
-1,0and1are accepted by Core's input storage and raiseValueErrorwhen an active exact-year decision reads them. Core truncates floats to integers first:0.5→0,1.5→1, and-1.5→-1. A multi-year input such asyear:2027:2creates no exact annual decision for either year. A yearless scalar uses the simulation's default input year. Calculating a default value does not turn it into supplied input, and people with no explicit control in a situation retain automatic decisions.Current-law Medicare compatibility.
earliest_input_yearexcludes the four new component controls by their exact names, so supplying a neutral or nonneutral decision in another year does not change IRMAA's income lookback. The calculator control is already excluded by its existing prefix rule. The exclusion does not include the legacysocial_security_*_cola_withheldinputs: they retain main's behavior. With the reform at its defaults, adding a new control leaves current-law calculation outputs unchanged. The regression pins the 70-year-old, $500,000-earnings 2027 case at $463,100.31 IRMAA MAGI / $7,790.40 annual Part B premiums, including historical neutral and nonneutral component controls; generated-year/value coverage checks the disabled-policy invariant across the supported routes.Dataset extension. Single-year dataset extension resets all five new controls to
-1in generated future rows. Every year the source dataset actually supplies keeps its supplied decision, including explicit multi-year datasets; the audit found no generator that extends those supplied multi-year rows. Legacy boolean columns retain main's extension behavior. The 2027-only paid-decision regression pins generated 2028 benefits at $12,352.54 reported / $1,036 for the January calculator check rather than paying the previous year's explicit decision again.Per-person isolation. The existing override simulations now put A and B in the same tax unit and C in another. B's paid override leaves A's automatic withholding intact at $11,583.01 reported / $1,000 for the January calculator check. The same simulations exercise both within-unit and across-unit isolation, without another model setup.
Legacy calculator compatibility. With the reform disabled, the calculator reads only
ss_calculator_withhold_cola, in its exact year, as on main. Component boolean inputs keep their existing carry-forward behavior on the reported route. The regression pins main's January 2027–2030 calculator checks at $1,036, $1,062, $1,086 and $1,111. Literal compatibility expectations come from main; direct calculator expectations and calculated public-source equality remain separate checks because main lacks the new public-source selector.Test cost. The former one-case
social_security_cola_means_test.yamlis removed. Its case remains covered through Core'sSimulationBuilderin the Python means-test file, sharing the existing calculated-source reform system. The merged Makefile routes all SSA YAML toother-shard-3, so removal avoids a serial SSA-file invocation there. The review's 65–66-second file runs, including approximately 47 seconds of startup, measured the previous head on its earlierother-shard-1routing; current savings have not been measured. Compatible retirement cases share one vector simulation with unchanged expected values and no additional reform. No workflow, matrix, concurrency or timeout setting changes.CPI scoring assumption
The line index reads
gov.bls.cpi.cpi_uat December 1 of yeary − Land December 1 of the base year. This returns a BLS December figure where one exists, otherwise the latest authored value. It does not construct a separate December projection series. The current authored path returns 332.568 at2026-12-01, June 2026's BLS observation, and 348.3 at2028-12-01, CBO's value keyed at2028-02-01and annotated as the 2029 value. Tests pin both reads and the A9 preset's 2029 lines of $117,800 single / $235,700 joint. OACT's displayed lines are $117,300 / $234,600 under its CPI projection. No shared BLS CPI parameter file changes.This December lookup is a scoring assumption: a single filer at $117,500 falls below PolicyEngine's line and above OACT's line, so the projected paths can make different withholding decisions.
Chained CPI-U uses Q3 lookups and half-up rounding to 0.1 percentage point with the high-water/no-decrease rules. The review separately found that
chained_cola_ratessplices June-2026 BLS levels onto CBO annual averages, producing a 1.2% January 2028 chained COLA. This PR reaches that function when chained CPI-U is selected for an active, in-scope event; default statutory settings do not. A9 begins with January 2029 checks. The Q3 splice remains a separate follow-up.Validation
For reform scoring, the focused
-kinventory guard passed, matches registered COLA overrides to invariance and dataset reset coverage, verifies the exact component exclusions and calculator prefix, and rejects a scratchsocial_security_auxiliary_cola_overrideregistration.The current head is
b33afe8dba0ac6a948500d1ba096c4e62528e61d. All 260 required local cases passed, along with formatting and all six mutation checks. Current-head CI is green. The required wide differential remains incomplete; the partial results below do not establish full equivalence. Historical comparisons remain separate from this round's evidence.test_ss_cola_means_test.py, disjoint split runstest_social_security_inputs.pytest_social_security_lever_properties.pymake formatPull requestrun's conclusionPull requestrun 41175342483, attempt 1 concluded success; 35 checks / 33 jobs terminal, none failed, cancelled or pending, confirmed at 07:14:45 UTC on October 31The initial default-invariance run exposed a Medicare fixture-date error. After explicitly dating the head status to 2027, the clean full-file rerun passed all 13 cases. Earlier partial/focused passes are excluded from the 260-case primary total.
The split-run audit confirms that the tested commit, current commit and working test file agree. Runtime logs record the source path and collected node IDs; they do not themselves print a test-file hash. Local split measurements include process startup:
Completed #10484 file runs use the same completed-case cleanup hook, with one release per passed case:
earliest_input_yearexclusion0and1instead of automatic-1; 7.94 pytest secondsAll six mutant runs completed with ordinary test failures under the 2 GB / 900-second cap; none hit a memory or time limit. Each mutation ran in an isolated source tree; the fixed branch source remained unchanged and hashes were checked.
This round's differential against merged main
cbe322b51c6c9f791a318a32f0b093abee683fabremains incomplete. Python captured 12 head / 16 main cases out of 32 required, leaving 20 head / 16 main cases missing. The 12 paired cases / 1,470 outputs / 347,367 main cached arrays have zero unexpected differences, with 14 allowed new raw override-input cache entries (4 IRMAA / 10 extended dataset). Both new integration cases are included; their separate pair matches 62 outputs / 18,204 arrays. YAML captured 37 head / 25 main cases out of 42, leaving 5 head / 17 main cases missing. Its 25 paired cases / 301 outputs / 150,581 main cached arrays have only retirement case 9's 120 allowed differences: 3 outputs / 117 cached arrays. Both literal pins were checked: main $8,800, head $10,000. The strict final verifier returns failure solely for missing coverage, with zero unexpected differences or unchanged-output exceptions. Full 32-case Python / 42-case YAML equivalence is not established.Four serial differential capture processes reached the existing 1,800-second time cap. No cap was raised, and all model processes have ended; the remaining head catch-up runs were not started. Saved captures and hub resume instructions are available to complete the missing cases without changing the fixed source. These incomplete comparisons are separate from the 260 passing local cases, six killed mutants, and green CI.
The round-2 review previously compared the old head against main
37dd5099ed7fad694419fb652eb3ba7bd14122ba: 30 Python cases / 4,100 outputs / 1,003,099 cached arrays matched exactly, with no new cache entries. Its 42 YAML builder cases / 466 outputs / 254,321 cached arrays differed only in the already disclosed retirement case 9. That nondefault case supplies a complete calculated base of $10,000, an across-the-board rate of 0.1 and payable share of 0.8: main returns $8,800, while this branch returns the complete supplied $10,000. Its three output differences and 117 cached differences accounted for all 120 YAML differences. Those historical results do not complete the current rerun.Earlier composition with #10484 matched the reported route's reported benefit × PIA ratio × joint COLA factor: $23,695.91 withheld / $24,584.51 paid. The calculator applied the factors once, producing annual amounts of $12,660 / $13,140, with population factors equal to 1. Those composition amounts remain prior-review evidence; the current-head test results appear above.
The previous-head Pull request run 41067837131 concluded cancelled when
other-shard-1reached its existing 60-minute limit. Its Contrib YAML phase passed and the Python phase reached 79% with no recorded failures before cancellation; that run was not green. A zero exit status fromgh pr checksdoes not establish that every check succeeded.The current-head run above concluded success.
other-shard-1completed successfully in 1,743 seconds (29m03s), from 06:40:50 to 07:09:53 UTC. The watcher confirmed the latestPull requestrun's exact head and terminal conclusion after 14 polls / 3,947 seconds, respecting the five-minute polling minimum and 100-minute limit.Local Python checks run one foreground process at a time, with four numeric threads and load1 at or below 100 before startup. Tests retain the 4 GB / 1,800-second cap; mutants retain 2 GB / 900 seconds. The retirement rerun uses the evidence-only completed-case cleanup hook because earlier ordinary retirement runs exceeded 4 GB: it releases completed simulations and reform-cache references after assertions without changing situations, formulas, assertions or expected values. It released all nine completed cases and passed in 552 process seconds (04:44:21–04:53:33 UTC), with 198.74 pytest seconds and a 195.12-second plugin timer. This local hook does not cover cross-case reform-cache reuse. No population microsimulation is run locally. The clean 13-case default-invariance run took 574 process seconds (05:57:38–06:07:12 UTC), with 307.64 pytest seconds and a 529.56-second plugin timer. Local measurements are not a controlled before/after comparison.
Scoring limits
Population scoring uses a static-cross-section prior-income proxy: the same record's dataset-extended
y−1copy, not observed longitudinal income. Complete supplied histories override it; MAGI/source and filing-status arrays must supply every TaxUnit's value for each supplied variable/year. The engine does not carry a person's income, cohort or composition across projected rows. Useincome_sensitivity=1and2for the never/always envelope while retaining automatic timing, cohort and receipt restrictions.Missing entitlement and worker information retains A5's documented exposure proxies. Identified records can supply component worker, eligibility, entitlement and first-COLA fields. Integer-age uncertainty calls for adjacent-cohort sensitivities. Auxiliary PIA inversion without worker/share information remains unidentified.
The calculator retains its existing scope of own retirement and restricted survivors, with its source, history and scope assertions. Disability, spouse, child, minimum and dual-entitlement calculations remain outside that route. See the calculator guide, benefit-lever guide, and means-test guide.
No population impact or OACT solvency result is inferred from these mechanics. Population scoring requires pinned policy settings, data and sensitivity results.
impact: none under current law (contributed reform); the hub pair measured no change in 2026 or 2030
axiom: n/a: contributed reform, not enacted law
Generated with Codex.
Hub microsimulation impact (current head)
A real microsimulation on the default dataset, main at the merge base
cbe322b51cagainst this PR's headb33afe8dba, for 2026 and 2030. The runs went one at a time under the shared heavy-job lock. Sums are weighted.social_securitystate_income_taxincome_taxhousehold_net_incomehousehold_benefitsThe reform stays off by default. The pair ran at b33afe8; the later commit changes one test file.
Comparison files are in
fixes/10486-impact/on the hub host.