Skip to content

Stop projecting group amounts onto every Riverside General Relief member - #9901

Open
MaxGhenis wants to merge 7 commits into
mainfrom
fix-riverside-gr-group-projection
Open

MaxGhenis wants to merge 7 commits into
mainfrom
fix-riverside-gr-group-projection

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

The bug

Two Riverside County General Relief (GR) variables were Person variables whose adds lists included group-level variables. policyengine-core evaluates each adds entry with calculate(added_variable, period, map_to=variable.entity.key), and map_result turns a group value into a person value with entity_pop.project(values) when no how is given (policyengine_core/simulations/simulation.py, _run_formula and map_result). So every member of the group received the whole group amount.

Property. ca_riv_general_relief_countable_property_value (Person) added spm_unit_cash_assets (SPMUnit), ca_riv_general_relief_countable_vehicle_value (SPMUnit) and personal_property (Person). ca_riv_general_relief_property_eligible then summed it over the SPM unit, so cash and countable vehicle value counted once per member. Before this fix, 2024:

Household Countable property Property test (< $500) GR, 2024-01
Two adults, $300 in one bank account [300, 300], so 600 fails $0 (the Case 8 setup should receive $438.50)
Three adults, $4,850 of vehicles [200, 200, 200], so 600 fails

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) added state_withheld_income_tax (TaxUnit) and additional_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_tax is [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:

  • Riverside DPSS's General Assistance fact sheet (archived 2021, page 1) lists the income limits by unit size and says: "Total combined property cannot exceed $500". The one-page PDF was retrieved on 2026-10-14; SHA-256 5760c47280bfb42f8f60cd2d1243f4a77a2443edf6a0a28cf8e1b4c9c103f130.
  • The contributor's summary linked from California Riverside County General Relief Program #6252 describes p. 22 as counting all non-exempt property, and p. 20 as allowing earned-income deductions for federal, state and local taxes, Social Security/Medicare contributions and mandatory payroll deductions. A payroll deduction comes out of an earner's own pay; Alameda County's handbook makes the employer-withholding restriction explicit (90-05.151, p. 3). Alameda's rule corroborates the concept; obtaining Riverside's own manual is a source blocker for the county deduction encoding.
  • Additional Medicare Tax is withheld per employee: under 26 U.S.C. 3102(f)(1), an employer withholds on the wages it pays that employee above $200,000 and may disregard spouse wages. 26 CFR 31.3102-4(a) adds that this applies regardless of filing status. The tax unit's additional_medicare_tax is 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_value becomes an SPMUnit variable with quantity_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 sums personal_property over members. The variable now references the archived public fact sheet. ca_riv_general_relief_property_eligible retains the existing strict boundary pending the methodology follow-up.
  • The deduction sources list replaces state_withheld_income_tax with each person's own ca_withheld_income_tax, and additional_medicare_tax with 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) liability exclusion it 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.
  • Every source in the deduction list is now Person-level, so none is explicitly projected from a tax unit.
  • Merged main at 8ae01aa3cb in merge commit ef8b4290c0. Removed the four fixed Riverside triples from KNOWN_VIOLATIONS in policyengine_us/tests/code_health/test_no_group_variables_in_person_adds.py; retained the unrelated mt_misc_deductions entry.

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 adds person3.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.5 format and check: 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.py checks 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.

  1. Countable property equals a numpy sum of the members' bank, stock and bond assets, vehicle value above $4,650 and personal property. Eligibility is that sum below $500 under the current model boundary.
  2. Raising a member's assets never lowers countable property.
  3. 0 ≤ 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 equals additional_medicare_tax.
  4. Each member's monthly GR deductions equal their own Social Security, Medicare and Additional Medicare withholding plus their own CA withholding, ÷ 12. The members' CA withholding sums to state_withheld_income_tax, counted once.
  5. In these generated cases, raising the first member's wages leaves every other member's deductions unchanged.

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 8ae01aa3cb against this PR's head 96e929e8e2, for 2025 and 2026. The runs went one at a time under the shared heavy-job lock. Sums are weighted.

Output 2025 change 2026 change 2025 records changed (weighted) 2026 records changed (weighted)
ca_riv_general_relief +0 +0 0 (0) 0 (0)
state_income_tax +0 +0 0 (0) 0 (0)
income_tax +0 +0 0 (0) 0 (0)
household_net_income +0 +0 0 (0) 0 (0)
household_benefits +0 +0 0 (0) 0 (0)

Riverside General Relief is $0 for every record in both runs, so no total moves. This PR moves ca_riv_general_relief_countable_property_value from 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_relief feeds no household benefit, net income or poverty aggregate; it is listed only in programs.yaml. Only GR and its intermediates move.
  • No file under partners/ is edited. The original PR reported passing unchanged partner files partners/analytics_coverage/signatures/ca.yaml (29 cases) and edge_cases/state/ca/riv_general_relief.yaml (3 cases).
  • API: ca_riv_general_relief_countable_property_value is 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_tax is 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.
  • METHODOLOGY: the public fact sheet's inclusive wording supports <= $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_withheld treats 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's documentation string.

Response to David Trimmer's review

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, and additional_medicare_withholding_zero_at_threshold; rate us:statutes/26/3101/b/2#additional_medicare_tax_rate; rulespec-us f468c8da) | 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

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>
@DTrim99

DTrim99 commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Stop projecting group amounts onto every Riverside General Relief member

PR #9901 (author: MaxGhenis, head 3f7cd2fbee, merge-base b79705b4bd). 11 files, +724/−4, 1 commit.

Summary

The PR fixes two places where Riverside County General Relief (GR) gave every member of a unit the whole unit's amount.

  • Property. ca_riv_general_relief_countable_property_value was a Person variable whose adds list included two SPM-unit variables (spm_unit_cash_assets, ca_riv_general_relief_countable_vehicle_value). Core projects a group value onto each person when it is listed in a person variable's adds (Simulation._run_formula calls calculate(..., map_to=variable.entity.key), and map_result returns entity_pop.project(values)). ca_riv_general_relief_property_eligible then summed over the unit, so cash and vehicle value counted once per member. The variable is now an SPMUnit STOCK, like the Orange County and San Bernardino GR property variables.
  • Wage deductions. ca_riv_general_relief_earned_income_deductions (Person) added the tax unit's state_withheld_income_tax and additional_medicare_tax, so every member deducted the whole unit's withholding. The list now uses each person's own ca_withheld_income_tax and a new federal Person variable, additional_medicare_tax_withheld (0.9% of the employee's wages above $200,000, 26 U.S.C. 3102(f)(1)), with a new withholding_threshold parameter.

