Skip to content

Add a national LIHEAP total, an Illinois regular heating payment and LIHEAP cooling registry notes - #10730

Merged
hua7450 merged 7 commits into
mainfrom
liheap-aggregate
Nov 12, 2026
Merged

hua7450 merged 7 commits into
mainfrom
liheap-aggregate

Conversation

@hua7450

@hua7450 hua7450 commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

Adds a national liheap total that sums each state's regular LIHEAP payment, an Illinois regular heating payment without crisis assistance, a Michigan Home Heating Credit registry entry, and a cooling note for every LIHEAP registry entry that needs no code change. It also fixes the state LIHEAP formulas that raised errors when calculated over microdata.

Fixes #10729

What and why

National total. liheap (SPMUnit, YEAR, USD) adds the variables in a new list parameter, gov.hhs.liheap.regular_payments. The list has 52 entries for 49 states and DC. Each entry is the variable the state's registry entry names, with three exceptions:

  • Illinois: the new il_liheap_regular_heating replaces il_liheap, which also counts crisis assistance.
  • California: all three regular components are listed (ca_liheap_utility_heating, ca_liheap_covered_liquid_heating, ca_liheap_wood_heating). They are exclusive by eligibility, so their sum is California's single regular payment.
  • Michigan and Riverside County: left out. The Michigan Home Heating Credit already counts as a refundable state credit, and Riverside County's variable is eligibility only.

The list is dated 2023-01-01, like gov.household.household_state_benefits. Each state variable keeps its own effective dates, so the list date changes no result. A later state PR, for example a state cooling payment, adds one line to this list.

Massachusetts unit. ma_liheap declared no unit; this adds unit = USD, the only change to that file.

Illinois. il_liheap_regular_heating is il_liheap_base_payment for households that pass il_liheap_eligible. The eligibility gate matters: the base payment alone is positive for a heat-in-rent renter who fails the rent test. il_liheap keeps its current meaning, base plus crisis.

Microsimulation fixes. liheap is the first variable that evaluates every state's LIHEAP formula over a mixed-state population. Two patterns already on main raised errors there, although each state's own tests pass:

  • options=[ADD] on an entity call, in 26 formula files across 19 states. In a microsimulation, Core's Microsimulation.calculate_add rejects the decode_enums keyword that an entity call passes. The option was redundant: a plain read already sums a monthly variable over the year through the same Core function, so no result changes. The comments that described the option now say that a plain read sums a monthly variable. Fix formula reads with ADD or DIVIDE in a Microsimulation policyengine-core#647 fixes the cause in Core; removing the option here lets liheap run on today's Core.
  • DC and Illinois matrix lookups (dc_liheap_payment, il_liheap_base_payment). A payment formula runs for every household once any household in its state qualifies, and a household elsewhere has income level 0, which neither matrix has a row for. Both formulas now floor the level at 1, as Maryland and Kansas already do. The floor changes no in-state result.

Registry (programs.yaml, LIHEAP entry).

  • Adds variable: liheap and parameter_prefix: gov.hhs.liheap at the program level and MI to the coverage line.
  • Adds a Michigan entry for mi_home_heating_credit (parameters gov.states.mi.tax.income.credits.home_heating), noting that the credit is not in the liheap total and that cooling help comes only through crisis and weatherization.
  • Rewrites the cooling wording in 33 state notes, one or two sentences each, from a source review of every state's FY2026 and FY2027 plans and manuals:
    • Regular benefit also covers cooling bills: MN, MO, MS, NM, TN, TX, UT. The Utah note had called cooling out of scope, which contradicted its code.
    • Cooling help only through crisis, equipment or weatherization (out of scope): IA, ID, IL, KS, MD, MT, NC, ND, NH, NY, OH, PA, WA, WI, WV, WY. Each note names the state's channel. North Dakota's note also says the state reserves an optional direct cooling payment with no set amount, and West Virginia's that its plans reserve a regular cooling payment of up to $2,000 if funds remain.
    • No cooling assistance: AK, CO, CT, IN, MA, ME, RI, SD, VT.
    • New Jersey: the medically necessary cooling benefit is not modeled, because it needs a medical certification no input carries.
  • AZ already described heating and cooling. The other 16 state entries are edited by the separate state cooling PRs.

Not counted in net income. liheap is not added to household_benefits, state benefits or SPM resources in this PR. A follow-up PR will:

  • add a take-up input, as SNAP and TANF have;
  • list liheap in household_benefits, as housing_assistance is, while SPM benefits keep spm_unit_energy_subsidy, the survey-reported energy assistance, so neither total counts both;
  • check for cycles, since several state formulas read SNAP, TANF or SSI.

Test plan

Tests were not run locally. CI passed at 6bee540633; the commit since adds tests and changes comments and registry text.

  • YAML tests whose expected values come from the state benefit tables already on main (FY2026; the Michigan case uses the TY2025 MI-1040CR-7). 8 cases in 2 files:
    • liheap:
      • Texas CEAP flows through ($1,200).
      • Michigan: a 2025 household whose Home Heating Credit is $173.40 (0.6 × ($604 − 3.5% × $9,000)) adds $0.
      • Illinois counts the $425 regular payment, not the $1,500 crisis amount.
      • California's $850 wood payment counts once, and a Madera County propane household's $516 liquid-fuel payment counts once, with no utility payment.
      • Texas, DC and Illinois households in one simulation are each paid by their own state's program ($1,200, $529 and $425). This case raises an error without the DC and Illinois fixes.
    • il_liheap_regular_heating:
      • excludes crisis ($546, where il_liheap is $2,046)
      • $0 for an ineligible heat-in-rent renter whose base payment is $310, and $0 in liheap
  • regular_payments.yaml added to AGGREGATE_LISTS in test_aggregate_list_integrity.py, which checks that every member exists, is limited to its own state, and is not dropped between year blocks.
  • A new check in the same file: every dollar-amount variable in the LIHEAP registry entry, or the entries that stand in for it (Illinois's regular heating payment for il_liheap; California's three components), must be in the list, and il_liheap and mi_home_heating_credit must not be.
  • liheap calculated over the default microdata for 2026 (59,900 SPM units), on this branch before its latest merge of main: all 52 list entries and the total evaluate. Every amount is $0, because the microdata has no heating type, fuel bill or cooling-expense input.
  • Source-conflict register: not applicable; this PR adds no policy values.
  • make format (ruff)
  • Changelog fragments in changelog.d/

