Repository navigation
Make Riverside General Relief eligible at exactly $500 of property - #10582
Conversation
The DPSS General Assistance fact sheet's property bullet says total combined property cannot exceed $500, so a unit holding exactly $500 meets the limit. The model required less than $500. Compare with <=, cite the fact sheet on the limit, and add cases at $499.99, $500 and $500.01 for one and two adults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Ready for hub review Head: Precondition: #9901 merged on 2026-10-22, and no open PR or branch covered this change. What the item asked, and how I resolved it
Tests (one process per file): all 13 Riverside GR files pass, 61 cases in all (property eligibility 11, integration 17, the other 11 files 33). Mutant (not committed): restoring CI: not run, because the PR is a draft. Judgment calls
Generated by Claude Code |
|
Ready for hub review Independent review, round 1. The head has not moved past The comparison change is right, and the new YAML cases pin it. Two things in the same rule are still wrong at this head. The Python invariant test still encodes the old strict rule, and float32 sums can push an exact $500.00 total over the limit. Both fixes are one or two lines. P2-1. The Riverside invariant test still asserts the old
|
|
Picking this up for the US hub. I'll post a hand-off comment here when it's ready. Generated by Claude Code |
Bring the branch up to date before the round-1 fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nt test Inputs are float32, so three accounts totalling exactly $500.00 can sum to 500.00003 and fail the <= limit check. Round the total to whole cents before comparing, as is_optional_senior_or_disabled_asset_eligible does, and add the three-account case. The Python invariant test still encoded the old strict < rule and passed only because no draw hit exactly $500; its oracle and docstring now say at or below. The parameter description now says units, not filers, since the limit applies to the SPM unit's combined property. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Ready for hub review Head: Fix round for the round-1 review (#10582 (comment)). The starting head was Merge of main
Findings
Tests (one process per file, at
|
| File | Result |
|---|---|
ca_giv_general_relief_countable_income.yaml |
2 passed |
ca_riv_general_relief_countable_property_value.yaml |
4 passed |
ca_riv_general_relief_countable_vehicle_value.yaml |
3 passed |
ca_riv_general_relief_earned_income_deductions.yaml |
3 passed |
ca_riv_general_relief_excluded_income.yaml |
2 passed |
ca_riv_general_relief_needs_standards.yaml |
2 passed |
eligibility/ca_riv_general_relief_eligible.yaml |
2 passed |
eligibility/ca_riv_general_relief_immigration_status_eligible.yaml |
3 passed |
eligibility/ca_riv_general_relief_income_eligible.yaml |
2 passed |
eligibility/ca_riv_general_relief_ineligible_person.yaml |
6 passed |
eligibility/ca_riv_general_relief_meets_work_requirements.yaml |
4 passed |
eligibility/ca_riv_general_relief_property_eligible.yaml |
12 passed |
integration.yaml |
17 passed |
test_riverside_general_relief_attribution_invariants.py |
2 passed |
code_health/test_source_conflicts.py |
103 passed |
partners analytics_coverage/edge_cases/state/ca/riv_general_relief.yaml (read-only) |
3 passed |
partners amplifi/2025.yaml (read-only) |
1 passed |
partners amplifi/2026.yaml (read-only) |
3 passed |
That is 62 Riverside YAML cases (61 last round, plus Case 12). No partner file moved or was edited. make format: clean.
Mutants (each in its own processes; git status clean after each)
- (a)
total_property_value < p.limit. The property file fails Cases 2, 7, 10 and 12 (4 of 12).integration.yamlfails Cases 13 and 16 (2 of 17). The invariant test passes (2 of 2), because its draws are whole dollars that never total exactly $500, so it isn't a guard for this mutant. - (b)
total_property_value <= p.limit(no rounding). The property file fails Case 12 only (1 of 12).integration.yamlpasses 17 of 17, and the invariant test passes 2 of 2. - (c)
np.round(total_property_value, 2) <= p.limit + 1. The property file fails Cases 8 and 11 (2 of 12).integration.yamlfails Cases 14 and 17 (2 of 17). The invariant test failstest_seeded_population.
CI
Pull request run 41536094536 at 9fb39dd7 completed at 23:19 UTC with conclusion success: all 35 jobs succeeded, including PR validation, Household API Partners, Quick Feedback, Package and compatibility, every Baseline, Contrib and Python shard, and Microsimulation. It is the only workflow run at this head.
Judgment calls
- I used the review's exact replacement text for the formula, docstring, oracle, Case 12 and description.
- I left the invariant oracle unrounded. Its inputs are whole-dollar draws summed in float64, so it can't drift the way the model's float32 sum does.
- P3-1 (income
<=against the lead-in's "below") is untouched, as the review marked it a follow-up for the hub's register. - I didn't refetch the fact sheet; the ruling and quotes are unchanged from the hub's logged fetch.
Proposed follow-up
Riverside GR income limit: settle <= vs "below" at the needs standard. ca_riv_general_relief_income_eligible uses countable_income <= needs_standards, while the fact sheet's lead-in says "below". Decide from the source (or record a source conflict) and pin the boundary with a YAML case.
Generated by Claude Code
|
Hub review in progress Superseded: the round-2 review is posted at #10582 (comment). Generated by Claude Code |
|
Ready for hub review Independent review, round 2. The head has not moved past P3-1. A test comment still says it avoids choosing eligibility at exactly $500
Case 4 builds a two-person unit whose combined property is exactly $500: cash 300 + 100, countable vehicle 25, and member property 50 + 25. Its comment reads "Assert the stock amount without choosing eligibility at exactly $500." This PR makes that choice, so the comment now describes a rule that no longer exists. #10232's acceptance criterion 3 also asks for "a multi-member combined-property input" at the boundary. The PR's boundary cases put all the cash with the first adult, and Case 12 has a single person, so no case asserts eligibility for property combined across members at exactly $500. Case 4 already has that input, and one line pins it. Replacement: # Cash 300 + 100, countable vehicles 25, member property 50 + 25.
- # Assert the stock amount without choosing eligibility at exactly $500.
+ # The total does not exceed the $500 limit, so the unit is property-eligible.
ca_riv_general_relief_countable_property_value: 500
+ ca_riv_general_relief_property_eligible: trueOn a scratch copy at this head, the file passes 4 of 4. With mutant (a) ( This file is outside the five the round-1 fix was limited to, so whether to add it now is the hub's call. The PR says P3-2 (follow-up, identical on main). Income test:
|
| Mutant | Property file | integration.yaml |
Invariant test |
|---|---|---|---|
(a) total < p.limit |
fails Cases 2, 7, 10, 12 | fails 13, 16 | passes 2/2 |
(b) total <= p.limit |
fails Case 12 | passes 17/17 | passes 2/2 |
(c) round(total, 2) <= p.limit + 1 |
fails Cases 8, 11 | fails 14, 17 | fails test_seeded_population |
(d, mine) round(total, 0) <= p.limit |
fails Cases 8, 11 | fails 14, 17 | passes 2/2 |
(a) to (c) match the hand-off and the body exactly. YAML cases kill all four mutants. The invariant test can't kill (a), (b) or (d) because its draws are whole dollars. Its strategies can reach $500 (one to four members with four items each, up to $400 apiece), but the derandomized draws and the seeded population don't land on it.
A boundary-hitting draw in the invariant test doesn't belong in this PR. The YAML cases already pin $499.99, $500, $500.01 and the three-account drift, and the repo's guidance puts policy boundaries in YAML rather than in more simulation-heavy Python. One caveat for anyone who later widens the draws to cents: the oracle sums float32-read values in float64 without rounding. It would then need np.round(property_value, 2), or it would disagree with the model at exactly $500. With whole-dollar draws, the hand-off is right that it needs no rounding.
Defect at main. I ran the PR's two YAML files against main's code (d4d39178). The property file fails Cases 2, 7, 10 and 12 (4 failed, 8 passed), and integration.yaml fails Cases 13 and 16 (2 failed, 15 passed).
Hand values, computed from the parameter files before reading the cases:
- single adult: housing min(300, 223) + food 92 + personal needs 11 = 326, plus transportation 30 / 12 = 2.50, giving 328.50;
- two adults: 257 + 160 + 19 = 436, plus 2.50, giving 438.50.
Both match the body's table. The parameter has one value from 2020-01-01, and the formula has no year branch. It runs on the SPM unit, and filing status doesn't enter.
Tests at this head, one file per process:
- Riverside YAML: 13 of 13 files pass, 62 cases. The property file has 12,
integration.yaml17, and the other 11 files 2+4+3+3+2+2+2+3+2+6+4 = 33. test_riverside_general_relief_attribution_invariants.py: 2 passed.test_source_conflicts.py: 103 passed.- Partner files, run read-only and not edited:
analytics_coverage/.../ca/riv_general_relief.yaml3 passed,amplifi/2025.yaml1 passed,amplifi/2026.yaml3 passed. ruff format --checkon the two changed Python files: already formatted.
Readers. ca_riv_general_relief_property_eligible is read only by ca_riv_general_relief_eligible, which gates ca_riv_general_relief. property.limit is read only by the changed variable. ca_riv_general_relief appears in no aggregator; only programs.yaml names it. So the diff can move only ca_riv_general_relief and its eligibility, and no household net income.
Body. Each number in the tests and mutants section matches my runs, and so do the float claims, the case table and the hand computations. Refs #10232 (no Fixes or Closes), impact: the hub will measure and axiom: TheAxiomFoundation/rulespec-us#1983 queued are present and in the right form. The closing line is the server's footer, which the hub restores at landing.
CI at this head. Pull request run 41536094536, the only workflow run at 9fb39dd7, finished with conclusion success. All 35 jobs succeeded: PR validation, Quick Feedback, Package and compatibility, Household API Partners, 11 Baseline shards, 9 Contrib shards, Microsimulation, the 8 Rest Python shards, and their plan and check jobs. None was skipped or cancelled. No reviews are on the PR.
Overlap. Open-PR searches for ca_riv_general_relief_property_eligible, test_riverside_general_relief_attribution_invariants, ca_riv_general_relief_income_eligible and "riverside general relief" return this PR plus #10607 and #9888. Neither of those touches a Riverside file.
Baseline impact. At baseline, units whose float32 ca_riv_general_relief_countable_property_value lies in [499.995, 500.005] can flip from ineligible to eligible for ca_riv_general_relief. Nothing else can move. The hub's population check should count Riverside SPM units in that band and report the change in ca_riv_general_relief. I expect it near zero.
What I couldn't check
- The fact sheet. web.archive.org and dpss.co.riverside.ca.us were refused through this VM's proxy (403), and rivcodpss.org is outside this session's allowed domains. So I couldn't read either quote, match the SHA-256, or look for an income bullet (P3-2). I rely on the hub's logged fetch for the property ruling and don't reargue it.
- The county manual (DP 90-001 p.22) isn't public.
- Population impact needs microdata, which the hub measures.
- rulespec-us#1983 is outside this session's repository scope.
- Source-conflict register. The ruling settles a tension inside one document (lead-in against bullet), and the PR adds no
# Source conflictmarker. Whether that needs a register entry is the hub's call;test_source_conflicts.pypasses.
Generated by Claude Code
Case 4 combines property across two members to exactly $500. Its comment said it avoided choosing eligibility there, which this PR now does; assert it (round 2 review P3-1, kills the strict-limit mutant). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Hub impact pair, progress The base side of the impact pair for this PR finished. The head side runs next, on the same household chunks.
Weighted sums at the base:
Generated by Claude Code |
|
Ready for hub review No variable moves. All seven outputs are identical, row by row, at the two SHAs. Every largest absolute difference is 0.0, and pandas The head's test,
The whole-process wall time and the maximum resident set size come from Dropped variables: none. All seven exist on both sides, on the same entity, and the helper accepted each one. Commands.
In run order, from a work folder that holds curl -sI https://huggingface.co/api/datasets/policyengine/populace-us | head -1 # the proxy's CONNECT line
curl -s -o /dev/null -w '%{http_code}\n' https://huggingface.co/api/datasets/policyengine/populace-us # 200
git clone https://github.com/PolicyEngine/policyengine-us.git pe && cd pe
git fetch origin pull/10582/head:pr-10582 pull/10561/head:pr-10561
HEAD_SHA=$(git rev-parse pr-10582); BASE_SHA=$(git merge-base origin/main $HEAD_SHA); echo $HEAD_SHA $BASE_SHA
git diff $BASE_SHA $HEAD_SHA -- pyproject.toml uv.lock # empty
git worktree add --detach ../head $HEAD_SHA
git worktree add --detach ../base $BASE_SHA
git show pr-10561:policyengine_us/tools/calculate_chunked.py > ../calculate_chunked.py
cd ../head && uv sync
.venv/bin/python -c "from huggingface_hub import hf_hub_download; print(hf_hub_download(repo_id='policyengine/populace-us', filename='populace_us_2024.h5', repo_type='dataset', revision='populace-us-2024-spm-20260909'))"
sha256sum "$(readlink -f <the path printed above>)"
apt-get install -y -q time || (apt-get update -q && apt-get install -y -q time) # the first install could not locate the package
cd ..
nohup setsid bash run_side.sh base 8 > out_base_runner.log 2>&1 < /dev/null &
nohup setsid bash run_side.sh head 8 > out_head_runner.log 2>&1 < /dev/null & # after the base side finished
cd pe; ../head/.venv/bin/python ../compare.py ../out/base ../out/head ../out/compare.json
sha256sum "$(readlink -f <the path printed above>)" # again after the head side: the same hashrun_side.shThe script's first line held the work folder's absolute path; here it reads #!/bin/bash
# Usage: run_side.sh <base|head> <chunks>. Runs the brief's command for one side.
side=$1
chunks=$2
cd pe || exit 1
mkdir -p ../out
H=$PWD/../calculate_chunked.py; PY=$PWD/../head/.venv/bin/python
export OMP_NUM_THREADS=4 OPENBLAS_NUM_THREADS=4 MKL_NUM_THREADS=4
rm -f ../out/$side.exit
date -u +%FT%TZ > ../out/$side.start
# Once a minute, record the side's RSS and peak RSS (kB) and the VM's free memory.
(
while true; do
pid=$(pgrep -f "^$PY \.\./pair_side\.py" | head -1)
if [ -n "$pid" ]; then
echo "$(date -u +%T) $(grep -E 'VmRSS|VmHWM' /proc/$pid/status 2>/dev/null | tr -s ' \t' ' ' | tr '\n' ' ')$(grep MemAvailable /proc/meminfo | tr -s ' ')"
fi
sleep 60
done
) > ../out/$side.mem 2>&1 &
sampler=$!
PYTHONPATH=$PWD/../$side /usr/bin/time -v $PY ../pair_side.py $H $chunks 2026 ../out/$side > ../out/$side.log 2> ../out/$side.err
status=$?
kill $sampler 2>/dev/null
date -u +%FT%TZ > ../out/$side.end
echo $status > ../out/$side.exitcompare.json{
"household_net_income": {
"entity": "household",
"base_sum": 17161428162125.828,
"head_sum": 17161428162125.828,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"household_benefits": {
"entity": "household",
"base_sum": 2059793926050.1262,
"head_sum": 2059793926050.1262,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"ca_riv_general_relief": {
"entity": "spm_unit",
"base_sum": 0.0,
"head_sum": 0.0,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"ca_riv_general_relief_property_eligible": {
"entity": "spm_unit",
"base_sum": 119676.02529076242,
"head_sum": 119676.02529076242,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"ca_riv_general_relief_eligible": {
"entity": "spm_unit",
"base_sum": 0.0,
"head_sum": 0.0,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"income_tax": {
"entity": "tax_unit",
"base_sum": 2595105852713.508,
"head_sum": 2595105852713.508,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"state_income_tax": {
"entity": "tax_unit",
"base_sum": 578044599346.724,
"head_sum": 578044599346.724,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
}
}Generated by Claude Code |
|
US + core hub merge audit, us#10582 at
🤖 Generated with Claude Code |
Ruling
Riverside County General Relief now counts a unit holding exactly $500 of total combined countable property as property-eligible.
The DPSS General Assistance fact sheet has two relevant lines:
The lead-in is the general sentence covering both limits. The property bullet is the specific rule for property, and the specific governs, so $500 meets the limit. The county manual (DP 90-001 p.22) isn't public.
Source: the fact sheet archived 2021-07-13, https://web.archive.org/web/20210713051521/http://dpss.co.riverside.ca.us/files/pdf/dpss-general-assistance.pdf#page=1 (SHA-256
5760c47280bfb42f8f60cd2d1243f4a77a2443edf6a0a28cf8e1b4c9c103f130).Change
ca_riv_general_relief_property_eligible.py:np.round(total_property_value, 2) <= p.limit(wastotal_property_value < p.limit). The variable now cites the fact sheet.<=reads as over the limit. Rounding the total to whole cents first fixes this, asis_optional_senior_or_disabled_asset_eligible.pyalready does. $500.01 stays ineligible: float32 500.01 is 500.0100098, which rounds to 500.01.property/limit.yaml: the description now reads "Riverside County limits General Relief to units whose total combined countable property does not exceed this amount." (The limit applies to the SPM unit's combined property, not to a tax filer.) The fact sheet is added as a reference (title, then href with#page=1), and the manual reference is kept.tests/core/test_riverside_general_relief_attribution_invariants.py: the property-eligibility oracle is nowproperty_value <= PROPERTY_LIMITand the docstring says "at or below $500". It still encoded the old<rule and passed only because no draw totalled exactly $500.changelog.d/riv-gr-property-boundary-500.fixed.md.Cases
The property is cash held by the first adult, with no vehicle and no personal property.
eligibility/ca_riv_general_relief_property_eligible.yaml: the existing Case 2 ($500 given directly as countable property) flips from ineligible to eligible. Cases 6-11 cover a single adult and a two-adult unit at $499.99 (eligible), $500 (eligible) and $500.01 (ineligible). Case 12 spreads exactly $500.00 over bank ($265.92), stock ($133.82) and bond ($100.26) accounts: eligible.integration.yaml(TY2024-01, $300 monthly housing cost, no income):At $500 the grant equals the $0-property grant: Case 13 equals Case 11, and Case 16 equals Case 1.
Tests run (one process per file)
integration.yaml17, the other 11 files 33.test_riverside_general_relief_attribution_invariants.py: 2 passed.test_source_conflicts.py: 103 passed.analytics_coverage/.../ca/riv_general_relief.yaml3 passed,amplifi/2025.yaml1 passed,amplifi/2026.yaml3 passed.Mutants (not committed, code restored after each):
total_property_value < p.limit(main's rule): the property-eligibility file fails Cases 2, 7, 10 and 12 (4 of 12), andintegration.yamlfails Cases 13 and 16 (2 of 17). The invariant test still passes (2 of 2), since its draws are whole dollars and never total exactly $500.total_property_value <= p.limit(no rounding): the property-eligibility file fails Case 12 only (1 of 12);integration.yamlpasses 17 of 17.np.round(total_property_value, 2) <= p.limit + 1: the property-eligibility file fails Cases 8 and 11,integration.yamlfails Cases 14 and 17, and the invariant test failstest_seeded_population.CI
At head
9fb39dd72dedc061a37e33289851075163fac9ae: Pull request run 41536094536 completed with conclusion success, all 35 jobs green, including Household API Partners, every Baseline, Contrib and Python shard, and Microsimulation.Refs #10232
impact: no output moves in 2026. No Riverside SPM unit in the default dataset holds from $500 to $500.005 of countable property, where the new limit test and the old one disagree. No record in it receives General Relief in 2026, before or after this change (pair).
axiom: TheAxiomFoundation/rulespec-us#1983 queued
Generated by Claude Code