Repository navigation
Add modeled OASDI liabilities, benefits and balance - #10563
Merged
Merged
Conversation
Six new outputs for design v3 section B (lever 5): Person payroll liability, TaxUnit benefit-taxation and NIIT-transfer revenue, and Household modeled liabilities, benefits and their modeled balance. No existing variable changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hand-computed YAML for payroll, benefit taxation, the S.770 section 8 transfer, owners' draw and household identities; Hypothesis identities with a differential against existing outputs; a weighted Microsimulation fixture. The stored-column preflight now also rejects the modeled balance and the S.770 and owners' draw tax leaves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a case with the owners' draw and S.770 section 8 both on, showing each liability counted once. Document that tob_revenue_oasdi includes NIIT, which overlaps the section 8 transfer, and the scoring data's missing passive subset. Score the file the preflight hashed. Make the simplest Hypothesis draw a boundary witness, place preflight inputs on their own entities, and drop a partner namespace from the docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The section 8 transfer shares the existing-rate NIIT only in bill and combined-rate modes, and the TOB term overstates even at baseline. The missing passive subset affects both parts of the NIIT transfer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
US + core hub merge audit, us#10563 at
|
MaxGhenis
added a commit
that referenced
this pull request
Nov 1, 2026
Brings in #10563, whose OASDI accounting text this branch's chapter 1 measure makes stale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Nov 1, 2026
#10563 described tob_revenue_oasdi as differencing income_tax, so the benefit-taxation term carried benefit-triggered NIIT and overlapped the S.770 NIIT transfer. The TOB outputs now difference income_tax_chapter_1, which leaves NIIT out, so that overlap no longer exists. State the actual-inclusion cap wherever the text defines the pre-1993 share, and list the chapter 1 tax among the columns the preflight rejects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Nov 1, 2026
* Measure benefit-taxation revenue as chapter 1 liability * Check chapter 1 benefit-taxation differentials independently * Cap the OASDI old-law inclusion at actual inclusion tob_revenue_oasdi taxed the pre-1993 section 86 amount in its old-law branch whatever a reform did to current inclusion. A reform that cut inclusion below that amount credited OASDI with inclusion the unit never reached: under repeal the total was $0, OASDI positive and HI the same amount negative. The old-law branch now taxes min(pre-1993 amount, actual inclusion), read from the outer simulation before any branch exists. At current law the cap changes nothing, because both laws share the first tier. HI stays total minus OASDI, with signed shares and no per-unit floor. Reform scoring cases in tob_old_law_bound.yaml cover repeal, raised thresholds and higher inclusion. The generated property test now requires pre-1993 inclusion never to exceed actual inclusion at current law, with no tolerance. Ruling: us:tob-oasdi-old-law-bound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop a state the reform scoring witness never sets The witness gives no state, so the header no longer says Texas. Say "counterfactual" where "state" meant the inclusion scenario. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Run the old-law cap reform cases on one private policy system The three reform cases in tob_old_law_bound.yaml made Core's YAML runner build and keep a cloned policy system for each parameter override set. In CI that file alone peaked at 8,244 MB, above the batch runner's 8,000 MB budget, and took its batch process to 13,540 MB. One Python test now runs the same three cases in turn on a single clone of the shared system and the same retiree situation. Before each case it restores all eight reformed leaves, applies the case's parameter inputs with the modifier the YAML runner uses, clears the parameter caches and builds a new simulation, then asserts the same six outputs to a cent. The shared system's leaves are checked unchanged in a finally block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Correct the OASDI accounting text for the chapter 1 measure #10563 described tob_revenue_oasdi as differencing income_tax, so the benefit-taxation term carried benefit-triggered NIIT and overlapped the S.770 NIIT transfer. The TOB outputs now difference income_tax_chapter_1, which leaves NIIT out, so that overlap no longer exists. State the actual-inclusion cap wherever the text defines the pre-1993 share, and list the chapter 1 tax among the columns the preflight rejects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Reject a stored chapter 1 tax in the Social Security preflight The TOB outputs difference income_tax_chapter_1. A stored value is an input that every counterfactual branch keeps, so it freezes each measured tax state and zeroes TOB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Copy the policy's nodes for the reform cases instead of cloning it A full clone rebuilds every dated parameter entry. Together with the TOB branches' own clones, that took the test's process past a 4 GB cap locally. Copy the nodes and each leaf's list with copy_parameter_nodes, borrowing the entries, which Parameter.update replaces rather than edits; the process then peaked at 3.53 GiB. The shared system's tree is still never written, and the test checks that each copied leaf holds its own list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
PolicyEngine-US #10566 later moved the benefit-taxation measure to chapter 1 liability, capped historical inclusion at actual inclusion, and removed the NIIT overlap this description reports. The description below records this PR as merged.
Design v3 section B (lever 5): modeled OASDI liabilities, modeled OASDI benefits and their difference, the modeled balance. Six new outputs over existing variables; no existing variable changes. There is no stack: this branches from main, where everything B reads has merged (the signed TOB split #10381,
s770_revenue_oasdifrom #10402, the owners' draw reform #10483 and the scoring preflight #10400).Definitions
oasdi_payroll_tax_revenueadds:employee_social_security_tax + employer_social_security_tax + self_employment_social_security_taxoasdi_benefit_taxation_revenueadds:tob_revenue_oasdi(no HI share)oasdi_niit_revenueadds:s770_revenue_oasdi(zero unless a section 8 option applies)oasdi_incomeoasdi_costadds: publicsocial_security, after any enabled benefit policiesoasdi_modeled_balanceaddsoasdi_income,subtractsoasdi_costgov.irs.payroll.social_security.rate.employeeand.employer, the wage basegov.irs.payroll.social_security.cap, andgov.irs.self_employment.rate.social_security. B adds no rate.self_employment_social_security_taxwith a subclass that addsowners_draw_oasdi_tax, so the draw's OASDI tax reachesoasdi_payroll_tax_revenuethrough that leaf, and B doesn't add it again. The YAML case "The owners' draw OASDI tax arrives once, through the SECA leaf" turns the reform on and confirms it: an owner with $100,000 of wages and $200,000 of S-corporation profits under a $200,000 base gets $24,800 (12.4% of the base), not the $37,200 a second copy would give.oasdi_income$44,055.98. The draw's OASDI tax sits once in payroll liability, and the active base excludes only the $108,283.70 of profits that tax reaches (M7). Leaving those profits in the active base would give $44,888.employer_total_social_security_taxapplies the employer rate toemployer_total_taxable_earnings_for_social_security, a separate input for employer-only payroll tax calculations (default zero), not to a worker's own wages. A YAML case sets that input to $1,000,000 and leaves payroll liability unchanged.tob_revenue_oasdiis Fix OASDI/HI split of Social Security benefit tax revenue #10381's statutory split: tax with section 86 inclusion as before 1993, less tax with none, with no per-unit floor. A YAML witness keeps a −$17.50 unit negative. The vendored source-conflict register has nous:tob-oasdi-hi-signed-allocationentry at its current pin, so B cites none. B adds no reading of its own, and any later correction totob_revenue_oasdiflows through unchanged.oasdi_niit_revenueis the selected section 8 transfer, never the wholenet_investment_income_taxliability. A baseline case owes $3,800 of NIIT and gets a zero transfer.oasdi_incomehas a formula, notadds. Core's group-to-groupaddsdivides a tax unit's amount by its member count and credits each member's share to that member's household.Microsimulationweights a tax unit by its first member's household weight (_project_household_weights→value_from_first_person), so the formula credits each tax unit's amount to that same household. Household-weighted totals then equal the Person and TaxUnit weighted totals. PE-US declares that tax units nest in households; if data ever breaks that, the first-member credit still keeps the totals equal.social_security_preflightalready rejected storedoasdi_payroll_tax_revenue,oasdi_benefit_taxation_revenue,oasdi_niit_revenue,oasdi_incomeandoasdi_cost. This PR addsoasdi_modeled_balance, plus the S.770 section 8 and owners' draw tax and OASDI-transfer leaves that B reads: names startings770_orowners_draw_and ending_taxor_revenue_oasdi. A test keeps their nine classification inputs valid sources. None of the five artifact inventories in Microcosm's pinned stored-column fixture holds any newly rejected name; the certified artifact has 377 columns.Stated limitations
We state these without measuring them (design amendment M5). No residual from another model closes them.
oasdi_costsums benefits for the people in the household dataset. The dataset misses or only partly represents beneficiaries outside its frame, such as those living abroad.payroll_tax_gross_wagesis employment income less FICA pre-tax contributions, with no general OASDI-coverage flag. The model therefore taxes wages from noncovered employment (for example, some state and local government jobs) as covered wages. This wage-coverage assumption differs from the missing cash flows.tob_revenue_oasdidifferencesincome_tax, which includesnet_investment_income_tax, and benefit inclusion raisesniit_magi. So NIIT that inclusion adds lands in the TOB term, although the 42 U.S.C. 401 note transfers only chapter 1 liabilities and §1411 sits in chapter 2A;oasdi_benefit_taxation_revenueoverstates by that amount even at baseline. With a section 8 option on,oasdi_niit_revenuealso transfers shares of the add-on and active taxes (and, in bill or combined-rate mode, of the existing-rate NIIT), sooasdi_incomecounts that part of the increase twice. The fix belongs intob_revenue_oasdi; it's out of scope here because B changes no existing variable (follow-up below).passive_partnership_s_corp_income, so the owners' draw term and both the NII and active parts of the section 8 transfer stay provisional on it until a repaired artifact passes the preflight.unreported_payroll_tax(Forms 4137 and 8919) is a TaxUnit input with no OASDI/HI split.employer_ss_tax_income_tax_revenue, the income tax on employer contributions under the contributed reform that counts employer payroll tax as income, isn't inoasdi_income. A score that enables that reform adds it separately.Usage
docs/usage/social-security-oasdi-accounting.mdgives the definitions, the limitations and the population route:python -m policyengine_us.tools.social_security_preflight <h5>) on the exact file you'll score.calculate_chunked(Add household-chunked microsimulation calculations #10561, in review) once it merges, passing the same file. On main,tob_revenue_oasdirecalculates income tax in two branches of the whole simulation, so its working memory grows with the population. This PR builds no chunking.Invariants
Each holds for every input under current-law payroll parameters. Two kinds of reform lift invariant 1's bound by design: those that tax earnings above the base, and the owners' draw, whose covered earnings exceed wages plus SE net earnings. With the draw on at 2026 parameters, $100,000 of wages and $200,000 of profits give $22,878 of payroll against a $12,400 bound.
oasdi_payroll_tax_revenueequals the three leaves, is ≥ 0, and is ≤ the combined OASDI rate (max(employee + employer, SECA), from parameters) ×min(wage base, covered wages + SE net earnings).oasdi_incomeequals the members' payroll liabilities plus each tax unit'stob_revenue_oasdi + s770_revenue_oasdi, once. The test builds its reference from the generated memberships, not the model's mapping.oasdi_costequals the members'social_security, andoasdi_modeled_balance = oasdi_income − oasdi_cost.oasdi_niit_revenue = 0, including for units that owe NIIT.oasdi_income= Σ_p w_p·payroll + Σ_tu w_tu·(TOB + NIIT transfer). Cost and balance follow the same pattern. Tax-unit weights equal the first member's household weight. Projecting a tax-unit amount to members double counts, and the fixture detects it.map_result) agree for every tax unit inside one household. They differ only for a tax unit that spans households, where only the first-member credit matches the weights.social_security, taxable benefits,income_tax, NIIT, the OASDI and HI TOB shares,s770_revenue_oasdi) identical to a fresh simulation's. Statically, the diff modifies no existing variable, parameter or reform file.No intended violations.
Tests
Local runs on a loaded shared macOS host, one file per process, each under a 4 GiB RSS cap and a 1800 s timeout with four numeric threads. Peak RSS is the largest single process; wall time includes model import. CPU time shows the load: wall time ran 7–20 times CPU.
oasdi_payroll_tax_revenue.yamlSimulation(reform=…), same inputs and expectationsoasdi_benefit_taxation_revenue.yamlSimulationper case (the runner hit the cap in case 1)oasdi_income.yamlSimulationper caseoasdi_modeled_balance.yamlSimulationper caseoasdi_niit_revenue.yamlSimulation(reform=…)for section 8oasdi_cost.yamltest_oasdi_accounting.pytest_oasdi_accounting_weights.pytest_social_security_preflight.pyemployee_social_security_tax.yaml(existing)self_employment_social_security_tax.yaml(existing)Not run locally: the other existing payroll and SECA files, the TOB files (
tob_revenue_*.yaml,tob_tier_split.yaml,test_tob_revenue.py) and the section 8 files (fours770_*.yamlfiles,test_s770_section_8.py). Each file took 15–27 minutes of wall time on this host, and under the cap the section 8 YAML would need one process for each of its 17 structural combos. CI runs them all. B modifies none of their variables, and every run above built the full model with the six new variables registered.Why some cases ran outside the YAML runner. Under the 4 GiB cap, the core YAML runner holds its own system beside the module-level one. A reform clone, or a TOB branch's system clone, then pushes past the cap. For those cases I evaluated the same inputs and expected outputs through
Simulation, letting the structural reforms install from theirin_effectparameters as in production. The committed YAML files stay as written, and CI runs them through the runner on 16 GB machines.CI routing and cost
tests/policy/baseline/gov/ssa/oasdi/.test-yaml-no-structural-other-ssa's per-subdir pass (gov/ssa --exclude revenue) runs it as its own subprocess, with combo weight 3.25: three structural combos (owners' draw, section 8, both) and one parameter-only combo, under the batcher's 5.0. No file holds more than one structural combo, and the heavygov/ssa/revenuebatches don't change.test_oasdi_accounting.pyruns in the Rest Python "policy" group,test_oasdi_accounting_weights.pyintest-microsimulation-base, and the preflight cases incode_health.Follow-ups proposed (not opened)
tob_revenue_oasdiandtob_revenue_totaldifferenceincome_tax, which adds NIIT (chapter 2A); difference income tax less NIIT in both branches. (unreported_payroll_taxis an input, so it cancels between branches.) Coordinate with Add household-chunked microsimulation calculations #10561, which rewrites both files.owners_draw_oasdi_base,owners_draw_niit_exclusion,s770_nii_taxable_baseands770_active_business_income, and a storedincome_tax, which would freeze both TOB branches.impact: none; new accounting outputs only (no existing variable changes)
axiom: n/a: modeled accounting aggregates over existing variables, not a rule
🤖 Generated with Claude Code