Repository navigation
Conversation
ca_riv_general_relief_countable_property_value was a Person variable whose adds listed the SPM-unit cash assets and countable vehicle value. Core projects a group value onto every member, and the property test then summed the members, so cash and vehicle value counted once per member. It is now an SPMUnit stock: unit amounts are added once and personal property is summed. ca_riv_general_relief_earned_income_deductions added the tax unit's state_withheld_income_tax and additional_medicare_tax, so every member deducted the whole unit's withholding from their own wages. It now adds each person's own ca_withheld_income_tax and the new person-level additional_medicare_tax_withheld (26 U.S.C. 3102(f)(1): 0.9% of the employee's wages above $200,000, regardless of filing status). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PR Review: Stop projecting group amounts onto every Riverside General Relief memberPR #9901 (author: MaxGhenis, head SummaryThe PR fixes two places where Riverside County General Relief (GR) gave every member of a unit the whole unit's amount.
The law and the code agree:
One blocker and three documentation-level items remain:
Critical (must fix)1. Merged with current
Should address1. The Riverside half of the axiom line uses an
2. The PR body says every new case fails on
3. The new parameter's description doesn't follow the house template.
Suggestions
Verified correct
Axiom line assessmentThe line has two halves.
CI statusAll green at the head: 35/35 checks pass ( That run predates #9897. CI on the merged result would fail Branch status
Verdict: REQUEST_CHANGES |
DTrim99
left a comment
There was a problem hiding this comment.
The unit-level property test and per-earner deductions match the sources, and the federal 3102(f)(1) withholding examples hold. Details in the comment above. C1: merged with current main, the code-health ratchet fails. #9897 lists all four Riverside edges in KNOWN_VIOLATIONS, and this PR fixes them, so test_known_violations_only_shrink errors. Rebase and delete the four ca_riv tuples. Should items: S1, the Riverside half of the axiom line uses an n/a reason the guide doesn't allow, so file a queued pe-parity issue as #9916 did; S2, three cases the PR body says fail on main actually pass, and property-value Case 2 can't catch multi-counting; S3, the withholding_threshold description.
The bug
Two Riverside County General Relief (GR) variables were Person variables whose
addslists included group-level variables. policyengine-core evaluates eachaddsentry withcalculate(added_variable, period, map_to=variable.entity.key), andmap_resultturns a group value into a person value withentity_pop.project(values)when nohowis given (policyengine_core/simulations/simulation.py,_run_formulaandmap_result). So every member of the group received the whole group amount.Property.
ca_riv_general_relief_countable_property_value(Person) addedspm_unit_cash_assets(SPMUnit),ca_riv_general_relief_countable_vehicle_value(SPMUnit) andpersonal_property(Person).ca_riv_general_relief_property_eligiblethen summed it over the SPM unit, so cash and countable vehicle value counted once per member. Before this fix, 2024:An otherwise eligible multi-member unit is wrongly denied when its true combined countable property is below $500 but duplicating its unit-level amounts makes the tested total at least $500.
Deductions.
ca_riv_general_relief_earned_income_deductions(Person) addedstate_withheld_income_tax(TaxUnit) andadditional_medicare_tax(TaxUnit), so every member deducted the whole tax unit's CA withholding and Additional Medicare Tax from their own wages. Before this fix, 2024-01: a couple where person1 has a $30,000 pension and person2 has $4,800 of wages.ca_withheld_income_taxis [31.80, 0], but the deductions were [31.80, 62.40]: person2 deducted person1's withholding. Countable income fell from $369.40 to $337.60, and the grant rose from $69.10 to $100.90 a month.The rule
The parameter files cite the Riverside DPSS manual "General Assistance Eligibility" (DP 90-001), pp. 20, 22 and 25. Its linked PDF required Google sign-in in this review, so its operative wording remains unavailable. The public fact sheet supports combined property; the federal sources establish withholding per employee:
5760c47280bfb42f8f60cd2d1243f4a77a2443edf6a0a28cf8e1b4c9c103f130.additional_medicare_taxis the Form 8959 liability, which uses the filing-status threshold and adds self-employment income, so it is the wrong concept for a wage deduction even for one earner.The fix
ca_riv_general_relief_countable_property_valuebecomes an SPMUnit variable withquantity_type = STOCK, as in the San Bernardino and Orange County GR property variables. Core then adds the SPM-unit cash and vehicle amounts once and sumspersonal_propertyover members. The variable now references the archived public fact sheet.ca_riv_general_relief_property_eligibleretains the existing strict boundary pending the methodology follow-up.state_withheld_income_taxwith each person's ownca_withheld_income_tax, andadditional_medicare_taxwith a new Person variable,additional_medicare_tax_withheld= 0.9% × max(payroll_tax_gross_wages− $200,000, 0). Its threshold is a new parameter,gov.irs.payroll.medicare.additional.withholding_threshold(3102(f)(1)), kept separate from the 3101(b)(2) liabilityexclusionit happens to equal for single filers. Its description now starts "The IRS requires employers" and follows the parameter template. The variable's API documentation exposes the one-employer assumption.mainat8ae01aa3cbin merge commitef8b4290c0. Removed the four fixed Riverside triples fromKNOWN_VIOLATIONSinpolicyengine_us/tests/code_health/test_no_group_variables_in_person_adds.py; retained the unrelatedmt_misc_deductionsentry.Why keep Additional Medicare rather than drop it. It is a mandatory wage deduction, and it can co-occur with GR eligibility in the model: a self-employment loss offsets wages in GR earned income, but not the Additional Medicare base. Integration Case 10 is such a household: $300,000 of wages, a $280,800 business loss, and a $50.90 monthly grant only because the $75 of withholding is deducted. This single-person case is a preservation guard for the withholding leg, rather than a regression against the old attribution code.
Tests
The cases cover the projection regressions and preservation guards. The property-value vehicle case now distinguishes the old person array from the corrected unit total:
ca_riv_general_relief_countable_property_value.yaml(4 cases): cash, vehicles and personal property each counted once. Case 2 addsperson3.personal_property: 50; $200 countable vehicles + $50 personal property = $250, whereas the old Person variable returned [200, 200, 250]. New Case 4 requests a month and checks $400 cash + $25 countable vehicles + $50 and $25 personal property = $500 of combined stock. It asserts the amount only, leaving the eligibility methodology unchanged.ca_riv_general_relief_property_eligible.yaml(5 cases): Cases 3 and 5 catch the projection regressions: a $300 account in a two-person unit passes; three adults with $4,850 of vehicles pass. Case 4, $300 in each of two accounts, fails under both old and corrected code and guards against over-correction. Existing Case 2 records the current ineligible result at exactly $500.ca_riv_general_relief_earned_income_deductions.yaml(3 cases): the pension/wages couple above has deductions [31.80, 30.60], leaving $400 − $30.60 = $369.40 of net earned income for person2. Two earners have $560 × 7.65% + ($6,720 − $5,540) × 1% / 12 = $43.82 and $30 × 7.65% = $2.30, respectively. A $300,000 earner keeps their $75/month Additional Medicare withholding and $1,993.93/month CA withholding: $871.10 Social Security + $362.50 Medicare + $75 + $1,993.93 = $3,302.53; the other earner deducts only $30.60.integration.yaml(10 cases): Case 8 is the $300 account and $436 + $30/12 = $438.50 grant; Case 9 is the pension/wages couple and $436 + $2.50 − $369.40 = $69.10 grant. Case 10 has $1,600 − ($871.10 + $362.50 + $75 + $165.64/12) = $277.60 of countable income and $326 + $2.50 − $277.60 = $50.90 of GR; it preserves the Additional Medicare deduction in a single-person unit and passes under both attribution versions. Case 2 changes from [38.63, 0.38] to [38.63, 0]: person2 has no wages or withholding of their own. Its other outputs are unchanged.additional_medicare_tax_withheld.yaml(6 cases): the threshold boundary ($200,000 → $0), $250,000 → $450, both examples in 26 CFR 31.3102-4 (spouses with $100,000 and $300,000: [0, 900] withheld against $1,350 of joint liability; spouses with $190,000 and $150,000: nothing withheld against $810 of liability), self-employment income alone, and a 401(k) deferral. These test the new withholding API, including cases where withholding differs from tax-unit liability.Local validation at
96e929e8e2: passed for all five changed YAML files, each run separately with the prescribed thread caps (28 cases total); the invariants file (2 tests); and the person-adds ratchet (10 tests). Ruff 0.15.5formatandcheck: passed.GitHub Actions on the final head: workflow run is queued at this update. The obsolete C1 run was cancelled to release the workflow concurrency group.
Invariants
policyengine_us/tests/core/test_riverside_general_relief_attribution_invariants.pychecks these with Hypothesis batches of one- to four-adult Riverside units plus a seeded population of 120. Each batch is run once, then again with the first member's assets and wages raised.additional_medicare_tax_withheld= 0.9% × (wages − $200,000)⁺ ≤ 0.9% × wages. The constants are typed from the statute, not read from parameters. For a one-person single filer it equalsadditional_medicare_tax.state_withheld_income_tax, counted once.The original baseline comparison reported that the seeded test failed on (1), and with only the property fix applied, on (4): 138 of 263 people.
The Python tests exercise vectorized entity shapes and cross-member locality across generated populations, and remain in the existing Rest Python core group. Standalone local elapsed time including setup: 469 seconds; the prior merge validation recorded 313.01 seconds with the same test file. These local timings were collected under different shared-host loads. Peak RSS was not collected: the resource wrapper was blocked by sandbox access to
kern.clockrate. This review adds one YAML case; the Python invariants file is unchanged.Hub microsimulation impact (current head)
A real microsimulation on the default dataset, main at the merge base
8ae01aa3cbagainst this PR's head96e929e8e2, for 2025 and 2026. The runs went one at a time under the shared heavy-job lock. Sums are weighted.ca_riv_general_reliefstate_income_taxincome_taxhousehold_net_incomehousehold_benefitsRiverside General Relief is $0 for every record in both runs, so no total moves. This PR moves
ca_riv_general_relief_countable_property_valuefrom Person to SPMUnit, so its records can't be paired, and the table leaves it out. Summed over every record in the dataset, not only Riverside, its weighted total is $225.1bn at person level on main and $95.6bn at SPM-unit level in this PR for 2025. For 2026 it is $230.9bn and $98.2bn.Comparison files are in
fixes/9901-impact/on the hub host.Impact
ca_riv_general_relieffeeds no household benefit, net income or poverty aggregate; it is listed only inprograms.yaml. Only GR and its intermediates move.partners/is edited. The original PR reported passing unchanged partner filespartners/analytics_coverage/signatures/ca.yaml(29 cases) andedge_cases/state/ca/riv_general_relief.yaml(3 cases).ca_riv_general_relief_countable_property_valueis now an SPM-unit variable, so a caller that set it as a person input must set it on the SPM unit. No repo file or partner test does.Caveats
ca_withheld_income_taxis an estimate from the person's whole AGI, so withholding attributable to a pension or interest is still deducted from that same person's wages. The fix removes explicit projection of tax-unit withholding. Riverside GR: confirm payroll withholding scope and missing federal income tax deduction #10233 tracks employer-withheld earnings scope and the missing federal income tax withholding source, pending DP 90-001 page 20.<= $500; the contributor's manual summary contains both inclusive wording and "less than $500", and the model uses< $500. Riverside GR: resolve eligibility at exactly $500 of combined property #10232 records both readings and requests authoritative page 22. In the otherwise eligible two-adult zero-income Case 8 setup, exactly $500 means $0 under the current strict test or $436 + $30/12 = $438.50/month under the inclusive reading. This PR makes no choice: existing eligibility Case 2 retains false at $500, and new property Case 4 asserts only the combined amount in a month.additional_medicare_tax_withheldtreats each person's aggregate wages as paid by one employer because wages are not split by employer in the model. The legal threshold applies separately to each employer: two employers paying $150,000 each can withhold $0 of mandatory Additional Medicare Tax, while the modeled aggregate yields $900/year. The simplification is now in the variable'sdocumentationstring.Response to David Trimmer's review
main, deleted the four fixed Riverside entries fromKNOWN_VIOLATIONS, retainedmt_misc_deductions, and reran the ratchet (10 tests; passed).n/aclaim with Queue Riverside County GR combined-property parity for PolicyEngine/policyengine-us#9901 TheAxiomFoundation/rulespec-us#1774queued, blocked on Ingest Riverside DPSS General Assistance fact sheet; obtain DP 90-001 pages 20 and 22 TheAxiomFoundation/axiom-corpus#889 source ingestion; the issue quotes the public combined-property rule, specifies unit-level outputs and source-derived tests, and defers the county per-earner rule pending DP 90-001 page 20.withholding_threshold.yamlnow follows the house template: "The IRS requires employers to withhold the Additional Medicare Tax from an employee's wages above this amount in a calendar year, regardless of filing status."$500readings and their consequences; existing strict eligibility Case 2 is retained, and new monthly property Case 4 checks $400 + $25 + $50 + $25 = $500 without choosing eligibility.referenceonca_riv_general_relief_countable_property_value.additional_medicare_tax_withheld.documentation, making it visible to API users.axiom: us:statutes/26/3102/f/1#additional_medicare_withholding_wages_subject_to_subsection_a encoded-correct (companion tests
additional_medicare_withholding_applies_only_to_employer_wages_above_threshold, $250,000 from one employer → $50,000 subject to withholding, the base of this PR's $450 case, andadditional_medicare_withholding_zero_at_threshold; rate us:statutes/26/3101/b/2#additional_medicare_tax_rate; rulespec-usf468c8da) | Riverside GR TheAxiomFoundation/rulespec-us#1774 queued (property aggregation blocked on TheAxiomFoundation/axiom-corpus#889 source ingestion; exact-$500 eligibility remains a methodology item; county per-earner deductions deferred pending DP 90-001 page 20)🤖 Generated with Claude Code