Repository navigation
Guard against person variables that add group variables (every member gets the whole group amount) - #9897
Conversation
policyengine-core evaluates adds/subtracts with calculate(..., map_to=<the variable's entity>), and map_result projects a group value onto every member with no split. A person variable that lists a tax unit or SPM unit variable therefore gives each member the whole group amount, and a group total counts it once per member (MT casualty loss #9888, MS HSA #9865, WV senior #9856, and OH, IL, CA Riverside and LA cases found the same day). The new code-health test scans the shared baseline system, resolving parameter-path lists across their whole history. Three boolean enrollment flags are allowed; the 10 current cases are known bugs that may only be removed. A premise test pins the projection on core's template country. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merge order: eight fix PRs now remove entries from This PR's body names #9888, #9865 and #9856. Five fixes opened after it remove the other seven entries:
Combined merge. I merged this PR and the eight fixes onto
Why order matters.
Suggested order. Land this PR last.
That end state is the guard as specified: it fails on any monetary Person variable that lists a group variable, with an allowlist for boolean receipt flags. If this PR has to land earlier, delete the entries for fixes that have already merged. After that, each fix that lands deletes its own line. #9925 / #9939 overlap. Both PRs edit the same lines of
#9939's Each fix fails on
None of them sums a dollar amount over members. |
|
Reopened by the US + core hub to start a fresh CI run at the same head. The previous run failed on |
|
US + core hub merge audit, us#9897 at
|
#9902 made il_aabd_expense_exemption_person count only the person's own withholding, so it no longer adds the tax-unit state_withheld_income_tax. test_known_violations_only_shrink (#9897) then fails on main because the entry is still listed in KNOWN_VIOLATIONS. Remove it. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Adds
policyengine_us/tests/code_health/test_no_group_variables_in_person_adds.pyto reject unreviewed group variables in a Person variable'saddsorsubtractslist. This is a code-health guard; it changes no policy calculation.Review round
Merged
mainat05907577140ce06f38b45777a05f479181161164with a plain merge. Removed three stale known violations already fixed by Mississippi HSA #9865, West Virginia government interest #9856, and Ohio medical expenses #9925. Seven pairs in five variables remain: 10 - 3 = 7 pairs; 8 - 3 = 5 variables.Narrowed the three boolean exceptions to their 11 existing reviewed
(person variable, attribute, group variable)triples. New sources and all subtraction edges require review. Added the Kansas SNAP-subtraction regression and bounded Hypothesis coverage for registry, history, and projection invariants. The original merge-order discussion and open-PR census have been replaced by this verified merge snapshot.Mechanism and sources
Verified the installed policyengine-core 3.32.15 implementation against the tagged source (the two files' SHA-256 hashes match):
_run_formulacalculates listed variables withmap_to=variable.entity.key.map_resultprojects a group amount unchanged to each member. Three members each receiving 100 total 100 + 100 + 100 = 300. Person-to-group mapping sums; mapping between different groups splits across members before summing.add()formula helper rejects person-from-group aggregation, whileaddsandsubtractsreach automatic projection._cast_formula_resultcasts the numeric result to the target's dtype. For a boolean flag, 100 - 100 = 0 becomes false.No statute, regulation, or form determines static exception granularity or whether a recorded source edge still exists. Those findings are decided from the merged model and Core's implementation. The existing Kansas participation criterion is documented in the Kansas CSFP State Plan, PDF page 4, and 7 CFR 247.9(b)(1) and (b)(3) address adjunctive income eligibility. This PR retains that calculation and its existing attribution interpretation.
What the guard checks
It scans the shared baseline
system, rather than constructing another US model. Both literal lists and every dated value of a parameter-backed list are checked. A removed historical source remains detectable at its earlier dates; parameters and group targets are skipped. A path to a parameter folder fails explicitly.ALLOWEDcontains only these positive receipt edges, with a reason for each:snap,receives_snap,fdpir.ma_eaedc,ma_tafdc,snap,receives_snap.snap,receives_snap,tanf,receives_tanf.KNOWN_VIOLATIONSrecords seven existing defects in five variables. Fixing a defect makes its precise entry stale and requires deleting it. This is the same maintenance approach as the dead-URL guard.Invariants
(Person variable, attribute, group source)triple is explicitly reviewed or recorded as a known violation.adds. Adding a new source or a subtraction on an allowed name is rejected.add()rejects the same group-to-Person request.Five bounded Hypothesis tests exercise these invariants using synthetic registries/parameter trees and Core's small template country. They do not construct or copy a full US model for each example. The Kansas regression temporarily mutates one shared variable and restores it with
monkeypatch; a one-person simulation reuses the existing US system and confirms that 100 - 100 = 0 becomes false before checking guard rejection. Python is necessary to mutate the declaration and exercise the static guard; YAML cannot test that interaction.Methodology
Existing positive program-receipt edges remain exempt as exact
(person variable, attribute, group variable)triples. The alternative is a whole-variable exemption, which permits new sources or subtraction without review. We preservemain's existing SPM-unit-to-member interpretation and change no modeled result relative tomain.Validation and CI cost
Reproductions ran before fixes, one file at a time in the foreground:
main, the original file had 4 passed, 1 failed.test_known_violations_only_shrinknamed exactly the MS, OH, and WV triples above. That existing regression passes after their removal.subtracts = ["snap"], while all original guard checks accepted the edge. The rejection regression failed before the fix. The same runtime scenario is now in the committed regression and passes because the guard rejects the subtraction.git diff --checkon the changed files and the diff againstmainpass.Local macOS / CPython 3.13.9 / Core 3.32.15, including fresh US model loading:
The final tests' call time totals about 20.85 s: the generated Core premise takes 10.39 s, the one-person Kansas regression 4.84 s, and registry/history properties about 5.62 s. Peak process RSS increased by 70 MiB between these local runs. These measurements include setup and host scheduling and are not Linux CI timings. The initial sandbox attempt failed collection because Core's template-country import creates storage beside its installed package; subsequent runs used an unchanged workspace copy of installed Core so that storage was writable. Core implementation files match the tagged source linked above.
The file remains in the Rest job's
remaininggroup; no runner or matrix was added.The
make -n test-other-python-rest-remainingselector includes this code-health file. I downloaded the existing Linux CI timing artifact, run 37462569599:remainingcompleted in 13:10.63 with maximum RSS 10,228,880 KiB (about 9.76 GiB), exit 0. These are historical whole-group measurements, not measurements of this revision. No extra CI job or full-suite run was started to benchmark this change.Not covered
The scan covers the baseline system. Contrib reform variables are outside its scope.
Hub microsimulation impact
None to measure. Against current
main, this PR changes only the code-health guard test and a changelog fragment: no variable, parameter or formula changes (US + core hub, 2026-10-10; confirmed by the independent delta review).axiom: n/a: tooling (code-health test only; no policy change)