Skip to content

Add Louisiana LIHEAP regular cooling assistance - #10750

Merged
hua7450 merged 21 commits into
mainfrom
la-liheap-cooling
Nov 15, 2026
Merged

hua7450 merged 21 commits into
mainfrom
la-liheap-cooling

Conversation

@hua7450

@hua7450 hua7450 commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

Adds Louisiana's regular LIHEAP cooling benefit, which uses the same matrix as heating.

Fixes #10749

Builds on the national LIHEAP total from #10730, merged on 2026-11-12: this PR adds la_liheap_cooling to gov.hhs.liheap.regular_payments.

What and why

The April 2026 Service Delivery Guide says "Applicants may be eligible to receive one Heating and one Cooling LIHEAP benefit each Program year" (section 4.2 B, p. 48). It sets one requirement list for "the LIHEAP Heating/Cooling regular benefits" (section 3.6, p. 33). The FFY2026 Heating and Cooling Benefit Matrix applies to both seasons: burden band by size group ($200 to $700), plus one $100 supplement, maximum $800 (FY2026 plan items 3.4-3.6, p. 12). Subsidized households whose utility allowance exceeds the bill are not eligible, with applicants 60 or older exempt (plan 3.3; guide 4.1 C.1, p. 46). Cooling only gets a larger share of the funds (45.5% against 25%); the per-household award uses the same matrix.

  • New la_liheap_matrix_amount holds the matrix formula, moved unchanged out of la_liheap, which now reads it. New la_liheap_income_eligible holds the income test. Heating results are unchanged; no case from main was edited. Four existing Louisiana test headers now give the EIA 2024 Louisiana average residential bill as $140.96 a month instead of $145 (comment only), and the payment/burden_band.yaml comment now names la_liheap_matrix_amount for the zero-income band.
  • la_liheap_cooling_eligible: household size, the income test, has_cooling_expense or energy costs included in the rent, and the subsidized utility-allowance test through la_liheap_energy_cost. Guide 3.6 E.1 (p. 41) counts "A renter whose energy costs are included as a part of the monthly rental amount", and the plan's cooling section 3.3 (p. 11) answers Yes for renters with utilities in rent, with a landlord agreement to pass the benefit on as a rent reduction.
  • la_liheap_cooling: the shared matrix amount.
  • The registry note no longer claims a larger summer award.
  • Cooling eligibility does not read heating_type: no Louisiana cooling rule names a heating fuel, so NONE, UNSPECIFIED and every known fuel can be paid. Heating eligibility is unchanged.
  • has_cooling_expense is required, except for renters whose utilities are included in rent. Those renters are identified by the existing tenant_pays_utilities input (false) with positive rent; no input is added. Requiring has_cooling_expense is a PolicyEngine convention of the all-state cooling work; the guide's responsibility test covers heating and cooling costs together (section 3.6 E, p. 41).

Test plan

Tests were not run locally. This PR now targets main, so CI runs them.

  • YAML tests with expected values worked from the sources above. 18 cases in 4 files: the shared matrix amount, cooling payments by band, the income test, the subsidized utility-allowance test (including an allowance equal to the bill and a public housing tenant), renters with energy costs in rent (including one with an unspecified fuel who also gets heating), a rent-free household whose utilities others pay, a renter who pays its utilities separately with no cooling cost, a household of only undocumented members that passes the income test, no cooling cost, an unspecified fuel, and the national liheap total.
  • Source-conflict register: candidate us-la:liheap-utility-allowance-age (ruled 2026-10-06, guide over plan) now cites plan item 3.3 and the April 2026 guide section 4.1 C.1, and lists la_liheap_cooling_eligible.py and this PR.
  • make format (ruff)
  • Changelog fragment in changelog.d/

Not modeled

  • With annual bill inputs, a household eligible in both seasons gets the same amount for cooling as for heating.- Supplemental cooling payments depend on available funds and are not modeled.
  • A renter with energy costs in rent has no separate bill, so its burden band counts only billed energy and is a lower bound, as for heating.
  • The cooling start date is 04/01/2026 in the plan and 04/13/2026 in the plan changes and on the LHC page; neither changes an annual amount.
  • Only FY2026 is verified. The accepted FY2027 plan repeats the FY2026 cooling text, the 60% limit and the $200 to $800 range (pp. 11-12), but no FY2027 matrix or income notice is posted, so FY2027 values carry forward unverified.

axiom: TheAxiomFoundation/rulespec-us#2185 queued

🤖 Generated with Claude Code

hua7450 and others added 14 commits November 8, 2026 22:01
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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Guide section 3.6 E.1 pays these renters; the subsidized energy-cost
gate still applies. Adds the allowance-equals-bill boundary, an in-rent
renter case, and the national liheap total check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ogram prefix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from liheap-aggregate to main November 12, 2026 19:06
hua7450 and others added 2 commits November 12, 2026 14:08
PR 10730 was squash-merged into main as f67bb1e, so this merge uses its
final head 4625f4e, already merged here, as the merge base.

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

DTrim99 commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Add Louisiana LIHEAP regular cooling assistance

