Skip to content

Count housing subsidies as CPUC does, and tighten the dataset, default and batch guards - #9464

Closed
MaxGhenis wants to merge 12 commits into
mainfrom
max/cpuc-housing-and-guards-20260914
Closed

MaxGhenis wants to merge 12 commits into
mainfrom
max/cpuc-housing-and-guards-20260914

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Four fixes from an external review of the SPM integration on upstream/main, plus three more found by an adversarial review of this branch.

1. Count housing subsidies as the CPUC form does

gov.states.ca.cpuc.income_sources counted spm_unit_capped_housing_subsidy — the Census SPM's housing subsidy capped at the housing portion of the SPM threshold less the tenant payment. That is a poverty-measurement construct, and it made a utility discount depend on which SPM geography the request selected.

Reproduced. A Los Angeles renter, 2024 earnings $40,700, rent and PHA payment standard $36,000, receiving housing assistance. Changing only spm.geography_kind:

Output National County
Housing assistance $23,790 $23,790
HUD tenant payment $12,210 $12,210
Capped housing subsidy $0 $310.88
CPUC countable income $40,700 $41,010.88
ca_care_eligible true false

Requesting CARE with no SPM configuration raised SPM_GEOGRAPHY_REQUIRED, though no SPM output had been asked for.

The source does not support the cap. PG&E Form 01-9077, the CARE/FERA application, footnotes its income guideline tables:

*Total gross annual household income includes all taxable and non-taxable revenues from all people living in the home, from whatever sources derived, including, but not limited to, wages, salaries, interest, dividends, spousal and child support payments, public assistance payments, Social Security and pensions, housing and military subsidies, rental income, income from self-employment and all employment-related, non-cash income.

— PG&E Form 01-9077, CARE/FERA Program Application (Rev. 6.26). The same footnote appears on the CPUC-filed tariff sheet, Revised Cal. P.U.C. Sheet No. 61313-E (Electric Sample Form 79-1055, Advice 7905-E, effective 1 June 2026). Both PDFs were fetched and read; grepping either for net of|capped|cap the|tenant share|rent portion|poverty measure|deduct|minus|offset returns nothing substantive. The form names the category and stops — it never says "gross" or "amount received", and it states no valuation rule at all.

So the entry becomes housing_assistance. Countable income for that renter is now $64,490 under national selection, county selection and no SPM configuration alike, and computes with no county input. The dead bha.berkeleyca.gov mirror (DNS no longer resolves) is replaced by PG&E's own URL plus the tariff sheet.

