Skip to content

Add Utah LIHEAP regular heating assistance - #10344

Merged
hua7450 merged 15 commits into
mainfrom
ut-liheap-wave2b
Oct 27, 2026
Merged

hua7450 merged 15 commits into
mainfrom
ut-liheap-wave2b

Conversation

@hua7450

@hua7450 hua7450 commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Adds Utah's LIHEAP regular heating benefit, the Home Energy Assistance Target (HEAT) program, under the rules in effect from March 1, 2026. The model counts citizens and eligible noncitizens for household size, counts the gross income of members aged 18 and over (other than 18-year-old students) with 20% of earned income disregarded, less paid medical costs (including Medicare premiums) and support, tests it against 150% of the 2026 poverty guideline, and pays $250 minus the percent of poverty plus $10 for each energy-burden point from Utah's fuel and dwelling cost standards, adds $150 for a target-group member and $150 for propane, kerosene or fuel oil heat, with no floor or ceiling on the sum. In 2026, a 40-year-old living alone in a house heated with natural gas, with $15,960 of wages, receives $370: countable income is $15,960 x 0.8 = $12,768, or $1,064 a month; $1,064 / $1,330 = 80% of poverty gives $250 - 80 = $170; the $211 gas-house standard / $1,064 = 19.83% rounds to 20 points x $10 = $200; and $170 + $200 = $370.

Fixes #10338

Regulatory authority

Sections 607 and 608 are cited at their live URLs, whose content changes when DWS re-issues the tables. The Wayback Machine's CDX index has no capture of either page or of the standalone 608 PDF, so no archived copy is linked.

Eligibility

Requirement Source How modeled
Utah residence Section 202 defined_for = StateCode.UT
At least one member 18 or older, or emancipated Section 202 ut_heat_eligible requires a member aged 18 or older; emancipated minors are not identified
At least one citizen or eligible noncitizen Sections 202, 203-1 and 203-2B ut_heat_eligible_person accepts citizens, lawful permanent residents, refugees, asylees, people with deportation withheld and Cuban or Haitian entrants. Conditional entrants and people paroled for at least one year also need five years since entry (years_since_us_entry) or a veteran or active-duty connection (is_veteran, is_military) through themselves, a spouse in their marital unit or, for a dependent child, a parent. Undocumented, DACA and TPS members are ineligible.
Household size counts eligible members only; all members' income counts Section 204 ut_heat_household_size adds ut_heat_eligible_person over the SPM unit; a unit with no eligible member is ineligible
Income at or below 150% of the poverty guideline Sections 202 and 608; R982-403-1; plan section 2.1 (page 8) ut_heat_fpg reads the poverty guideline with no lag (the 2026 guideline for the March 2026 rules) for the eligible household size, and ut_heat_income_limit applies the 1.5 rate; the comparison is inclusive. For one person, $15,960 x 1.5 = $23,940 a year, or $1,995 a month.
Energy responsibility Section 208; plan section 2.3 (page 8) The household pays an energy bill (has_heating_expense, heating_expense or pre_subsidy_electricity_expense), pays for heat through unsubsidized rent (heat_expense_included_in_rent), or heats with wood, which covers the manual's self-supplied firewood. A household in subsidized housing (receives_housing_assistance or is_in_public_housing) qualifies only with a directly paid bill.
Heating fuel with a published cost standard Sections 207, 208, 603 and 609; plan section 1.1 (page 4) Each fuel takes its Table III row (see Benefit). HEAT is one benefit a program year covering heating or cooling, and section 603 uses the household's primary energy source, so SOLAR and a NONE household with an electric bill (pre_subsidy_electricity_expense > 0) take the electricity row. A NONE household with no electric bill is not connected to an energy source (section 208) and is ineligible; UNSPECIFIED pays nothing. Eligibility Cases 8 and 18 pin both zero standards.
No categorical eligibility and no asset test Plan sections 1.4 (page 5) and 2.3 (page 8) The model has no categorical path and no asset test

Countable income

Item Source How modeled
Gross income received in the income eligibility month, the month before application Sections 300 and 401; plan section 1.8 (page 5) Annual inputs; the benefit formula divides annual amounts by 12
Members whose income counts Sections 204 and 405; R982-403-5(1) All SPM unit members, including ineligible residents, except members under 18 and 18-year-olds in school (is_in_k12_school or is_in_secondary_school). Section 405 excludes all income of these members; R982-403-5(1) excludes only an unemancipated member's earned income. The model follows section 405 (candidate us-ut:heat-child-unearned-income).
Earned income R982-403-2, R982-403-4(2) and R982-403-6; sections 204 and 403; worksheet xlsx cells C8 and B11 One list (earned_income_sources.yaml): employment_income, military_basic_pay, self-employment (including SSTB), farm operations, partnership or S corporation, rental and farm rental income, each floored at zero. The model disregards 20% of the sum (earned_income_disregard.yaml). Basic pay entered as military_basic_pay should not also be in employment_income.
Business and rental income Sections 403, 404 and 404-1; R982-403-3(2)(m), R982-403-4(2)(k), R982-403-6(2) and R982-403-8 The business inputs enter as supplied current net amounts, so the disregard applies after business costs, as R982-403-6(2) orders, and a current business loss does not offset other income. Section 403 treats rental income as self-employment. The rule makes rent unearned when the client does not manage the property, which the inputs do not identify, so all rent counts as earned income.
Unearned income Section 402; R982-403-3(2) Each source floored at zero: Social Security, SSI, railroad retirement, pensions, military retirement pay and military survivor annuities (military_retirement_income), retirement account distributions, survivor benefits, veterans benefits, unemployment compensation (total_unemployment_compensation), workers' compensation, disability benefits, strike benefits, cash gifts from outside the household (financial_assistance), alimony, child support received, estate and trust income (trust payments, R982-403-3(2)(h)), and gambling winnings
Excluded income Section 405; R982-403-5(12); plan section 1.9 (page 6) TANF/FEP/GA, other DWS, agency or nonprofit cash assistance (including the Utah state supplement, ut_ssp), SNAP, interest, ordinary dividends and student aid are left out of the source lists. R982-403-5(12) and plan section 1.9 both exclude dividends. R982-403-3(2)(l) and (n) count DWS payments and TANF; the model follows section 405 and the plan (candidate us-ut:heat-tanf-dws-payments).
Medical deductions Section 407; R982-403-7(1)(b) medical_expense_health_insurance_premiums (a nonzero health_insurance_premiums input; otherwise non-Medicare premiums plus the modeled Part B premium for enrollees, net of Medicare Savings Program coverage), pre_tax_health_insurance_premiums, medicare_part_a_premium and other_medical_expenses, each floored at zero except Part A. The model does not read medicare_part_b_premiums_reported; a caller with a different premium enters health_insurance_premiums.
Support paid Section 407; R982-403-7(2) child_support_expense plus alimony_expense; countable income is floored at zero

Benefit

  • Income points (sections 602 and 607; worksheet section 1): $250 minus the percent of poverty. The percent of poverty divides monthly countable income by the monthly 100% guideline, which is the annual 2026 guideline divided by 12 and rounded up to the next dollar ($1,330 for one person, $1,804 for two), and rounds up to a whole percent. An income exactly at a percentage boundary keeps that percentage.
  • Energy burden (sections 603 and 607; worksheet section 2): the monthly cost standard divided by monthly countable income, as a percent rounded to the nearest whole number with halves rounded up and capped at 25, times $10. A household with zero countable income receives the full $250 burden amount.
  • Cost standards (section 609), monthly, house / apartment: natural gas $211 / $161, propane $541 / $408, fuel oil $468 / $354, electricity $287 / $218, coal $92 / $71, wood $343 / $260. Kerosene takes the propane row, OTHER takes the coal, other and steam row, and SOLAR and NONE with an electric bill take the electricity row. The new ut_heat_dwelling_type input (HOUSE for single-family homes, mobile homes, basement apartments and duplexes; APARTMENT for complexes of three or more units, condominiums, townhouses and small trailer homes) defaults to HOUSE.
  • Target-group credit (section 604; plan sections 2.3 to 2.5, pages 8 to 9): $150 once when any eligible member is under 6, aged 60 or older, or disabled (is_disabled). Ineligible noncitizens do not earn the credit.
  • Propane or fuel oil credit (section 605; plan section 2.5, page 9): $150 when propane, kerosene or fuel oil is the primary fuel. Table III groups kerosene with propane, and the March 2026 worksheet's fuel list has no kerosene entry, so a kerosene household is entered as propane or oil, both of which carry the credit. The plan pays both $150 credits as part of the regular heating benefit.
  • No floor or ceiling (section 607; worksheet sections 1 to 5): the award is the sum of the four components. Plan section 2.6 (page 9) reports a $140 minimum and an $800 maximum as estimated benefit levels. $800 is the formula's highest award, reached by a zero-income propane household with a member aged 60 or over: $250 + $250 + $150 + $150 = $800. $140 is its lowest one-person award: income at the $1,995 monthly limit gives $250 - 150 = $100, and the $71 coal or other apartment standard / $1,995 = 3.56% rounds to 4 points, or $40. A larger household can receive less: 14 eligible members at the $11,225 monthly limit in a coal apartment receive $100 + $10 = $110. The award uses the cost standard, so the actual bill does not cap it; heating_expense only shows responsibility.

Years covered

Model year Program year Guideline One-person limit
2026 FY2026 rules effective March 1, 2026; the program year runs October 1, 2025 to September 30, 2026 2026 $23,940 ($1,995 a month)

The registry entry records verified_years: "2026". The annual 2026 period stands for the March 1, 2026 rules. Periods outside March to September 2026 use the March 2026 values: January and February 2026 fell under the October 2025 to February 2026 rules, which used the 2025 guideline and a $300 income base (the obsolete section 607), and October to December 2026 fall under the manual revisions effective October 1, 2026. Neither window is modeled. Parameters carry their source effective dates and the formulas are undated, so values backfilled to earlier years are unverified estimates.