Reviewed at head e2e841c285dfc19a1284da3740cd01e6ff653d90 (16 files, +494/-81). Nothing was run locally: every claim below rests on reading the code at the head, hand traces, the source documents, and the CI logs.

Sources used:

Summary

The core of the PR is right. Louisiana pays a regular cooling benefit that is separate from heating, a household can get one of each in a program year, and both come from the one FFY2026 matrix, so adding la_liheap_cooling to the national list does not double count. All eight matrix cells, the $100 supplement, the $800 maximum, the 60% limit and every sentence the body quotes match the documents at the cited pages. The two refactors are exact moves: la_liheap and la_liheap_eligible return what main returns for every household, and no existing test was edited. CI is green (36 of 36), and the branch merges cleanly with main and all eight sibling heads.

Two things need a change before merge. The cooling test excludes an UNSPECIFIED heating type on a claim that is not true of Louisiana's own heating variable, and that exclusion zeroes the new utilities-in-rent route for the very input the heating_type documentation tells such renters to send. Three terms of the new eligibility formula have no pinning test.

0 critical, 2 should, 6 suggestions.

Critical (must fix)

None.

Should address

1. The UNSPECIFIED exclusion has no Louisiana source, is described inaccurately, and defeats the utilities-in-rent route

policyengine_us/variables/gov/states/la/lhc/liheap/cooling/la_liheap_cooling_eligible.py, lines 26-29 and 44:

# A home with no heating or any known heating fuel qualifies; an
# unspecified fuel is paid $0, as in every LIHEAP program (#9754).
heating_type = spm_unit("heating_type", period)
fuel_known = heating_type != heating_type.possible_values.UNSPECIFIED

The body repeats it: "heating_type UNSPECIFIED pays $0, as in every state LIHEAP program".

The claim is wrong for Louisiana. la_liheap_eligible.py lines 28-32 at this head pay an UNSPECIFIED fuel when heat is in the rent ("The matrix has no fuel column, so an unspecified fuel qualifies when heat is included in the rent"), and eligibility/la_liheap_eligible.yaml Case 8 ("an unknown heating fuel with heat included in rent is eligible") pins it. Issue #9754, which the comment cites, is an open proposal that itself lists DC, IL, MA and MI as programs that pay UNSPECIFIED.

No Louisiana text ties cooling to the heating fuel. The matrix has two inputs, "Energy Burden Percent" and "Household Size". Guide section 3.6 (p. 33) lists the requirements for "the LIHEAP Heating/Cooling regular benefits" and none is a heating fuel. Section 4.1 (p. 43) says "The primary source of energy for cooling is electricity". Heating needs a known fuel only so has_heating_expense can find the heating bill; cooling reads has_cooling_expense, which is a stated fact and needs no fuel. The queued rulespec-us issue says the same: "No Louisiana source ties cooling to the heating fuel."

It breaks the route the PR adds. The heating_type variable's own documentation says UNSPECIFIED "is also the right value when heat is included in rent and the fuel is unknown". The PR's rent case (la_liheap_cooling_eligible.yaml Case 4) passes only because it sends heating_type: ELECTRICITY.

Household A (rent route), period 2026, Louisiana:

  • One adult aged 40, wages $15,080, rent $12,768, tenant_pays_utilities: false, heat_expense_included_in_rent: true, heating_type left at its default (UNSPECIFIED).
  • Rule: guide 3.6 E.1 (p. 41) counts "A renter whose energy costs are included as a part of the monthly rental amount" as vulnerable, and plan item 3.3 (p. 11) answers Yes for "Renters with utilities included in the rent". Income $15,080 is under the $30,618 limit.
  • Expected: cooling $200 (burden 0 / 15,080, "9.9% and Less", size 1), the same lower-bound amount Case 4 gives.
  • Actual by hand trace: la_liheap = $200 (heating accepts UNSPECIFIED with heat in rent), la_liheap_cooling = $0 (fuel_known is false).

Household B (bill payer): one adult aged 40, wages $15,080, electricity $1,740, has_cooling_expense: true, heating_type not sent. Expected $350 (1,740 / 15,080 = 11.54%); actual $0. la_liheap_cooling.yaml Case 6 pins this $0.