axiom: n/a: no policy rule changes; this PR sums existing state payments, edits registry notes, and fixes formulas that raised errors over microdata without changing a result

🤖 Generated with Claude Code

Sum each state's regular LIHEAP payment in a new liheap variable, add an
Illinois regular heating payment without crisis assistance, add a Michigan
Home Heating Credit registry entry, and record each state's cooling
treatment in its registry note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hua7450 and others added 2 commits November 9, 2026 20:34
Add regular_payments.yaml to the aggregate-list integrity test, which checks
that each member exists, is state-gated and is not dropped between year
blocks. Say that Rhode Island plans no regular cooling payment, since its
FY2027 plan still allows a leftover-funds cooling grant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hua7450 and others added 2 commits November 11, 2026 20:59
The national `liheap` total evaluates every state's formula over a
mixed-state population, which no household test does. Two patterns on main
raised errors there:

- 26 formula files passed options=[ADD] to an entity call. Core's
  Microsimulation.calculate_add rejects the keyword an entity call passes.
  The option is redundant: a plain read already sums a monthly variable over
  the year through the same Core function.
- The DC and Illinois payment formulas look up a matrix by an income level
  that is 0 for households outside the state. Add the floor Maryland and
  Kansas already use.

Also:
- Reword the North Dakota registry note: the state reserves an optional
  direct cooling payment, so its cooling component is not only equipment.
- Add a `liheap` case with Texas, DC and Illinois households in one
  simulation.
- Add a code-health check against options=[ADD] and options=[DIVIDE].
- Check that every state payment in the LIHEAP registry entry is in the
  list `liheap` sums.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@hua7450
hua7450 marked this pull request as ready for review November 12, 2026 02:01
PolicyEngine/policyengine-core#647 fixes the cause in Core: it lets a
Microsimulation accept the keyword an entity call passes with
options=[ADD] or options=[DIVIDE]. Once that is released the option works
again, so a check that bans it here would be wrong. The 26 formula files
stay without the option, which was redundant and lets `liheap` run on
today's Core.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@DTrim99

DTrim99 commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Add a national LIHEAP total, an Illinois regular heating payment and LIHEAP cooling registry notes

Head reviewed: 6bee540633. 38 files, +477/-68. First review.

Summary

The PR does what it says, and no result changes for any household that was covered before.

  • National total. All 52 list entries exist, are SPMUnit / YEAR / float / USD, are gated to one state, and are that state's regular payment. No state with a LIHEAP dollar variable on main is missing, apart from Michigan, which is left out on purpose.
  • options=[ADD] removal. All 30 reads in the 26 files return the same annual value without the option. I read policyengine-core 3.34.2 (the version in uv.lock) to confirm both the error the body describes and the equivalence.
  • DC and Illinois floors. A resident's level is never below 1, so no in-state result changes.
  • Registry notes. I read item 1.1 of the FY2026 plan for all 33 rewritten states plus Michigan, and the FY2027 plan for 21 of them. No note contradicts its plan or its state's code.
  • Mixed-state run. I ran one simulation at the head with 410 households (51 jurisdictions × 8 heating arrangements, plus two Michigan households) for 2026 and 2027, on policyengine-core 3.33.12 rather than the pinned 3.34.2. All 52 entries and liheap evaluate without error, no entry pays outside its own state, and liheap equals the sum of its members. Alaska and Hawaii paid $0 to every household in the grid, so their payment branches were not exercised.

Two test gaps need closing before merge: the Michigan case passes whether or not the Home Heating Credit is in the list, and two list lines (California liquid fuel, and the choice of the Illinois variable) can be changed with no test failing. Everything else is a suggestion.

Critical (must fix)

None.

Should address

1. The Michigan case does not test the Michigan exclusion

policyengine_us/tests/policy/baseline/gov/hhs/liheap/liheap.yaml:32-54; policyengine_us/tests/test_aggregate_list_integrity.py:181-187, 205-207.

Case 2 is named "a Michigan household gets nothing because the Home Heating Credit is not in the total". Its household (one adult aged 41, wages $24,000, gas bill $1,020) has a Home Heating Credit of $0, so the case would pass with the credit in the list:

  • Standard credit: resources of $24,000 exceed 110% of the poverty guideline (1.10 × $15,960 = $17,556), and 3.5% × $24,000 = $840 is more than the one-exemption allowance ($604 for 2025, uprated for 2026).
  • Alternate credit: $24,000 is above the one-exemption resource cap ($18,111 for 2024, uprated since), and $1,020 − 11% × $24,000 is negative.
  • At the head, mi_home_heating_credit is $0 for this household.

Nothing else pins the exclusion. LIHEAP_PAYMENTS_NOT_SUMMED only removes mi_home_heating_credit from the "missing" set; it does not fail when the name is in the list. test_members_are_state_gated passes too, because the credit is gated to Michigan.

  • Why it matters. The Michigan plan says "Heating assistance is administered by Treasury through the Home Heating Credit Program (HHC)" (FY2026 plan, item 1.1, p. 4), and the body leaves the credit out because it already counts as a refundable state credit. Once liheap joins household_benefits, a stray list line would count it twice.
  • Household that shows the gap. Add - mi_home_heating_credit to regular_payments.yaml. By my reading of the tests, every one still passes (I did not run this change). A Michigan adult with wages of $9,000 and the same gas bill would then get liheap = $181.61 instead of $0. At the head, a run gives this household mi_home_heating_credit = $181.61 (60% of the standard allowance less 3.5% of $9,000) and liheap = $0.
  • Fix. Lower the Case 2 wages to $9,000, add mi_home_heating_credit: 181.61 to the outputs and keep liheap: 0. In the code-health test, also assert that no name in LIHEAP_PAYMENTS_NOT_SUMMED is in the list.