Please weigh this before merging. housing_assistance is not a report of receipt: is_eligible_for_housing_assistance is receives_housing_assistance | (is_renter & is_income_eligible) and takes_up_housing_assistance_if_eligible defaults to true. A 40-year-old Los Angeles renter, 2026 earnings $35,000, rent $24,000, sending receives_housing_assistance: false, is modelled with $11,580 of assistance, taking CPUC countable income from $35,000 to $46,580 and ca_care from $585 to $0. That is consistent with how the rest of the list already works — snap, tanf, ssi and wic are this model's amounts, not an applicant's reported ones — and the capped entry consulted the same modelled amount. But the cap incidentally suppressed it (that household's capped subsidy is $0), so the change is user-visible. It is pinned in test_cpuc_counts_modelled_housing_assistance_for_a_non_recipient_renter so anyone who wants the entry gated on receipt has a named test to change.

In microsimulation the change is immaterial — the effect above is specific to the household/API path. Measured over the certified default build (2026, weighted, CA), running the parameter file swapped on disk in three separate processes:

Housing entry ca_care_income_eligible ca_care_eligible Mean CA countable income
housing_assistance (this PR) 2,712,528 5,611,876 $209,072
spm_unit_capped_housing_subsidy (before) 2,712,528 5,611,876 $209,062
entry removed entirely 2,743,814 5,611,876 $208,956

Income eligibility is unchanged to the person, and mean countable income moves $10. The certified build supplies receives_housing_assistance and takes_up_housing_assistance_if_eligible as inputs, so take-up is not imputed there, and for genuinely assisted units the SPM cap seldom binds — the capped figure was already close to the full subsidy. Dropping the entry altogether would move income eligibility by 31,286 (+1.15%), so the entry does carry weight; it is the valuation that microsimulation barely notices.

Other consumers of spm_unit_capped_housing_subsidy, left as they are. The form says nothing about any of them.

Consumer Why unchanged
spm_unit_benefits → spm_unit_net_income The SPM resource measure itself; the cap is the measure's own definition
household_benefits (gov.household.household_benefits) → household_net_income Same measure, household level
gov.household.cbo_means_tested_transfers → CBO household income CBO's own income concept; I did not read CBO's housing-subsidy valuation this session and have no basis to change it
Tlaib ECPA / EDAA / BOOST and tx_rebate reforms Poverty-measure-shaped benefit substitutions

2. Reject derived poverty aliases from stored datasets

The dataset guard rejected only the calculator's FORMULA_OWNED_INPUTS, so a dataset supplying in_poverty, in_deep_poverty, deep_poverty_line, deep_poverty_gap or person_in_poverty was accepted unchanged, and the documented poverty calculation could publish a different rate from spm_unit_is_in_spm_poverty for the same population. DERIVED_POVERTY_OUTPUTS now sits beside the calculator contract and the union is rejected, with a closure test asserting no further pure function of the poverty chain can be stored.

3. Verify the default dataset resolves to the certified build

DEFAULT_DATASET pins a Hugging Face tag, a mutable pointer: re-tagging, or a truncated download, would hand the model another schema-valid H5 and change every standalone-country result with nothing rejecting it. DEFAULT_DATASET_SHA256 now sits beside the URI and is checked after resolution — for the country default only, since an explicit dataset= names the caller's own artifact. A mismatch raises with the URI, the resolved path and both hashes. The ~830 MB digest is memoised on (path, size, mtime), so a default Microsimulation pays it once per process.

4. The batch runner's exit status is authoritative

test_batched.py decided a batch by parsing pytest's summary line: absent an "N failed" count it called the batch passed, then terminated the child without reading its status. pytest prints no failure count for an errored run, so 1 passed, 1 error with child exit 1 was reported as passing and make test-yaml-* exited 0 on it. An interrupted run is worse: with policyengine-core's argv, a session whose second test raises KeyboardInterrupt exits 2 beneath 1 passed in 0.10s — measured — which the old parse also scored a pass.

The status cannot simply be waited for. Once pytest.main() returns, interpreter teardown frees the tax-benefit systems the run cached, and that is slow: measured here, one small state took 18.2s to exit after printing its summary, and the large state batches took over 30s. Waiting turned every big green batch red. So the command now runs through a shim that writes pytest's return value to stdout the instant it has one, before teardown — measured, that marker arrives in the same read as the summary line, so it costs no wall clock. The runner reads the marker, terminates the child exactly as before, and decides the batch on the reported status. A child that dies before reporting is judged by its process status, which is what an OOM kill leaves behind.

Exit 5 (no tests collected) is a failure here, unlike in run_selective_tests.py: that runner selects paths from a changed-file list, where a changed test helper legitimately collects nothing, while this one enumerates the YAML suite itself, so an empty batch means the suite or the enumeration is broken. Every batch line now carries its exit status as release evidence.

.yml is enumerated alongside .yaml everywhere the runner lists files — policyengine-core collects both (tools/test_runner.py:413), so listing only *.yaml would drop a .yml test from any file-level batch, the heavy-subdirectory split among them, while the batch still reported green. No .yml test files ship today, so no current batch changes.

Found reviewing this branch

  • The monitoring loop was unbounded, in this revision and before it: the 30-minute budget sat below the loop, reachable only once the child had already exited, so the wait it guarded returned instantly and never fired. Reading the status from the runner also left pytest's session-finish window open. Both are enforced inside the loop now. A/B on a child that prints a summary then sleeps: the previous revision was still running after 40s; this one returns with a status.
  • batch_status's docstring claimed pytest exit 2 prints no summary count. It does (above). Corrected.
  • _static_variable_reads discarded any variable whose class body held no direct entity call — 884 of the system's 6,173, among them poverty_line, which is adds = ["spm_unit_spm_threshold"] and no formula at all, a live member of the chain the guard protects. The closure test was blind to the idiom the next alias would most likely use. It now trusts a formula-less variable's adds/subtracts and the helpers that name their reads as string literals: 5,665 variables resolve, and the closure still passes.

Verification

Every suite below was run in this worktree on Python 3.14 with the pinned .venv; exit codes are the runner's own.

Suite Result
tests/core + tests/unit + tests/code_health 429 passed, exit 0
CA CPUC YAML + partner CA care/fera fixtures 96 passed, exit 0
policy/baseline/gov/states, 16 batches (test-yaml-no-structural-states) 17 batches, all exit 0, 3,322 files
partner YAML target (test-yaml-no-structural-other-partners), all five invocations exits 0 0 0 0 0
policy/baseline/household + tests/variables exit 0, exit 0
policy/contrib/congress per-subdir + policy/reform per-file exit 0, exit 0
make format (ruff format + check) clean

Partner contract fixtures under tests/policy/baseline/partners/** pass unchanged and were not edited (git diff upstream/main -- .../partners/ is empty).

Not addressed here

Astra's P1 #2 and #3 and P2 #4 and #5 — later apply_reform mutating a shared parameter tree, a reformed clone's calc() routing to the original, private-variable registration through shared Entity objects, and shared parameter trace caches — are spm.py/policyengine-core concerns outside this branch.

The CPUC ESA Policy & Procedures Manual §2.2.2 lists housing subsidies among receipts not counted as household income, footnoted to D.14-08-030 OP 40. Its own scope line reads "the ESA Program", the Attachment Q footnote naming that rule is written for "the ESA and CARE Programs" jointly, and OP 40 itself could not be retrieved (docs.cpuc.ca.gov's search endpoint returns HTTP 000; the APEX proceeding page needs a browser session). So whether the exclusion reaches CARE is genuinely unresolved. This PR keeps the application form's treatment — housing subsidies count — and only corrects how they are valued. Settling the include-versus-exclude question needs OP 40.

🤖 Generated with Claude Code

MaxGhenis and others added 12 commits September 14, 2026 14:51
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The dataset guard rejected only the calculator's FORMULA_OWNED_INPUTS, so a
dataset supplying in_poverty, in_deep_poverty, deep_poverty_line,
deep_poverty_gap or person_in_poverty was accepted unchanged. Those are
country-side functions of the same measurement, so the documented poverty
calculation and spm_unit_is_in_spm_poverty could publish different poverty
rates for one population.

Add DERIVED_POVERTY_OUTPUTS beside the calculator contract and reject the
union. A closure test asserts no further pure function of the poverty chain
can be stored.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
DEFAULT_DATASET pins a Hugging Face tag, which is a mutable pointer: re-tagging
the repository, or a truncated or substituted download, would hand the model
another schema-valid H5 and change every standalone-country result with nothing
rejecting it anywhere.

Record the certified build's sha256 beside the URI and check it after
resolution, for the country default only - an explicit dataset= argument names
the caller's own artifact and stays the caller's responsibility. A mismatch
raises with the URI, the resolved path, and both hashes. The ~830 MB digest is
memoised on (path, size, mtime), so a default Microsimulation pays it once per
process rather than a third of a second per construction.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The runner decided a batch by parsing pytest's summary line: with no "N failed"
count it called the batch passed, then terminated the child without reading its
exit status. pytest prints no failure count for an errored run, so a real child
producing "1 passed, 1 error in 0.01s" and exiting 1 was reported as passing,
and make test-yaml-* exited 0 on it. Reproduced end to end against both
revisions: old status='passed' returncode=None, new status='failed'
returncode=1.

Wait for the child to exit after it reports, and read its status. Only 0
passes. A child that stops responding is killed and reported rather than
credited a pass, and a summary that claimed no failures alongside a nonzero
exit is called out by name in the batch log. Exit 5 (no tests collected) is a
failure here, unlike in run_selective_tests.py: that runner selects paths from
a changed-file list, where a changed helper legitimately collects nothing,
while this one enumerates the YAML suite itself, so an empty batch means the
suite or the enumeration is broken. Every batch line now carries the exit
status as release evidence.

Enumerate both suffixes policyengine-core collects. Listing only "*.yaml" would
drop a .yml test from any file-level batch - the heavy-subdirectory split among
them - while the batch it belonged to still reported green. No .yml test files
ship today, so this changes no current batch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gov.states.ca.cpuc.income_sources counted spm_unit_capped_housing_subsidy: the
Census SPM's housing subsidy capped at the housing portion of the SPM threshold
less the tenant payment. That is a poverty-measurement construct, and it made a
utility discount depend on which SPM geography a request selected. A Los Angeles
renter earning $40,700 with $23,790 of housing assistance had a $0 capped
subsidy under national selection and $310.88 under county selection, moving
countable income from $40,700 to $41,010.88, across the CARE limit, and flipping
ca_care_eligible from true to false. Requesting CARE with no SPM configuration
raised SPM_GEOGRAPHY_REQUIRED, though no SPM output had been asked for.

The parameter's own cited source does not support the cap. PG&E Form 01-9077,
the CARE/FERA application, footnotes its income tables: "Total gross annual
household income includes all taxable and non-taxable revenues from all people
living in the home, from whatever sources derived, including, but not limited
to, wages, salaries, interest, dividends, spousal and child support payments,
public assistance payments, Social Security and pensions, housing and military
subsidies, rental income, income from self-employment and all
employment-related, non-cash income." The same footnote appears on the
CPUC-filed tariff sheet (Revised Cal. P.U.C. Sheet No. 61313-E). Both were
fetched and read; neither contains any netting, cap, tenant-share or
poverty-measurement language. Count housing_assistance instead, and replace the
dead berkeleyca.gov mirror with PG&E's own URL and the tariff sheet.

Countable income for that renter is now $64,490 under national selection,
county selection and no SPM configuration alike, and computes with no county
input. Three contract tests pin that, all failing on the old parameter, and two
YAML cases pin the arithmetic. The partner CARE and FERA fixtures pass
unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Waiting for the batch subprocess to exit turned every large green batch red.
policyengine-core takes tens of seconds to tear down once pytest.main()
returns, freeing the tax-benefit systems the run cached: measured here, a
single small state took 18.2s to exit after printing its summary, and the
first two state batches of make test-yaml-no-structural-states blew through a
30s grace and were reported as killed by signal 15 despite 1,708 and 2,177
passing tests. Terminating the child promptly is why the previous code never
had a status to read in the first place.

Ask for the status where it is both known and free instead. The command now
runs through a shim that writes pytest's return value to stdout the instant
pytest.main() hands it back, before any teardown; measured, that marker
arrives in the same read as the summary line, so it costs no wall clock. The
runner reads the marker, terminates the child exactly as promptly as before,
and decides the batch on the reported status. A child that dies before
reporting is judged by its process status, which is what an OOM kill or an
import error leaves behind.

The reproduction still holds against both revisions: for a child printing
"1 passed, 1 error" and finishing 1, old status='passed' returncode=None,
new status='failed' returncode=1. A new end-to-end case runs the real
policyengine-core against an empty directory and asserts the runner reports
pytest's exit 5.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Three findings from an adversarial review of this branch.

The batch runner's monitoring loop is unbounded, and was before this branch
too: the 30-minute budget sits below the loop, reachable only once the child
has already exited, so the wait it guards returns instantly and its
TimeoutExpired never fires. Reading the status from the runner rather than
terminating a second after the summary removed the only bound run_batch ever
enforced in practice, leaving pytest's session-finish window open as well.
Enforce both inside the loop. A/B on a child that prints a summary and then
sleeps: the previous revision was still running after 40s, this one returns
with a status.

batch_status's docstring claimed pytest exit 2 prints no summary count. It
does. Under policyengine-core's argv, a session whose second test raises
KeyboardInterrupt exits 2 beneath "1 passed in 0.10s" - measured - and the old
parse read that as success by matching the summary, not by falling through to
a status it had discarded. Corrected; it is the clearest example of the defect.

_static_variable_reads discarded any variable whose class body held no direct
entity call. That dropped 884 of the system's 6,173 variables, among them
poverty_line, which is `adds = ["spm_unit_spm_threshold"]` and no formula at
all - a live member of the chain the guard protects. The closure test was
blind to the idiom the next alias would most likely use, so "no further alias
can be stored" claimed more than it checked. Trust a formula-less variable's
adds/subtracts, and the helpers that name their reads as string literals:
5,665 variables now resolve, the whole poverty chain among them, and the
closure still passes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An adversarial review found the entry counts a voucher the household may not
hold. `housing_assistance` is not a report of receipt:
is_eligible_for_housing_assistance is
`receives_housing_assistance | (is_renter & is_income_eligible)` and
takes_up_housing_assistance_if_eligible defaults to true, so a California
renter sending receives_housing_assistance: false is still modelled as holding
one. Reproduced: a 40-year-old Los Angeles renter, 2026 earnings $35,000, rent
$24,000, no voucher, is modelled with $11,580 of assistance, which takes CPUC
countable income from $35,000 to $46,580 and ca_care from $585 to $0.

That is how the rest of the list already works - snap, tanf, ssi and wic are
this model's amounts, not an applicant's reported ones - and the entry this
replaced consulted the same modelled amount, merely capped. But the cap
incidentally suppressed it: that household's capped subsidy is $0. So the
change is real and user-visible, and it deserves to be a named decision rather
than an incidental one.

Add a test that pins it with the mechanism spelled out, so a reviewer who
wants the entry gated on receipt has something to change, and say "the
modelled housing subsidy" in the parameter comment and changelog rather than
"the housing subsidy the household receives", which the variable does not
mean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`gov.states.ca.cpuc.income_sources` counted `housing_assistance`, which is
eligibility times take-up rather than a report of receipt:
`is_eligible_for_housing_assistance` is
`receives_housing_assistance | (is_renter & is_income_eligible)` and
`takes_up_housing_assistance_if_eligible` defaults to true in the household
path, so every income-eligible California renter who supplied a rent was
modelled as holding a voucher and carried it into CARE/FERA income. A Los
Angeles renter earning $35,000 against $24,000 of rent, sending
`receives_housing_assistance: false`, picked up $11,580 of imputed assistance
and lost the discount.

PG&E Form 01-9077 asks an applicant what the household receives, so the entry
now counts receipt: `reported_housing_assistance` is `housing_assistance`
gated on `receives_housing_assistance`, an input defaulting to false. The
form's valuation still holds for a household that reports the voucher - the
subsidy counts in full, with no netting against rent and no cap.

Drops PROGRESS.md; lane bookkeeping does not ship.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An adversarial review of the receipt gate raised no defect in it, but three
things it could not tell from the files: that `reported_housing_assistance`
is the model's amount gated on a reported flag rather than an amount the
applicant reports, so a caller who sends `housing_assistance` without
`receives_housing_assistance: true` has nothing counted; that
`receives_housing_assistance` is already this model's report of Section 8
receipt, read by FCC Lifeline, MA LIHEAP, CT TFA, RI Works and Orange County
general relief; and which of the two housing variables a new consumer wants -
`housing_assistance` carried the documentation string "Housing assistance".

Also pins the worked example in the parameter comment to its inputs: $11,580
is the payment standard the model derives for Los Angeles County, $13,500 the
supplied $36,000 standard the contract test uses, and the comment now says so
rather than reading as one household with two answers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.30%. Comparing base (f56fabe) to head (cafcd94).
⚠️ Report is 27 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##              main    #9464      +/-   ##
===========================================
- Coverage   100.00%   92.30%   -7.70%     
===========================================
  Files           17        2      -15     
  Lines          264       26     -238     
  Branches        10        1       -9     
===========================================
- Hits           264       24     -240     
- Misses           0        1       +1     
- Partials         0        1       +1     
Flag Coverage Δ
unittests 92.30% <100.00%> (-7.70%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Superseded by #9467, which carried this work to main (see its provenance table).

@MaxGhenis MaxGhenis closed this Sep 15, 2026
MaxGhenis added a commit to DTrim99/policyengine-us that referenced this pull request Sep 15, 2026
From PolicyEngine#9448, reduced. General household benefits, CBO means-tested transfers
and the three Tlaib household-benefit overrides now add actual
`housing_assistance` instead of the Census SPM's capped housing subsidy, so a
change to an SPM threshold no longer moves a non-SPM result. SPM resources keep
the capped valuation, now allocated across SPM units by member share.

CARE/FERA countable income excludes housing subsidies from the August 14, 2014
parameter date of CPUC D.14-08-030; annual values resolve at January 1, so
calendar 2014 retains the earlier modelled inclusion (using the actual benefit)
and calendar 2015 first applies the exclusion.

Also carries PolicyEngine#9448's release-candidate wheel tooling and the isolated build
backend pin.

Dropped from PolicyEngine#9448: its `spm.py` and `system.py` isolation hunks (PolicyEngine#9463 owns
the mechanism), its default-dataset hash and batch-runner hunks (PolicyEngine#9464 owns
those), its `docs/usage/microsimulation.md` edit (PolicyEngine#9463's is more specific),
its `reforms/reforms.py` change (no consumer) and its
`tests/core/test_spm_system.py` edits (they invert PolicyEngine#9463's contract).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
MaxGhenis added a commit to DTrim99/policyengine-us that referenced this pull request Sep 15, 2026
…tatus

From PolicyEngine#9464, reduced.

`REJECTED_DATASET_INPUTS` extends the calculator's `FORMULA_OWNED_INPUTS` with
the country-side poverty aliases (`in_poverty`, `in_deep_poverty`,
`deep_poverty_line`, `deep_poverty_gap`, `person_in_poverty`) and with the two
SPM housing-allocation outputs PolicyEngine#9448 adds, so a dataset can no longer store a
formula-owned measurement under a name the calculator does not police.
`DATASET_SOURCE_INPUTS` keeps `is_spm_independent_minor_role` declarable by a
producer, and the two sets are asserted disjoint.

`DEFAULT_DATASET_SHA256` pins the certified build the mutable Hugging Face tag
must resolve to, verified once per process and only for the country default; an
explicit `dataset=` argument remains the caller's own artifact.

The batch runner now decides a batch on the child's real exit status, reported
through a marker written before interpreter teardown, and enumerates `.yml`
alongside `.yaml` everywhere it lists files.

Dropped from PolicyEngine#9464: `reported_housing_assistance` and the CPUC entry that
counted it, plus the three tests that pinned housing assistance as countable -
CPUC D.14-08-030 excludes housing subsidies outright, so there is nothing to
gate on receipt and the variable had no other consumer. Its citation research
is folded into the CPUC parameter instead.

Adaptations: the dataset guard is grafted onto PolicyEngine#9463's `set_input`;
`test_spm_system.py`'s import block is the union of both PRs' needs;
`test_care_eligibility_is_the_same_under_every_spm_geography` expects
`(40_700, True)` because $40,700 of countable income is inside the 2024
one-person CARE limit of $40,880 once the subsidy is excluded; and the two
counting non-vacuity tests are replaced by their exclusion analogues so the
suite still fails if the housing entry is silently dropped rather than excluded.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
MaxGhenis added a commit to DTrim99/policyengine-us that referenced this pull request Sep 15, 2026
Folds PolicyEngine#9464's citation research into PolicyEngine#9448's dated exclusion list. The ~25
non-housing entries regain a source: PG&E Form 01-9077 (Rev. 6.26) at PG&E's
own URL, plus Revised Cal. P.U.C. Sheet No. 61313-E (Electric Sample Form
79-1055, Advice 7905-E, effective June 1, 2026), which carries the same income
footnote as a CPUC filing. Both are annotated as being corrected: PG&E's
June 5, 2026 reply brief in A.26-01-003 states it does not use housing and
military subsidies in its income analysis and will revise the application. The
dead bha.berkeleyca.gov href is gone; all nine references return HTTP 200.

Records review item R8: FERA coverage of the exclusion rests on the 2026 brief,
not on D.14-08-030. Verified first-hand against the decision PDF - it contains
zero case-sensitive occurrences of "FERA", "Family Electric" or "739.12",
while the brief's Section B answers the income definition under Public
Utilities Code 739.1(a) and 739.12(a) for CARE and FERA alike.

Verified against the live parameter tree: 2013-01-01, 2014-01-01 and
2014-08-13 resolve to 27 entries including housing_assistance; 2014-08-14,
2015-01-01 and 2026-01-01 resolve to 26 without it, the only set difference
being housing_assistance, and all 27 names resolve to model variables.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

1 participant