Not modeled and source conflicts

  • Earned income disregard: R982-403-2 and R982-403-6, section 204 (effective October 1, 2024), the March 2026 worksheet xlsx (cell C8, =B8*0.2, labeled "20% disregard"; total income B11, =B8+B9-C8-B10) and the FY2026 Clearinghouse workbook all disregard 20% of earned income, and the model applies the disregard. The worksheet's print area is A1:B67, so its PDF copy omits column C. Candidate us-ut:heat-earned-income-disregard (follows: statute) keeps one open question: section 406 marks income disregards no longer applicable from September 30, 2023, and sections 401 and 603 describe countable income without naming a disregard. A one-person household with $27,000 of wages heating a house with natural gas receives $234 with the disregard and $0 without it.
  • Rule-versus-manual income items: R982-403-3(2)(l) and (n) count financial payments made by DWS and TANF, which section 405 excludes and plan section 1.9 leaves unchecked; R982-403-5(1) excludes only a child's earned income, while section 405 excludes all income of children and of 18-year-olds in school. The model follows the manual and the plan and records the questions as candidates us-ut:heat-tanf-dws-payments and us-ut:heat-child-unearned-income (follows: conflicting-source, state: open). Where the rule and the plan agree, the model follows them: neither counts ordinary dividends.
  • Income limit for 14 people: the section 608 web page lists a monthly 150% limit of $10,225, while the standalone PDF of the same table lists $11,225, which equals 1.5 x $89,800 / 12. The model follows $11,225.
  • Fuel credit wording: section 607 names only propane for the $150 credit, while section 605, the section 608 table (web page and standalone PDF) and plan section 2.5 name propane or oil. The model pays the credit for propane, kerosene and fuel oil: Table III groups kerosene with propane, and the worksheet's fuel list has no kerosene entry.
  • Rounding: sections 602 and 603 round the poverty and burden percentages. The workbook rounds the burden (cell B31) but only formats the poverty percentage as a whole number for display. The model follows the manual.
  • No statute is on either side of these disagreements, so the PR adds no # Source conflict marker; the three regulation-versus-manual questions above are recorded as candidates.
  • Utah counts income received in the month before application (sections 300 and 401). The model divides annual inputs by 12 and uses the SPM unit for the household; section 207's rules on guests, absences and shared custody, and the foster-child election, are not observed.
  • Immigration: COFA citizens, trafficking victims, Afghan and Ukrainian parolees, Amerasians and battered noncitizens have no separate immigration_status value. years_since_us_entry stands for years in qualified status, and is_veteran and is_military do not distinguish honorable discharge or National Guard duty.
  • Business income uses the existing net inputs; section 404-1's expense rules and its bar on prior-period losses are not reconstructed. Rental losses do not offset other income. Section 404 counts passive S corporation income as unearned, which partnership_s_corp_income does not separate.
  • Medical and support deductions use annual amounts in place of payments made in the eligibility month. Qualifying-purpose limits, reimbursements and child support paid for a child counted in the household are not identified. Section 407 also lets target-group households deduct up to $500 for buying a window or room air conditioner or a space or unit heater; these equipment purchases are outside this scope and are not modeled.
  • Lump sums, combat pay, third-party payments, adoption payments, refugee cash assistance and passive royalties are not separable from the existing inputs.
  • Emancipated minors, the habitable-dwelling requirement, institutions, vehicle and RV residences, tribal duplicate assistance, earlier payments in the season, utility credit balances and application timing are not modeled.
  • Funding-dependent supplemental payments (section 606; plan section 2.3, pages 8 to 9) have no set amount and are not modeled.
  • This PR models regular heating assistance only. Crisis assistance, cooling, weatherization and heating-equipment repair or replacement are out of scope.

Test plan

  • 6 YAML files with 122 cases under policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ (ut_heat.yaml 54, ut_heat_countable_income.yaml 25, ut_heat_eligible.yaml 18, ut_heat_eligible_person.yaml 18, ut_heat_household_size.yaml 2, ut_heat_income_limit.yaml 5); expected values are hand computed from the rule, the manual, the March 2026 worksheet and the plan in case comments. The review fixes were not run locally; CI runs the full suite.
  • YAML tests whose expected values come from an external source (statute, official form or worksheet, official calculator)
  • Source-conflict register entry and # Source conflict <jurisdiction>:<id> marker: not applicable; this PR adds no marker, and no statute is on either side of the disagreements listed above. Three regulation-versus-manual questions are recorded as backlog candidates in source_conflicts/candidates.yaml: us-ut:heat-earned-income-disregard (the model follows the rule), and us-ut:heat-tanf-dws-payments and us-ut:heat-child-unearned-income (the model follows the manual and the plan).
  • make format
  • Changelog fragment in changelog.d/

axiom: TheAxiomFoundation/rulespec-us#1904 queued

🤖 Generated with Claude Code

hua7450 and others added 6 commits October 3, 2026 19:26
One added changelog fragment for the new program and a one- or two-sentence programs.yaml note; limitations stay in code comments and the PR body.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hua7450 and others added 3 commits October 23, 2026 22:46
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	policyengine_us/programs.yaml
@hua7450
hua7450 marked this pull request as ready for review October 24, 2026 03:00
@codecov

codecov Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (909176a) to head (1264dd9).
⚠️ Report is 712 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main    #10344   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4         9    +5     
  Lines           76       165   +89     
  Branches         2         2           
=========================================
+ Hits            76       165   +89     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

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

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

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

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

DTrim99 commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Add Utah LIHEAP regular heating assistance

PR #10344 (author: hua7450), head 1f79e33814. This is a first review. The PR adds Utah's Home Energy Assistance Target (HEAT) regular benefit under the rules effective March 1, 2026 (model year 2026). It includes:

  • 19 parameter files and 9 variables (one new input, ut_heat_dwelling_type);
  • 6 test files (109 cases: ut_heat 49, countable income 23, eligible 14, eligible person 16, household size 2, income limit 5);
  • a programs.yaml entry (coverage line and state block);
  • one source_conflicts/candidates.yaml entry (us-ut:heat-earned-income-disregard);
  • a changelog fragment.

The PR has no reviews and no comments apart from Codecov. It touches no partner test file.

Path prefixes:

  • P/ = policyengine_us/parameters/gov/states/ut/dwf/heat/
  • V/ = policyengine_us/variables/gov/states/ut/dwf/heat/
  • T/ = policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/

Sources read for this review:

  • HEAT Operations Manual, in the versions in effect on March 1, 2026: sections 202, 203-1, 203-2B, 204, 207, 208, 300, 401-407, 602-609. I also read the obsolete 406 (March 1 to September 30, 2023), the obsolete 401 (March to September 2024) and the obsolete 607 (October 1, 2025 to February 28, 2026). For example: 407 (3/1/26-9/30/26) and 204.
  • Rule: Utah Admin. Code R982-403, sections 1-9 (last change June 14, 2022; continuation notice June 17, 2022; Office of Administrative Rules).
  • Worksheets: the March 1, 2026 Benefit Calculation Worksheet in both formats, the PDF the PR cites and the xlsx at the same address (all cell formulas and the print area). Also the FY2026 Clearinghouse workbook ("effective 10/1/25") and the standalone 608 PDF.
  • FY2026 plan: the Model Plan, pages 4-11. I read items 1.1-1.2, 1.4, 1.8, 1.9 (from the rendered check boxes, p. 6), 2.1-2.7 and 3.1-3.7.
  • Other: 42 U.S.C. 8624(b)(2)(B), and on main the premium, Medicare, heating, military-pay and Utah SSP variables.

Summary