2. Two list lines are pinned by no test, because the registry check sees one variable per state

policyengine_us/parameters/gov/hhs/liheap/regular_payments.yaml:10, 21; policyengine_us/tests/test_aggregate_list_integrity.py:181-212.

The new check compares each state implementation's single variable with the list. California's registry variable is ca_liheap_utility_heating, so the other two California lines are outside the check. Illinois's registry variable il_liheap is skipped, so the Illinois line is outside it as well, and nothing requires il_liheap_regular_heating in its place. By my reading of the tests, two changes would pass every one at the head (I did not run them):

  • Delete line 10 (ca_liheap_covered_liquid_heating). Case 4 is a wood household and no other case has a liquid-fuel payment. A Madera County propane household would lose its whole payment (up to the $1,000 maximum) from liheap.
  • Replace line 21 with il_liheap_base_payment. Cases 3 and 5 are eligible households, where both variables are $425. The renter in the new Illinois Case 2 (heat in rent, rent $8,400, which is not above 30% × $31,200 = $9,360) would get liheap = $310 instead of $0. This is the household the body uses to explain why the eligibility gate matters.

For comparison, these would be caught: deleting line 21 (Cases 3 and 5), deleting the wood line (Case 4), deleting the utility line or the line of any of the 48 single-variable jurisdictions (the registry check), and adding il_liheap (Case 3).

Fix.

  • Replace the skip set with a mapping from each registry variable to the list entries that stand for it: il_liheap → il_liheap_regular_heating; ca_liheap_utility_heating → the three California components; mi_home_heating_credit → none. Assert that every stand-in is in the list and that il_liheap and mi_home_heating_credit are not.
  • Add liheap: 0 to Illinois Case 2 in il_liheap_regular_heating.yaml (line 65).
  • Add a Madera propane row to liheap.yaml.

Suggestions

  1. Open question: yes, add parameter_prefix: gov.hhs.liheap (programs.yaml:1039). At the head, liheap is the only program-level entry that names a variable without a parameter_prefix; the other 104 give both. The closest analogues do too: school_meals (free_school_meals, gov.usda.school_meals) and head_start (head_start, gov.hhs.head_start); tanf gives gov.hhs.tanf even without a variable. The node exists (regular_payments, fpg_floor, fpg_limit, smi_limit), so test_parameter_prefix_resolves passes.
  2. Nothing guards the microsimulation fix now. The last commit deleted the check that banned options=[ADD], and policyengine-core#647 is still open and unreleased. Until the core floor in pyproject.toml moves past that release, a new formula that copies the old pattern brings the error back, and no test fails. The seven sibling PRs do not add the option (checked on each merged tree). Consider keeping the check until the floor is raised, with a comment to delete it then.
  3. Six comments still describe the removed option. az_liheap_countable_unearned_income.py:34, ia_liheap_countable_unearned_income.py:51, mn_eap_countable_income.py:37, ok_liheap_countable_income.py:27, sd_liheap_countable_income.py:37, va_eap_countable_income.py:50 ("ADD sums monthly sources such as ssi over the year"). Reword them to say the plain read sums the monthly variable.
  4. The microdata run in the body proves less than it reads. Every amount is $0, so a formula gated on eligibility that needs a fuel bill never runs there. The mixed-state run above closes that gap for this head. A multi-state YAML case wider than Texas, DC and Illinois would keep it closed.
  5. Say on liheap itself what it leaves out (variables/gov/hhs/liheap/liheap.py:8). A documentation line that Michigan's Home Heating Credit is excluded, and that the variable is not yet in net income, would save a reader a trip to the registry.
  6. West Virginia note (programs.yaml:1418-1421). The plan also reserves a regular cooling payment when money remains: "WV LIHEAP will have a cooling if funds remain to do so, up to the limit of $2000" (FY2026 plan, item 3.6, p. 14); "Max benefit for cooling LIHEAP is up to $2000" (FY2027 plan, p. 16). Item 1.1 says the program "will open if funding is available". The North Dakota note discloses the same kind of reserved payment; this one could too.
  7. Further hardening of the registry check, if wanted. It does not check that a member is a dollar amount on SPMUnit and YEAR (a flag would be summed as 1), or that a member is not a component of another member (pa_liheap_supplement beside pa_liheap would pass).
  8. Outside this diff, no action here. Indiana pays its $125 electric allowance to a home with heating_type NONE or UNSPECIFIED and an electric bill. That is the allowance, not cooling assistance, so the note is not contradicted, but it is worth a look when Indiana is next touched.

Verified correct

The 52 list entries

All 52 are SPMUnit, YEAR, float, USD. "Registry" means the entry is the variable the state's registry entry names. Every entry is the regular payment only; the notes say what is inside it.