Since no dataset stamps heating_type (#9754), the term also means the new variable pays $0 on every microsimulation record, so Louisiana cooling adds nothing to the national liheap total there.

Fix. Preferred: drop fuel_known from the cooling test, since the cooling rule does not use the heating fuel, and change Case 6 to expect $350. Minimum: mirror the heating variable, fuel_qualifies = utilities_in_rent | (heating_type != UNSPECIFIED), add Household A as a test, and reword the comment and the body so they call the remaining exclusion a PolicyEngine input convention and stop saying every program applies it. Update interpretation 3 and companion case 8 of the rulespec-us issue to match.

2. Three terms of la_liheap_cooling_eligible have no pinning test (by my reading)

la_liheap_cooling_eligible.py:

  • (size > 0), line 41. The only zero-size case is la_liheap_cooling_eligible.yaml Case 3 (one undocumented adult, wages $15,080). That household already fails the income test: the limit for size 0 is $0, and the federal floor is 1.1 x (15,650 - 5,500) = $11,165, below $15,080. By my reading, deleting (size > 0) leaves every case unchanged. A case with wages of $10,000 (under $11,165, so the income test is true) would pin it. The guide rule is on p. 40: "A household with only ineligible aliens does not qualify for LIHEAP assistance."
  • add(spm_unit, period, ["rent"]) > 0, line 23. Every case with tenant_pays_utilities: false has positive rent. Add an owner or rent-free household with the flag false, no rent and no cooling expense, expecting false. The rulespec-us issue already notes this gap.
  • is_in_public_housing, line 35. No cooling case sets it. Add a public-housing tenant whose allowance exceeds the bill (the cooling twin of heating Case 15), expecting false.

If the fix for item 1 keeps a narrowed UNSPECIFIED test, pin both sides of it as well.

Suggestions

  1. Label has_cooling_expense as the model's stand-in. The guide's test is responsibility for the energy account (3.6 E, p. 41: "full or partial payment responsibility for the charges incurred to a residential household account owned by an energy vendor for heating and cooling costs"). has_cooling_expense defaults to false and has no formula, so a household that sends a $1,740 electric bill without the flag gets $0 cooling (la_liheap_cooling.yaml Case 4), though in the cooling season that electric bill is the cooling bill. The body says this is the batch convention; say so in the comment at lines 20-21 too.
  2. Correct the citation for the subsidized renter with utilities in rent. la_liheap_cooling_eligible.yaml Case 4 cites section 4.1 C.1 for the second household. That section covers households "receiving a Utility Allowance"; the same page quotes HUD: "If all utilities are included in the rent, there is not a utility allowance." The exclusion is a reading of 3.6 E.1's third example ("Subsidized housing tenants who are responsible for and have incurred excess energy charges"), carried over from the heating variable. Cite that and call it a reading.
  3. The same renter is treated differently by season. Cooling reads tenant_pays_utilities; heating on main reads only heat_expense_included_in_rent. A renter with tenant_pays_utilities: false, ELECTRICITY and nothing else set gets $200 cooling and $0 heating. Out of scope here, but worth a follow-up issue.
  4. One reference per page. Three edited titles now read "#page 9,11" or "#page 10,12" while the link opens the heating page (smi_rate.yaml line 14, payment/amount.yaml line 44, payment/priority_supplement.yaml line 16). Add separate plan entries anchored at #page=11 (item 3.1) and #page=12 (items 3.4-3.6). In la_liheap_cooling.py the guide link opens p. 43, but the sentence the variable rests on is on p. 48.
  5. Comments left behind by the move. payment/burden_band.yaml line 5 still says zero-income households "receive band 4 in la_liheap"; that logic now lives in la_liheap_matrix_amount. payment/energy_cost_sources.yaml explains the annual-bill proxy for the heating season only; add the cooling side (summer electric bills run above the annual monthly average, so the cooling band for bill payers is likely a lower bound).
  6. Extend the existing ruled record. us-la:liheap-utility-allowance-age cites plan item 2.3 and the January 2024 guide. Cooling now relies on the same ruling through la_liheap_energy_cost; add plan item 3.3 (p. 11, same "over 60" wording) and the April 2026 guide, section 4.1 C.1 (p. 46).

Verified correct

A separate regular cooling benefit, and both benefits in one year.

  • Guide 4.2 B (p. 48): "Applicants may be eligible to receive one Heating and one Cooling LIHEAP benefit each Program year. For example, if an applicant receives a benefit payment during the Heating season, they cannot receive another benefit payment until the Cooling season begins."
  • Plan 2.4 (p. 10): "Eligible households can receive one benefit payment during the heating season." Plan 3.4 (p. 12): "Eligible households can receive one benefit payments during the cooling season." Plan Changes: "Clients are eligible once for each of the 4 components." LHC page: "Applicant may only receive one LIHEAP benefit each season."
  • Plan 1.1 (p. 4): cooling 04/01/2026 to 09/30/2026; plan 1.2: heating 25.00%, cooling 45.50%. The Plan Changes and the LHC page give April 13; neither date changes an annual amount, as the body says.
  • Cooling is not contingent: the HHS FY2024 profile reports 80,008 cooling households (average $457) against 23,770 heating households (average $488). The near-equal averages fit one shared matrix and support dropping the old "larger summer cooling award" note.
  • So listing both la_liheap and la_liheap_cooling in regular_payments is right.

Amount.

  • All eight cells in payment/amount.yaml match the matrix: 9.9% and less 200/250, 10-17.9% 350/400, 18-24.9% 500/550, 25% and greater 650/700 (sizes 1-3 / 4 and over). Supplement $100 once per household per benefit, "maximum benefit will not exceed $800.00"; plan 3.4 and 3.6 (p. 12) repeat the $100, $200 and $800 for cooling.
  • Burden inputs for a cooling award: guide 4.1 (p. 43) uses "the applicant's total heating and/or cooling energy cost" with all vendors added, over monthly income (4.2, p. 48). la_liheap_energy_cost sums the electric bill and every fuel bill, so a home with heating_type NONE gets the burden its electric bill implies (Case 1: $350), not a zero burden.
  • Heating and cooling are always equal in the model for a household eligible for both. The guide would use each season's own month of bills; the body discloses the simplification.
  • Dating: values start 2025-10-01, so period 2026 reads FFY2026 for the April-September 2026 season. Period 2027 carries FFY2026 forward. The accepted FY2027 plan repeats items 3.1-3.6 word for word (60%, $100, $200-$800); no FFY2027 matrix is on the Clearinghouse benefits page and no FFY2027 notice is on the LHC page (latest is LHP-2025-01). verified_years: "2026" is backed.

Eligibility.

  • Same 60% of state median income for cooling (plan 3.1, p. 11); no categorical eligibility (guide p. 39); the 110% federal floor carries over through la_liheap_income_eligible.
  • Plan p. 11 check boxes, read from the page image: subsidized renters Yes, renters with utilities included in the rent Yes, assets test No.
  • Subsidized rule and age-60 exemption (guide 4.1 C.1-2, pp. 46-47; plan 3.3) come through la_liheap_energy_cost unchanged and follow the existing ruling (manual over plan), so the PR is consistent with both ruled Louisiana records.
  • Rent route: guide 3.6 E.1 (p. 41) and the note on p. 42 (landlord form, benefit passed on as a rent reduction). Appendix L has the landlord state the unit's monthly cooling cost; the model has no input for it, so the band is a lower bound, as disclosed. tenant_pays_utilities defaults to true, so the route is opt-in.

Refactors.

  • la_liheap_matrix_amount.formula is main's la_liheap.formula line for line; only the closing comment differs. la_liheap keeps defined_for = "la_liheap_eligible" and adds the one variable, and core applies the defined_for mask to adds results, so every household gets main's value.
  • la_liheap_income_eligible.formula is the income block of main's la_liheap_eligible, with the final assignment turned into the return. The rest of la_liheap_eligible is unchanged apart from the dropped fpg import and the moved statute link.
  • Other readers of the changed variables: only regular_payments.yaml, programs.yaml and the Louisiana files. Readers of the re-described parameters: la_liheap_matrix_amount and la_liheap_income_limit only. No parameter was renamed.
  • No existing test file is in the diff.

Source-conflict records. The two existing Louisiana LIHEAP items name la_liheap_countable_income.py, la_liheap_energy_cost.py, earned.yaml, unearned.yaml and elderly_age.yaml; none moved, so no path is stale. No Louisiana file carries a # Source conflict marker. The PR adds no conflict-sounding comment (static scan of the added lines against the ratchet's pattern: 0 matches) and no candidate, which is right.

National total and registry.

  • la_liheap_cooling is a USD float on SPMUnit, YEAR, gated to Louisiana through la_liheap_cooling_eligible, and not a component of another list entry.
  • The registry gets a second Louisiana implementation with its own name, so labels stay unique (Washington's two ECEAPs are the precedent). By my reading test_liheap_total_sums_every_registered_state_payment still holds and now requires the list line; LIHEAP_PAYMENT_STAND_INS needs no entry.
  • la_liheap_cooling.yaml Case 2 expects liheap: 800, so deleting the list line would fail a test.

Mixed-state, keys and periods. No state-gated int or enum is used as a parameter key in the new code. For a non-resident the matrix formula reaches band 4 and size group 1, both valid keys, exactly as on main. All four new variables have a state gate. Everything is YEAR; rent is read with add over members and no options=[ADD].

Hygiene. Labels, units, references and defined_for are present on all four variables; tests sit in variable-named files; changelog fragment changelog.d/la-liheap-cooling.added.md is present.

Hand checks (13 expectations, all agree).

File, case Arithmetic Expected
cooling, 1 1,740 / 15,080 = 11.54%, 10-17.9%, size 1; NONE fails heating cooling 350, heating 0
cooling, 2 (900 + 1,740) / 20,000 = 13.2%, size 4; income under 58,882; no priority member 400, 400, liheap 800
cooling, 3 loss floored to 0 income, top band, size 4: 700 + 100 (age 4) 800
cooling, 4 no cooling expense, tenant pays utilities; electric heat 11.54% cooling 0, heating 350
cooling, 5 32,000 > 30,618; floor 1.1 x 15,650 = 17,215 0
cooling, 6 UNSPECIFIED fails fuel_known (see Should 1) 0 by the code
eligible, 1 NONE and NATURAL_GAS both pass cooling; NONE fails heating [true, true]; heating [false, true]
eligible, 2 1,740 - 1,200 = 540; 1,500 - 1,800 = -300; 1,740 - 1,740 = 0 [true, false, false]
eligible, 3 size 0 (and income fails by itself, see Should 2) false
eligible, 4 rent route; burden 0, band 1, size 1; subsidized cost 0 [true, false]; [200, 0]
income, 1 30,618 <= 30,618; 30,619 above, floor 17,215 [true, false]
income, 2 floor (15,650 + 5,500 x 13) x 1.1 = 95,865 [true, false]
matrix, 1 11.54% gives 350; -300 floored to 0 gives band 1 [350, 200]; la_liheap [0, 0]

Axiom line assessment

axiom: TheAxiomFoundation/rulespec-us#2185 queued is valid. The issue is OPEN, labelled pe-parity, updated 2026-11-12, names this head (e2e841c285), and is dispatch-ready: blocker stated (depends on the unencoded heating rules in #1890), module paths, corpus citations, verbatim source text, four required outputs, and 11 companion cases derived from the sources. It will need two edits when the findings are fixed: interpretation 3 and companion case 8 ("an UNSPECIFIED heating type pays $0 in every state LIHEAP implementation" is not true of Louisiana heating), and the new pinning cases.

CI status

36 of 36 checks pass at the head; none pending or failed. The selective-test job ran the four new files green: 6 + 4 + 2 + 1 = 13 cases. The full baseline, contrib, Python and microsimulation jobs all pass.

Branch status

12 commits behind main; the merge with current main is clean. Test merges against all eight sibling heads are clean, including the three files every sibling edits (regular_payments.yaml, programs.yaml, source_conflicts/candidates.yaml): AR #10744, GA #10746, OK #10748, KY #10752, SC #10754, AL #10756, NE #10758, VA #10760. Main's changes since the merge base do not touch the Louisiana files or the list-integrity and registry tests.

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; neither is critical, and the core of the PR is right. Louisiana pays one heating and one cooling benefit each program year from one matrix, so the national total doesn't double count. All eight matrix cells, the $100 supplement and the $800 maximum match the documents, and both refactors are exact moves.

  1. The UNSPECIFIED exclusion. The comment and the description say an unspecified fuel pays $0 "as in every LIHEAP program", but la_liheap_eligible pays it when heat is in the rent (Case 8), and no Louisiana text ties cooling to the heating fuel. It also defeats the utilities-in-rent route this PR adds: a renter with utilities in the rent who leaves heating_type at its default gets $200 for heating and $0 for cooling. Drop fuel_known, or mirror the heating variable and reword the comment (Should Address 1).
  2. Three terms have no pinning test, by my reading: size > 0 (the one zero-size case already fails the income test), rent > 0, and is_in_public_housing. Three cases are sketched in the report (Should Address 2).

Six optional suggestions follow. CI is green at e2e841c285.

hua7450 and others added 4 commits November 13, 2026 14:53
Addresses DTrim99's round-1 review; upstream main merged first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nce candidate covers cooling

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

hua7450 commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator Author

This push, head d4215baf78, drops the heating-fuel condition from cooling eligibility, adds the pinning cases and applies suggestions 1, 2, 5 and 6 (fix commits 450e6979a9 and d4215baf78); upstream main is merged in.

Should address

  • 1. UNSPECIFIED exclusion. Took your preferred fix. cooling/la_liheap_cooling_eligible.py no longer reads heating_type: fuel_known and the comment citing issue 9754 are gone, and the new comment says the cooling requirements name no heating fuel (Section 3.6). la_liheap_eligible is unchanged. In cooling/la_liheap_cooling.yaml, Case 6 (your Household B) now expects $350, with a "Was 0, now 350" comment, and Household A is new Case 7: la_liheap $200 and la_liheap_cooling $200. The body now says cooling eligibility does not read heating_type, and the "as in every state LIHEAP program" sentence is gone. LIHEAP cooling: encode Louisiana LIHEAP regular cooling benefit from the shared heating and cooling matrix (PE-US PR 10750) TheAxiomFoundation/rulespec-us#2185 is updated: interpretation 3, companion case 8 (now $350) and new companion case 12 (Household A).
  • 2. Pinning tests. Four cases appended to cooling/la_liheap_cooling_eligible.yaml; in each, only the target term fails:
    • Case 5, size > 0: one undocumented adult with wages of $7,540 (20 hours a week at $7.25), under the $11,165 floor, so la_liheap_income_eligible is true and cooling eligibility is false.
    • Case 6, rent > 0: a rent-free household whose utilities a relative pays (tenant_pays_utilities false, rent 0, no cooling cost): false.
    • Case 7, is_in_public_housing: the cooling twin of heating Case 15, a $1,500 bill against a $1,800 allowance with receives_housing_assistance false: false.
    • Case 8, ~tenant_pays_utilities, which no case pinned either: a renter paying $12,768 rent with utilities billed separately and no cooling cost: false.
    • With the fuel term gone, there is no narrowed UNSPECIFIED test left to pin.

Suggestions

  • 1. has_cooling_expense label. Applied. The comment in la_liheap_cooling_eligible.py now cites Section 3.6 E and calls requiring has_cooling_expense from a household that pays its own bills "a PolicyEngine convention of the cooling programs; no Louisiana text asks for a cost separate from the energy bill." The body and interpretation 2 of the rulespec-us issue say the same.
  • 2. Citation for the subsidized renter. Applied. The la_liheap_cooling_eligible.yaml Case 4 comment now reads the exclusion from 3.6 E.1's "Subsidized housing tenants who are responsible for and have incurred excess energy charges" example, as the heating test does, in place of 4.1 C.1.
  • 3. Same renter treated differently by season. Not changed; it is outside this PR's scope. No follow-up issue is filed yet.
  • 4. One reference per page. Declined under the reference rule: one entry per source, with the href on the first cited page and the other pages in the title (#page 9,11). In la_liheap_cooling.py, the guide link's comment already lists pages 43-44 and 48.
  • 5. Comments left by the move. Applied. payment/burden_band.yaml now says zero-income households "receive band 4 in la_liheap_matrix_amount." The payment/energy_cost_sources.yaml comment adds the cooling side: "Cooling-season electric bills run above that average, so the cooling burden band of a household that pays its own bills is likely a lower bound." Its description and values are unchanged.
  • 6. Extend us-la:liheap-utility-allowance-age. Applied in source_conflicts/candidates.yaml. The entry adds the April 2026 guide, section 4.1 C.1 (PDF page 46), as a manual source; the plan source now reads "§§2.3 and 3.3, pages 9 and 11"; readings.other names plan items 2.3 (heating) and 3.3 (cooling); and pe_us adds cooling/la_liheap_cooling_eligible.py and this PR. The years, follows: gap-fill, the ruled state and the note are unchanged. The body's register line and interpretation 6 of the rulespec-us issue now say so.

Also: four Louisiana test headers from main said the EIA 2024 Louisiana average bill was $145 a month. They now say 1,740 is 145 a month, near the EIA average of $140.96 (comment only).

CI is running at d4215baf78.

@hua7450
hua7450 requested a review from DTrim99 November 13, 2026 20:39
@DTrim99

DTrim99 commented Nov 15, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 2): Add Louisiana LIHEAP regular cooling assistance

Reviewed at head d4215baf78cdcbd8d9abf22cd27ac9ba3d965e8c. Round 1 (2026-11-12) was at e2e841c285. Since then the branch has two fix commits from 2026-11-13 (450e6979a9, d4215baf78) and two merges of main (07c8350fab, 72a4d8295a). Each merge's tree is exactly the automatic merge of its two parents, so neither merge has a hand-resolved block. Nothing was run locally. Each claim below rests on reading the code at the head, a hand trace, the CI logs at this head, or a source document I opened again, and says which.

Summary

Both round-1 should items are fixed. Cooling eligibility no longer reads heating_type, so an UNSPECIFIED fuel is paid, both through the utilities-in-rent route and with a bill. Four new cases pin size > 0, rent > 0, is_in_public_housing and ~tenant_pays_utilities. Suggestions 1, 2, 5 and 6 were taken. Suggestion 3 was not taken, which is fine because it was optional. The author declined suggestion 4 and is right to: the repo's reference rule asks for the form the PR uses, so that finding is closed.

The fix is narrow. The only formula change is the removed term. Of the households that were right at round 1, only those with an UNSPECIFIED fuel get a different result. Heating results are unchanged. All 36 CI checks pass at this head, and the selective job ran all eight Louisiana LIHEAP test files green. The branch merges cleanly with current main and with each of the eight open sibling PRs.

0 critical, 0 should, 3 suggestions (all housekeeping).

Prior findings

Finding Status Evidence
Should 1: the UNSPECIFIED exclusion had no Louisiana source and blocked the utilities-in-rent route Fixed la_liheap_cooling_eligible.py no longer reads heating_type. fuel_known and the comment citing #9754 are gone. Lines 39-41 now return (size > 0) & income_eligible & responsible & energy_cost_eligible, under a comment that says the Section 3.6 requirements name no heating fuel. la_liheap_cooling.yaml Case 6 (round-1 Household B) now expects 350, and new Case 7 (Household A) expects la_liheap 200 and la_liheap_cooling 200. By my reading, putting fuel_known back would make both cases return 0 for cooling, so both pin the fix. CI ran both green. The body no longer says "as in every state LIHEAP program". In rulespec-us#2185, interpretation 3, companion case 8 ($350) and the new companion case 12 match.
Should 2: three terms of la_liheap_cooling_eligible had no pinning test Fixed la_liheap_cooling_eligible.yaml adds Cases 5-8. In each case only the target term is false (hand trace). Deleting the term flips the case to true by my reading: Case 5 for size > 0, Case 6 for rent > 0, Case 7 for is_in_public_housing and Case 8 for ~tenant_pays_utilities. That last term was not on my round-1 list. Details are in the mutation table below.
Suggestion 1: call has_cooling_expense the model's stand-in Taken Lines 20-24 now cite Section 3.6 E and say that requiring has_cooling_expense from a household that pays its own bills "is a PolicyEngine convention of the cooling programs; no Louisiana text asks for a cost separate from the energy bill." The body and interpretation 2 of the issue say the same.
Suggestion 2: citation for the subsidized renter with utilities in rent Taken The comment on la_liheap_cooling_eligible.yaml Case 4 now reads the exclusion from the 3.6 E.1 example ("Subsidized housing tenants who are responsible for and have incurred excess energy charges") and calls it a reading. The expectations are unchanged.
Suggestion 3: the same renter is treated differently by season Not taken (optional) The author calls it out of scope. No follow-up issue was filed. Not raised again.
Suggestion 4: one reference per page Declined, closed The repo's rule (CLAUDE.md) says: "One reference entry per source document; URLs that differ only by #page= are one source" and "Several cited pages: the href opens the first, and the title ends with #page 72-75 (consecutive) or #page 29,32-33,36,41 (nonconsecutive)." The three titles ("... Sections 2.1 and 3.1 #page 9,11", "... 2.4-2.6 and 3.4-3.6 #page 10,12", "... 2.4 and 3.4 #page 10,12") follow that form, quoted, with the href on the first page. My round-1 suggestion went against the rule.
Suggestion 5: comments left behind by the move Taken burden_band.yaml now names la_liheap_matrix_amount. The energy_cost_sources.yaml comment adds the cooling side, saying the cooling band for bill payers "is likely a lower bound". Values and the description are unchanged.
Suggestion 6: extend us-la:liheap-utility-allowance-age Taken The entry adds the April 2026 guide, 4.1 C.1, PDF page 46. The plan citation now reads "§§2.3 and 3.3, pages 9 and 11", and readings.other names both plan items. pe_us adds cooling/la_liheap_cooling_eligible.py and this PR. Sources checked under Verified.

New findings

Critical

None.

Should address

None.

Suggestions

  1. The ruled note does not link a ruling record. The body cites the ruling ("ruled 2026-10-06, guide over plan"), and the reply notes that the ruled state and the note are unchanged. The note reads, in full: 'Ruled 2026-10-06: follow the manual over the plan.' I found no dated record of that ruling. There is none on this PR. There is none on Add Louisiana LIHEAP regular heating assistance #10429, the PR the candidate first lists, which was opened on 2026-10-23, after the ruling date. A GitHub search of policyengine-us and rulespec-us found no comment that records it. Six other merged candidates carry the same unlinked note. Later rulings in the register do link their records, for example Ruled by the project lead, 2026-11-02: https://github.com/PolicyEngine/policyengine-us/pull/10640#issuecomment-6591157776.

    This does not block merge. The state and note are unchanged from main, the entry does not rest on the statute-first rule (readings.statute: "No statute is supplied"), and following the guide fits the CONTRIBUTING.md tie-break: "follow the administering agency's reading, so the model matches what households receive". The guide is the LHC manual that subgrantees must apply. If a record exists, link it in the note. If not, a one-line confirmation from the project lead on this PR that the ruling covers plan item 3.3 would give the cooling extension its own record.

  2. Two bullets in the PR body run together. The first "Not modeled" bullet ends "...the same amount for cooling as for heating.- Supplemental cooling payments depend on available funds and are not modeled." The 2026-11-13 body edit introduced this (the earlier versions do not have it). Put the second bullet on its own line.

  3. rulespec-us#2185 still says CI has not run. The "Why queued" section says the tests "have not yet run in CI at this head", and the "Tests" line says "They have not yet passed in CI at this head". CI has since run all 18 cases green at d4215baf78 (see CI). Update both sentences at the next edit. Everything else in the issue matches the head.

Verified

The fix is narrow. Between the round-1 head and this head, the PR's own source changes are:

  • the cooling eligibility formula;
  • two parameter comments, with no value or description change;
  • the candidate entry;
  • test files.

la_liheap_eligible, la_liheap_energy_cost, la_liheap_matrix_amount, la_liheap_income_eligible and la_liheap_cooling are identical to round 1 (diff of the two heads). Only Louisiana households with heating_type UNSPECIFIED and a cooling cost or utilities in rent get a different result: they now get the matrix amount instead of $0. That is the intended fix, and the body discloses it. The four main test files that changed differ only in a header comment.

Mutation check (by my reading; nothing was run).

Term Pinning case Result without the term
No heating_type read (putting fuel_known back) la_liheap_cooling.yaml Cases 6 and 7 cooling 0 against expected 350 and 200
size > 0 la_liheap_cooling_eligible.yaml Case 5 true against expected false; income passes because 7,540 < 11,165
rent > 0 Case 6 true; ~tenant_pays_utilities alone makes the household responsible
~tenant_pays_utilities Case 8 true; rent 12,768 > 0 alone makes it responsible
is_in_public_housing Case 7 true; with receives_housing_assistance false the unit is not subsidized, so energy_cost_eligible is true

Hand checks of every new or changed expectation (all agree).

File, case Arithmetic Expected
cooling, 6 (was 0) UNSPECIFIED no longer read; size 1, 15,080 ≤ 30,618; 1,740 / 15,080 = 11.54%, 10-17.9%, size group 1; no supplement at age 40 350
cooling, 7 (new) Heating: heat in rent lets UNSPECIFIED qualify (fuel_qualifies). Cooling: tenant_pays_utilities false and rent 12,768 > 0. Not subsidized; energy cost 0, burden 0 / 15,080 = 0, band 1, size 1 la_liheap 200, la_liheap_cooling 200
eligible, 4 (comment only) expectations unchanged [true, false]; [200, 0]
eligible, 5 (new) size 0, so the limit is 0 (where(size > 0, limit, 0)); floor 1.1 x (15,650 + 5,500 x (0 - 1)) = 11,165; 7,540 < 11,165 income eligible true; cooling false
eligible, 6 (new) has_cooling_expense false; ~false & (0 > 0) = false false
eligible, 7 (new) public housing, age 40: 1,500 - 1,800 = -300, not > 0 false
eligible, 8 (new) ~true & (12,768 > 0) = false; has_cooling_expense false false

The undocumented member's income counts in full (the comment in la_liheap_household_size), and fpg does not clip size, so the size-0 floor is as the Case 5 comment says. Interpretation 12 of the issue discloses this.

New and changed comments.

  • The cooling-eligibility comments match guide 3.6 (p. 33), where the requirements for "the LIHEAP Heating/Cooling regular benefits" name no fuel, and 3.6 E (p. 41).
  • The four test headers now read "1,740 is 145 a month, near the EIA 2024 Louisiana average residential bill of 140.96 a month". EIA Table 5A, "2024 Average Monthly Bill- Residential", gives Louisiana 140.96, and 1,740 / 12 = 145.

Source-conflict ratchet (#10287).

  • I scanned every comment in the 12 variable and parameter files the PR changes against the guard's conflict pattern: 0 matches.
  • The removed comment did not match either.
  • No Louisiana file is in KNOWN_UNMARKED.
  • CI Rest shard 2 ran test_source_conflicts.py green at this head, including the candidate field checks.

Candidate us-la:liheap-utility-allowance-age. The entry has every field CONTRIBUTING.md#source-conflicts lists:

  • id, program, question and years (2026, federal-fiscal);
  • three sources: two manual, one plan;
  • readings;
  • pe_us with follows: gap-fill, code paths git tracks at the head, and both PRs;
  • state: ruled.

I opened the sources again (all links return the PDF):

  • April 2026 guide, PDF p. 46: "C. Total Energy Cost for Persons Receiving Utility Allowances", "1. Utility Allowances and TEC" and "Applicants 60 years or older are exempted from this requirement".
  • FY2026 plan, p. 9 (item 2.3) and p. 11 (item 3.3): both read "Applicants over 60 years old are exempt from this requirement."

The body's Test plan line, interpretation 6 of the issue and the entry say the same thing. For the ruling record, see suggestion 1.

Registry, changelog, mixed state.

  • The programs.yaml cooling note ("for households with cooling costs or utilities included in rent") still matches the code, and regular_payments.yaml still lists la_liheap_cooling.
  • The changelog fragment is unchanged.
  • Dropping the heating_type read removes an enum read and adds no parameter-key lookup.

Main-side guards. The fix commits touch no income source list, so #9558, #10317 and #10408 do not apply.

Axiom

axiom: TheAxiomFoundation/rulespec-us#2185 queued is valid. The issue is OPEN, labelled pe-parity, last updated 2026-11-13 20:39 UTC, and names head d4215baf78. It matches the fix:

  • The required-outputs row for la_liheap_cooling_eligible has no fuel term, and the inputs say no cooling output reads heating_type.
  • Interpretations 2, 3, 4 and 6 are updated.
  • Companion case 8 is now $350, and case 12 is Household A.
  • Cases 13-16 are the four new pinning cases, each derived from quoted guide text.

It is still dispatch-ready: blockers stated, module paths, corpus status, verbatim text, four outputs and 16 companion cases. Only the CI status sentences are stale (suggestion 3).

CI

All 36 checks pass in run 43503260498 at d4215baf78 (pull_request event, started 2026-11-13 20:37 UTC, finished 21:56 UTC). None is pending. The selective job ran the eight Louisiana LIHEAP test files at this head, 68 cases in all:

Test file Cases passed
la_liheap_cooling 7
la_liheap_cooling_eligible 8
la_liheap_eligible 15
la_liheap_income_eligible 2
la_liheap_countable_income 13
la_liheap 17
la_liheap_energy_cost 5
la_liheap_matrix_amount 1

The full baseline (all four states shards), contrib, microsimulation and Rest Python shards also pass. Shard 1 ran test_aggregate_list_integrity.py; shard 2 ran test_source_conflicts.py and test_programs_registry.py.

Branch

The merge base is ee6fe8dc57 (main as of 2026-11-13 20:12 UTC). The branch is now 54 commits behind main at 7c516c74bb.

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 (round 2). Both round-1 items are fixed. The cooling formula no longer reads heating_type, and Cases 6 and 7 pin that. Eligibility Cases 5-8 pin size > 0, rent > 0, is_in_public_housing and ~tenant_pays_utilities. All seven new or changed expectations check by hand. CI is green at d4215baf78, and the branch merges cleanly with main and the open siblings.

The three suggestions are small:

  • the 2026-10-06 "ruled" note, which was already on main, links no record;
  • two "Not modeled" bullets in the description run together;
  • rulespec-us#2185 still says CI hasn't run.

@hua7450
hua7450 merged commit b4f6040 into main Nov 15, 2026
36 checks passed
@hua7450
hua7450 deleted the la-liheap-cooling branch November 15, 2026 01:21
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 Louisiana LIHEAP regular cooling assistance

2 participants