What is right. The tables and benefit mechanics are encoded carefully:

  • Tables.
    • All 12 Table III cost standards match section 609 and both workbooks.
    • The 100% column: ceil(guideline / 12) reproduces all 14 printed values (for example $1,804, $2,277 and $7,484). Rounding to the nearest dollar would miss size 2 ($1,803).
    • The 150% limits: 1.5 × the 2026 guideline equals every printed amount for sizes 1-14, and the $710 increment above 14. The PR rightly follows the standalone PDF's $11,225 for 14 people over the web page's $10,225 misprint.
  • Formula.
    • The percent of poverty rounds up (602), and the burden rounds to the nearest point with halves up (603), capped at 25.
    • Zero income gets the full $250 burden, as workbook cell B30 does.
    • One $150 target credit, earned only by eligible members (204, 604), and the $150 propane or oil credit (605).
    • There is no floor or ceiling (607). The cent-level ceiling cases (Case 47) are a good guard.
  • Eligibility.
    • The limit is inclusive, and a member aged 18 or over is required.
    • The noncitizen lists match 203-2B: LPRs through eligible criterion 5, and the five-year or service rule for conditional entrants and parolees.
    • Ineligible residents count for income but not size (204).
    • Responsibility covers heat in unsubsidized rent, own firewood, and any energy bill, not only the primary fuel's. Subsidized housing needs a directly paid bill (208; plan 2.3).
  • Income.
    • Social Security and SSI count gross (401; plan 1.9 checks "Including MediCare deduction").
    • railroad_benefits, military_retirement_income and total_unemployment_compensation are listed.
    • Child income is excluded (405).
    • Losses are floored per source and per person.
    • Bank interest, TANF, FEP and GA are excluded (405).
    • Pretax and non-pretax premiums are deducted without double counting (Document disjoint health premium inputs and correct consumer treatment #10046).
  • Tests and CI. I hand-checked 45 expectations (all match the head), and CI is 34/34 green.
  • Guards. The Integrate modeled state unemployment insurance into income flows #9558, Count military SBP annuities in state benefit income lists #10317 and Count each Social Security benefit once in Pennsylvania CCW countable income #10408 patterns and the Tie source-conflict markers to the register shared with Axiom #10287 ratchet pass on the merged tree. The NH Add New Hampshire state supplementation for 2026 #10156 key pattern is safe.

There is one critical finding:

  • C1. No earned-income disregard. Utah Admin. Code R982-403-6(1) disregards 20% of earned income. DWS's own March 2026 worksheet applies it too: the xlsx has C8 = B8*0.2 "20% disregard", and the PDF the PR cites simply omits column C (print area A1:B67). The head counts earnings in full, and 30 ut_heat cases, 16 countable-income cases and 2 eligibility cases pin that. A worker with $27,000 of wages gets $0 instead of $234.

There are five should-address items:

  • S1. Medicare premiums. Part B is deducted only when medicare_part_b_premiums_reported is supplied, although 407 allows health insurance premiums and main's medical-expense aggregate derives Part B for enrollees. Case 43 pins no deduction. An enrollee with $24,500 of Social Security gets $0 instead of $371.
  • S2. Military basic pay. military_basic_pay is omitted, although 403 and R982-403-4(2)(a) count military base pay. A two-person military household gets $500 instead of $249.
  • S3. Unrecorded rule differences. R982-403 differs from the manual on dividends, TANF and DWS payments, and children's unearned income. Only the disregard is recorded. Dividends are counted against both the rule and plan 1.9: $0 instead of $326 in the example.
  • S4. No-heat households. HEAT is one year-round benefit, so a NONE household that pays an electric bill qualifies on the electricity standard. The head gives it $0, and two tests pin that.
  • S5. Untested entries. 11 of the 24 income-source entries, and five eligibility branches, have no test that would fail if they were removed.

There are also six suggestions:

  • G1: a note that ut_ssp is correctly excluded.
  • G2: the kerosene credit.
  • G3: SOLAR.
  • G4: references.
  • G5: periods outside March to September 2026.
  • G6: merge housekeeping.

Status.

  • CI: 34/34 pass.
  • Branch: 10 commits ahead of main and 59 behind. It conflicts with main in programs.yaml and source_conflicts/candidates.yaml (branch note).

Critical (must fix)

C1. Earned income is counted in full, although Utah Admin. Code R982-403-6(1) disregards 20% of it and DWS's own March 2026 worksheet applies the disregard

  • Where:
    • V/income/ut_heat_countable_income.py:37 counts employment_income (and, at :38-40, the business sources) in full. The comment at :29-36 and the body say that sections 401 and 406 "and the March 2026 worksheet" count gross earnings.
    • The PR's candidate entry repeats the claim: "section 401 and the March 2026 worksheet compute countable income with no disregard".
    • The tests that pin it:
      • T/ut_heat_countable_income.yaml Case 1 (line 6, "gross earnings have no twenty percent disregard");
      • ut_heat Cases 3-10, 21, 23-39, 44-47;
      • countable-income Cases 2-5, 7 and 12-21;
      • eligibility Cases 1-2.
  • The rule (binding).
    • R982-403-2: "Countable income is gross income minus exclusions, disregards, and deductions."
    • R982-403-6: "(1) 20% of earned income, including self-employment earned income, will be disregarded ... (2) For self-employed members of a household, the cost of doing business will be deducted. The 20% disregard will be applied to the remainder."
    • The rule is current: last change June 14, 2022, with a continuation notice on June 17, 2022.
  • DWS's March 2026 calculator applies it.
    • The PR cites the worksheet's PDF. The same file is published as an xlsx, titled "HEAT Benefit Formula - effective 3/1/26", with the March 2026 poverty table.
    • In it, C8 = B8*0.2 is labelled "20% disregard" (D8), and TOTAL INCOME is B11 = B8+B9-C8-B10.
    • The sheet's print area is A1:B67, so the PDF shows only columns A and B. "No disregard line" is a print-area artifact, not a policy change.
    • The FY2026 Clearinghouse workbook ("effective 10/1/25") has the same C8 and B11.
  • Current manual 204 (revised May 21, 2024, effective October 1, 2024, after 406 was retired): "Allow the 20% disregard on the earned income of all household members, including the ineligible resident."
  • The other side.
    • 406 reads "No Longer Applicable - Income Disregards ... Obsolete: September 30, 2023". Its obsolete text was this same 20% disregard.
    • 401, 602 and 603 describe countable income as gross income minus exclusions and deductions, without naming disregards.
  • Why critical rather than a sources-conflict should.
    • Under CONTRIBUTING "Source conflicts", regulations are law, and a manual does not override them.
    • The agency's reading breaks only true ties, where "the text and the canons don't settle a reading". R982-403-6 is unambiguous.
    • Even on the agency question, DWS's current calculators (both FY2026 worksheets) and current section 204 apply the disregard. So the agency practice the PR relies on is not what DWS's own tool computes.
    • The PR's candidate labels its choice follows: conflicting-source. The candidates.yaml header defines that as "against the statute; the 2026-10-19 rule calls for a model change".
    • As in Wyoming Add Wyoming LIHEAP regular heating assistance #10346 C1, documenting the departure does not make the result right, and the error reaches every household with earnings in the claimed year.
  • Effect (hand-traced; 2026, one person, gas house):
Household Head With the disregard
Wages $27,000 (the candidate's example) 2,250 > 1,995: $0 21,600 → 1,800; 135.34% → 136, so 114; 211 / 1,800 = 11.72 → 12, so 120: $234
Wages $15,960 (Case 3) $310 12,768 → 1,064; 80% → 170; 19.83 → 20, so 200: $370
Wages $15,000 plus UI $10,696 (Case 44) 25,696 > 23,940: $0 12,000 + 10,696 = 22,696 → 1,891.33; 142.21 → 143, so 107; 11.16 → 11, so 110: $217
  • Fix:
    • The formula. Disregard 20% of employment_income and of the floored business sources, after business costs as R982-403-6(2) orders. The existing net inputs already are net of costs.
    • The parameter. Add one (for example P/income/earned_income_disregard.yaml, 0.2) citing R982-403-6 and the worksheet xlsx (cell C8).
    • Rental income. Manual 403 treats it as self-employment (so disregarded), while R982-403-3(2)(m) makes rent from a property the client does not manage unearned. Say which reading the code takes.
    • Re-pin the cases listed under Where. The limit-edge cases (ut_heat Cases 5-6, eligibility Cases 1-2) need wages of $29,925 and $29,925.15 (23,940 / 0.8), or unearned income.
    • The candidate. Move section 204 and the March 2026 worksheet (xlsx C8) to the rule's side. Set follows to the rule's side (statute is the closest defined value), and keep 406 as the open question. Then correct the code comment, the body's "Not modeled and source conflicts" bullet, and Axiom interpretation 1 and companion test 14.
    • If DWS disagrees. If DWS confirms in writing that eREP applies no disregard, record that under unpublished_sources and take the rule-versus-practice question to the project lead. Until then, the rule governs.

Should address

S1. Medicare premiums are deducted only when a reported premium is supplied, so an enrollee's Part B premium is ignored by default, and a test pins it

  • Where:
    • V/income/ut_heat_countable_income.py:59-73 builds premiums from health_insurance_premiums, or else health_insurance_premiums_without_medicare_part_b plus medicare_part_b_premiums_reported. Line 80 says "Medicare premiums are not derived".
    • T/ut_heat.yaml Case 43 (line 1311, "Medicare enrollment without reported premiums gives no deduction") pins $424 for an enrollee.
  • Sources:
    • 407 (March 1 to September 30, 2026), "Allowable Medical Deductions": "Health insurance (including telehealth and telemedicine premiums), dental insurance, and vision insurance premiums". It adds: "a Medicare supplement is a health insurance policy ... and is an allowable deduction".
    • R982-403-7(1)(b): "Health and accident insurance payments ... are considered medical expenses."
    • Social Security counts gross (401; plan 1.9 p. 6 checks "Including MediCare deduction"), so a Part B premium withheld from the check is netted nowhere else.
    • The PR agrees that Part B is deductible (Case 40). It just never derives it.
  • main:
    • medical_expense_health_insurance_premiums is the aggregate for medical-expense definitions. It uses the direct input when that is nonzero, and otherwise the non-Medicare premiums plus the modeled medicare_part_b_premium for enrollees (out of pocket, net of MSP).
    • The merged LIHEAP states read medicare_part_b_premium (AR, GA, IA, IN, KY, MD, NJ, NY, SD), and AZ and VA also read medicare_part_a_premium.
  • Effect (hand-traced; 2026, one person aged 68, enrolled, Social Security $24,500, not MSP-eligible, gas house):
    • Head: 24,500 / 12 = 2,041.67 > 1,995, so $0.
    • With the derived Part B of $2,434.80 (12 × $202.90): 22,065.20 → 1,838.77; 138.25% → 139, so 111; 211 / 1,838.77 = 11.48 → 11, so 110; plus the $150 target credit: $371.
    • Case 43's facts (pension $18,360): $424 in the head, $460 with Part B (15,925.20 → 1,327.10; 99.78% → 100, so 150; 15.90 → 16, so 160; plus 150).
  • Precedent: Add Tennessee LIHEAP regular energy assistance #10430 S2 (Medicare netting narrower than the state rule) and the wave 2b notes.
  • Fix:
    • Use medical_expense_health_insurance_premiums + pre_tax_health_insurance_premiums, and add medicare_part_a_premium, since 407 covers health insurance premiums generally. If a reported Part B amount should win when supplied, take medicare_part_b_premiums_reported when it is positive and the derived premium otherwise.
    • Re-pin Case 43 and the reported-premium cases (ut_heat 40-42, countable 22-23) as the chosen rule requires.
    • Update the body's deductions row and Axiom interpretations 11 and 13.

S2. Military basic pay is not counted, although section 403 and R982-403-4(2)(a) count military base pay

  • Where: wages are only employment_income (V/income/ut_heat_countable_income.py:37), and P/income/business_income_sources.yaml has no military entry. The body: "Military basic pay entered as military_basic_pay is not counted, although section 403 counts military pay as earned income."
  • Sources:
    • 403, and 402 in the same words: "Count military pay as earned income. Count the regular pay, bonuses, and allowances, including the Family Subsistence Supplemental Allowance (FSSA) and the Basic Allowance for Subsistence (BAS)."
    • R982-403-4(2): "(a) wages, including military base pay; ... (i) military payments to cover Basic Allowance for Housing and Basic Allowance for Subsistence".
    • Plan 1.9 checks "Wages".
  • main: military_basic_pay is a separate person input. Iowa LIHEAP (ia/hhs/liheap/income/sources/earned.yaml) and Oregon ERDC list it next to employment_income.
  • Effect (hand-traced; 2026, two adults aged 30, military_basic_pay $30,000, gas house):
    • Head: zero income, so 250 + 250 = $500.
    • Counted: 2,500 / 1,804 = 138.58% → 139, so 111; 211 / 2,500 = 8.44 → 8, so 80: $191.
    • Counted with the C1 disregard: 2,000; 110.86% → 111, so 139; 10.55 → 11, so 110: $249.
  • Severity: should. This is an omitted countable source with a material effect, as for railroad annuities in Maine Add Maine HEAP Fuel Assistance #10437 S1 and Vermont Add Vermont Seasonal Fuel Assistance #10436 S1. Documenting the gap does not change the result.
  • Fix: add military_basic_pay as earned income (floored, and subject to the C1 disregard), add a case, and update the body and Axiom interpretation 11.

S3. R982-403 differs from the manual in three more places, and none is recorded; dividends are counted against both the rule and the plan

  • Where:
    • P/income/unearned_income_sources.yaml:19 lists ordinary_dividend_income, and T/ut_heat_countable_income.yaml Case 9 (line 237) pins $1,200 of dividends as countable.
    • The body says "No statute is on either side ... only the regulation-versus-manual disregard question is recorded". source_conflicts/candidates.yaml gets only the disregard.
    • The rulespec-us#1904 body (interpretation 2) lists the differences below, but the PR does not.
  • The differences:
    • (a) Dividends.
      • R982-403-5: "The income listed in this section is not counted ... (12) interest or dividend income". Only 3(2)(h) "trust payments, withdrawals, or dividends received on a regular basis" count.
      • Plan 1.9 (p. 6, rendered) leaves "Interest, dividends, or royalties" unchecked.
      • Manual 402 and 404 count mineral-rights royalties "as dividends", trust dividends, and dividends an incorporated owner receives from the corporation.
      • The head counts all ordinary dividends, against both the rule and the plan.
    • (b) TANF and DWS payments. R982-403-3(2) counts "(l) financial payments made by the Department" and "(n) Temporary Assistance to Needy Families". Manual 405 and plan 1.9 (TANF and GA unchecked) exclude them. The head excludes them.
    • (c) Children's unearned income.
      • R982-403-5(1) excludes only "earned income of an unemancipated household member".
      • Manual 405 excludes all income, earned or unearned, of members aged 17 or younger and of 18-year-olds still in school.
      • The head follows the manual: countable-income Cases 2-3 exclude a minor's SSI and pension.
  • Effect of (a) (hand-traced; 2026, one person aged 50, pension $15,000 and dividends $9,000, gas house):
    • Head: 24,000 > 23,940, so $0.
    • Rule and plan: 1,250; 93.98% → 94, so 156; 211 / 1,250 = 16.88 → 17, so 170: $326.
  • Precedent: Maine Add Maine HEAP Fuel Assistance #10437 S2 (TANF left out on the plan's authority against the rule's definition, with neither choice recorded) was rated should.
  • Fix:
    • (a) Where the rule and the plan agree, follow them. Drop ordinary_dividend_income and keep estate_income for trust payments under 3(2)(h). Re-pin countable Case 9 to $2,400 and add a case like the one above.
    • (b) and (c) Add us-ut: candidates that give both sides (follows: conflicting-source, state open), so the project lead can rule on the rule-versus-manual-and-plan question.
    • Update the body's conflicts section.

S4. A household with no heating that pays an electric bill gets $0, although HEAT is one year-round benefit keyed to the household's primary energy source

  • Where:
    • V/ut_heat_energy_cost_standard.py:38-39 gives NONE a zero standard ("never qualifies"), and V/eligibility/ut_heat_eligible.py:35 (supported_fuel) then denies eligibility.
    • T/ut_heat_eligible.yaml Case 8 (line 165) and T/ut_heat.yaml Case 39 (fourth unit) pin ineligibility despite a $1,200 bill.
  • Sources:
    • One benefit, heating or cooling.
      • Plan 1.1 (p. 4) runs both heating and cooling assistance from 10/01/2025 to 09/30/2026, with 15% of funds for cooling (1.2).
      • Section 3 (pp. 10-11) gives cooling the same 150% limit, the same $150 credits and the same $140-$800 range.
      • Section 207: "A household member may receive one HEAT benefit during any program year."
      • 407 lets target groups deduct an air conditioner bought "between April 1 through September 30" "to qualify for the HEAT Program".
    • Vulnerability. It requires responsibility for "home energy costs" (202, 208). Only "Households not connected to an energy source" are excluded (208).
    • Benefit. 603: "Determine the primary energy source the household uses for energy to decide what energy source to use from Table III." For a home with no heating but an electric account, that source is electricity.
  • Effect (hand-traced; 2026, one adult, wages $15,960, NONE, pre_subsidy_electricity_expense $900):
    • Head: $0.
    • Electricity house standard ($287): 150 + (21.58 → 22) × 10 = $370.
    • With the C1 disregard: 80% gives 170; 26.97 is capped at 25, so 250: $420.
  • Precedent: Tennessee Add Tennessee LIHEAP regular energy assistance #10430 S1 (a NONE household with an electric bill at $0 under a single heating-or-cooling benefit) and New Mexico Add New Mexico LIHEAP regular heating assistance #10431 S1, both rated should.
  • Fix:
    • Give a NONE household that pays an energy bill the electricity standard. Keep NONE with no bill ineligible, per 208.
    • Re-pin eligibility Case 8 and Case 39's fourth unit, or remove their bill.
    • Add a case, and update the body row and Axiom interpretation 6. UNSPECIFIED stays at $0, which the wave accepts.

S5. Eleven of the 24 income-source entries, and five eligibility branches, have no test that would fail if they were removed

  • Untested entries (never positive for a counted member):
    • Unearned: railroad_benefits, retirement_distributions, survivor_benefits, disability_benefits, strike_benefits, gambling_winnings, and estate_income (only −$12,000 in Case 45, which floors to 0, so dropping it fails nothing).
    • Business: sstb_self_employment_income, farm_operations_income, partnership_s_corp_income.
    • Rental: farm_rent_income.
  • Untested branches:
    • is_in_public_housing (V/eligibility/ut_heat_eligible.py:25); only receives_housing_assistance is ever set.
    • is_in_k12_school (only ever false).
    • is_military (always false).
    • The dependent-child service path (V/eligibility/ut_heat_eligible_person.py:23-29); no case sets is_tax_unit_dependent.
    • has_heating_expense set on its own.
  • Tested: wages, self-employment, rental, Social Security, SSI, pension, military_retirement_income (both components), veterans benefits, unemployment, workers' compensation, alimony, child support, gifts and dividends.
  • Severity: should, under the mutation rule (Add Nevada Energy Assistance Program Fixed Annual Credit #10432 S1, Add Wisconsin WHEAP regular heating benefit #10434 S1, Add Tennessee LIHEAP regular energy assistance #10430 S3, Add Wyoming LIHEAP regular heating assistance #10346 S3).
  • Fix:
    • Add one countable-income case that gives the 11 sources distinct positive amounts for a member aged 18 or over.
    • Add eligibility cases for public housing with heat in rent (ineligible) and with an electric bill (eligible), and an 18-year-old with is_in_k12_school true.
    • Add an active-duty is_military parolee, and a parolee dependent child of a veteran (eligible) next to a non-dependent adult child (ineligible).
    • Add the new cases to the rulespec-us#1904 companion tests.

Suggestions

G1. Item 17: ut_ssp is correctly left out, so say why.

  • Why it is out. Manual 405 excludes "financial payments made by the Department of Workforce Services, or other financial assistance from non-profits / other government agencies".
    • ut_dws_ssp is a DWS payment. main's SNAP list also exempts it, citing DWS Eligibility Manual 425-11.
    • ut_dhhs_ssp is another agency's payment.
    • The amounts are small (DWS single person $3.91 a month).
  • Fix: add a comment to P/income/unearned_income_sources.yaml, so that a later item-17 sweep does not add ut_ssp. Note that R982-403-3(2)(l) would count DWS payments, and fold this into S3's (b) candidate.

G2. Kerosene's $150 credit.

  • The case for paying it.
    • Table III groups "Propane/Kerosene", and 605 pays "propane or fuel oil".
    • Kerosene is No. 1 fuel oil, and main's heating_expense reads fuel_oil_expense for KEROSENE.
    • The worksheet's fuel list (Sheet2) has no kerosene: GAS, PROPANE, OIL, ELECTRICITY, COAL/STEAM, WOOD. So a kerosene household is entered as PROPANE or OIL, both of which carry the credit.
  • Fix: consider adding KEROSENE to P/benefit/fuel_credit_types.yaml (Cases 27-28 become $448 and $408), or keep the documented literal reading.

G3. SOLAR gets $0.

G4. References.

  • Live pages. Sections 402-404-1, 602, 603, 605, 607, 608 and 609 are cited by their live URLs. 607 and 608 are re-issued each March, so add Wayback captures, or switch to the obsolete-version URLs once they roll.
  • The limit. P/eligibility/income_limit.yaml cites only the 608 web page, which misprints the size-14 limit. Add the standalone PDF or plan 2.1 (p. 8).
  • The rule. Cite R982-403 in the countable-income variable and parameters, since it is the governing rule (C1, S3).

G5. Periods outside March to September 2026 (unclaimed and documented).

  • The gap. January and February 2026 ran on the obsolete 607 ($300 base) and the 2025 table. October to December 2026 fall under the revisions effective October 1, 2026 (202, 401 and 604 were re-issued). Both use the March 2026 values.
  • Fix: none needed now (NJ S3). Consider a mixed Utah and other-state test. The NH Add New Hampshire state supplementation for 2026 #10156 pattern is safe by analysis (below), but no case runs two states together.

G6. Merge housekeeping.

  • When resolving the candidates.yaml conflict, keep main's adjacent Virginia AG candidates.
  • Fill the Utah candidate's pe_us.code once the files are on main, and drop "Code not yet on main".

Verified correct

Hand-checked expectations (45; all match the head, though C1 and S1 change several of them):

Test Hand computation Expected
ut_heat Case 1 250 + 25 points × 10 (zero income) 500
ut_heat Case 2 250 + 250 + 150 + 150 800
ut_heat Case 3 1,330 / 1,330 = 100 → 150; 211 / 1,330 = 15.86 → 16 → 160 310
ut_heat Case 4 150 + (161 / 1,330 = 12.11 → 12) × 10 270
ut_heat Case 5 1,995 / 1,330 = 150 → 100; 10.58 → 11 → 110 210
ut_heat Case 6 1,995.01 > 1,995 0
ut_heat Case 7 1,331 / 1,330 = 100.08 → 101 → 149; 15.85 → 16 → 160 309
ut_heat Cases 8-9 size 3, P 2,277: 124.73 → 125 → 125; 71 / 2,840 = 2.50 → 3; 71 / 2,840.04 = 2.49996 → 2 155; 145
ut_heat Case 10 size 14: 11,225 / 7,484 = 149.99 → 150 → 100; 71 / 11,225 = 0.63 → 1 → 10 110
ut_heat Cases 11-16 500, plus one 150 credit for 60, under 6 or disabled; none at 59 or 6 650, 500, 650, 500, 650, 650
ut_heat Cases 17-18 ineligible resident adds no credit; size 1, 15,960 counted, 310 + 150 for the citizen child 500; 460
ut_heat Case 21 20,400 − 2,400 − 2,040 = 15,960 310
ut_heat Cases 23-38 (8 of 16 recomputed) P 2,277, I 3,000: 131.75 → 132 → 118; gas house 7.03 → 7: 188; propane house 18.03 → 18, + 150: 448; kerosene 298; oil 15.6 → 16, + 150: 428; electricity 9.57 → 10: 218; coal apartment 2.37 → 2: 138; wood house 11.43 → 11: 228; wood apartment 8.67 → 9: 208 as stated
ut_heat Case 39 [310, 800, 460, 0] as stated
ut_heat Cases 40-43 15,960 → 460; 14,160 → 89 → 161, 17.88 → 18, + 150 = 491; 16,560 → 104 → 146, 15.29 → 15, + 150 = 446; 18,360 → 116 → 134, 13.79 → 14, + 150 = 424 as stated
ut_heat Case 44 764 × 14 = 10,696; 25,696 > 23,940 0
ut_heat Case 46 22,800 → 143 → 107, 11.11 → 11 = 217; 21,600 → 136 → 114, 11.72 → 12 = 234 [217, 234]
ut_heat Case 47 26.599 / 26.60 / 26.6008 monthly → 2, 2, 3 points; burden capped at 25 [498, 498, 497]
ut_heat Cases 48-49 36,000 and 26,000 > 23,940 0; 0
countable Cases 2, 9, 10, 11 15,960 (minor excluded); 1,200 + 2,400; 2,400 + 12,000 + 3,600; 1,200 + 2,400 + 3,600 + 4,800 15,960; 3,600; 18,000; 12,000
countable Cases 16, 17, 23 18,000 − 1,200; 18,000 − 1,200 − 840; 18,360 − 2,400 and 18,360 − 1,200 16,800; 15,960; [15,960, 17,160]
income limit Cases 1-5 1.5 × {15,960, 21,640, 27,320, 89,800, 95,480} 23,940 ... 143,220
eligible person Cases 7-8, 14-16 parolee at 4.99 and 5 years; veteran self; veteran spouse; unrelated veteran F, T; T; [T, T]; [T, F]

Batch checks:

  • 1. Plan, manual and matrix.
    • Cells. All 12 Table III cells, the 14 printed 100% amounts and the 14 printed 150% amounts reconcile.
    • Timing. The program year runs October 1, 2025 to September 30, 2026 (plan 1.1), and the rules took effect March 1, 2026.
    • Plan range. Plan 2.6's $140 and $800 are the formula's one-person minimum and overall maximum, as the body explains.
    • See C1 for the disregard.
  • 2. Income.
    • Gross. Gross income is used (plan 1.8; 401, 403). Cafeteria-plan withholdings count as wages (403), and employment_income is pre-deduction gross. HSA and FSA contributions are not deducted (407).
    • Losses. They are floored per source and per person (404-1 bars prior-period losses; neither source lets a current loss offset other income).
    • See C1, S1, S2 and S3.
  • 3. Backdating. Only 2026 is claimed (registry verified_years: "2026"), and the other periods are documented (G5). The registry claim depends on C1.
  • 4. Arrangements.
    • Eligibility rules. There is no categorical eligibility (plan 1.4) and no asset test (2.3).
    • Housing. Heat in unsubsidized rent uses the standard formula (603; Case 19). Subsidized housing needs a direct bill (Cases 12-14), and own firewood qualifies (Case 10).
    • Fuel. UNSPECIFIED pays $0, which the wave accepts. See S4 for NONE.
  • 5. Tests. Periods are plain. The limit edges, the sizes 1, 2, 3, 14 and 15, cent-level rounding edges, mixed units (Cases 39, 45-47) and every fuel and dwelling pair are covered.
  • 6. Descriptions. They follow the house template with the program's own name, which siblings use.
  • 7. References. The worksheet and plan anchors show their values (worksheet p. 1; plan pp. 8-9). The variable tuple's # PDF pages 8-9. comment follows CLAUDE.md. See G4.
  • 8. Wiring.
    • Registry. The programs.yaml block is complete.
    • Aggregate. No merged LIHEAP state feeds a household aggregate, so none is needed.
    • Gating. Every variable is gated by StateCode.UT or ut_heat_eligible.
  • 11, Integrate modeled state unemployment insurance into income flows #9558. The list reads total_unemployment_compensation (Case 44 exercises modeled ut_ui), and no Python reads the raw input.
  • 12. IIJA. There is no supplement.
  • 13. Mutation. See S5. The poverty ceiling, burden rounding, cap, target ages, the ineligible-resident credit bar, heat in rent, subsidized housing and firewood are all pinned.
  • 14. Health premiums (Document disjoint health premium inputs and correct consumer treatment #10046). pre_tax_health_insurance_premiums is deducted once, alongside the non-pretax aggregate or its components (Case 46). 407 covers premiums generally, so deducting payroll premiums is right.
  • 15, NH Add New Hampshire state supplementation for 2026 #10156. Safe.
    • ut_heat_dwelling_type is a state-gated enum input whose default (HOUSE) is a valid key of monthly_energy_cost_standard.
    • heating_type is used only in select.
    • ut_heat_household_size is used only arithmetically (fpg(max_(size, 1))).
  • 16, Wire military retirement pay and survivors into federal and state tax (#10181) #10182 / Count military SBP annuities in state benefit income lists #10317. military_retirement_income is used (Cases 48-49).
  • 17. State SSP. ut_ssp is rightly excluded under 405 (G1).
  • Count each Social Security benefit once in Pennsylvania CCW countable income #10408 pattern. social_security is listed once, and no component is listed.
  • Federal floor. The 150% limit is above 42 U.S.C. 8624(b)(2)(B)'s 110%, so the floor never binds.
  • Changelog. changelog.d/ut-liheap-wave2b.added.md is a top-level fragment.
  • Local runs. None. CI ran all six Utah YAML files green at this head, and the findings rest on source text and hand traces.

Axiom line assessment

axiom: TheAxiomFoundation/rulespec-us#1904 queued is valid.

  • The issue. It is OPEN and labelled pe-parity. It is dispatch-ready: module paths and corpus citations, verbatim manual, rule, plan and worksheet text, required outputs, and 14 companion tests derived from the sources.
  • The blocker. The March 2026 manual pages, the worksheet, the 608 PDF, the Clearinghouse workbook and R982-403 are missing from the corpus; the signed release has not been checked; there is no reviewed oracle revision; and billed runs need approval.
  • The match. It describes head 1f79e33814 and the 109 cases (49/23/14/16/2/5), with total_unemployment_compensation and military_retirement_income in the lists.
  • After the fixes, refresh:
    • interpretation 1 (its worksheet claim) and companion test 14 (C1);
    • interpretations 11 and 13 and companion test 11 (S1);
    • interpretations 2 and 11 (S2, S3);
    • interpretation 6 and companion test 5 (S4);
    • the new companion tests (S5);
    • the countable-income required output;
    • the head SHA and case counts.

CI status

gh pr checks 10344 at head 1f79e33814, re-checked at the end of this review: 34 pass, none failing or pending (run 40057986027, 2026-10-24).

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 changes: 1 critical (no 20% earned-income disregard, which Utah Admin. Code R982-403-6(1) requires and DWS's March 2026 worksheet applies), 5 should. Full report in the comment above.

hua7450 and others added 2 commits October 27, 2026 15:41
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	policyengine_us/programs.yaml
#	source_conflicts/candidates.yaml
@hua7450

hua7450 commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the review, David. The fixes are in 3e16d52, and b06ffa0 merges current main.

  • C1 (earned-income disregard). Fixed. The worksheet xlsx resolved the earlier reading: its cell C8 is =B8*0.2 ("20% disregard"), B11 is =B8+B9-C8-B10, and the print area A1:B67 leaves column C off the PDF, so the PDF's missing disregard line was a print-area artifact. New income/earned_income_disregard.yaml (0.2) cites R982-403-6, the xlsx (C8 and B11) and section 204, and ut_heat_countable_income.py counts 80% of earned income. One list, earned_income_sources.yaml, replaces business_income_sources.yaml; rental and farm rental income count there as self-employment (section 403) and take the disregard, and the list comment notes that R982-403-3(2)(m) makes rent from a property the client does not manage unearned, which the inputs cannot identify. Candidate us-ut:heat-earned-income-disregard now has follows: statute, puts section 204 and the xlsx on the rule's side, and keeps retired section 406 as the open question. Your $27,000 example now gives $234. Re-pinned: Case 3 $310 -> $370, Case 4 $270 -> $320, Case 7 $309 -> $369, Cases 23-38 (gas house $188 -> $234, propane house $448 -> $524), Case 39's first unit $310 -> $370, Case 44 $0 -> $217, Case 46 [$217, $234] -> [$277, $294], and countable Cases 1-5, 7, 12-18, 20 and 21 (Case 1: $14,400, renamed). Cases 5-6 and eligibility Cases 1-2 keep the limit edge with wages of $29,925 and $29,925.15; Cases 8-10 and 21 keep their values with wages of $42,600, $42,600.60, $168,375 and $25,500.
  • S1 (Medicare premiums). Fixed. The deduction is medical_expense_health_insurance_premiums + pre_tax_health_insurance_premiums + medicare_part_a_premium (the AZ and VA pattern) + other_medical_expenses, citing section 407 and R982-403-7(1)(b). medicare_part_b_premiums_reported is no longer read; a caller with a different premium enters health_insurance_premiums. New Case 50 is your $24,500 Social Security example: $371. Case 43 is now $460 (was $424) only because it sets msp_part_b_premium_coverage to 0; at $18,360 of pension the Medicare Savings Program (QI) would pay Part B and the award would be $424. Countable Case 23 covers that MSP path (Part B $0) with a reduced Part A premium of $3,732: $14,628. Case 40 enters its $2,400 as health_insurance_premiums ($460), Case 41 is $14,125.20 and $491, Case 42 stays $446, countable Case 22 is $14,125.20, and Case 49 (age 70, $26,000 survivor annuity) now deducts $2,434.80 of Part B and pays $362 (was $0).
  • S2 (military basic pay). Fixed. military_basic_pay is on the earned list, floored and disregarded, citing section 403 and R982-403-4(2)(a). New Case 51 is your two-adult example: $249.
  • S3 (rule differences). Fixed. (a) ordinary_dividend_income is off unearned_income_sources.yaml, which cites R982-403-5(12) and plan 1.9 (page 6); estate_income stays for trust payments under R982-403-3(2)(h). Countable Case 9 is now $2,400 (renamed), and new Case 52 is your pension-and-dividends example: $326. (b) and (c) are new candidates us-ut:heat-tanf-dws-payments and us-ut:heat-child-unearned-income (follows: conflicting-source, state: open), each quoting both sides, with examples (manual $605 against rule $513; manual $455 against rule $337). The code still follows section 405 and the plan there.
  • S4 (no-heat households). Fixed. ut_heat_energy_cost_standard.py gives a NONE household with pre_subsidy_electricity_expense above zero the electricity standard, citing plan 1.1 (page 4), section 207 and section 603. NONE with no bill stays ineligible (section 208), and UNSPECIFIED stays at $0. Eligibility Case 8 and Case 39's fourth unit drop their bill and test NONE without one; new Case 53 is your $900 example: $420.
  • S5 (untested entries and branches). Fixed. New countable Case 24 gives the 11 sources distinct amounts: $8,400 earned after the disregard + $14,700 unearned = $23,100. New countable Case 25 excludes an 18-year-old with is_in_k12_school: $12,768. New eligibility Cases 15-17 cover public housing with heat in rent (ineligible), public housing with a $600 electric bill (eligible) and has_heating_expense alone (eligible). New eligible-person Case 17 is an active-duty parolee (eligible), and Case 18 is a veteran's dependent parolee child (eligible) beside a non-dependent adult parolee child (ineligible). The Axiom companion tests mirror every new case.
  • G1 (ut_ssp). Adopted. The unearned_income_sources.yaml comment says section 405 leaves out the Utah state supplement (ut_dws_ssp, a DWS payment, and ut_dhhs_ssp, another agency's payment) and points to us-ut:heat-tanf-dws-payments, whose note says the same.
  • G2 (kerosene credit). Adopted. KEROSENE is in fuel_credit_types.yaml, which now also cites section 609 and the worksheet's Sheet2 fuel list, and the fuel_credit.yaml label and description name kerosene. Cases 27-28 are $524 and $464 (your $448 and $408 plus the disregard).
  • G3 (SOLAR). Adopted. SOLAR takes the electricity standard; new Case 54 (three people, $36,000 of wages, a $1,200 electric bill) pays $264.
  • G4 (references). Fixed. income_limit.yaml adds plan 2.1 (page 8) and R982-403-1, and the countable-income variable and the income-source, disregard and child-age parameters cite the R982-403 sections on Cornell LII. The Wayback CDX index has no capture of 607, 608 or the standalone 608 PDF, so the live URLs stay.
  • G5 (other periods). Documented. The body's Years covered section says January-February 2026 and October-December 2026 use the March 2026 values. No mixed-state case is added.
  • G6 (housekeeping). Done. All three Utah candidates list their code paths and no longer say "Code not yet on main", and the merge keeps main's Virginia AG candidates beside them.

The PR body and TheAxiomFoundation/rulespec-us#1904 are updated for b06ffa0. I did not run the tests locally, so CI will check them.

@hua7450
hua7450 requested a review from DTrim99 October 27, 2026 19:43
@DTrim99

DTrim99 commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 2): Add Utah LIHEAP regular heating assistance

PR #10344 (author: hua7450), head b06ffa00fe. Round 1 reviewed 1f79e33814 and requested changes: one critical (C1, the 20% earned-income disregard), five should-address items (S1-S5) and six suggestions (G1-G6). Since then the author has pushed:

  • one fix commit, 3e16d526d3;
  • a merge of main (b06ffa00fe) with two hand-resolved blocks, in programs.yaml and source_conflicts/candidates.yaml.

The author answered item by item and updated the PR body and rulespec-us#1904. The PR touches no partner test file.

Path prefixes:

  • P/ = policyengine_us/parameters/gov/states/ut/dwf/heat/
  • V/ = policyengine_us/variables/gov/states/ut/dwf/heat/
  • T/ = policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/

Summary

Fixed. The fix commit resolves every round-1 critical and should item. A test would fail without each fix:

  • C1. 20% of earned income is now disregarded, under R982-403-6, section 204 and worksheet cell C8.
  • S1. Modeled Medicare Part B and Part A premiums are now deducted.
  • S2. military_basic_pay now counts.
  • S3. Dividends are dropped, and the two remaining rule-versus-manual questions are recorded as open candidates.
  • S4. A no-heat (NONE) household that pays an electric bill now takes the electricity standard.
  • S5. All 11 untested sources and five untested branches now have tests.

Suggestions. The author adopted G1, G2, G3 and G6, did G4 apart from Wayback captures (none exist), and documented G5.

Hand checks and guards.

  • I hand-checked every new or changed expectation (121 cases; all match).
  • The worksheet-derived and candidate examples reproduce, including $234, $371, $249, $326, $420, $605/$513 and $455/$337.
  • Both merge resolutions are correct.
  • The merged tree is clean, and the main-side guards pass.

One new should-address item. It is a side effect of the S4 fix.

  • N1. Two lines keep households without a cost standard at $0:
    • the new electric-bill condition for NONE households;
    • the supported_fuel eligibility condition.
  • Why neither is pinned. The only NONE test households have no bill and no other energy responsibility, so they are ineligible either way. No test uses UNSPECIFIED.
  • What round 1 had. It pinned supported_fuel through eligibility Case 8's $1,200 bill. Our round-1 fix text offered "or remove their bill", and that removal is what dropped the pin.
  • Fix. One output line and one case.

Two new suggestions:

  • the Medicare Savings Program category in a comment (SLMB, not QI);
  • a note on entering military basic pay in only one input.

Status.

  • CI: at b06ffa00fe, 34 checks pass and one contrib shard is still running; none fails. All six Utah files ran green.
  • Branch: 12 commits ahead of main and 2 behind, and it merges cleanly.

Prior findings

Finding Status Evidence
C1 No 20% earned-income disregard (R982-403-6(1)) Fixed V/income/ut_heat_countable_income.py:42-48: the floored earned sources × (1 − earned_income_disregard), from P/income/earned_income_disregard.yaml (0.2, citing R982-403-6, xlsx C8/B11 and section 204). Business inputs are net, so the disregard falls after costs (R982-403-6(2)). Rent is earned, following section 403, and the list comment notes R982-403-3(2)(m) and 4(2)(k). Mutation: countable Case 1 (18,000 → 14,400) and 40+ re-pinned cases fail without it. The limit edges keep their cents: wages $29,925 / $29,925.15 → $23,940 / $23,940.12. The candidate now reads follows: statute, with section 204 and the xlsx on the rule's side and retired 406 as the open question. The body, the code comment and Axiom interpretation 1 / companion test 14 match.
S1 Medicare deducted only if reported Fixed V/income/ut_heat_countable_income.py:59-64: medical_expense_health_insurance_premiums (a nonzero direct input, else non-Medicare premiums + modeled Part B net of MSP) + pretax premiums + medicare_part_a_premium + other medical. That is the AZ/VA pattern, and 407 covers health insurance premiums generally. The aggregate on main excludes Part A and pretax premiums, so nothing is counted twice. Mutation: ut_heat Case 50 ($24,500 SS → $371) and Case 43 ($460) fail without derived Part B; countable Case 23 (Part A $3,732 → $14,628) fails without Part A. medicare_quarters_of_coverage defaults to 40 on main, so other enrollee cases get $0 Part A, as expected.
S2 military_basic_pay omitted Fixed P/income/earned_income_sources.yaml:7 (cites R982-403-4(2)(a), which I re-read: "Wages, including military base pay"). ut_heat Case 51 fails without it: two adults, $30,000 → $249. See also new suggestion G-b.
S3 Rule differences unrecorded; dividends counted Fixed (a) ordinary_dividend_income is off P/income/unearned_income_sources.yaml, whose comment cites R982-403-5(12) and plan 1.9 (p. 6). Countable Case 9 ($2,400) and ut_heat Case 52 ($326) fail if dividends return. (b), (c) New candidates us-ut:heat-tanf-dws-payments and us-ut:heat-child-unearned-income (follows: conflicting-source, state: open) quote both sides. Their examples hand-check (below). The model still follows section 405 and the plan there, which is what round 1 asked to be recorded for the project lead.
S4 NONE with an electric bill gets $0 Fixed (see N1) V/ut_heat_energy_cost_standard.py:30-40: NONE with pre_subsidy_electricity_expense > 0 takes the electricity row, citing plan 1.1 (p. 4), 207 and 603. ut_heat Case 53 ($900 bill → standard $3,444, $420) fails without it. Eligibility Case 8 and Case 39's fourth unit now have no bill and stay ineligible.
S5 11 untested sources, 5 untested branches Fixed Countable Case 24 gives all 11 sources distinct positive amounts: 0.8 × 10,500 + 14,700 = $23,100. So dropping any one changes the total, and moving farm rent to the unearned list adds $200. The new cases: countable Case 25 (is_in_k12_school, $12,768); eligibility Cases 15-17 (public housing with heat in rent: ineligible; with a $600 electric bill: eligible; has_heating_expense alone: eligible); eligible-person Cases 17-18 (an active-duty parolee: true; a veteran's dependent parolee child, then a non-dependent adult child: [true, true, false]). Each fails if its branch is removed: for example, without is_in_public_housing, Case 15 becomes eligible through heat in unsubsidized rent.
G1 Say why ut_ssp is excluded Adopted The comment in P/income/unearned_income_sources.yaml and the us-ut:heat-tanf-dws-payments note.
G2 Kerosene credit Adopted P/benefit/fuel_credit_types.yaml:5 lists KEROSENE (cites 609 and the xlsx Sheet2 list). Cases 27-28 are $524 and $464 (pinned).
G3 SOLAR Adopted It takes the electricity row; ut_heat Case 54 ($264, standard $3,444) is pinned.
G4 References Done; Wayback declined with reason income_limit.yaml adds plan 2.1 (p. 8) and R982-403-1, and R982-403 is cited throughout. I re-read the Cornell LII copies of R982-403-1, -4 and -6. They carry the 2014 wording and match the cited 2022 provisions in substance, as the body states. On Wayback captures, the author says the CDX index has none. Today the live 607 and 608 still read "Effective: March 1, 2026" with the encoded values, so there is no drift yet. A Save Page Now capture would still help before DWS re-issues the tables.
G5 Other periods Documented The body's Years covered section. The optional mixed-state case was not added. The NH #10156 pattern is still safe by analysis: the new code reads only a float bill and enum comparisons.
G6 Merge housekeeping Done All three UT candidates list existing code paths, and main's Virginia AG candidates are kept (see Branch).

New findings

Critical

None.

Should address

N1. The S4 fix leaves the two guards that keep households without a cost standard at $0 unpinned

  • Where:
    • V/ut_heat_energy_cost_standard.py:30-32: a NONE household takes the electricity standard only when pre_subsidy_electricity_expense > 0.
    • V/eligibility/ut_heat_eligible.py:39: supported_fuel requires a standard above 0. It is the only thing that keeps an otherwise responsible UNSPECIFIED household (the heating_type default) at $0, and UNSPECIFIED stays at $0 by the wave's settled reading.
  • Why no test pins either line.
    • The only NONE cases are eligibility Case 8 (T/ut_heat_eligible.yaml:166) and ut_heat Case 39's fourth unit (T/ut_heat.yaml:1169).
    • Both have no bill, heat not in rent and no wood, so responsible is already false: heating_expense is 0 for NONE on main, and has_heating_expense follows from it.
    • Neither outputs ut_heat_energy_cost_standard.
    • Every test SPM unit sends a known heating_type (121 of 121), so none is UNSPECIFIED.
    • So the electric-bill condition could be deleted (every NONE household would get $3,444), or supported_fuel dropped, and no test would fail.
  • What changed since round 1. Round 1 pinned supported_fuel: Case 8 then had a $1,200 heating bill, so it was responsible and failed only on the zero standard. Our round-1 fix text offered "re-pin ... or remove their bill". The author took the second option, and that dropped the pin.
  • Effect if the guard regresses (hand-traced; 2026, one adult, no income, UNSPECIFIED, heat included in unsubsidized rent):
    • Head: standard 0, so not eligible: $0.
    • Without supported_fuel: eligible, with the zero-income burden of 25 points: 250 + 250 = $500. No test catches this.
  • Severity: should. This is the mutation rule's "new branch with no test" (wave 2b notes; Add Alabama LIHEAP regular heating assistance #9967 S3), and here it also undoes a pin round 1 had. The fix is small.
  • Fix:
    • Add ut_heat_energy_cost_standard: 0 to eligibility Case 8's output. That pins the electric-bill condition.
    • Add one eligibility case: UNSPECIFIED with heat_expense_included_in_rent: true (or a $1,200 heating_expense), no housing assistance, expecting ut_heat_energy_cost_standard: 0 and ut_heat_eligible: false. That pins supported_fuel.
    • rulespec-us#1904 companion test 5(b) and 5(d) already state these expectations. Map them to the new cases and update the head SHA and case counts.

Suggestions

G-a. Medicare Savings Program category wording.

  • The comments. Countable Case 23's comment (T/ut_heat_countable_income.yaml:629, "below the QI limit"), Axiom interpretation 13 and the author's reply say QI pays the Part B premium at $18,360 of pension.
  • The category. Countable MSP income is 18,360 / 12 − 20 = $1,510. That is 113.5% of $1,330, inside the SLMB band (main: QMB 100%, SLMB 120% = $1,596, QI 135% = $1,795.50).
  • Fix. The result is unchanged, since msp_part_b_premium_coverage pays the standard premium in either band. Say "SLMB" or just "the Medicare Savings Program".

G-b. Military basic pay in one input only.

  • Background. S2 added military_basic_pay next to employment_income, as the IA LIHEAP, OR ERDC, GA CAPS and WA exemption lists on main do. But main's Virginia military-pay subtraction treats basic pay as already inside wages.
  • The risk. A caller who enters a service member's pay in both inputs is counted twice.
  • Fix. Add a line to the P/income/earned_income_sources.yaml comment: basic pay entered as military_basic_pay should not also be in employment_income. Optional.

Verified

Hand checks (every new or changed expectation; all match the head):

Test Hand computation Expected
ut_heat Case 3 15,960 × 0.8 = 12,768 → I 1,064; 1,064 / 1,330 = 80% → 170; 211 / 1,064 = 19.83 → 20 → 200 370; countable 12,768
ut_heat Case 4 I 1,064 → 170; 161 / 1,064 = 15.13 → 15 → 150 320
ut_heat Cases 5-6 29,925 × 0.8 = 23,940 → 1,995: 150% → 100, 10.58 → 11 → 110; 29,925.15 × 0.8 = 23,940.12 → 1,995.01 > 1,995 210; 0
ut_heat Case 7 15,972 × 0.8 = 12,777.60 → 1,064.80; 80.06 → 81 → 169; 19.82 → 20 369
ut_heat Cases 8-9 42,600 × 0.8 = 34,080 → 2,840; P 2,277: 124.73 → 125 → 125; coal apartment 71 / 2,840 = 2.50 → 3; 42,600.60 → 2,840.04 → 2.49996 → 2 155; 145
ut_heat Case 10 168,375 × 0.8 = 134,700 → 11,225; P 7,484: 150 → 100; 0.63 → 1 → 10 110
ut_heat Case 21 25,500 × 0.8 − 2,400 − 2,040 = 15,960 → 1,330: 150 + 160 310
ut_heat Cases 23-38 36,000 × 0.8 = 28,800 → 2,400; P 2,277: 105.40 → 106 → 144. House / apartment burdens: gas 8.79 / 6.71 → 9 / 7; propane and kerosene 22.54 / 17.00 → 23 / 17 (+150); oil 19.50 / 14.75 → 20 / 15 (+150); electricity 11.96 / 9.08 → 12 / 9; coal and other 3.83 / 2.96 → 4 / 3; wood 14.29 / 10.83 → 14 / 11 234/214, 524/464, 524/464, 494/444, 264/234, 184/174, 184/174, 284/254
ut_heat Case 39 unit 1 as Case 3; unit 3 pension 15,960 (unearned) → 310 + 150; unit 4 has no bill and no responsibility [370, 800, 460, 0]; countable [12,768, 0, 15,960, 0]
ut_heat Case 40 direct 2,400 wins over 2,434.80: 15,960 → 150 + 160 + 150 460; aggregate 2,400
ut_heat Case 41 18,360 − 1,200 − 2,434.80 − 600 = 14,125.20 → 1,177.10; 88.50 → 89 → 161; 17.93 → 18 → 180; + 150 491
ut_heat Case 42 18,360 − 1,200 − 600 = 16,560 → 1,380; 103.76 → 104 → 146; 15.29 → 15 → 150; + 150 446
ut_heat Case 43 18,360 − 2,434.80 = 15,925.20 → 1,327.10; 99.78 → 100 → 150; 15.90 → 16 → 160; + 150 460
ut_heat Case 44 15,000 × 0.8 + 10,696 = 22,696 → 1,891.33; 142.21 → 143 → 107; 11.16 → 11 → 110 217
ut_heat Case 45 15,000 × 0.8 + 12,000 = 24,000 > 23,940 (estate and business losses floored) [24,000, 24,000]; [0, 0]
ut_heat Case 46 19,200 − 1,200 = 18,000 → 1,500: 112.78 → 113 → 137, 14.07 → 14 → 140; 19,200 − 2,400 = 16,800 → 1,400: 105.26 → 106 → 144, 15.07 → 15 → 150 [277, 294]
ut_heat Case 47 398.99 / 399 / 399.01 × 0.8 = 319.192 / 319.20 / 319.208; ÷ 12 ÷ 1,330 = 1.99995% / 2% / 2.00005% → 2, 2, 3; burden capped at 25. The float32 boundary compare keeps 319.20 at exactly 2 [498, 498, 497]
ut_heat Case 49 MSP 26,000 / 12 − 20 = 2,146.67 > 1,795.50, so Part B is paid; 26,000 − 2,434.80 = 23,565.20 → 1,963.77; 147.65 → 148 → 102; 10.74 → 11 → 110; + 150 362, eligible
ut_heat Case 50 MSP 2,021.67 > 1,795.50; 24,500 − 2,434.80 = 22,065.20 → 1,838.77; 138.25 → 139 → 111; 11.48 → 11 → 110; + 150 371
ut_heat Case 51 30,000 × 0.8 = 24,000 → 2,000; P ceil(21,640 / 12) = 1,804; 110.86 → 111 → 139; 10.55 → 11 → 110 249; limit 32,460
ut_heat Case 52 pension 15,000 only → 1,250; 93.98 → 94 → 156; 16.88 → 17 → 170 326
ut_heat Case 53 287 × 12 = 3,444; I 1,064 → 170; 26.97 → 27, capped at 25 → 250 420
ut_heat Case 54 SOLAR → 3,444; as Case 31 264
countable Cases 1-5, 7 14,400; 12,768; 12,768; (15,960 + 12,000) × 0.8 + 2,400 = 24,768; 27,960 × 0.8 = 22,368; 18,000 × 0.8 = 14,400 as stated
countable Cases 9, 12-18, 20, 21 2,400; 15,600 × 0.8 = 12,480; 12,768; 16,320 − 4,440 = 11,880; 17,568 − 6,000 = 11,568; 14,400 − 1,200 = 13,200; 14,400 − 2,040 = 12,360; 14,720 − 2,440 = 12,280; 12,768; 12,768 as stated
countable Case 22 aggregate 1,200 + 2,434.80 = 3,634.80; 18,360 − 3,634.80 − 600 14,125.20
countable Case 23 35 quarters → reduced Part A 311 × 12 = 3,732; MSP 1,510 ≤ 1,795.50, so Part B 2,434.80 covered; 18,360 − 3,732 14,628
countable Case 24 0.8 × (5,000 + 2,500 + 2,000 + 1,000) + (1,200 + 2,400 + 3,600 + 4,800 + 600 + 300 + 1,800) 23,100
countable Case 25 the 18-year-old K-12 student is excluded; 15,960 × 0.8 12,768
eligible Cases 1-2, 8, 15-17 23,940 ≤ limit; 23,940.12 >; NONE with no bill or responsibility; public housing with heat in rent; with an electric bill; flag alone T, F, F, F, T, T
eligible-person Cases 17-18 parolee, 0 years, is_military; dependent child of a veteran head; adult child heading their own tax unit T; [T, T, F]
Candidate examples TANF: size 2, P 1,804; 9,600 → 800: 44.35 → 45 → 205, burden capped at 250, + 150 = 605; with FEP 14,400 → 1,200: 66.52 → 67 → 183, 17.58 → 18 → 180, + 150 = 513. Child: 800 → 205 + 250 = 455; 15,600 → 1,300: 72.06 → 73 → 177, 16.23 → 16 → 160 = 337. Disregard: 27,000 → 21,600 → 1,800: 135.34 → 136 → 114, 11.72 → 12 → 120 = 234 605 / 513; 455 / 337; 234

Batch checks:

  • CI coverage. At b06ffa00fe, the Quick Feedback job ran all six Utah files (54 + 25 + 17 + 18 + 2 + 5 = 121 cases) green, as did the baseline state shards. No local runs were needed.
  • Income (checks 2, 14).
    • Social Security and SSI still count gross, and Part B is now netted as a deduction (S1).
    • Losses are floored per source, and Case 45 pins both the estate and the business floor.
    • Pretax premiums are added once (Case 46).
  • Count each Social Security benefit once in Pennsylvania CCW countable income #10408. social_security is listed once with no component. Case 50 enters social_security_retirement, which flows through the aggregate.
  • Count military SBP annuities in state benefit income lists #10317 and Integrate modeled state unemployment insurance into income flows #9558. military_retirement_income (Cases 48-49) and total_unemployment_compensation (Case 44, modeled ut_ui).
  • NH Add New Hampshire state supplementation for 2026 #10156 (check 15). Still safe. ut_heat_dwelling_type defaults to a valid key, and the new code compares enums and a float bill.
  • Item 17. ut_ssp is rightly excluded under section 405, and the exclusion is now recorded in the comment and candidate (G1).
  • Descriptions and references (checks 6, 7).
    • The new earned_income_disregard.yaml and earned_income_sources.yaml follow the template, and so do the reworded fuel-credit description and label.
    • The new references are single sources. Plan anchors pp. 4, 6 and 8 show the cited items.
  • Tie source-conflict markers to the register shared with Axiom #10287 ratchet. I applied the guard's comment regex line by line to every PR parameter, variable and test file: no matches. The new comments ("excludes ...; R982-403-5(1) excludes ...", "Section 405 excludes ...") avoid the trigger phrases. No marker was added, and none is needed (no registered id). No KNOWN_UNMARKED count changes.
  • Candidates file.
    • All kinds (regulation, manual, worksheet, calculator, plan), follows values, states and year types are in the guard's allowed sets.
    • Every code path exists at the head.
    • The 268 ids are unique.
  • Registry. The notes now say "propane, kerosene or fuel oil". verified_years: "2026" is now backed, since C1 is fixed.
  • Federal floor. 150% is above the 110% of 42 U.S.C. 8624(b)(2)(B).
  • Changelog. changelog.d/ut-liheap-wave2b.added.md is unchanged and still accurate.

Axiom

axiom: TheAxiomFoundation/rulespec-us#1904 queued is valid.

  • The issue. It is OPEN and labelled pe-parity, last updated 2026-10-27 19:43 UTC, after the merge push. It is dispatch-ready: module paths and corpus status, verbatim manual, rule, plan, worksheet and CMS text, required outputs, 14 companion tests, and blockers (missing corpus sources, the signed release unchecked, billed runs needing approval, no reviewed oracle revision).

  • The match. It describes head b06ffa00fe and the review fixes:

    • 121 cases (54/25/17/18/2/5) and 20 parameter files (8 earned and 17 unearned sources);
    • the disregard, the Part B/Part A deduction, military pay, no dividends, SOLAR, NONE with a bill, the kerosene credit and the three candidates (interpretations 1, 2, 6 and 13);
    • companion tests re-derived with E = 0.8 × earnings, including every new case.
  • After N1:

    • map companion test 5(b)/(d) to the new cases;
    • update the head SHA and counts;
    • change "QI" in interpretation 13 (G-a).

    "CI passed at 1f79e33814" can also now say b06ffa00fe.

CI

gh pr checks 10344 at head b06ffa00fe (run 40571554533, started 2026-10-27 19:42 UTC), re-checked at the end of this review (20:56 UTC): 34 pass, 1 pending, none failing.

  • Pending. Contrib tests (other-shard-1) has been running since 20:17 UTC. It runs contributed reforms, and the PR adds none.
  • What ran. All baseline shards (including all four state shards), Quick Feedback (selective Utah tests plus coverage), the partner suite, microsimulation, Rest Python shards 1-6, PR validation (lock, guard tests, lint, changelog), package compatibility and Codecov patch and project all pass.
  • Known main failures. Rest (Python shard 3) passed. The known test_social_security_input_state_machine NameError did not occur on this run, so there is no main-caused note.

Branch

  • Distance. 12 commits ahead of main and 2 behind: Count CTC-qualifying children only when the filer meets the identification requirements #10280 (CTC filer identification) and a version bump. Neither touches HEAT, the Medicare/MSP inputs or the code-health guards.
  • Merge check. git merge-tree --write-tree PolicyEngine/main b06ffa00fe succeeds with no conflicts.
  • The hand-resolved blocks are correct:
    • programs.yaml coverage line. It is main's line (AR, AL, AZ, DC, GA, Riverside County, MA, FL, IL, IA, IN, KS, KY, MD, MS, NC, NE, NJ, NY, OH, OK, OR, SD, RI, TX, VA) plus UT. No sibling is dropped or duplicated.
    • programs.yaml UT block. It follows main's VA block, which is intact. A diff against main shows only the coverage token and the UT block.
    • candidates.yaml. The three UT candidates sit after us-tx:... and before main's us-va:ag-... entries, which are all kept unchanged. The ids stay sorted and unique, and a diff against main shows only the three UT entries.
    • PR files. The merge changed none of the PR's own parameter, variable, test or changelog files.

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 changes, round 2: 0 critical, 1 should. Every round-1 item is fixed. One new should: the NONE electric-bill condition and the supported_fuel guard can be deleted without any test failing (pin them with a standard-0 output on eligibility Case 8 and one UNSPECIFIED heat-in-rent case). Full report in the comment above.

hua7450 and others added 2 commits October 27, 2026 17:03
Eligibility Case 8 now outputs the zero standard for a NONE household
with no electric bill, and new Case 18 pins the zero standard and
ineligibility for an UNSPECIFIED household with heat in rent. Correct the
Medicare Savings Program category in countable Case 23 (SLMB) and note
that military basic pay belongs in one input only.

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

hua7450 commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, David. The round-2 items are in 97a8f48, and 51d1166 merges current main.

  • N1 (unpinned zero standards). Fixed. Eligibility Case 8 (NONE, no electric bill) now also outputs ut_heat_energy_cost_standard: 0, which pins the electric-bill condition. New eligibility Case 18 is Case 11 with heating_type: UNSPECIFIED: heat in unsubsidized rent makes the household responsible, and it expects a standard of 0 and ut_heat_eligible: false, which pins supported_fuel. Without supported_fuel the household would be eligible. LIHEAP wave 2b: encode Utah HEAT regular heating assistance TheAxiomFoundation/rulespec-us#1904 companion test 5 now maps (b) and (d) to Cases 8 and 18 and gives the head SHA and the new count (122 cases; eligibility 18).
  • G-a (Medicare Savings Program category). Adopted. Countable Case 23's comment now says $1,510 falls within the SLMB limit of 1,330 x 1.2 = 1,596, and interpretation 13 says SLMB. My earlier reply also said QI; SLMB is the right category, and the result does not change.
  • G-b (military basic pay in one input). Adopted. The earned_income_sources.yaml comment now says basic pay entered as military_basic_pay should not also be included in employment_income, or it is counted twice. The PR body's earned-income row says the same.

I did not run the tests locally, so CI will check them.

@hua7450
hua7450 requested a review from DTrim99 October 27, 2026 21:03
@DTrim99

DTrim99 commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 3): Add Utah LIHEAP regular heating assistance

PR #10344 (author: hua7450), head 51d1166082. Round 2 reviewed b06ffa00fe and requested changes. It found every round-1 item fixed and left one should-address item (N1, two unpinned zero-standard guards) and two suggestions (G-a, the Medicare Savings Program category; G-b, military basic pay in one input). Since then the author has pushed:

  • one fix commit, 97a8f486a0 (3 files, test and comment changes only);
  • a merge of main (51d1166082) with no hand-resolved blocks.

The author answered item by item and updated the PR body and rulespec-us#1904. The PR touches no partner test file.

Path prefixes:

  • P/ = policyengine_us/parameters/gov/states/ut/dwf/heat/
  • V/ = policyengine_us/variables/gov/states/ut/dwf/heat/
  • T/ = policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/

Summary

Fixed. N1 is fixed: each of the two guards now has a test that fails if the guard is deleted.

  • Electric-bill condition. Eligibility Case 8 (NONE, no bill) now also expects ut_heat_energy_cost_standard: 0.
  • supported_fuel. New eligibility Case 18 is Case 11 with heating_type: UNSPECIFIED. It expects a standard of 0 and ut_heat_eligible: false.

Suggestions. G-a and G-b are adopted, in the code comments, the PR body and Axiom interpretation 13.

Unchanged since round 2. The fix commit changes no formula, parameter value or other expectation. The merge brings in only #10280 (CTC filer identification, which touches Utah's income-tax CTC, not HEAT) and a version bump. So the round-1 and round-2 verifications still hold.

Checks.

  • I hand-checked the three new or changed expectations and the corrected comment arithmetic. All match.
  • The guard regex finds no conflict wording in the changed files.
  • main moved during this review: Add Washington LIHEAP regular heating assistance #10345 (Washington LIHEAP) merged at 21:21 UTC. The branch now conflicts with main only in programs.yaml, a sibling conflict (branch note).
  • rulespec-us#1904 matches the head.

New findings. No critical or should-address items. One optional wording suggestion (G-c).

CI. At 51d1166082, 19 checks pass and 6 are still running; none fails. Quick Feedback ran all six Utah files green (122 cases).

Prior findings

Finding Status Evidence
N1 The electric-bill condition and supported_fuel were unpinned Fixed Electric-bill condition (V/ut_heat_energy_cost_standard.py:30-32). T/ut_heat_eligible.yaml:167 (Case 8: NONE, pre_subsidy_electricity_expense 0, HOUSE) now outputs ut_heat_energy_cost_standard: 0 (line 186). Mutation: without the > 0 bill condition, NONE takes the HOUSE electricity row, 287 × 12 = 3,444. Case 8 fails (3,444 ≠ 0). Its eligibility stays false, since the household has no responsibility. supported_fuel (V/eligibility/ut_heat_eligible.py:39). New Case 18 (T/ut_heat_eligible.yaml:417) is Case 11 (line 236, eligible: true) with only heating_type changed to UNSPECIFIED. Each other condition holds: size 1, an adult aged 40, income 0 ≤ 23,940, responsible through heat in rent with receives_housing_assistance false and no public housing, and housing-eligible. UNSPECIFIED matches no select row (the NONE electric row needs NONE), so the standard is 0. Mutation: without supported_fuel, Case 18 returns true, as Case 11 does, and fails. Mapping UNSPECIFIED to any row would fail its ut_heat_energy_cost_standard: 0 line. Case 53 (NONE with a $900 bill, $420) still pins the other side of the electric-bill condition.
G-a The MSP category is SLMB, not QI Adopted T/ut_heat_countable_income.yaml:626: "within the SLMB limit of 1,330 x 1.2 = 1,596". MSP countable income is 18,360 / 12 − 20 = 1,510: above 1,330 (QMB, 100%) and at or below 1,596 (SLMB, 120%). The expected 14,628 is unchanged. Axiom interpretation 13 now says SLMB, and the author's reply corrects the earlier "QI".
G-b Military basic pay belongs in one input Adopted P/income/earned_income_sources.yaml:5-7: "Basic pay entered here should not also be included in employment_income, or it is counted twice." The PR body's earned-income row says the same.

All round-1 items (C1, S1-S5, G1-G6) stay fixed. The fix commit edits only the two test files and one parameter comment, and the merge changes none of the PR's own files.

New findings

Critical

None.

Should address

None.

Suggestions

G-c. Case 18's comment reads as a legal conclusion (optional).

  • The comment. T/ut_heat_eligible.yaml:414-416 says "an unspecified fuel has no energy cost standard, so the household is not eligible."
  • Why it matters. main's heating_type documentation calls UNSPECIFIED the right value when heat is included in rent and the fuel is unknown. Under section 208, a household with heat in unsubsidized rent is responsible for an energy cost. The $0 is the wave's settled input rule (callers send the fuel; Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754 tracks the default), not a Utah standard. The variable's own comment already says this: "zero denotes unsupported model coverage, not a legal zero-cost standard".
  • Fix. Reword the case comment to match, for example "UNSPECIFIED has no modeled standard (unsupported coverage, not a legal zero), so the model pays nothing; callers send the fuel." The expectations stay the same. This doesn't block merging.

Verified

Hand checks (every new or changed expectation; all match the head):

Test Hand computation Expected
eligible Case 8 (changed) NONE with a $0 bill: electric_without_heating is false, no other row matches, so default=0 × 12 = 0. Responsible: heating_expense is 0 for NONE, so has_heating_expense is false; no bill, no heat in rent, not wood. So not responsible standard 0; not eligible
eligible Case 18 (new) UNSPECIFIED: no row matches, so the standard is 0. Size 1, adult, income 0 ≤ 1.5 × 15,960 = 23,940, responsible (heat in rent, not subsidized), housing-eligible; supported_fuel is false standard 0; not eligible
countable Case 23 (comment) 18,360 / 12 = 1,530 − 20 = 1,510; QMB 1,330 < 1,510 ≤ SLMB 1,330 × 1.2 = 1,596. Part B is covered, so 18,360 − 3,732 (Part A, 311 × 12) = 14,628 14,628 (unchanged)

Mutation summary (N1):

Guard deleted Test that fails Why
pre_subsidy_electricity_expense > 0 in electric_without_heating eligible Case 8 its standard becomes 3,444
electric_without_heating entirely ut_heat Case 53 its standard becomes 0 and its award $0 (expected $420)
supported_fuel eligible Case 18 it becomes eligible
UNSPECIFIED mapped to any fuel row eligible Case 18 its standard is no longer 0

Other checks:

Axiom

axiom: TheAxiomFoundation/rulespec-us#1904 queued is valid.

  • The issue. It is OPEN and labelled pe-parity, and was last updated 2026-10-27 21:03 UTC, with the round-3 push. It remains dispatch-ready (module paths, verbatim sources, required outputs, companion tests and blockers, as verified in round 2).
  • The match. It describes head 51d1166082, including the round-3 fix commit 97a8f486a0.
    • Companion test 5 now cites ut_heat_eligible.yaml Cases 8 and 18. Its expectations (b) "standard $0, not eligible" and (d) "standard $0, not eligible" match the two cases.
    • The test count is 122, with 18 eligibility cases.
    • Interpretation 13 says SLMB.
    • The blocker note correctly says there is no reviewed oracle revision yet.

CI

gh pr checks 10344 at head 51d1166082 (run 40583466311, started 2026-10-27 21:03 UTC), re-checked at the end of this review (21:29 UTC): 19 pass, 6 pending, none failing.

  • Utah tests. Quick Feedback ran all six Utah files green: 54 + 25 + 18 + 18 + 2 + 5 = 122 cases. That includes eligibility Cases 8 and 18 and countable Case 23.
  • Also passing. Baseline states-shard-2 and -3, household, irs, reform, rest-a, rest-b, ssa-usda and contrib-hhs; Rest Python shards 2, 4 and 5; the partner suite; package compatibility; PR validation (lock, guard tests, lint, changelog); and Codecov patch and project.
  • Still running. Baseline states-shard-1 and -4, Rest Python shards 1, 3 and 6, and Microsimulation. The fix commit changes only YAML expectations and comments, and round 2's run at b06ffa00fe passed every shard.
  • Known main failure. If Rest (Python shard 3) fails only on test_social_security_input_state_machine (NameError 'operator'), that comes from main, not this PR. The PR touches neither that test nor the Social Security inputs.

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 round 3: 0 critical, 0 should. N1 is fixed and both guards are now pinned. Full report in the comment above. Branch note: #10345 (WA) merged, so programs.yaml now conflicts; keep WA and add UT, then rerun CI before merging.

@hua7450
hua7450 merged commit d8ab695 into main Oct 27, 2026
35 checks passed
@hua7450
hua7450 deleted the ut-liheap-wave2b branch October 27, 2026 23:02
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 Utah LIHEAP regular heating assistance

2 participants