State Variable defined_for Registry Regular payment only
AK ak_hap StateCode.AK yes yes, federal LIHEAP heating component
AL al_liheap StateCode.AL yes yes, heating award plus the IIJA supplement paid with it
AR ar_liheap ar_liheap_eligible yes yes
AZ az_liheap az_liheap_eligible yes yes, one Standard benefit for heating and cooling
CA ca_liheap_utility_heating ca_liheap_utility_eligible yes yes, see exclusivity below
CA ca_liheap_covered_liquid_heating ca_liheap_liquid_heating_covered no yes
CA ca_liheap_wood_heating ca_liheap_basic_eligible no yes
CO co_leap co_leap_eligible yes yes
CT ct_ceap ct_ceap_eligible yes yes, basic or rental benefit with the vulnerable add-on
DC dc_liheap_payment dc_liheap_eligible yes yes
DE de_liheap de_liheap_eligible yes yes
FL fl_liheap StateCode.FL yes yes, base plus elderly, disabled and child add-ons
GA ga_liheap ga_liheap_eligible yes yes
HI hi_liheap_subsidized_amount hi_liheap_eligible yes yes, the subsidized-dwelling benefit; private dwellings are $0 (a coverage gap, not a double count)
IA ia_liheap ia_liheap_eligible yes yes
ID id_liheap id_liheap_eligible yes yes
IL il_liheap_regular_heating il_liheap_eligible no (il_liheap) yes, base payment without crisis
IN in_eap in_eap_eligible yes yes, heating plus the winter electric allowance
KS ks_liheap ks_liheap_eligible yes yes
KY ky_liheap ky_liheap_eligible yes yes, Subsidy component
LA la_liheap la_liheap_eligible yes yes, base plus priority add-on
MA ma_liheap ma_liheap_eligible yes yes
MD md_meap md_meap_eligible yes yes
ME me_heap me_heap_eligible yes yes
MN mn_eap mn_eap_eligible yes yes, Primary Heat
MO mo_liheap mo_liheap_eligible yes yes
MS ms_liheap ms_liheap_eligible yes yes, fuel and electric columns
MT mt_liheap mt_liheap_eligible yes yes
NC nc_lieap nc_lieap_eligible yes yes
ND nd_liheap nd_liheap_eligible yes yes
NE ne_liheap ne_liheap_eligible yes yes
NH nh_fap nh_fap_eligible yes yes
NJ nj_liheap nj_liheap_eligible yes yes
NM nm_liheap nm_liheap_eligible yes yes
NV nv_liheap nv_liheap_eligible yes yes, Fixed Annual Credit
NY ny_heap ny_heap_eligible yes yes, base plus Tier I and vulnerable add-ons
OH oh_liheap oh_liheap_eligible yes yes
OK ok_liheap StateCode.OK yes yes
OR or_liheap or_liheap_eligible yes yes
PA pa_liheap pa_liheap_eligible yes yes, matrix amount plus the vulnerable-household supplement; neither is listed separately
RI ri_liheap ri_liheap_eligible yes yes
SC sc_liheap sc_liheap_eligible yes yes
SD sd_liheap sd_liheap_eligible yes yes
TN tn_liheap tn_liheap_eligible yes yes
TX tx_ceap tx_ceap_eligible yes yes, Utility Assistance
UT ut_heat ut_heat_eligible yes yes
VA va_eap va_eap_eligible yes yes
VT vt_liheap vt_liheap_eligible yes yes
WA wa_liheap wa_liheap_eligible yes yes
WI wi_wheap wi_wheap_eligible yes yes
WV wv_lieap wv_lieap_income_eligible yes yes
WY wy_lieap wy_lieap_eligible yes yes
  • Registry comparison. The LIHEAP entry has 51 state implementations at the head. 49 name a listed variable (California's names the utility component). The other two are il_liheap and mi_home_heating_credit, the two deliberate exceptions. No listed variable is a component of another listed variable, and none includes crisis assistance.
  • California. The three components cannot pay together. ca_liheap_utility_eligible requires that the liquid and wood components sum to zero. The liquid component needs heating_type PROPANE or FUEL_OIL; the wood component needs WOOD.
  • Michigan. mi_home_heating_credit is a TaxUnit variable in Michigan's refundable credit list, so leaving it out avoids a later double count.
  • Riverside County. Its only LIHEAP variables are ca_riv_liheap_eligible (a flag) and a countable-income helper. It is a separate registry program, not a LIHEAP state implementation, so the new check never reads it.
  • List date. 2023-01-01 matches gov.household.household_state_benefits.

il_liheap_regular_heating

  • il_liheap adds il_liheap_base_payment and il_liheap_crisis_assistance_amount for il_liheap_eligible units. The new variable adds only the base payment under the same gate, so it is the non-crisis payment for an eligible unit and $0 otherwise.
  • In the mixed-state run, il_liheap_regular_heating equals base payment × eligible for all 410 units. No household in that run had a crisis, so the split from crisis assistance rests on the code and on the two CI cases below.
  • The reference (IL_BenefitMatrix_Heat-Cool_2026.pdf#page=2) is the "No LID for Electric" matrix the parameters use. The three cells used in the tests are on it: all-electric, 101-150%, two people $425; non-LID natural gas, 101-150%, three people $546; cash, top band, one person $310.

options=[ADD] removal

In policyengine-core 3.34.2:

  • Population.__call__ passes decode_enums=False to calculate_add when ADD is given. Microsimulation.calculate_add takes only variable_name, period, map_to and use_weights, so the call raises a TypeError in a microsimulation. The body's description is right.
  • A plain call goes to Simulation._calculate, which sums a MONTH variable over a YEAR period with the same calculate_add, unless the variable is a stock. A variable is a stock by default only when it is bool, int, Enum, str, date or has unit /1.

All 26 calling formulas are YEAR-defined. All 13 MONTH variables read are float, USD flows. Main had the option in exactly these 26 files (30 reads); the head has none.

# Formula (entity) Read Read's entity and period Same value
1 az_liheap_countable_unearned_income (Person) 18 unearned sources ssi Person MONTH; 17 Person YEAR yes
2 co_leap_countable_income_person (Person) ssi Person MONTH yes
3 ct_ceap_categorically_eligible (SPMUnit) ssi, ct_ssp_person through members; ct_ssp Person MONTH; SPMUnit MONTH yes
4 fl_liheap_categorically_eligible (SPMUnit) add of snap, tanf, ssi SPMUnit MONTH; SPMUnit YEAR; Person MONTH yes
5 fl_liheap_countable_income (SPMUnit) 18 unearned sources through members; add of tanf ssi MONTH, rest YEAR; tanf YEAR (no-op) yes
6 hi_liheap_eligible (SPMUnit) ssi through members Person MONTH yes
7 hi_liheap_points (SPMUnit) ssi through members Person MONTH yes
8 ia_liheap_countable_unearned_income (Person) 16 unearned sources ssi, ia_ssa MONTH; 14 YEAR yes
9 ia_liheap_fixed_income_only (SPMUnit) earned plus non-fixed unearned sources all 17 Person YEAR yes (option was a no-op)
10 id_liheap_income_eligible (SPMUnit) snap over the October-September year; ssi through members SPMUnit MONTH; Person MONTH yes, same 12 months
11 la_liheap_countable_income (SPMUnit) ssi through members Person MONTH yes
12 mn_eap_countable_income (SPMUnit) 11 unearned sources ssi MONTH; 10 YEAR yes
13 mt_liheap_categorical_eligibility (SPMUnit) snap SPMUnit MONTH yes
14 mt_liheap_countable_income (SPMUnit) 5 earned, 19 unearned sources ssi, mt_ssp MONTH; rest YEAR yes
15 mt_liheap_benefit_income (SPMUnit) mt_tanf_payment_standard SPMUnit MONTH yes
16 nm_liheap_countable_income (SPMUnit) 21 unearned sources ssi, nm_ssi_state_supplement MONTH; rest YEAR yes
17 nv_liheap_countable_income (SPMUnit) 23 unearned sources ssi, nv_oss MONTH; rest YEAR yes
18 oh_liheap_countable_income (SPMUnit) 5 earned, 20 unearned sources ssi MONTH; rest YEAR yes
19 ok_liheap_countable_income (Person) add of each of 20 unearned sources ssi MONTH; rest YEAR yes
20 ok_liheap_net_income (SPMUnit) ok_ccs_copay SPMUnit MONTH yes
21 pa_liheap_supplement (SPMUnit) ssi through members Person MONTH yes
22 sd_liheap_countable_income (SPMUnit) 17 unearned sources ssi MONTH; rest YEAR yes
23 tn_liheap_countable_income (SPMUnit) 14 unearned sources ssi MONTH; rest YEAR yes
24 va_eap_countable_income (SPMUnit) 21 unearned sources ssi, va_ag MONTH; rest YEAR yes
25 wi_wheap_categorically_eligible (SPMUnit) snap SPMUnit MONTH yes
26 wi_wheap_countable_income (SPMUnit) 16 unearned sources ssi, wi_ssp_regular, wi_cts MONTH; rest YEAR yes

For a YEAR variable read over its own year, the option summed one period, so it was already a no-op. Person-level reads through spm_unit.members and through add take the same path as direct reads.

DC and Illinois floors

  • dc_liheap_income_level returns 1 to 10 for any DC unit (income at or below the first step, including negative income, is level 1). It is 0 only outside DC.
  • il_liheap_income_bracket floors the poverty ratio at 0 and its first bracket starts at 0, so an Illinois unit is in bracket 1 to 4. It is 0 only outside Illinois.
  • max_(level, 1) therefore changes no resident's lookup. It is the same line as md_meap (md_meap_level) and ks_liheap_matrix_amount (ks_liheap_income_band, ks_liheap_household_size_group).
  • Case 5 exercises both floors: its expected dc_liheap_income_level: [0, 2, 0] and il_liheap_income_bracket: [0, 0, 3] show the level-0 rows that the matrices have no key for.

Registry notes against the plans and the code

Item 1.1 of the FY2026 plan read for all 33 states and Michigan; FY2027 plan read for CT, IL, IN, KS, MA, MI, MN, MO, MS, ND, NJ, NM, NY, OH, PA, RI, SD, TN, TX, UT and WV.

  • Regular benefit also covers cooling (7).
    • MS, NM, TN, TX, UT: item 1.1 gives cooling dates in both years. In each state's code a home with no heating and an electric bill is paid (MS $600, NM $280, TN $305, TX $1,200, UT $605 in the run), which matches the note. The Utah note now agrees with ut_heat_eligible.
    • MO: no cooling component, and the plan says "payments made toward electric accounts may be utilized for cooling costs October through May" (FY2026 and FY2027, p. 9). The note says the same, and the code pays the electric amount only to a home that heats.
    • MN: no cooling component; Primary Heat is based on all energy costs, electricity included, and is "first distributed to the household's electric vendor" (FY2026, p. 24). The code counts the electric bill and requires a heating type, which fits the note.
  • Crisis, equipment or weatherization only (16). No cooling dates in item 1.1 for IA, ID, IL, KS, MD, MT, NC, NH, OH, PA, WA, WI, WY. ND, NY and WV list a cooling component that pays no bill:
    • ND: "NOT for payment of electric bills" (p. 14); the reserved direct payment is on p. 12.
    • NY: "NYS does not provide a cash Cooling benefit" (p. 12).
    • IL: "Illinois will not administer a separate Cooling assistance program" (p. 4, both years).
    • KS: "Kansas does not provide cooling assistance" (FY2027, p. 4).
    • WV: see suggestion 6.
  • No cooling assistance (9). No cooling dates, and no cooling payment or equipment program, found for AK, CO, CT, IN, MA, ME, RI, SD, VT. AK: "does not operate a Cooling Assistance component" (p. 11). VT: the same (p. 21). RI: "not planning to offer a cooling component in FFY 2026" (p. 19) and no funds expected in FFY2027 (p. 4).
  • NJ. "Depending on availability of funds, cooling is a medically necessary program benefit" (FY2026, p. 11; same in FY2027).
  • MI. Heating through the Home Heating Credit (p. 4); weatherization covers "Cooling system repair and replacement" (p. 19); year-round crisis pays energy bills, with the cooling-equipment boxes in item 4.15 unchecked (p. 16, viewed as an image).

The coverage line keeps every state on main and adds MI. The count of 33 rewritten notes and the three groups match the body.

Tests (hand-checked from the tables on main; Illinois cells also from the FY2026 matrix PDF)

2026 guideline: $15,960 plus $5,680 per extra person.

  1. liheap Case 1, Texas: 18,720 / 15,960 = 117%, above 75%, cap $1,200; min(1,200, 1,980 + 0) = 1,200. liheap 1,200.
  2. Case 2, Michigan: no Michigan entry in the list, 0 (see Should 1).
  3. Case 3, Illinois: guideline 21,640; 25,740 / 21,640 = 119%, bracket 3; all-electric, two people 425; min(425, 1,800) = 425; crisis 1,500; il_liheap 1,925; regular and liheap 425.
  4. Case 4, California: wood min(850, 1,000) = 850; utility 0 because another component pays; liquid 0; liheap 850.
  5. Case 5: Texas 1,200. DC: 9,334 is in the second $5,000 step, level 2; multi-family electricity, one person 529; min(529, 1,320) = 529. Illinois 425. liheap [1,200, 529, 425].
  6. Illinois Case 1: guideline 27,320; 31,200 / 27,320 = 114%, bracket 3; natural gas, three people 546; min(546, 1,080); + 1,500 = 2,046; regular 546.
  7. Illinois Case 2: 31,200 ≤ 2 × 15,960 = 31,920, income eligible; rent 8,400 ≤ 9,360, so not eligible; 31,200 / 15,960 = 195%, bracket 4; cash, one person 310; regular 0.

No existing test expectation changed.

What the new code-health checks do

  • Adding the list to AGGREGATE_LISTS catches: a member that is not a variable; a member with no state gate in its defined_for chain; a member dropped between year blocks.
  • The new registry check catches: a registered dollar variable missing from the list, so a deleted line for any of 49 jurisdictions (48 states and DC), or a new state added to the registry but not the list.
  • They miss: the items in Should 1 and 2 and suggestion 7. The Illinois and Michigan exceptions are exact names, which is narrow enough, but they are encoded as a skip only.

Other

  • Changelog: liheap-aggregate.added.md and liheap-aggregate.fixed.md, both sensible.
  • ma_liheap: only unit = USD added.
  • Parameter description follows the template; reference is 42 U.S.C. 8624(b)(1)(A).
  • Not checked: the DC $529 cell against DOEE's own matrix file (taken from the parameter on main); the microdata run described in the body; FY2027 plans for the 13 states not listed above.

Axiom line assessment

axiom: n/a is valid.

  • liheap is a sum over existing state rules.
  • il_liheap_regular_heating exposes a computation il_liheap already makes (base payment under the same eligibility gate). It encodes no new rule, parameter or eligibility test, so it needs no rulespec-us issue of its own. I found no existing Illinois LIHEAP issue in rulespec-us; that gap predates this PR.
  • The DC and Illinois floors and the options=[ADD] removal change no result.
  • The registry notes are documentation.

CI status

All 36 checks pass on run 43146746119, which ran at the current head 6bee540633. The PR's own tests ran green there: liheap.yaml (5 passed), il_liheap_regular_heating.yaml (2 passed) and test_aggregate_list_integrity.py (including the new registry check). CI cannot show the microsimulation path; that rests on the core source and the mixed-state run above.

Branch status

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.

Requesting two changes, both test gaps in the new list:

  1. The Michigan case passes whether or not mi_home_heating_credit is in the list, because its household's credit is $0, and the code-health check only skips the name; it does not fail if the name is added. Lower the wages to $9,000 and assert the exclusion.
  2. Two list lines are pinned by no test: ca_liheap_covered_liquid_heating can be deleted, and the Illinois line can be swapped for il_liheap_base_payment, with every test still passing by my reading. A mapping in the registry check plus two rows closes it.

Details are in Should Address 1-2 in the report above. Everything else holds: all 52 entries are the state's regular payment on the right entity and period, the 30 options=[ADD] reads return the same annual value without the option, the DC and Illinois floors change no resident's result, and no rewritten registry note contradicts its plan or its state's code. On the open question: yes, add parameter_prefix: gov.hhs.liheap. The eight suggestions are optional. CI is green at 6bee540633.

…ogram prefix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hua7450

hua7450 commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, David. Both changes are in 4625f4e59a:

  1. Michigan case. Case 2 now has wages of $9,000 and uses period 2025, where the TY2025 MI-1040CR-7 allowance ($604) is published; the 2026 allowance is still an uprating projection. It asserts mi_home_heating_credit = 173.40 (0.6 × (604 − 3.5% × 9,000); the alternate credit is 0.7 × (1,020 − 990) = 21) and liheap = 0. The registry check now also fails if il_liheap or mi_home_heating_credit is in the list.
  2. Unpinned lines. LIHEAP_PAYMENTS_NOT_SUMMED is replaced by LIHEAP_PAYMENT_STAND_INS: il_liheap → il_liheap_regular_heating, ca_liheap_utility_heating → the three California components, mi_home_heating_credit → none. Every stand-in must be in the list. Illinois Case 2 asserts liheap: 0, and a new Madera County propane case asserts the $516 liquid-fuel payment, $0 utility and $516 liheap.

Suggestions taken: parameter_prefix: gov.hhs.liheap (1), the six comments (3), a documentation line on liheap (5) and the West Virginia note (6). Left as they are: an options=[ADD] guard (2), since we chose to rely on PolicyEngine/policyengine-core#647, and suggestions 4, 7 and 8. Tests were not run locally.

@hua7450
hua7450 requested a review from DTrim99 November 12, 2026 17:10
@DTrim99

DTrim99 commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 2): Add a national LIHEAP total, an Illinois regular heating payment and LIHEAP cooling registry notes

Head reviewed: 4625f4e59a (round 1 was at 6bee540633). 38 files, +557/-75. One fix commit since round 1, touching 11 files.

Summary

Both round-1 findings are fixed, and each fix is pinned by a test that fails without it.

  • Michigan. Case 2 now uses a 2025 household whose Home Heating Credit is $173.40, asserts that amount and asserts liheap: 0. I checked the amount against the TY2025 MI-1040CR-7 form and instruction book. The registry check now also fails if mi_home_heating_credit or il_liheap is in the list.
  • California and Illinois list lines. The skip set is replaced by a mapping from each registry variable to the list entries that stand for it. A new Madera County propane case pins the liquid-fuel line ($516), and Illinois Case 2 now asserts liheap: 0, which pins the choice of the Illinois variable.
  • No formula changed. Outside tests and the registry, the commit changes six comments and adds a documentation string to liheap.
  • Suggestions. Four taken (1, 3, 5, 6), four left (2, 4, 7, 8). Suggestion 2 stays open as an option: policyengine-core#647 is still unmerged.

CI is green at the new head (37 checks), and the PR's own test files ran and passed there. Two wording suggestions are new; neither blocks.

How each claim below was checked is marked: CI (the run at this head), hand (arithmetic from the parameters at the head and the cited form), reading (the code or tests), or stub run (described under "The rewritten registry check").

Prior findings

Finding Status Evidence
Should 1. The Michigan case did not test the Michigan exclusion (credit was $0, and the skip set did not fail when the credit was listed) Fixed liheap.yaml Case 2: period 2025, wages $9,000, mi_home_heating_credit: 173.4, liheap: 0. With mi_home_heating_credit in the list, the one-person household's liheap would be $173.40, so the liheap: 0 line fails (reading). The registry check also fails: "lists ['mi_home_heating_credit'], which LIHEAP_PAYMENT_STAND_INS replaces or leaves out on purpose" (stub run). The case passes at the head (CI), so the credit really is positive there.
Should 2a. Deleting ca_liheap_covered_liquid_heating from the list passed every test Fixed New liheap.yaml Case 6 (Madera County propane): ca_liheap_covered_liquid_heating: 516, ca_liheap_utility_heating: 0, liheap: 516. Without the list line liheap is $0 (reading). The registry check fails too, naming the missing component (stub run).
Should 2b. Swapping il_liheap_regular_heating for il_liheap_base_payment passed every test Fixed il_liheap_regular_heating.yaml Case 2 now asserts liheap: 0; with the base payment listed it would be $310 (reading). The registry check fails because the stand-in for il_liheap is no longer in the list (stub run).
Suggestion 1. Add parameter_prefix: gov.hhs.liheap Taken programs.yaml:1040. The node holds regular_payments, fpg_floor, fpg_limit and smi_limit. test_parameter_prefix_resolves[liheap] and test_variable_is_defined[liheap] passed (CI).
Suggestion 2. Keep a guard against options=[ADD] returning until Core is fixed Not taken (optional) The author's reply says the PR relies on policyengine-core#647, and the body names #647 as the fix for the cause. #647 is still open and unmerged (checks green, no review yet), the lock pins policyengine-core 3.34.2 and the floor is >=3.33.1. So the gap described in round 1 is unchanged: a new formula that copies the old pattern would bring the microsimulation error back with no test failing. No LIHEAP formula at the head, on the tree merged with main, or on the three open sibling PRs merged with this head uses the option.
Suggestion 3. Six comments still described the removed option Taken All six reworded ("Reading a monthly source such as ssi over the year sums its months"; Oklahoma: "add() sums its months exactly once here", which matches its add(person, period, [source]) read). No "ADD sums" comment is left in the LIHEAP variables.
Suggestion 4. A multi-state YAML case wider than Texas, DC and Illinois Not taken (optional) Unchanged.
Suggestion 5. Say on liheap what it leaves out Taken liheap.py:9-14, a documentation string: Michigan's credit, crisis, weatherization and equipment are left out, and the variable is not yet in household benefits or net income. All four statements match the head.
Suggestion 6. West Virginia note Taken See "West Virginia note" below.
Suggestion 7. Further hardening of the registry check (entity, period and component checks) Not taken (optional) Unchanged.
Suggestion 8. Indiana electric allowance Not taken (outside this diff) Unchanged.

New findings

Critical (must fix)

None.

Should address

None.

Suggestions

  1. Case 6 comment names the two amounts the wrong way round (liheap.yaml:214-216). It reads "Base benefit 411 for one person in burden group 4, plus 105 for the priority category at exactly 20 points". By the parameters, $411 is Madera's base for poverty group 1, and $105 is the addition for energy burden group 4. The 20 points are the eligibility minimum, not an amount. The expected values are right. A clearer comment: "Base benefit 411 for one person in poverty group 1, plus 105 for energy burden group 4 (2,040 / 12,000 = 17%). Priority points 10 + 10 = 20 meet the minimum of 20."
  2. The second failure message could say what to do (test_aggregate_list_integrity.py:231-234). The "missing" message tells the contributor to add the entry or record a stand-in. The "double counted" message says only that the mapping "replaces or leaves out on purpose" the listed name. Adding "Remove it from the list, or change LIHEAP_PAYMENT_STAND_INS if it should now be summed" would match the first message.

Verified

Michigan Case 2 (hand, form and CI)

TY2025 MI-1040CR-7, lines 36 to 45, read from the form and its instruction book:

  • Line 36, Table A: "0 or 1 ... $604" standard allowance, income ceiling $17,243 (book, Table A). Parameter standard/base.yaml, first bracket, 2025: 604.
  • Line 37: "Multiply line 35 by 3.5% (0.035)". 3.5% × 9,000 = 315.
  • Line 38: 604 − 315 = 289. Line 39 (heat included in rent, 50%) does not apply: the case has no heat in rent.
  • Line 40: "Total heating costs from line 13 or $3,765 (whichever is less)": 1,020. Line 41: "Multiply line 35 by 11% (0.11)": 990. Line 42: 30. Line 43: "Multiply line 42 by 70% (0.70)": 21.
  • Line 44: "enter the larger of lines 38 or 43": 289.
  • Line 45: "Multiply line 44 by 60% (0.60)", which the book describes as "the percentage of federal home heating assistance funds available for this year": 173.40.

The 0.6 is the line 45 funding percentage (credit_percentage.yaml, 2025: 0.6). It applies to every claimant, so no input has to switch it on. The formula takes max_(standard, alternate) × credit_percentage × eligible rate, and the rate is 1 for a single adult who is not a student dependent.

The household qualifies for both methods at the head:

  • Household resources are the $9,000 of wages. The other listed sources are zero for a 41-year-old with no children.
  • Standard: 9,000 is within 110% of the 2025 guideline (1.1 × 15,650 = 17,215) and within the form's $17,243 ceiling.
  • Alternate: 9,000 is under the one-exemption cap (18,111 for 2024, uprated; the form prints $18,592), and the gas bill of $1,020 is the heating cost for a sole tax unit with heating_type NATURAL_GAS.

Moving the case to 2025 breaks nothing: the list parameter starts 2023-01-01, every Michigan parameter used has a 2025 value, and no other state's formula runs for a Michigan-only household. The case passes at the head (CI).

The 2025 period also explains the change from the $181.61 I quoted in round 1: that figure used the 2026 allowance, which is an uprating projection.

Madera County propane Case 6 (hand and CI)

Inputs match Case 5 of ca_liheap_covered_liquid_heating.yaml, which already passes on main. The new assertions are the $0 utility payment and the $516 total.

  • Countable income 12,000. Poverty group: California uses the prior year's guideline (15,650 for one person); 1,000 a month is under 1,304.17, so group 1.
  • Energy burden: (2,028 electricity + 12 propane) / 12,000 = 17.00%, or 1,700 basis points, at or above 1,601: burden group 4.
  • Priority points: 10 (poverty group 1) + 10 (burden group 4) + 0 for age and disability = 20, which meets the minimum of 20.
  • Amount: Madera base, group 1, 411 + 19 × 0 extra people + burden group 4 addition 105 + no vulnerable member = 516, under the $1,000 maximum. The propane bill of $12 is above zero, so the household has its own fuel cost.
  • Utility payment 0: ca_liheap_utility_eligible requires that the liquid and wood components sum to zero. Without that term this household would qualify on its electric bill, so the assertion pins the exclusivity for liquid fuel, as Case 4 does for wood.
  • liheap = 516 + 0 + 0 = 516.

Illinois Case 2 (hand and CI)

Unchanged inputs. Rent of 8,400 does not exceed 30% of 31,200 (9,360), so il_liheap_eligible is false, il_liheap_regular_heating is 0 and liheap is 0, while il_liheap_base_payment is 310.

The rewritten registry check

Read whole. It builds the set of float registry variables for the LIHEAP entry, replaces each by its stand-ins from LIHEAP_PAYMENT_STAND_INS (or by itself when it has none), and requires every resulting name in the list. It then requires that no mapped registry variable that is absent from its own stand-ins is in the list. The mapping uses exact names: il_liheap to il_liheap_regular_heating; ca_liheap_utility_heating to the three California components; mi_home_heating_credit to nothing.

It builds no simulation. It reads programs.yaml and the list file with the YAML loader and uses the module's existing variables fixture, which other tests in the file already load, so it adds no model construction. That respects the "Test design and CI cost" section of CLAUDE.md. In CI the test took 0.11 seconds.

Stub run. I executed the head's test function against the head's programs.yaml and list file, with a stand-in variable table (the value type of each of the 51 registry variables, read from its source file) in place of the loaded model. The model itself was not loaded, so these results cover the check's logic, not the fixture.

Change to the list or registry Result
None (head) passes
Delete ca_liheap_covered_liquid_heating fails, names it
Delete ca_liheap_wood_heating fails, names it
Delete ca_liheap_utility_heating fails, names it
Swap il_liheap_regular_heating for il_liheap_base_payment fails, names il_liheap_regular_heating
Add il_liheap fails (double counted)
Add mi_home_heating_credit fails (double counted)
Delete the line of any one of the 48 single-variable jurisdictions fails in 48 of 48
New float variable in the registry, not in the list fails, names it
New bool flag in the registry passes, as intended

What it still does not check is suggestion 7 from round 1, and a mapping key that is no longer a registry variable is not reported as stale.

No formula changed

Files the fix commit touches outside tests and the registry:

  • variables/gov/hhs/liheap/liheap.py: adds the documentation string only. adds, entity, period, unit and reference are unchanged.
  • az_liheap_countable_unearned_income.py, ia_liheap_countable_unearned_income.py, mn_eap_countable_income.py, ok_liheap_countable_income.py, sd_liheap_countable_income.py, va_eap_countable_income.py: comment lines only. I filtered each diff for non-comment lines and found none.

programs.yaml gains the parameter_prefix line and the West Virginia note. None of the reworded comments reads like a source conflict, so the source-conflict ratchet is unaffected; test_conflict_sounding_comments_do_not_grow passed (CI).

West Virginia note

The note now reads: "West Virginia's plans reserve a regular cooling payment of up to $2,000 if funds remain, which is not modeled; Emergency Cooling crisis payments, unit repair or replacement, weatherization and the utility discount are out of scope."

I re-opened both plans. FY2026, item 3.6, p. 14: "WV LIHEAP will have a cooling if funds remain to do so, up to the limit of $2000." FY2027, p. 16: "Max benefit for cooling LIHEAP is up to $2000." The note matches both, and the body's registry section now says the same.

PR body

The body matches the head: 8 cases in 2 files, the 2025 Michigan case at $173.40, the Madera case at $516, the stand-in check, the parameter_prefix line and the West Virginia wording. Its line "CI passed at 6bee540633" is now out of date in the PR's favor.

Axiom

axiom: n/a remains valid. The fix commit adds tests, a documentation string, comments and registry text, and changes no rule, parameter or eligibility test. No rulespec-us issue is queued or needed, for the reasons given in round 1.

CI

All 37 checks pass on run 43266397351, which ran at the current head 4625f4e59a. The PR's own tests ran and passed there:

  • liheap.yaml: 6 passed.
  • il_liheap_regular_heating.yaml: 2 passed.
  • test_aggregate_list_integrity.py: 38 passed, including test_liheap_total_sums_every_registered_state_payment.
  • test_programs_registry.py: test_parameter_prefix_resolves[liheap] and test_variable_is_defined[liheap] passed.
  • test_source_conflicts.py: passed.

The author did not run tests locally, so CI is the only full run. No check is pending.

Branch

Verdict: APPROVE

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

Approving. Both round-1 findings are fixed, and each fix is pinned by a test that fails without it:

  • Michigan. Case 2 now has a household whose Home Heating Credit is $173.40 (checked line by line against the TY2025 MI-1040CR-7) and asserts liheap: 0. The registry check also fails if mi_home_heating_credit or il_liheap is in the list.
  • California and Illinois list lines. The stand-in mapping, the Madera County propane case ($516) and liheap: 0 on Illinois Case 2 pin all three.

No formula changed in the fix commit. Two optional wording suggestions are in the report above; the Case 6 comment names its two amounts the wrong way round. The options=[ADD] guard stays an open option while policyengine-core#647 is unmerged. CI is green at 4625f4e59a.

@hua7450
hua7450 merged commit f67bb1e into main Nov 12, 2026
37 checks passed
@hua7450
hua7450 deleted the liheap-aggregate branch November 12, 2026 19:06
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.

Implement a national LIHEAP total and LIHEAP cooling registry notes

2 participants