The law and the code agree:

  • Riverside DPSS's public fact sheet says "Total combined property cannot exceed $500". That is one limit for the unit's combined property.
  • The deductions are payroll deductions, which come out of each earner's own pay.
  • The new withholding variable reproduces both examples in 26 CFR 31.3102-4.
  • All 53 YAML cases in the touched folders pass at the head. The seeded invariant test passes, and CI is green (35/35).
  • I reproduced the PR's headline examples, and single-person units are unchanged.

One blocker and three documentation-level items remain:

  1. Blocker. Since this PR's CI ran, main has added a ratchet test (Guard against person variables that add group variables (every member gets the whole group amount) #9897) that lists these four Riverside edges as known bugs. It fails once they're fixed. The merge is textually clean, but the merged tree fails test_known_violations_only_shrink.
  2. The Riverside half of the axiom line uses an n/a reason the guide doesn't allow.
  3. The PR body says every new case fails on main. Three of them pass on main, and one of those can't detect the bug it names.
  4. The new parameter's description doesn't follow the house template.

Critical (must fix)

1. Merged with current main, the person-adds ratchet fails, because this PR fixes all four of its Riverside entries.

  • Where: main's policyengine_us/tests/code_health/test_no_group_variables_in_person_adds.py:113-136 (KNOWN_VIOLATIONS). It was added by Guard against person variables that add group variables (every member gets the whole group amount) #9897 (merged 2026-10-10), after this PR's only CI run (head committed 2026-10-06).
  • What the ratchet says. The module docstring: "KNOWN_VIOLATIONS holds open bugs. Fixing one makes this file fail until its entry is deleted." test_known_violations_only_shrink calls assert_known_violations_live, which asserts that every listed triple is still projected.
  • The listed triples (lines 116–135) are exactly the four this PR removes:
    • ("ca_riv_general_relief_countable_property_value", "adds", "ca_riv_general_relief_countable_vehicle_value")
    • ("ca_riv_general_relief_countable_property_value", "adds", "spm_unit_cash_assets")
    • ("ca_riv_general_relief_earned_income_deductions", "adds", "additional_medicare_tax")
    • ("ca_riv_general_relief_earned_income_deductions", "adds", "state_withheld_income_tax")
  • Reproduced. I ran main's module logic against the PR head's tax-benefit system. The Riverside variable and parameter files are byte-identical between the head and the merge tree. The result: "No longer projected; delete these from KNOWN_VIOLATIONS:" followed by all four triples. No Riverside triple is still projected at the head.
  • Why GitHub doesn't show it. git merge-tree --write-tree PolicyEngine/main 3f7cd2fbee is clean, so GitHub reports no conflict. The failure appears only when CI runs on the merged result.
  • Fix: merge or rebase onto main and delete the four Riverside tuples from KNOWN_VIOLATIONS, keeping the mt_misc_deductions entry (Count a Montana casualty loss once per joint return and only for the spouse who had it #9888). Then rerun CI.

Should address

1. The Riverside half of the axiom line uses an n/a reason the guide doesn't allow.

  • Where: PR body, last line: ... | Riverside GR n/a: rulespec-us has no Riverside County (or any California county) General Relief module, and the only source, Riverside DPSS manual DP 90-001, is not public and not in axiom-corpus, so a queued issue could not quote the rule.

  • Why it isn't valid. The shared guide limits n/a to "infrastructure, data, UI, microsimulation-only or emulator-mapping changes". This PR changes Riverside GR eligibility and grants: a two-adult unit with $300 in one account moves from $0 to $438.50.

  • A queued issue can quote the rule. The property rule has a public, quotable source: the DPSS fact sheet's "Total combined property cannot exceed $500" (archived 2021). The PR body already cites it.

  • Precedent. The sibling Los Angeles GR fix, Count CalWORKs once in Los Angeles County General Relief income #9916 (merged 2026-10-12), filed TheAxiomFoundation/rulespec-us#1522 queued, blocked on source ingest axiom-corpus#784. Its proposed path is us-ca/policies/la-county-dpss/gr/....

  • Context. axiom-oracles' bridges/program_surfaces/us.yaml marks ca_riv_general_relief as axiom_status: deferred_jurisdiction. That is worth recording in the issue, but the guide doesn't list it as an n/a ground.

  • Fix: file a dispatch-ready, pe-parity rulespec-us issue for the Riverside half, on the Fix MA excess exemption logic #1522 model:

    • an ingest blocker for the DPSS fact sheet;
    • the verbatim "Total combined property cannot exceed $500";
    • a required output for unit-level countable property;
    • companion tests from the fact sheet;
    • the per-earner deduction rule recorded as blocked on obtaining DP 90-001 p. 20.

    Then change that half of the line to <issue> queued. If maintainers agree that deferred_jurisdiction programs are out of scope, cite that status instead of "not public".

2. The PR body says every new case fails on main. Three pass on main, and one can't detect the bug it names.

  • Where: PR body, "Tests": "Every new case fails on main and passes here (I ran each file against main's Riverside code)".
  • Reproduced. I ran the PR's new and changed Riverside YAML files against the merge-base code. main hasn't touched these files since. Result: 10 failed and 11 passed. These three new cases pass on the old code:
    • ca_riv_general_relief_countable_property_value.yaml Case 2 (line 22). On the old code the variable was person-level and returned [200, 200, 200]. A scalar 200 expectation broadcasts across that array and passes. So the case labelled "countable vehicle value counts once in a three-person unit" cannot detect multi-counting. The bug is caught only by ca_riv_general_relief_property_eligible.yaml Case 5.
    • ca_riv_general_relief_property_eligible.yaml Case 4 (line 37), "$300 in each of two accounts fails". The old code gives 1,200, which also fails. That makes it a guard against over-correction, not a regression test.
    • integration.yaml Case 10 (line 251). For a one-person single filer, the old additional_medicare_tax liability (0.9% × (300,000 − 200,000) / 12 = 75) and state_withheld_income_tax equal the new person-level amounts.
  • Fix:
    • Make property-value Case 2 discriminating. For example, give person3 personal_property: 50 and expect 250. The old code would return [200, 200, 250].
    • Reword the PR-body sentence to say which cases are regressions (fail on main) and which are guards.

3. The new parameter's description doesn't follow the house template.

  • Where: policyengine_us/parameters/gov/irs/payroll/medicare/additional/withholding_threshold.yaml:1: "Employers withhold the Additional Medicare Tax from the wages they pay an employee above this amount in a calendar year, regardless of the employee's filing status."
  • The template. It is "[Jurisdiction] [verb] ... this amount ...", and IRS parameters lead with the government actor (120 descriptions under parameters/gov/irs start "The IRS").
  • Suggested: "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."

Suggestions

  1. Follow up on the property boundary. This is pre-existing, and the PR's Caveats acknowledge it.
    • ca_riv_general_relief_property_eligible.py:16 tests total_property_value < p.limit, so exactly $500 fails.
    • The public fact sheet says "Total combined property cannot exceed $500" (#page=1), so $500 passes.
    • The contributor's manual summary linked from California Riverside County General Relief Program #6252 contradicts itself: "Countable property must not exceed $500 (p.22)" and "Eligibility is established if countable property is less than $500".
    • Open an issue, and pin whichever rule is chosen with a $500 boundary test. No new test covers the boundary.
  2. Give the property variable a public reference. The PR's case rests on "combined" property, but ca_riv_general_relief_countable_property_value has no reference, and every Riverside parameter reference is title-only ("The program manual is not publicly available"). Add the archived fact sheet (dpss-general-assistance.pdf#page=1) as the variable's reference, and optionally to property/limit.yaml.
  3. Follow-up issue on deductions (documented, out of scope).
    • ca_withheld_income_tax is estimated from the person's whole AGI. Withholding attributable to a pension or interest is still deducted from that person's wages, as the PR's Caveats say.
    • The manual summary also lists "Federal, state, and local taxes" (p. 20). The deduction list has no federal income tax withholding, and no person-level federal withholding variable exists.
    • The Alameda analog the PR cites counts payroll deductions only "when deducted from the gross earnings by the employer" (GA Handbook 90-05.151).
  4. Put the one-employer simplification in documentation. additional_medicare_tax_withheld.py:18-20 explains in a code comment that wages "are not split by employer, so this treats them as paid by one". 26 CFR 31.3102-4(a) has each employer disregard "any wages ... paid to the employee by another employer", so a multi-job earner's actual withholding can be lower. Add one sentence to the documentation string (line 8) so API users see it.

Verified correct

  • The rule.
    • Cal. Welf. & Inst. Code § 17001 has each county "adopt standards of aid and care for the indigent and dependent poor" (text), under the § 17000 duty to "relieve and support" them.
    • Riverside's fact sheet gives unit-size limits ("Income limit for one (1) person is $326; two (2) persons is $436") and "Total combined property cannot exceed $500" (#page=1). The limits match the model's needs standards (223 + 92 + 11; 257 + 160 + 19).
    • The grant (ca_riv_general_relief, SPMUnit) and the property limit are per unit. Payroll deductions are per earner.
  • The bug is real and the fix is right.
    • Core 3.26's map_result projects group values onto persons with no how, as the PR describes.
    • Every remaining Riverside deduction source is Person-level: ca_withheld_income_tax, employee_social_security_tax, employee_medicare_tax, additional_medicare_tax_withheld, traditional_ira_contributions.
    • The SPMUnit STOCK property variable adds the SPM-unit cash and vehicle amounts once and sums personal_property. The month-period integration output of 300 confirms STOCK, with no ÷12.
  • Base vs head probes, 2024-01, Riverside:
    • Two adults, $300 in one account: countable [300, 300] becomes 300. Eligibility goes false → true. In the PR's Case 8 setup, GR goes $0 → $438.50. In my variant where the 65-year-old receives SSI, GR goes $0 → $328.50, the size-1 grant.
    • Three adults, $4,850 of vehicles: [200, 200, 200] becomes 200, and the unit becomes eligible.
    • Pension/wages couple: deductions [31.80, 62.40] become [31.80, 30.60], countable income 337.60 → 369.40, and GR $100.90 → $69.10. This matches the PR body.
    • SSI member plus an earner, $200 cash each: [400, 400] becomes 400. The unit becomes eligible for 326 + 2.50 − 277.05 = $51.45.
    • Counter-examples unchanged:
      • a single adult with $4,800 of wages and $400 cash;
      • two roommates in separate tax units;
      • a married couple with $3,000 of wages each (no CA withholding).
  • The withholding variable.
    • It implements "only ... to the extent to which the taxpayer receives wages from the employer in excess of $200,000, and the employer may disregard the amount of wages received by such taxpayer's spouse" (26 U.S.C. 3102(f)(1)).
    • It also implements "This rule applies regardless of the employee's filing status or other income" (26 CFR 31.3102-4(a)).
    • The base is payroll_tax_gross_wages, which keeps 401(k) deferrals in, as Medicare wages do. No self-employment income is included.
    • Both new references corroborate the $200,000.
  • Callers (grep at the head).
    • ca_riv_general_relief_countable_property_value is read only by ca_riv_general_relief_property_eligible.
    • The deduction variable is read only by ca_riv_general_relief_net_earned_income.
    • additional_medicare_tax_withheld and withholding_threshold are used only by the Riverside list and the new variable.
    • ca_riv_general_relief feeds no benefit, net-income or poverty aggregate. It appears only in programs.yaml. So there's no microsimulation effect on household net income, poverty or the budget.
    • No partner test sets or asserts the re-entitied variable. The Riverside partner cases are single-person or in_riv: false.
  • Tests.
    • 53/53 YAML cases pass at the head across gov/local/ca/riv/general_relief/** and additional_medicare_tax_withheld.yaml.
    • The seeded invariant test passes (test_seeded_population). The Hypothesis-decorated test isn't installed locally, but it ran green in CI. Its settings (derandomize=True, health-check suppression, deadline=None) match its siblings.
    • All periods are plain (2024, 2024-01, 2025).
  • Hand checks:
    • CA withholding on a $30,000 pension: (107.56 + 2% × (30,000 − 5,540 − 10,756)) / 12 = 31.80.
    • $300,000 of wages:
      • Social Security: 6.2% × 168,600 / 12 = 871.10.
      • Medicare: 362.50.
      • Additional Medicare: 0.9% × 100,000 / 12 = 75.
      • CA withholding: 107.56 + 294.86 + 589.84 + 937.26 + 1,179.20 + 9.3% × (294,460 − 70,606), all ÷ 12 = 1,993.93.
      • Total 3,302.53.
    • Two earners: 560 × 7.65% + 1% × 1,180 / 12 = 43.82, and 30 × 7.65% = 2.30.
    • Integration Case 10: 1,600 − (871.10 + 362.50 + 75 + 165.64 / 12) = 277.60, so 326 + 2.50 − 277.60 = 50.90.
    • Integration Case 9: 436 + 2.50 − 369.40 = 69.10.
    • Property Case 3: 150 + 200 + (4,700 − 4,650) + 50 + 75 = 525.
    • CFR examples: [0, 900] withheld against a joint liability of 0.9% × 150,000 = 1,350; and [0, 0] against 810.
    • Integration Case 2's change from [38.63, 0.38] to [38.63, 0]: person2 has no AGI, so ca_withheld_income_tax is 0.
  • Code patterns. The code is vectorized (max_) and has no hard-coded policy values. The reference is a tuple. Parameter metadata is complete (unit, period, label, two section-anchored references). The changelog fragment changelog.d/fix-riverside-gr-group-projection.fixed.md is top-level and fixed.

Axiom line assessment

The line has two halves.

  • Federal half: valid encoded-correct. us:statutes/26/3102/f/1#additional_medicare_withholding_wages_subject_to_subsection_a is not unrelated: it is the provision behind the PR's new additional_medicare_tax_withheld.
    • The module exists at rulespec-us f468c8da (a 2026-10-04 merge to main). It is unchanged on main since 2026-06-10.
    • Its formula is max(0, taxpayer_wages_received_from_employer − 200000).
    • Its .test.yaml contains both named companion cases:
      • additional_medicare_withholding_applies_only_to_employer_wages_above_threshold: $250,000 gives $50,000 subject. That is the base of the PR's Case 2: 0.9% × 50,000 = 450.
      • additional_medicare_withholding_zero_at_threshold: $200,000, matching the PR's Case 1.
    • The rate rule us:statutes/26/3101/b/2#additional_medicare_tax_rate is 0.009.
  • Riverside half: not a valid n/a. See Should address 1.

CI status

All green at the head: 35/35 checks pass (gh pr checks 9901). They include the 22 Full Suite jobs (Microsimulation among them), Household API Partners, Lint, the changelog check and the smoke imports.

That run predates #9897. CI on the merged result would fail test_known_violations_only_shrink (Critical 1). The PR touches no partner test files.

Branch status

  • Position: 184 commits behind main, 1 ahead.
  • Merge: git merge-tree --write-tree PolicyEngine/main 3f7cd2fbee merges cleanly with no textual conflicts. main has not touched any Riverside GR, Medicare payroll or ca_withheld_income_tax file since the merge-base.
  • Semantic conflict: the KNOWN_VIOLATIONS ratchet (Critical 1).
  • Overlapping open PR: Count a Montana casualty loss once per joint return and only for the spouse who had it #9888 (Montana casualty loss) deletes the mt_misc_deductions entry from the same KNOWN_VIOLATIONS set. Whichever lands second resolves a trivial conflict there.

Verdict: REQUEST_CHANGES

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants