Skip to content

Add Missouri LIHEAP Energy Assistance - #10438

Merged
hua7450 merged 9 commits into
mainfrom
mo-liheap-wave1d
Oct 30, 2026
Merged

hua7450 merged 9 commits into
mainfrom
mo-liheap-wave1d

Conversation

@hua7450

@hua7450 hua7450 commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Adds Missouri LIHEAP Energy Assistance (EA), the one-time regular heating payment the Department of Social Services, Family Support Division pays for FY2026 and FY2027. The model:

Fixes #10428

  • counts citizens and qualified noncitizens in household size; other members are left out of the count, but their income still counts;
  • computes countable income from the October 2025 LIHEAP Policy and Procedure Manual (FFY2026 edition), with each earned and unearned source floored at zero, a 20% earned income deduction, children's earnings and disability payments excluded, the SMI (Medicare Part B) premium deducted for members receiving Social Security or railroad retirement, TANF received, child support paid deducted and a $100 monthly medical deduction for an elderly or disabled applicant or spouse;
  • tests that income against Missouri's published 60% state median income limits, with the federal 110% poverty floor;
  • applies the $3,000 resource limit;
  • pays the published amount for the fuel the household selects: its own heating fuel, its electric account or the renter share ($153 to $495). A renter whose heat is included in the rent receives 8% of annual rent, up to the amount for its heating fuel.

The Energy Crisis Intervention Program (ECIP), cooling, weatherization and emergency services are out of scope.

Regulatory authority

Eligibility

Requirement Source How modeled
Missouri residence 13 CSR (6)(A), (7)(A) defined_for = StateCode.MO
Citizens and qualified noncitizens 13 CSR (6)(A), (8)(A); manual p.42-44; 8 U.S.C. 1611(a), 1612(b), 1641(b) mo_liheap_household_size sums is_citizen_or_legal_immigrant. The rule names citizens and legal permanent residents; manual p.44 also accepts I-94 refugee, parolee and asylee annotations and counts COFA citizens as qualified noncitizens. Other members are left out of size, but their income counts (manual p.42-43); size 0 is ineligible. Candidate us-mo:liheap-qualified-noncitizen-members (unreviewed) records the reading
Income at or below 60% of SMI Plan 2.1; Appendix K; application p.1; RSMo 660.100.2 mo_liheap_income_limit: liheap_smi_limit / 12, rounded to the nearest dollar, x 12. Reproduces all 20 FFY2026 rows and all 10 FFY2027 rows. The 2017 income ranges in 13 CSR (6)(D) and (14) are not applied (see the source notes below)
Federal 110% poverty floor 42 U.S.C. 8624(b)(2)(B) 2025 guidelines in FY2026, 2026 in FY2027; binds from household size 16 in both years. Candidate us-mo:liheap-110-percent-poverty-floor
Resources at most $3,000 13 CSR (2)(N), (6)(B); plan 2.3; manual p.40 spm_unit_cash_assets (bank, stock and bond assets)
Responsibility for heating costs 13 CSR (2)(M), (6)(C), (7)(D); plan 2.5; manual p.51 has_heating_expense; or a positive pre_subsidy_electricity_expense (an electric supplier account) when heating_type is neither NONE nor UNSPECIFIED; or heat_expense_included_in_rent without subsidized housing
Subsidized housing 13 CSR (7)(D); plan 2.3; manual p.43 A household that receives_housing_assistance or is_in_public_housing with heat included in the rent qualifies only through its own fuel bill or an electric supplier account. A subsidized tenant with gas heat in the rent and a $1,080 electric bill is eligible for the $318 electric amount
Coal Manual Appendix E (p.122), denial code O Coal-heated households are ineligible

Countable income

Item Source How modeled
Earned income Plan 1.9; manual p.32, 36 Wages and net self-employment, farm and partnership income, each floored at zero, less the 20% earned income deduction
Children's income Plan 1.9; manual p.38 (Earnings of Children); application p.4 Members under 18: earned income and short- and long-term disability payments (disability_benefits) are excluded; their Social Security and SSI count as SSA income. Members 18 or older have earnings counted whether or not they attend high school, GED classes or home school
Unearned income Plan 1.9; application p.5; manual p.32-34 Unemployment, strike benefits, SSI, Missouri SSP/SAB/Blind Pension (mo_ssp), pensions, military retirement (with survivor annuities), railroad retirement, rental and farm rent, alimony, child support received, VA benefits, workers' compensation, contributions from friends or family (financial_assistance); private short- and long-term disability for members 18 or older. Each source is floored at zero per person, so a rental or farm-rent loss does not offset wages
Income of members left out as non-citizens Manual p.42-43 Counted like any member's income
SMI (Medicare Part B) premium Plan 1.9 ("Excluding MediCare deduction"); manual p.36 Social Security counts in full. The full medicare_part_b_premium is deducted from the countable income of a member with social_security or railroad_benefits, and each member's total is floored at zero. Manual p.36 allows the premium "to persons receiving SS and RRB" for members "who are paying this premium"
TANF Plan 1.9 tanf, which is gated by takes_up_tanf_if_eligible and includes Missouri TANF
Child support paid Manual p.36, 42; application Part 6; 13 CSR (9)(B)7 child_support_expense of every member is deducted. The manual deducts child support "paid by any household member" and calls members left out as non-citizens "ineligible household members", so their payments are deducted with their income counted
Medical deduction 13 CSR (2)(C), (2)(D), (6)(D)1; manual p.36, 48 $100 a month, once per household, when the applicant or spouse (tax-unit head or spouse) is 65 or older, or is disabled or blind and receives SSI, Social Security disability, Medicaid, a state supplementary payment (mo_ssp), veterans_benefits, railroad_benefits, social_security_dependents or social_security_survivors. With the disabled-or-blind requirement, the last four inputs stand in for VA and Railroad Retirement disability benefits and for disabled-adult-child and disabled-widow(er) benefits. They also include benefits a disabled member receives for other reasons (Social Security spousal, aged-survivor or child-in-care benefits, a railroad age-and-service annuity), which no input separates, so the deduction can reach some disabled members whose benefit is not paid for disability. The manual is silent on a spouse left out as a non-citizen; such a spouse does not qualify the household
Not counted Manual p.37-40; 13 CSR 40-19.020(2)(N) Interest and dividends, capital gains and other lump sums, tax refunds, general assistance, student aid, loans, EITC, SNAP; retirement-account (IRA, Keogh, 401(k)) withdrawals, since those balances count as resources. Plan 1.9 (p.6) checks "Interest, dividends, or royalties" and leaves cash gifts unchecked; the manual excludes interest and dividends (p.38) and counts contributions (p.33). The model follows the manual; candidate us-mo:liheap-interest-dividends-and-contributions records the choice

Benefit

Fuel Payment
Natural gas $326
Propane (tank) $495
Electricity, solar $318
Fuel oil $326
Wood $219
Kerosene $153
Other fuel, unspecified $0

The same amounts appear in the October 2025 manual (Appendix K, p.129), the FFY26 Appendix K and the FFY2027 application. The household selects the fuel to be paid: "If the household has multiple fuel types, they can choose the fuel type to pay when they apply" (manual p.51; application p.3; plan 2.5, "including electric"). The model pays the largest of three amounts:

  • the amount for its heating fuel, when it has its own bill for that fuel;
  • the $318 electric amount, when it pays an electric supplier and heating_type is neither NONE nor UNSPECIFIED;
  • for an unsubsidized renter whose heat is included in the rent, 8% of annual rent (pre_subsidy_rent), up to the amount for its heating fuel (13 CSR 40-19.020(13); manual p.63).

A kerosene household with an electric bill receives $318, and a natural gas household keeps $326. An unsubsidized renter paying $3,000 a year in rent receives the $318 electric amount over the $240 renter share. A home with no heating (NONE) receives no electric amount, because rule (3)(A) pays costs "incurred for home heating". heating_type is a required input, and UNSPECIFIED pays $0 (#9754): a renter with heat in the rent and an unspecified fuel is eligible but receives $0, with or without an electric bill.

Not modeled and source notes

Item Source Model treatment
Renter rate 13 CSR (13) sets "no more than eight percent" of annual rent "not to exceed the maximum EA benefit payment"; the October 2025 manual, the FFY2024-FFY2026 plans and the draft FFY2027 plan describe 16%; RSMo 660.115.2 limits Utilicare renter payments to the lesser of 8% or $100 The regulation's 8% is applied, capped at the amount for the household's fuel (the manual's reading of the maximum payment). Section (14) sets payments "using metrics established by the division", so the agency's operating limits and amounts apply there (next row); section (13) sets a fixed ceiling without that delegation, and 16% exceeds it. RSMo 660.115.2 governs payments from the Utilicare stabilization fund (660.115.1); EA is paid from federal LIHEAP funds, so the $100 limit is not applied. Candidate us-mo:liheap-renter-rent-share
Rule income ranges and payment table 13 CSR (6)(D) and (14) (effective May 30, 2017) set monthly income ranges from 0 to 135% of the poverty level and payments that fall by range (natural gas $296 in range A to $203 in range F); the FFY2026 plan, Appendix K, the October 2025 manual and the 8-2026 application set 60% of SMI and one amount per fuel. RSMo 660.100.2 allows income up to 150% of the poverty level or 60% of SMI and requires eligibility under LIHEAP, so both readings fall within it The FFY2026-FFY2027 agency values are applied (ruling of 2026-10-22). In FFY2026, a family of four with $39,936 of countable income (124% of the 2025 guideline) that heats with natural gas receives $326; the rule's range E pays $221. A family of four with $50,000 (155%) receives $326 and would be above range F. Candidate us-mo:liheap-rule-income-ranges-and-payments (conflicting-source, ruled)
FFY2027 "+$170 per member above 10" Application p.1 The plan's 60% SMI amount is kept; at sizes 12-15 it is $1-2 a month below the shorthand (the federal floor governs from size 16). To be rechecked against the FFY2027 Appendix K once posted
Cylinder propane ($177) Appendix K Not reachable: heating_type has one propane value, paid at the tank amount
A second fuel other than electric Manual p.51; application p.3-4 The model reads one heating fuel plus the electric bill, so a household with two non-electric fuels is paid for its heating_type only
13 CSR (7)(B), (C), (E)-(H) exclusions; one EA per household per year; roomers, boarders and live-in attendants 13 CSR (7), (8) No inputs; not modeled
IRAs, Keoghs, deferred compensation and annuities in resources 13 CSR (2)(N) No asset inputs
Foster care payments, royalties, armed forces allotments, installment income Plan 1.9; manual No inputs
Part D premiums Manual p.36, 59 Not deducted. The October 2025 manual lists only the SMI (Part B) premium among deductions; page 59 mentions a Part D field on a screen
Civil Service Disability and Black Lung benefits for the medical deduction 13 CSR (2)(C); manual p.48 No separate inputs
Prior-month income 13 CSR (9)(B)5; manual p.30 Annual income stands in for 12 times the prior month
ECIP, cooling, weatherization, emergency services, supplemental EA Plan 1.1 Out of scope
Earlier years Parameter backfilling; unverified historical estimates

The October 2025 manual replaces the FFY2024 manual (updated 10/2023), which left out the income of members excluded as non-citizens and also excluded the earnings of a child in high school, GED classes or home school; the model follows the October 2025 edition.

The five Missouri entries in source_conflicts/candidates.yaml list this PR and the files that apply each reading. They are unregistered, so the code carries no # Source conflict marker yet:

  • us-mo:liheap-110-percent-poverty-floor (follows the statute; ruled);
  • us-mo:liheap-interest-dividends-and-contributions (gap-fill, manual over plan; ruled);
  • us-mo:liheap-qualified-noncitizen-members (follows the statute by inference from 8 U.S.C. 1612(b); unreviewed);
  • us-mo:liheap-renter-rent-share (follows the rule; ruled);
  • us-mo:liheap-rule-income-ranges-and-payments (follows the agency values under the 2026-10-22 ruling, with RSMo 660.100.2 as the statute; ruled).

Test plan

64 YAML cases in 8 files (mo_liheap.yaml 10, mo_liheap_household_size.yaml 4, integration.yaml 3, eligibility/mo_liheap_eligible.yaml 13, eligibility/mo_liheap_income_limit.yaml 2, income/mo_liheap_countable_income.yaml 7, income/mo_liheap_countable_income_person.yaml 15, income/mo_liheap_medical_deduction.yaml 10) cover:

  • every heating fuel; the electric amount selected over kerosene, wood and other fuel but not over natural gas; NONE and UNSPECIFIED with an electric bill, which are not paid;
  • the renter share below and above the fuel cap, a renter whose electric amount exceeds its renter share, and a renter with an unspecified fuel (eligible, $0, also with an electric bill);
  • subsidized housing through either input, and a subsidized tenant with heat in the rent who pays an electric supplier ($318);
  • income limit rows for FFY2026 sizes 1, 4, 16 and 20 and FFY2027 sizes 1 and 10;
  • income and asset boundaries, and the federal floor at size 16 in both years;
  • the 20% deduction; a 17-year-old's wages excluded and an 18-year-old high school student's wages counted; a minor's disability payments excluded while the minor's Social Security counts; a 25-year-old parent in GED classes whose $58,240 of earnings count ($46,592 after the deduction, over the $44,568 limit);
  • per-source loss floors, including a $5,000 rental loss that does not offset $45,000 of wages (countable $36,000, ineligible);
  • the full Part B premium deducted for a Social Security recipient whose premium exceeds the check ($7,800 - $2,434.80 = $5,365.20) and for a railroad retiree aged 70 ($37,000 - $2,434.80 - $1,200 = $33,365.20, eligible for $326);
  • the Social Security or railroad gate on that deduction: a retired public employee aged 70 with a $36,500 pension and neither benefit has no premium deducted ($36,500 - $1,200 = $35,300, over the $34,080 limit, so $0; deducting the $2,434.80 premium would give $32,865.20 and $326);
  • the per-member floor: a dependent parent aged 66 whose $2,434.80 premium exceeds $1,800 of Social Security counts $0, so the household counts the worker's $41,600 ($40,965.20 without the floor);
  • every earned and unearned income source and every medical-deduction benefit with a distinct amount, so dropping any list entry changes a result;
  • TANF counted through tanf; a household that does not take up $3,504 of Missouri TANF counts only its $2,400 of child support received (reading mo_tanf would give $5,904);
  • child support paid and the medical deduction, including Medicaid as the only qualifying benefit;
  • mixed-status households, where an undocumented member is left out of household size but that member's earnings count and child support paid is deducted, and three end-to-end integration cases.

Expected values are hand-computed from the sources above. CI run 40792597007, at 8c016379ed, passed 61 of the 62 cases then on the branch. It failed countable-income Case 6 because the model computed $11,928 of SSI for the railroad retiree; the model's SSI income test does not count railroad annuities (#10533). Commit 864ef4a072 sets ssi: 0 in that case; no CI ran on that commit because the branch conflicted with main. Commit f9336d119a adds person-income Cases 14 and 15, rewords two comments in mo_liheap.yaml and lists the code files in the five candidates; the head, dd863edc6c, merges main at a24c9cb55d. CI has not yet run on the head. The two new cases were hand-derived and not run locally; CI validates them and the Case 6 fix.

  • YAML tests whose expected values come from external sources
  • make format (ruff on changed Python)
  • Changelog fragment in changelog.d/
  • 64 YAML tests pass
  • CI passes

axiom: TheAxiomFoundation/rulespec-us#1899 queued

🤖 Generated with Claude Code

hua7450 and others added 2 commits October 23, 2026 18:11
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	policyengine_us/programs.yaml
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DTrim99

DTrim99 commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Add Missouri LIHEAP Energy Assistance

PR #10438 (author: hua7450), head 566343d074. This is a first review. The PR adds Missouri's LIHEAP Energy Assistance (EA), the one-time regular heating payment from DSS / Family Support Division, for FFY2026 and FFY2027. It includes:

  • 13 parameters and 7 variables;
  • 8 test files (52 cases);
  • a programs.yaml entry (coverage line and state list);
  • two source_conflicts/candidates.yaml entries;
  • a changelog fragment.

The PR has no comments yet and touches no partner test file.

Path prefixes:

  • P/ = policyengine_us/parameters/gov/states/mo/dss/liheap/
  • V/ = policyengine_us/variables/gov/states/mo/dss/liheap/
  • T/ = policyengine_us/tests/policy/baseline/gov/states/mo/dss/liheap/

Sources read for this review:

Summary

What is right. The published FFY2026 and FFY2027 values are encoded correctly:

There are no critical findings and five should-address items:

  • S1. Source hierarchy. The income test and payment table follow the agency documents over 13 CSR 40-19.020(6)(D) and (14), while the renter rate follows the rule over those same documents. The candidate entries leave out RSMo 660.100.2, mark follows: unknown and list no PR, and the renter-rate conflict has no entry of its own.
    • Example: a family of four with $50,000 (155% of the guideline) gets $326 at the head and $0 under the rule.
  • S2. Missouri pays the fuel the household selects. Missouri lets a household with more than one energy account choose which fuel EA pays, electric included, but the model pays only the primary heating fuel. It also bars a subsidized tenant with heat in the rent who pays an electric supplier.
    • Kerosene households with an electric bill get $153 instead of $318.
    • Such tenants get $0 instead of $318.
  • S3. The SMI premium is not deducted for railroad beneficiaries, although the manual allows it "to persons receiving SS and RRB". A railroad retiree with $37,000 gets $0 instead of $326.
  • S4. Rental and farm-rent losses offset wages. The unearned sum has no per-source floor. A worker with $45,000 of wages and a $5,000 rental loss gets $326 instead of $0.
  • S5. Untested sources. 10 of the 20 income-source entries and 5 of the 8 medical-deduction benefit entries never take a positive value in any test.

There are also five suggestions:

  • G1: cite the manual's page 44 for the citizenship proxy.
  • G2: fix the plan citation for interest and dividends, and record the plan-vs-manual choices.
  • G3: read the take-up-gated tanf.
  • G4: decide what an UNSPECIFIED heat-in-rent renter receives.
  • G5: add Wayback captures for the overwritten application URL.

Status.

  • CI: 27 of 27 pass. All 52 Missouri cases passed in states-shard-2.
  • Branch: 3 commits ahead of main and 41 behind; main moved during this review with the Iowa merge, Add Iowa LIHEAP regular heating assistance #10292. It merges cleanly.
  • Siblings: this PR adds MO to the LIHEAP coverage line, so it conflicts with 15 open LIHEAP siblings in programs.yaml only (branch note).

Critical (must fix)

None.

Should address

S1. The income test and payment table follow the agency documents over 13 CSR 40-19.020(6)(D) and (14), while the renter rate follows the rule, and the backlog entries do not record the conflicts fully

  • Where:
    • P/eligibility/smi_rate.yaml sets 0.6, and P/amount.yaml sets one amount per fuel.
    • P/rent_share.yaml sets 0.08.
    • source_conflicts/candidates.yaml:3421-3490 holds us-mo:liheap-110-percent-poverty-floor and us-mo:liheap-rule-income-ranges-and-payments.
  • The rule.
    • (6)(D): "Each household must meet the specified income guidelines based on their household size, as established in section (14) of this rule."
    • (14): "EA payments are determined by household size, income range, and fuel source, using metrics established by the division. Monthly Income Federal Poverty ranges are A: 0-25% of the Federal Poverty Level ... E: 101-125% and F: 126-135%." The table follows: natural gas pays $296 in range A down to $203 in range F (PDF page 4).
    • (13): "no more than eight percent (8%) of their annual rental charge not to exceed the maximum EA benefit payment" (page 4).
  • The agency documents.
    • Plan 2.1 sets "State Median Income 60.00%" for all sizes, and 2.3 sets "16% of their annual rent, not to exceed the maximum allowed EA benefit" (p. 8; the same in FFY2024, FFY2025 and the FFY2027 draft).
    • Appendix K FFY26 and the 8-2026 application print 60% SMI limits and flat fuel amounts.
    • Manual page 63 gives "16% of their annual rent not to exceed the maximum EA benefit payment for their household size, income, and energy source" (p. 63).
  • The statute the candidate omits. RSMo 660.100.2 defines a "qualified individual household" as one "(1) ... whose combined household income is less than or equal to one hundred and fifty percent of the current federal poverty level or sixty percent of the state median income for the relevant household; and (2) ... also determined to be eligible for assistance under such program and related state programs of the Missouri department of social services." So the statute permits either limit and defers to the program's rules. It does not settle the rule-vs-agency question, but it is the statute, and the candidate says "No statute is supplied".
  • The project rule. CONTRIBUTING "Source conflicts" sets three principles:
    • "Statute first. When a statute conflicts with a form, instructions, a manual or stated agency practice, follow the statute";
    • "Regulations are law";
    • "The agency breaks true ties" only "where the text and the canons don't settle a reading".
  • What the PR does:
    • follows the agency documents over the rule for the income test and the payment table;
    • follows the rule over the same documents for the renter rate;
    • follows the manual's fuel-specific reading of the rule's renter cap, which is a reasonable agency tie-break.
    • The body and Axiom interpretation 2 say "Unlike interpretation 1, the rule governs here" but give no reason for the difference.
  • The backlog entries:
    • The rule-ranges entry has follows: unknown, although its note says "PE-US follows the plan, Appendix K, manual and application". It also has prs: [].
    • The renter-rate conflict appears only in that entry's note, with no entry of its own.
    • The floor entry says "no PR is open yet" and also has prs: [].
  • Effect (hand-traced, FFY2026, natural gas; the 2025 guideline for four is $32,150, so 135% is $43,402.50):
    • (a) Family of four with $50,000 of countable income (155%). Head: $50,000 ≤ $65,532, so $326. Rule: above range F, so $0.
    • (b) The PR's own example, $39,936 (124%). Head $326; the rule's range E pays $221.
    • (c) Renter rate, T/mo_liheap.yaml Case 2 (rent $6,000, propane). Head: 0.08 × 6,000 = $480. Agency: min(960, 495) = $495.
  • Severity: should. Authoritative sources conflict (the wave rates the state plan against the regulation should), and the PR applies the hierarchy one way for (14) and the other way for (13). It matches Add New Mexico LIHEAP regular heating assistance #10431 S2, where guidance was followed over the rule.
  • Fix: I recommend (b).
    • (a) Follow the rule: the (14) ranges, the range-based table, and the 8% rate with the manual's cap.
    • (b) Keep the agency values pending a ruling.
      • Add RSMo 660.100.2 to the candidate as the statute.
      • Mark the entry as following the agency documents against the rule, set state: open and list this PR.
      • Take the question to the project lead. The argument for the agency is (14)'s "using metrics established by the division".
      • Give the renter rate its own candidate: the rule's 8%, which is followed; the agency's 16%; and RSMo 660.115.2's 8%-or-$100 cap on state-fund payments.
      • In the body, either state why (14) delegates to the division and (13) does not, or apply the same source to both.
    • Either way, fix the floor entry's note and prs, and update Axiom interpretations 1–3.

S2. Missouri pays the fuel the household selects, electric included, but the model pays only the primary heating fuel and bars subsidized tenants who pay an electric supplier

  • Where:
    • V/mo_liheap.py:24-25 pays p.amount[heating_type].
    • V/eligibility/mo_liheap_eligible.py:36-42 accepts a subsidized household only through has_heating_expense. That is the primary fuel's bill, and it is always false with heat in the rent.
  • Sources:
    • Manual, EA (file p. 51): "DSS will only pay EA as a one-time payment towards one (1) fuel type per program year. ... If the household has multiple fuel types, they can choose the fuel type to pay when they apply. The EA payment will go towards the heating or cooling source that the applicant indicates on the LIHEAP Application under 'Fuel Source For My Home'." Electric is among the seven EA fuel types.
    • Application (p. 3): "DSS will only pay EA benefits for the fuel type selected under this section." Page 4 then asks for an "Additional Fuel Source For My Home".
    • Plan 2.5, Other (p. 9): "Households receiving LIHEAP assistance are able to select the fuel type for which they need assistance, including electric. While this is considered a heating program, payments made toward electric accounts may be utilized for cooling costs October through May."
    • Rule. (2)(E) defines the fuel source as "the fuel consumed in the operation of an appliance manufactured and used for the purpose of heating or cooling a household". (7)(D) bars subsidized housing "unless they are paying a home energy supplier or are billed by the landlord/housing authority for any out-of-pocket heating/cooling costs".
    • Plan 2.3 (p. 8): subsidized renters are "Only eligible if the client is paying an energy supplier out-of-pocket."
  • Effect (hand-traced from the code):
    • (a) Kerosene with an electric bill. Take T/mo_liheap.yaml Case 6 (FFY2027, kerosene with fuel_oil_expense $1,500) and add a $1,200 pre_subsidy_electricity_expense. Head: $153. Selecting Electric: $318.
    • (b) Purchased wood with an electric bill. $219 against $318.
    • (c) Subsidized tenant (2026, age 33, countable $14,000, receives_housing_assistance, gas heat in the rent, $1,080 electric bill). Head: has_heating_expense is false and the unit is subsidized, so $0. Rule and plan: the tenant pays an energy supplier, so it is eligible and selects Electric, $318.
    • (d) Unsubsidized renter with heat in the rent, rent $3,000 a year, who pays an electric bill. Head: min(240, 326) = $240. The renter can choose Electric instead, $318.
  • Tests: eligibility Cases 8–9 have no electric bill, so they stay correct after a fix.
  • Severity: should. This is a wrong result in a claimed year with existing inputs, which the wave rates should (Add New Mexico LIHEAP regular heating assistance #10431 S1, Add Rhode Island LIHEAP regular heating assistance #9972 S2).
  • Fix:
    • Pay the largest amount among the fuels for which the household has its own positive bill: at least the primary fuel, plus Electric when pre_subsidy_electricity_expense is positive.
    • Treat a positive own electric bill as paying a home energy supplier for (7)(D).
    • For heat-in-rent renters with an electric bill, take the larger of the renter amount and the electric amount.
    • Decide and document whether a home with no heating but an electric bill qualifies. The manual's "heating or cooling source" and (2)(E) point to yes; (3)(A)'s "home heating" points to no.
    • Add cases (a)–(c), and update Axiom interpretations 14 and 16.

S3. The SMI (Part B) premium is deducted only from Social Security, although the manual allows it for railroad beneficiaries and for the full premium paid

  • Where:
    • V/income/mo_liheap_countable_income_person.py:36-42 nets medicare_part_b_premium from social_security alone, floored at zero per person.
    • railroad_benefits counts gross (P/income/unearned_sources.yaml:27).
    • The variable documentation, the body ("Part B premium paid without Social Security (e.g. Railroad Retirement) ... Not deducted") and Axiom interpretation 10 accept this.
  • Source: manual, "Income Deductions", SMI Premium (p. 36): "SMI is an additional health cost that is available to persons receiving SS and RRB. ... Allowable for all household members of a B or C case who are paying this premium." It sits beside the 20% deduction and child support as a deduction, not as a netting of the SSA check. Plan 1.9 ("Excluding MediCare deduction", p. 6) agrees for Social Security.
  • Effect (hand-traced, 2026):
    • (a) Single railroad retiree aged 70. railroad_benefits $37,000, Part B $2,434.80, natural gas.
      • Head: 37,000 − 1,200 (medical deduction) = 35,800, above $34,080, so $0.
      • Manual: 37,000 − 2,434.80 − 1,200 = 33,365.20, so eligible and $326.
    • (b) A premium larger than the check. T/income/mo_liheap_countable_income_person.yaml Case 5, person 2 (Social Security $1,800, pension $6,000, Part B $2,434.80). Head: max(1,800 − 2,434.80, 0) + 6,000 = $6,000. Manual: the member pays the full premium, so 7,800 − 2,434.80 = $5,365.20.
  • Severity: should, as in the wave's Medicare precedent (Add Virginia EAP Fuel Assistance (LIHEAP regular heating) #10303, Add Arizona LIHEAP Standard benefit #10293, Add Iowa LIHEAP regular heating assistance #10292): income counted gross where the state deducts the premium. The input exists and is enrollee-only and net of MSP.
  • Fix:
    • Deduct medicare_part_b_premium for members with social_security or railroad_benefits, from that member's countable income, floored at zero per member.
    • Re-pin Case 5 and add a railroad case.
    • Update the documentation, the body row and Axiom interpretation 10.

S4. Rental and farm-rent losses offset wages, because the unearned sum has no per-source floor

  • Where:
    • V/income/mo_liheap_countable_income_person.py:35 sums p.unearned_sources, including rental_income and farm_rent_income (P/income/unearned_sources.yaml:30-31), as reported.
    • The earned loop floors each source (line 28), and P/income/earned_sources.yaml:2-3 says "business and farm losses do not offset other income".
  • Sources:
    • Manual (p. 32): "Schedule 1, Line 3 – Business income (don't include if a loss)" and "Schedule 1, Line 6 – Farm income (don't include if a loss)". For "Roomer-Boarder / Rent Income", the documents are "Rent receipts. Contracts. Signed and dated statement from the tenant or cancelled checks", and the manual says "Prorate income from the rent of land and/or buildings not received monthly to a monthly amount". Page 34 adds that CRP payments count "as rental income".
    • Application (p. 5): it asks for "Rent Received from Land or Buildings".
    • Rule. (2)(H) defines income as "monthly revenue obtained".
    • Rent received cannot be negative.
  • Effect (2026, single adult aged 40, wages $45,000, so $36,000 after the 20% deduction, a rental_income of −$5,000 and natural gas):
    • Head: 36,000 − 5,000 = 31,000 ≤ 34,080, so $326.
    • With the floor: 36,000 > 34,080, so $0.
  • Severity: should, as in the wave's losses precedent (rental, farm-rent or self-employment losses offsetting wages).
  • Fix: floor rental_income and farm_rent_income (or every unearned source) per person, as the earned loop does. Add a case with wages and a rental loss that expects the wages alone.

S5. 10 of the 20 income-source entries and 5 of the 8 medical-deduction benefit entries have no test that would fail if they were dropped

  • Where: P/income/earned_sources.yaml (5 entries), P/income/unearned_sources.yaml (14), P/income/adult_unearned_sources.yaml (1) and P/income/disability_benefit_sources.yaml (8).
  • Untested. No case gives these a positive value:
    • Earned: sstb_self_employment_income and partnership_s_corp_income. farm_operations_income is set only to −$3,000 in person Case 4, which floors to 0, so dropping it changes nothing.
    • Unearned:
      • strike_benefits, railroad_benefits, rental_income, farm_rent_income, alimony_income and workers_compensation;
      • mo_ssp, which is set only to 0 in medical-deduction Cases 4 and 9.
    • Medical-deduction benefits: social_security_dependents, social_security_survivors, veterans_benefits, railroad_benefits and mo_ssp. No medical-deduction case uses them. The deduction tests > 0 over the list, so each entry needs its own case.
  • Tested:
    • Income: wages, self-employment, reported unemployment (through total_unemployment_compensation), SSI, pensions, military retirement pay, child support received, VA benefits and financial_assistance, plus Social Security, adult-only disability_benefits and mo_tanf.
    • Medical deduction: ssi, social_security_disability and medicaid.
    • Branches: every eligibility and benefit branch is pinned, including the floor, resources, size 0, coal, both subsidy inputs, the renter cap on both sides and the earned floor.
  • Severity: should, as in Add New Mexico LIHEAP regular heating assistance #10431 S3, Add Louisiana LIHEAP regular heating assistance #10429 S1, Add Virginia EAP Fuel Assistance (LIHEAP regular heating) #10303 S4 and Add Alabama LIHEAP regular heating assistance #9967 S3 (the mutation rule).
  • Fix:
    • Add one countable-income case that gives every untested source a distinct positive amount for an adult, including mo_ssp, reported unemployment and military_retirement_pay_survivors. Re-pin farm_operations_income with a profit.
    • Add one medical-deduction case per untested benefit with is_disabled.
    • S4's rental-loss case pins the new floor.

Suggestions

G1. Cite manual page 44 for the citizenship proxy.

  • The proxy. mo_liheap_household_size counts is_citizen_or_legal_immigrant. The rule ((6)(A), (8)(A)) and manual pages 42–43 say "citizen of the United States or a Legal Permanent Resident".
  • Page 44 supports it. Page 44 accepts as documents of "legal permanent residence status" an I-94 annotated "refugee", "parolee", "paroled", "asylum" or 204, 207, 208, 212(d)(5), 243(h) or 244. It also says COFA citizens "are included in the definition of qualified non-citizens". So the proxy matches practice, as Axiom interpretation 6 says.
  • What omits it. The body row ("Citizens and legal permanent residents") and the variable's reference (pages 40–43) omit page 44. Household-size Case 2 counts a refugee on page 44's authority.
  • Fix: cite page 44 in the variable and the body, and record the rule-vs-manual reading as a candidate.

G2. Fix the plan citation for interest and dividends, and record the plan-vs-manual income choices.

  • The conflict. Plan 1.9 checks "Interest, dividends, or royalties" and leaves "Cash gifts" unchecked (p. 6, rendered; the same in the FFY2027 draft). The manual excludes "Interest-Dividend Income" (p. 38) and counts contributions (p. 33).
  • The PR's choice. It follows the manual, which is the operative document (Axiom interpretation 9).
  • The citation. The body's "Not counted" row cites "plan 1.9" for interest and dividends, but the plan counts them.
  • Fix:

G3. Read the take-up-gated tanf, as every merged LIHEAP state does.

  • The difference. V/income/mo_liheap_countable_income.py:23 adds mo_tanf. AL, AR, FL, GA, KY, NJ, OK, OR, RI and SD read tanf, which is gated by takes_up_tanf_if_eligible. In microsimulation, mo_tanf counts TANF for eligible households that do not take it up.
  • Effect. mo_liheap does not change, since no TANF-eligible unit is near the 60% SMI limit, but mo_liheap_countable_income does.
  • Fix: read tanf and re-pin countable Case 1 with a tanf input.

G4. Decide what a heat-in-rent renter with an UNSPECIFIED fuel receives.

  • The model. T/mo_liheap.yaml Case 4 pins eligible and $0.
  • Main's contract. heating_type documents UNSPECIFIED as "the right value when heat is included in rent and the tenant does not know the building's fuel".
  • The rule. (13) caps the renter payment at "the maximum EA benefit payment" without naming a fuel. That supports the schedule maximum ($495) for an unknown fuel, which with 8% of a $9,000 rent gives $495 rather than $0.
  • Tracking. Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754 tracks the default.
  • Fix: cap at the schedule maximum when the fuel is UNSPECIFIED, or keep $0 and say in the body that such an eligible renter receives nothing.

G5. Add Wayback captures for anchors at risk of revision drift.

  • The application. It sits at a wp-content/uploads/2022/02/ path that DSS overwrites; it now holds the 8-2026 edition, the only printed FFY2027 source. Once the 8-2027 edition replaces it, the FFY2027 anchors in amount, smi_rate, adult_age and unearned_sources will drift.
  • The rule. Its URL is the "current" copy.
  • Fix: add a Wayback capture beside each.

Verified correct

Values against sources:

  • Income limit (plan 2.1, 60% SMI for all sizes, p. 8; also FFY2024, FFY2025 and the FFY2027 draft):
    • FFY2026. From SMI 109,229: floor(0.6 × 109,229) = 65,537. Times 0.52, 0.68, 0.84, 1.00, 1.16, 1.32 and +0.03 a member from 7, floored, then / 12 rounded half up, gives 2,840, 3,714, 4,588, 5,461, 6,335, 7,209, 7,373, 7,537, 7,701, 7,864, 8,028, 8,192, 8,356, 8,520, 8,684, 8,847, 9,011, 9,175, 9,339 and 9,503. All 20 match Appendix K FFY26.
    • FFY2027. From 113,137 (67,882): 2,942, 3,847, 4,752, 5,657, 6,562, 7,467, 7,637, 7,806, 7,976 and 8,146. All 10 match application page 1.
    • Sizes 12–15 in FFY2027. These run $1–2 a month below the application's "+$170" shorthand (8,485 / 8,655 / 8,825 / 8,994 against 8,486 / 8,656 / 8,826 / 8,996). The choice is documented (interpretation 4).
    • The comparison. "Maximum" is <=.
  • Federal 110% floor (42 U.S.C. 8624(b)(2)(B); gov.hhs.liheap.fpg_floor; fpg_year_lag 1, per IM 2025-02 Attachment 2 "mandatory use in FY26"):
    • FFY2026: size 15 has a limit of 104,208 against a floor of 101,915; size 16 has a floor of 107,965 against a limit of 106,164.
    • FFY2027: size 16 has a floor of 111,276 against a limit of 109,968.
    • So the floor binds from size 16 in both years, as the body says.
  • Fuel amounts. Natural gas 326, tank propane 495, electric 318, fuel oil 326, wood 219, kerosene 153 and cylinder propane 177 match:
    • Appendix K FFY26 (parsed);
    • manual page 129 (labelled "FFY 24", with the same amounts);
    • application page 1;
    • plan 2.6's $153–$495 (p. 9), which is the same in FFY2024, FFY2025 and the FFY2027 draft.
    • SOLAR at the electric amount is a documented inference. COAL is denied (Appendix E code O, "Heats with Coal", p. 122).
  • Eligibility:
    • resources "may not exceed $3,000" (rule (6)(B); plan 2.3; manual p. 40) on spm_unit_cash_assets;
    • subsidized housing with heat in the rent is barred (rule (7)(D); code M), apart from S2;
    • unsubsidized heat in the rent is eligible (rule (2)(M), (13));
    • a size of 0 is ineligible;
    • no categorical eligibility (plan 1.4 "No", p. 5, rendered).
  • Countable income (plan 1.8 gross, p. 5; manual pp. 30–38; application pp. 4–5):
    • the 20% deduction for wages and self-employment (p. 36);
    • per-source floors on business and farm income ("don't include if a loss", p. 32);
    • minors' earned and disability income excluded while their "SSA income" counts (p. 38);
    • income of members left out as non-citizens counted (pp. 42–43);
    • child support paid "by any household member to someone not included in the LIHEAP household" deducted (p. 36);
    • interest, dividends, lump sums, capital gains, refunds, student income and loans excluded (pp. 37–38; G2 covers the plan citation);
    • Social Security net of Part B, per plan 1.9 "Excluding MediCare deduction" (rendered) and manual page 36. That is right for Social Security; S3 covers railroad benefits.
  • Medical deduction (rule (2)(C), (2)(D), (6)(D)1; manual pp. 36, 48):
    • $100 a month, once per household;
    • the applicant or spouse 65 or older, or "totally and permanently disabled or blind" and receiving a listed benefit;
    • the disability list matches the rule's nine benefits through documented stand-ins, and RSMo 660.100.2's "disabled" definition agrees.
  • Renter base. pre_subsidy_rent × 12 is "the regular monthly rental charge" (rule (13); manual p. 63).
  • Years:
    • 2026 and 2027 are verified.
    • Earlier years. The parameter start dates (2017-05-30, 2023-10-01, 2025-10-01) are documented, unclaimed backfills. The FFY2024 manual's different non-citizen and student rules are not reproduced for 2024–2025, and the body says earlier years are unverified estimates (NJ S3 pattern).

Hand-checked expectations (20). All are 2026 unless noted, with a size-1 limit of 34,080:

  • Eligibility Cases 1–2: 34,080 = 12 × 2,840 is eligible. 34,081 is ineligible (the floor is 17,215).
  • Eligibility Case 3 (size 16): limit 106,164; floor 1.1 × 98,150 = 107,965. 107,000 is eligible; 107,965 is not below the floor, so ineligible.
  • Eligibility Case 4 (2027, size 16): limit 109,968; floor 1.1 × 101,160 = 111,276. 110,500 is eligible.
  • Eligibility Cases 5–7: assets of 3,000 are eligible and 3,001 are not. An undocumented single adult has size 0, so ineligible.
  • Income-limit Case 1: 2,840, 5,461, 8,847 and 9,503 × 12 give 34,080, 65,532, 106,164 and 114,036.
  • Income-limit Case 2 (2027): 2,942 and 8,146 × 12 give 35,304 and 97,752.
  • Countable Case 2: 31,200 × 0.8 − 4,800 = 20,160.
  • Countable Case 3: 23,400 − 2,434.80 − 1,200 = 19,765.20.
  • Countable Case 4: max(4,992 − 6,000, 0) = 0.
  • Countable Case 5: 2 × 24,960 − 3,600 = 46,320, with size 1.
  • Person Case 4: max(24,000, 0) + max(−3,000, 0), times 0.8, = 19,200.
  • Person Case 5: 20,965.20 and 6,000 (head reading; S3 changes the second).
  • Person Case 6: 6,000 + 4,800 + 1,200 = 12,000; 24,000 + 6,000 = 30,000.
  • Person Case 9: 58,240 × 0.8 = 46,592, above 3,714 × 12 = 44,568, and the floor (23,265) does not reach it, so $0.
  • Person Case 10: 18,000, and for the minor, 9,600 (the minor's $1,440 is excluded).
  • Medical-deduction Cases 1, 2 and 7: 65 gives 1,200; 64 gives 0; a couple of 70 and 67 gives 1,200, not 2,400.
  • Integration Case 1: 24,960 + 14,976 = 39,936, at most 65,532, so natural gas $326.
  • Integration Case 2: 27,565.20 + 10,765.20 − 1,200 = 37,130.40, at most 44,568, so tank propane $495.
  • Integration Case 3 (2027): size 2; 24,960 + 20,800 = 45,760, at most 46,164; min(0.08 × 10,200 = 816, 326) = $326.
  • mo_liheap.yaml Cases 1–3 and 6:
    • The fuel vector is 326 / 495 / 318 / 326 / 219 / 153 / 318 / 0 / 0 / 0 / 0.
    • 0.08 × 6,000 = 480, below 495.
    • 0.08 × 9,000 = 720, capped at 326.
    • 2027: 41,600 × 0.8 = 33,280, at most 46,164, so kerosene $153.
  • Case count: 13 + 2 + 5 + 10 + 9 + 3 + 6 + 4 = 52, as the body says.

Batch checks:

Wiring and housekeeping:

  • Registry. The MO entry sits between MD and MS with verified_years: "2026-2027", and MO is added to the coverage line. test_programs_registry passed.
  • Aggregates. There is no aggregate wiring, which matches every merged LIHEAP state. Take-up is G3.
  • Gating. defined_for = StateCode.MO is on 6 variables, and mo_liheap is gated by mo_liheap_eligible.
  • Changelog. changelog.d/mo-liheap-wave1d.added.md is top level and typed added.
  • Descriptions (item 6): all 13 end "under the Low Income Home Energy Assistance Program." with nothing after it.
  • References (item 7):
    • Every anchor I opened shows its value: rule PDF pp. 2–4; plan pp. 5–9; draft FFY2027 plan pp. 5–9; FFY2024 and FFY2025 plans p. 8; manual pp. 32–34, 36, 38, 40, 48, 63, 122 and 129; application pp. 1, 4 and 5.
    • Multi-page titles use the quoted #page 32-34,38,40 form, and variables use # PDF pages comments.
  • Tests. The files are variable-named, every period is plain, and every case sends heating_type.

Checklist results (LIHEAP batch):

  1. State plan and manual: every published limit row and fuel amount matches. The rule's ranges and table are S1.
  2. Income definition: gross income and the 20% deduction are right. Railroad SMI is S3 and rental losses are S4.
  3. Backdating: pass for 2026–2027. Earlier years are documented, unclaimed backfills.
  4. Household and arrangement rules: fuel choice and subsidized tenants who pay electric are S2. Every other branch is tested.
  5. Tests: 52 cases, 20 hand-checked, all periods plain. Source coverage is S5.
  6. Descriptions: pass.
  7. References: pass (Wayback captures are G5).
  8. Wiring: pass (TANF take-up is G3).
  9. Axiom: valid (#1899).
  10. Branch and CI: a clean merge with main; 27 of 27 pass.
  11. Integrate modeled state unemployment insurance into income flows #9558: pass.
  12. IIJA / FY2027: not applicable / FY2027 encoded from the application.
  13. Mutation check: 10 income-source entries and 5 benefit entries are unpinned (S5).
  14. Document disjoint health premium inputs and correct consumer treatment #10046: not applicable.
  15. State-gated index defaults: safe.
  16. Wire military retirement pay and survivors into federal and state tax (#10181) #10182 / Count military SBP annuities in state benefit income lists #10317: pass.
  17. Merged state SSPs: pass (mo_ssp is counted).

Axiom line assessment

Valid: axiom: TheAxiomFoundation/rulespec-us#1899 queued.

  • State: OPEN, labelled pe-parity, created 2026-10-23 22:16 UTC.

  • It matches the head. It names head 2b62923ca0 and merge base 6a92d6a983. The current head 566343d074 adds one commit, which replaces sum() with a loop in mo_liheap_countable_income_person and changes no behavior. The issue lists the 13 parameters, the 7 output variables and the 52 cases by file. Its required outputs describe the head's code.

  • It is dispatch-ready in the rulespec-us#1467 format:

    • proposed us-mo/ module paths, with corpus status per source;
    • verbatim text of the rule, the manual, the plans, Appendix K and the application;
    • 13 companion tests derived from the sources, not from PE output;
    • 19 interpretations;
    • its blockers: sources missing from the corpus, an unchecked signed release, billed-run approval and no reviewed oracle revision.
  • Note. The fixes change these interpretations and outputs:

    • S1 changes interpretations 1–3;
    • S2 changes 14 and 16, plus the eligibility and benefit outputs;
    • S3 changes 10 and the per-person income output;
    • S4 changes 12–13;
    • S5 adds cases.

    Update the issue with the head that resolves them, and add RSMo 660.100 to its sources.

CI status

27 pass, 0 failed, none pending. Run 40047545306 tested head 566343d074 (pull_request, created 2026-10-24 00:57 UTC, after #10287 and the 2026-10-21 cutoff). I re-checked gh pr checks 10438 at the end of this review.

  • Missouri tests. States-shard-2, batch 7/14 (states/mo), ran all 8 files: 13 + 2 + 5 + 10 + 9 + 3 + 6 + 4 = 52 passed.
  • Guards (Rest jobs):
    • the four test_unemployment_compensation_readers.py tests;
    • the four test_military_retirement_readers.py tests;
    • the test_source_conflicts.py tests, including the ratchet and the four candidate checks;
    • test_members_are_state_gated;
    • test_programs_registry.
  • Also passing: Microsimulation, Household API Partners, PR validation, Package and compatibility, Quick Feedback, and every baseline and contrib shard.
  • Known main-caused failure: not applicable.
  • Locally: I ran no tests or model probes. The S1–S4 examples are hand traces of the code at the head. The only local scripts were the static comment-pattern scan, the Appendix K parse and the limit and floor arithmetic.

Branch status

0 critical, 5 should and 5 suggestions are open at this head.

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.

The tables check out (all FFY26 and FFY27 limit rows, the 7 fuel amounts, the 110% floor, the 20% and medical deductions, mo_ssp counted), the guards pass, and 20 expectations hand-check. Five should-address items: (1) the income test and table follow the agency (60% SMI, flat amounts) over 13 CSR's 135% FPG ranges, but the renter rate follows the rule (8% vs 16%), and the candidate omits RSMo 660.100.2 and the rent rate; (2) Missouri pays the fuel the household selects, electric included (kerosene plus electric: $153 vs $318); (3) the SMI premium isn't deducted from railroad benefits ($0 vs $326); (4) a rental loss offsets wages; (5) 10 of 20 income sources and 5 of 8 medical-deduction entries are unpinned. Five suggestions. Full report in the comment above.

hua7450 and others added 2 commits October 28, 2026 20:10
Apply DTrim99's round 1 review: tests that pin every income source, the
rule and documentation fixes, and source-conflict candidates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…into mo-liheap-wave1d

# Conflicts:
#	policyengine_us/programs.yaml
@hua7450

hua7450 commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, David. Round 1 is in c7f6fcef6e; 8c016379ed merges current main.

  • S1, rule vs agency values. The user's 2026-10-22 ruling keeps the operating 60% SMI limits and flat fuel amounts, because (14) sets payments "using metrics established by the division". source_conflicts/candidates.yaml now records it fully:
    • us-mo:liheap-rule-income-ranges-and-payments adds RSMo 660.100.2 as the statute (150% of the poverty level or 60% SMI, plus eligibility under LIHEAP, so both readings fall within it). It is now follows: conflicting-source, state: ruled, lists this PR and drops the renter sentence.
    • New us-mo:liheap-renter-rent-share records the rule's 8%, the manual and plans' 16% and RSMo 660.115.2's lesser of 8% or $100, with follows: statute and state: ruled. The code keeps 8% capped at the fuel amount. It reads 660.115.2 as limited to Utilicare stabilization-fund payments (660.115.1), and EA is paid from federal funds.
    • us-mo:liheap-110-percent-poverty-floor lists this PR, is state: ruled, and no longer says no PR is open.
    • The PR body now says why the two sections differ: (14) delegates the payment metrics to the division, while (13) sets a fixed ceiling without that delegation, which 16% exceeds. Your examples are unchanged: (a) $326, (b) $326 against the rule's $221, (c) mo_liheap.yaml Case 2 stays $480.
  • S2, fuel selection. mo_liheap.py now pays the largest of the own-fuel amount, the unsubsidized renter share and the $318 electric amount. The electric amount applies when pre_subsidy_electricity_expense is positive and heating_type is neither NONE nor UNSPECIFIED. eligibility/mo_liheap_eligible.py treats that electric account as paying a home energy supplier under (7)(D), subsidized or not. New cases in mo_liheap.yaml:
    • Case 7 (each with a $1,200 electric bill): kerosene $318 (was $153), wood $318 (was $219), other fuel $318, natural gas stays $326. NONE and UNSPECIFIED stay ineligible.
    • Case 8, your subsidized tenant with gas heat in the rent and a $1,080 electric bill: eligible, $318 (was ineligible, $0).
    • Case 9, a renter with $3,000 of rent and a $900 electric bill: $318 (was the $240 renter share).
    • Case 10, a heat-in-rent renter with an unspecified fuel and a $1,200 electric bill: eligible, $0.
    • On a home with no heating: NONE is not paid, because (3)(A) pays costs "incurred for home heating". amount.yaml's description now says "for the selected fuel".
  • S3, SMI premium. income/mo_liheap_countable_income_person.py deducts the full medicare_part_b_premium of a member with social_security or railroad_benefits from that member's countable income, floored at zero per member. New income/mo_liheap_countable_income.yaml Case 6 is your railroad retiree aged 70: $37,000 - $2,434.80 = $34,565.20, less $1,200 = $33,365.20, eligible, $326 (was $0). Person-income Case 5's second member is re-pinned to $5,365.20 (was $6,000). The documentation, income/unearned_sources.yaml comment and PR body no longer list RRB Part B as not modeled.
  • S4, rental losses. The same variable floors each unearned source per person in a loop over add(), so monthly SSI and mo_ssp keep their path. New person-income Case 11: $45,000 of wages and a $5,000 rental loss count $36,000, ineligible, $0 (was $326).
  • S5, untested entries.
    • Person-income Case 12 gives each earned source a distinct amount ($16,000, $1,000, $2,000, $4,000 farm profit, $8,000) for $24,800.
    • Case 13 pins strike benefits, rent, farm rent and alimony ($9,000), and railroad retirement, workers' compensation, military_retirement_pay_survivors and mo_ssp ($17,880).
    • income/mo_liheap_medical_deduction.yaml Case 10 gives five disabled 50-year-olds one benefit each (Social Security dependents and survivors, veterans, railroad, mo_ssp): $1,200 each.
  • G1. mo_liheap_household_size.py cites manual pages 40-44 and names page 44's qualified noncitizens; the body row cites page 44. New candidate us-mo:liheap-qualified-noncitizen-members follows 8 U.S.C. 1611(a), 1612(b) and 1641(b). It stays state: unreviewed, because reading 1612(b) as barring a LIHEAP limit is an inference.
  • G2. The body's "Not counted" row no longer cites plan 1.9. It says the plan checks interest, dividends and royalties while the manual excludes them (page 38) and counts contributions (page 33). New candidate us-mo:liheap-interest-dividends-and-contributions records the manual-over-plan choice (gap-fill, ruled 2026-10-06).
  • G3. income/mo_liheap_countable_income.py reads tanf. Countable-income Case 1 now inputs tanf ($5,904 unchanged). New Case 7 sets takes_up_tanf_if_eligible to false with $3,504 of mo_tanf and counts $2,400; reading mo_tanf would give $5,904.
  • G4. Declined: the renter with heat in the rent and an UNSPECIFIED fuel stays eligible for $0 (Cases 4 and 10). Callers must send heating_type, and Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754 tracks the default. The PR body states it.
  • G5. Every 13 CSR 40-19 href now points to the 2025-06-03 Wayback capture, which is byte-identical to the cited posting: 7 parameter files, 5 variables and the candidates, with each page anchor kept. The application keeps its live URL, because the newest capture holds the 9-2025 edition, not the 8-2026 edition the code cites.

The PR body and TheAxiomFoundation/rulespec-us#1899 now describe this head: RSMo 660.100, fuel selection, the SMI premium for SS or RRB, per-source floors, tanf, the five candidates and 62 cases.

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

The model's SSI income list does not count railroad retirement annuities,
so the 70-year-old annuitant otherwise received $11,928 of SSI.

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

DTrim99 commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 2): Add Missouri LIHEAP Energy Assistance

PR #10438 (author: hua7450), head 864ef4a072. Round 1 (2026-10-26, head 566343d074) requested changes with 0 critical, 5 should and 5 suggestions. Since then the author pushed three commits:

  • c7f6fcef6e answers round 1;
  • 8c016379ed merges main at c55b806541, with one hand-resolved block (the LIHEAP coverage line);
  • 864ef4a072 sets ssi: 0 in countable-income Case 6. It fixes the only failure in the last CI run.

The PR now has 62 cases in 8 files. It touches no partner test file.

Path prefixes:

  • P/ = policyengine_us/parameters/gov/states/mo/dss/liheap/
  • V/ = policyengine_us/variables/gov/states/mo/dss/liheap/
  • T/ = policyengine_us/tests/policy/baseline/gov/states/mo/dss/liheap/

Summary

All five round-1 should items are addressed, and S1–S5 are fixed with tests or recorded rulings:

  • S1 (sources). The income test and payment table keep the agency values under a recorded 2026-10-22 ruling. The candidate now has RSMo 660.100.2, a reason and this PR. The renter rate has its own ruled candidate.
  • S2 (fuel choice). The household can now take the $318 electric amount, and a subsidized tenant who pays an electric supplier is eligible.
  • S3 (railroad SMI). The SMI (Part B) premium is now deducted for Social Security and railroad beneficiaries, in full.
  • S4 (rental losses). Each unearned source is floored at zero.
  • S5 (untested sources). Every untested income source and benefit entry now has a test that would fail without it.

Suggestions. G1–G3 are fixed. G4 is declined with a reason I accept. G5 is fixed for the rule; the application's anchor is still live.

One new should item comes from the fixes (N2). N1 is a suggestion:

  • N1 (suggestion). Unspecified fuel with heat in the rent. The new electric-account branch treats heating_type UNSPECIFIED as "does not heat the home", even when heat is included in the rent. Main documents UNSPECIFIED as the right value for that case: the home has heat and only the fuel is unknown.
    • A renter with heat in the rent and a $1,200 electric bill gets $0 (Case 10). The same renter reporting natural gas gets $326.
    • A subsidized tenant who pays an electric supplier is ineligible. The same tenant reporting natural gas gets $318 (Case 8).
  • N2. The SMI deduction's two guards have no test. Either guard can be deleted with no test failing:
    • the "receives Social Security or railroad benefits" gate;
    • the per-member zero floor. The re-pin of person Case 5 removed the test that pinned it.

Two suggestions are open: N1 and G5 (the application's anchor).

Status.

  • CI: no CI has run on the head. The PR conflicts with main, so GitHub starts no pull_request run.
    • The last run, on 8c016379ed, failed only countable-income Case 6. The head fixes that, and I ran that file locally at the head: 7 of 7 pass.
  • Branch: the hand-resolved coverage line is correct. Since then, AK Add Alaska LIHEAP regular heating assistance #10341 has added AK on main, so the coverage line conflicts again (branch note).
  • Axiom: rulespec-us#1899 matches the head.

Prior findings

Finding Status Evidence
S1. Income test and payment table follow the agency over 13 CSR 40-19.020(6)(D) and (14); renter rate follows the rule; candidates incomplete Fixed (values kept under a recorded ruling) source_conflicts/candidates.yaml:3908-3950: us-mo:liheap-rule-income-ranges-and-payments adds RSMo 660.100.2 as the statute. It is follows: conflicting-source and state: ruled ("Ruled 2026-10-22 ... (14) sets payments using metrics established by the division"), and it lists this PR. New us-mo:liheap-renter-rent-share (3868-3903) records the rule's 8%, the manual and plans' 16%, and RSMo 660.115.2's lesser of 8% or $100, with state: ruled. Its reason holds: 660.115.1-.2 govern payments "from the utilicare stabilization fund", and EA is federally funded. The floor entry (3768-3796) lists this PR and drops "no PR is open yet". The body now says why (14) delegates to the division while (13) does not. Axiom interpretations 1-3 are updated. The wave accepts state: ruled with a reason for a manual-over-rule choice (AK r2 S1). The values are unchanged: (a) $326, (b) $326 against the rule's $221, (c) $480.
S2. Only the primary fuel is paid; subsidized tenants who pay an electric supplier are barred Fixed, except for UNSPECIFIED with heat in the rent (N1) V/mo_liheap.py:28-45 returns the largest of the own-fuel amount (gated by has_heating_expense), the unsubsidized renter share and $318 for an electric bill. V/eligibility/mo_liheap_eligible.py:39-52 adds pays_electric_supplier. T/mo_liheap.yaml: Case 7 gives kerosene 153→318, wood 219→318, other 0→318 and natural gas 326, with NONE and UNSPECIFIED ineligible. Case 8 is the round-1 subsidized tenant, now eligible for $318. Case 9 moves from the $240 renter share to $318. Mutation: dropping pays_electric_supplier fails Case 8. Dropping ~subsidized from the renter share makes Case 8 $326. Ungating the own-fuel amount makes Cases 8-9 $326. Dropping the electric bill condition fails Case 1. NONE is not paid, as documented under rule (3)(A).
S3. SMI premium deducted only from Social Security Fixed (its guards are unpinned: N2) V/income/mo_liheap_countable_income_person.py:43-50 deducts the full medicare_part_b_premium of a member with social_security or railroad_benefits. Countable Case 6: 37,000 − 2,434.80 = 34,565.20; less 1,200 = 33,365.20 ≤ 34,080, so $326. Without the railroad branch it would be 35,800, so $0. Person Case 5, person 2: 1,800 + 6,000 − 2,434.80 = 5,365.20 (was 6,000).
S4. Rental and farm-rent losses offset wages Fixed V/income/mo_liheap_countable_income_person.py:38-39 floors each unearned source. Person Case 11: 45,000 × 0.8 = 36,000 > 34,080 and above the 17,215 floor, so ineligible and $0. Netting the loss gives 31,000 and $326.
S5. 10 income entries and 5 benefit entries untested Fixed Person Case 12: 16,000 + 1,000 + 2,000 + 4,000 (farm profit) + 8,000 = 31,000 × 0.8 = 24,800. Person Case 13: 1,200 + 1,800 + 2,400 + 3,600 = 9,000, and 9,600 + 4,800 + 2,400 + 1,080 = 17,880 (no Part B at 60). Medical Case 10: five disabled 50-year-olds, each with one benefit and with ssi, social_security_disability and medicaid set to 0: 1,200 each. Every amount is distinct, so dropping any of the 15 entries changes a pinned value.
G1. Cite manual page 44 for the citizenship proxy Fixed V/mo_liheap_household_size.py cites pages 40-44 and names page 44's qualified noncitizens; so does the body row. New candidate us-mo:liheap-qualified-noncitizen-members (3831-3863) is unreviewed and flags its reading of 8 U.S.C. 1612(b) as an inference.
G2. Wrong plan citation for interest and dividends; record the plan-vs-manual choice Fixed The body row is corrected. New candidate us-mo:liheap-interest-dividends-and-contributions (3799-3828) is gap-fill and ruled, and lists this PR.
G3. Read the take-up-gated tanf Fixed V/income/mo_liheap_countable_income.py:23 reads tanf. Countable Case 1 now inputs tanf: 3,504 + 2,400 = 5,904. New Case 7 has a 3,504 mo_tanf entitlement that is not taken up, so tanf = 0 and countable = 2,400. Reverting to mo_tanf gives 5,904.
G4. UNSPECIFIED renter with heat in the rent Declined, accepted T/mo_liheap.yaml Case 4 stays eligible at $0, and the body says so (#9754 tracks the default). The renter-share cap needs a fuel's amount, so $0 is a documented reading. N1 is the separate electric-account case, which needs no fuel amount.
G5. Wayback captures for anchors at risk of drift Partly fixed; the application stays open as a suggestion Every 13 CSR 40-19 href now uses capture 20250603190025. Its Wayback CDX digest (4Q675JWW…) equals the SHA-1 of the live PDF I downloaded, so the copies are byte-identical, as the author says. The application keeps its live URL, since the newest capture is the 9-2025 edition. See Suggestions.

New findings

Critical

None.

Should address

N2. The SMI premium deduction's two guards, the Social Security or railroad gate and the per-member zero floor, have no test that fails without them
  • Where: V/income/mo_liheap_countable_income_person.py:43-47 (the gate) and :50 (max_(gross - smi_premium, 0)).
  • No case reaches either guard:
    • Every case with a positive premium (person Case 5, countable Cases 3 and 6, integration Case 2) gives it to a member who has Social Security or railroad benefits and whose gross income exceeds it.
    • No other member aged 65 or older has computed countable income in the 62 cases.
    • Round 1's person Case 5 pinned the old floor (person 2: max(1,800 − 2,434.80, 0)). The re-pin to 5,365.20 no longer reaches the new one.
  • Effect. Both cases were confirmed by local probes at the head:
    • (a) The gate. A single 70-year-old with only a $36,500 pension, a $2,434.80 Part B premium and natural gas heat.
      • Head: 36,500 − 1,200 = 35,300 > 34,080, so $0.
      • With the gate deleted: 36,500 − 2,434.80 − 1,200 = 32,865.20, so $326, and no test fails.
    • (b) The floor. A worker aged 40 with $56,500 of wages and a dependent parent aged 66 with $1,800 of Social Security and a $2,434.80 premium.
      • Head: [45,200, 0]; 45,200 > 44,568, so $0.
      • With the floor deleted: 45,200 − 634.80 = 44,565.20, so $326, and no test fails.
  • Severity: should. A fix that adds a guard, or re-pins away the test that pinned one, so that it can be deleted with no test failing (UT r2 N1, ND r2 N1, AK r3 N2).
  • Fix: add (a) and (b) as person-income or countable-income cases.

Suggestions

N1 (suggestion). A heat-in-rent household with an UNSPECIFIED fuel gets no electric amount, and a subsidized one is ineligible, although main documents UNSPECIFIED as the value for heat in the rent with an unknown fuel

  • Where:
    • V/mo_liheap.py:39 and V/eligibility/mo_liheap_eligible.py:39 set heats_home = (heating_type != NONE) & (heating_type != UNSPECIFIED), with no exception for heat in the rent.
    • T/mo_liheap.yaml Case 10 pins $0, and its comment says "An unspecified fuel does not heat the home". The body and Axiom interpretation 14 (test 14(c)) say the same.
  • Main's contract.
    • heating_type.py says UNSPECIFIED is "Also the right value when heat is included in rent and the tenant does not know the building's fuel ... Never use NONE there — the home has heat."
    • The variable documentation adds that it "is also the right value when heat is included in rent and the fuel is unknown, since the separate heat_expense_included_in_rent input carries that fact."
    • The PR itself already treats such a renter as heating its home: Case 10 is eligible through unsubsidized heat in the rent.
  • The PR's own rule.
    • The household chooses the fuel to be paid, its electric account included (manual p. 51; plan 2.5, "including electric").
    • Rule (7)(D) admits subsidized tenants who are "paying a home energy supplier".
    • The $318 electric amount does not depend on the heating fuel. That is why this differs from G4.
  • Effect. Hand traces, with (b) confirmed by a local probe at the head:
    • (a) Case 10: unsubsidized, heat in the rent, UNSPECIFIED, rent $9,000, $1,200 electric bill, countable $22,000.
      • Head: eligible, max(0, min(720, 0), 0) = $0.
      • The same renter reporting NATURAL_GAS gets max(min(720, 326), 318) = $326.
      • With the fuel unknown, the PR's selection rule gives $318.
    • (b) Case 8 with UNSPECIFIED: subsidized, heat in the rent, $1,080 electric bill, countable $14,000.
      • Head: pays_electric_supplier is false, so ineligible, $0.
      • Case 8 as written (NATURAL_GAS) is eligible for $318.
  • Severity: suggestion, for consistency with this wave. North Dakota Add North Dakota LIHEAP regular heating assistance #10342 had the same UNSPECIFIED gate on its subsidized electric-bill path, and we carried it as an optional suggestion (N-G1) through approval. The wave convention (Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754) keeps UNSPECIFIED at $0 unless a PR chooses otherwise. The reading below is still the better one under the PR's own rule (7)(D) and main's heating_type documentation, so it's worth taking.
  • Fix:
    • In both files, use heats_home = (heating_type != NONE) & ((heating_type != UNSPECIFIED) | heat_in_rent). A household without heat in the rent and with an UNSPECIFIED fuel stays at $0, as Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754 expects.
    • Re-pin Case 10 to $318, and add the subsidized UNSPECIFIED tenant ($318).
    • Update the eligibility and benefit documentation, the body, and Axiom interpretation 14 and test 14(c).
    • To keep $0 instead, give a source-based reason. "Does not heat the home" contradicts heat_expense_included_in_rent.

G5 (residual). Capture the 8-2026 application.

  • Why it matters. The application is still a live wp-content/uploads/2022/02/ URL. It is the only printed FFY2027 source for amount, smi_rate, adult_age and unearned_sources.
  • Fix. A Save Page Now capture of the current 8-2026 edition would fix the anchor before DSS overwrites it.

Verified

Hand-checked every new or changed expectation (12):

  • T/mo_liheap.yaml. Each case has countable income at or below the size-1 limit of 34,080.
    • Case 7: max(153, 318) = 318, max(219, 318) = 318, max(326, 318) = 326 and max(0, 318) = 318. NONE and UNSPECIFIED have no heating expense and no electric-account route, so they are ineligible, $0.
    • Case 8: own fuel 0 (heat in the rent), renter share 0 (subsidized), electric 318, so 318.
    • Case 9: min(0.08 × 3,000 = 240, 326) = 240; max(240, 318) = 318.
    • Case 10: $0 at the head (N1).
  • Countable income:
    • Case 1: 3,504 + 2,400 = 5,904.
    • Case 6: 34,565.20 for the person, a deduction of 1,200, countable 33,365.20, a limit of 34,080, eligible, $326.
    • Case 7: 0 and 2,400.
  • Person income:
    • Case 5: 20,965.20 and 5,365.20.
    • Case 11: 36,000, ineligible, $0.
    • Case 12: 24,800.
    • Case 13: 9,000 and 17,880.
  • Medical deduction Case 10: 1,200 × 5.
  • Case count: 10 + 4 + 3 + 13 + 2 + 7 + 13 + 10 = 62, as the body says.

The hand-resolved merge block (programs.yaml coverage line, in 8c016379ed) is correct.

  • It is main's line at c55b806541 with MO added between MD and MS. Nothing is dropped or duplicated: head − base = {MO}.
  • The LIHEAP state_implementations list has one MO entry and no duplicates.
  • source_conflicts/candidates.yaml merged without conflict.

Batch guards at the head, on the merged tree (main's code, with the coverage line resolved):

Sources and housekeeping:

  • RSMo 660.115. I re-read the text: payments under subsection 1 are "from the utilicare stabilization fund", and subsection 2 limits "utilicare payments" to the lesser of 8% of annual rent or $100. That supports the renter candidate's reason.
  • amount.yaml. Its new description follows the house template.
  • References. The changed variable references keep one href per document, with # PDF pages comments.

Outside this PR. 864ef4a072 sets ssi: 0 in countable Case 6. Without it, the model computes $11,928 of SSI for the railroad retiree, because SSI's income list omits railroad_benefits. That is a main-side bug, tracked in #10533. The input is a sound workaround; it can go once #10533 is fixed.

Axiom

Valid: axiom: TheAxiomFoundation/rulespec-us#1899 queued.

  • State: OPEN, labelled pe-parity, updated 2026-10-29 13:46 UTC.
  • It matches the head's behaviour. It describes 8c016379ed, and 864ef4a072 changes only one test input. It covers:
    • RSMo 660.100 and 660.115;
    • fuel selection and electric-supplier eligibility (interpretations 14 and 16);
    • the full SMI premium for SS or RRB recipients, with the gate and floor in its required outputs (interpretation 10);
    • per-source floors and tanf (interpretation 12);
    • the five candidates;
    • 14 companion-test rows covering the 62 cases.
  • It is dispatch-ready: module paths, corpus status and blockers, verbatim sources, and companion tests derived from the sources.
  • What the fixes will change:
    • N1 changes interpretation 14 and test 14(c).
    • N2 adds companion rows for the gate and the floor.
    • Update the issue with the head that resolves them.

CI

No checks on the head.

  • gh pr checks 10438 reports none, and the commit has 0 check runs.
  • The PR is CONFLICTING with main (see Branch), so GitHub runs no pull_request workflow. I re-checked at the end of this review; the head is still 864ef4a072.

The last run, 40792597007 (head 8c016379ed, 2026-10-29 00:13 UTC, including main at c55b806541):

  • Result: 22 pass, 1 skipped (Contrib) and 2 fail.
  • The failures: states-shard-2 and Quick Feedback both failed on one real, PR-caused failure, countable-income Case 6. The test expected 34,565.20 and the model gave 46,493.20, because it computed $11,928 of SSI (SSI does not count railroad retirement annuities as unearned income #10533).
  • The fix: 864ef4a072 adds ssi: 0. I ran T/income/mo_liheap_countable_income.yaml at the head (policyengine-core 3.33.12): all 7 cases pass. So the failure is not open.
  • The rest of that run passed: the other 61 Missouri cases, PR validation, the guards (source conflicts, unemployment and military readers, state gating, programs registry), Microsimulation and Household API Partners.

Next: CI must run again once the conflict is resolved.

Branch

  • Position: 3 commits ahead of main (1976c4b600) and 8 behind.
  • Merge: conflict. git merge-tree --write-tree PolicyEngine/main 864ef4a072 reports a conflict in policyengine_us/programs.yaml, on the LIHEAP coverage line only.
  • Main's 8 newer commits touch nothing Missouri LIHEAP reads: AK LIHEAP, the owners-draw contrib reform, Maine EITC, the self-employed health insurance deduction and a version bump.
  • Siblings (branch note). The other open LIHEAP PRs also edit the coverage line.

0 critical, 1 should and 2 suggestions are open at this head.

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. All five round-1 shoulds are addressed. New: the Medicare (SMI) deduction's two guards, the Social Security/railroad gate and the per-member zero floor, have no test that fails without them. N1 (UNSPECIFIED fuel with heat in rent on the electric path) is a suggestion, as for North Dakota. No CI ran on this head because the PR conflicts with main (coverage line, AK + MO). Full report in the comment above.

hua7450 and others added 2 commits October 29, 2026 21:59
Apply DTrim99's round 2 review: tests for the Medicare premium gate and
the per-member floor, comment wording, and candidate code paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p-wave1d

# Conflicts:
#	policyengine_us/programs.yaml
@hua7450
hua7450 requested a review from DTrim99 October 30, 2026 02:09
@DTrim99

DTrim99 commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 3): Add Missouri LIHEAP Energy Assistance

PR #10438 (author: hua7450), head dd863edc6c. Round 2 (2026-10-29, head 864ef4a072) requested changes with 0 critical, 1 should (N2) and 2 suggestions (N1, G5). Since then the author pushed two commits:

  • f9336d119a answers round 2. It adds person-income Cases 14 and 15, rewords two comments in mo_liheap.yaml, and lists code files in the five MO candidates. It changes no formula or parameter.
  • dd863edc6c merges main at a24c9cb55d, with one hand-resolved block (the LIHEAP coverage line).

The PR now has 64 cases in 8 files. It touches no partner test file.

Path prefixes:

  • V/ = policyengine_us/variables/gov/states/mo/dss/liheap/
  • T/ = policyengine_us/tests/policy/baseline/gov/states/mo/dss/liheap/

Summary

N2 is fixed. Both guards on the SMI (Part B) premium deduction now have a test that fails if the guard is deleted:

  • The Social Security or railroad gate: person Case 14.
  • The per-member zero floor: person Case 15.

N1 is declined, and I accept that under the wave convention. An UNSPECIFIED fuel still gets no electric amount, even with heat in the rent. The test comments, the body and Axiom interpretation 14 now give #9754 as the reason ("heating_type is a required input") rather than "does not heat the home". The wave treats UNSPECIFIED at $0 as a suggestion at most (ND N-G1).

G5 is still open as a suggestion. The application keeps its live URL. Wayback's newest capture is still from 2025-11-01, so the 8-2026 edition has none.

Status.

  • CI: 33 of 33 checks pass on the head.
  • Merge block: the hand-resolved block is correct.
  • Axiom: rulespec-us#1899 matches the head.
  • Branch: main has since added MN, NV, TN, VT and WI, so programs.yaml conflicts again (branch note).

0 critical and 0 should are open, so I approve.

Prior findings

Finding Status Evidence
N2. The SMI deduction's Social Security or railroad gate and per-member zero floor are unpinned Fixed Gate, V/income/mo_liheap_countable_income_person.py:43-47. T/income/mo_liheap_countable_income_person.yaml Case 14 (lines 364-400) is a single 70-year-old with a $36,500 pension, a $2,434.80 Part B premium, natural gas and no SS or RRB. It pins person 36,500, countable 35,300, ineligible and $0. Without the gate, the person line gives 34,065.20 and the household 32,865.20, eligible for $326, so four outputs fail. Floor, :50. Case 15 (lines 402-439) is a worker aged 40 with $52,000 of wages and a dependent parent aged 66 with $1,800 of SS and a $2,434.80 premium. It pins [41,600, 0] and countable 41,600. Without the floor, the person line gives −634.80 and the household 40,965.20, so both outputs fail. The household-level max_(…, 0) cannot hide this, because the total stays positive. Partial mutations are also caught: dropping the SS half of the gate fails person Case 5 (5,365.20), and dropping the railroad half fails countable Case 6 (34,565.20). Case 15 uses $52,000 rather than my $56,500, so it does not change eligibility, but the person-level pin is enough under the mutation rule.
N1 (suggestion). UNSPECIFIED fuel with heat in the rent gets no electric amount Declined, accepted (wave convention) The code is unchanged (V/mo_liheap.py:39, V/eligibility/mo_liheap_eligible.py). T/mo_liheap.yaml:167-169 and :247-249 now say "An unspecified fuel is not paid (heating_type is a required input; issue 9754)". So the reason is the #9754 convention, not the "does not heat the home" reading that contradicts heat_expense_included_in_rent. The body and Axiom interpretation 14 / test 14(c) say the same. Case 10 stays eligible at $0. The wave convention accepts a test that pins UNSPECIFIED at $0.
G5 (residual). Capture the 8-2026 application Open (suggestion) The application href is still the live wp-content/uploads/2022/02/ URL. Wayback CDX's newest capture is 20251101210249 (9-2025 edition), so no 8-2026 capture exists yet.

New findings

Critical

None.

Should address

None. The fix commit adds tests, comment wording and candidate code lists; it adds no branch, guard or term.

Suggestions

G5 (carried). A Save Page Now capture of the 8-2026 application (MO 886-4576) would fix the anchor for amount, smi_rate, adult_age and unearned_sources before DSS replaces the file.

Verified

The new expectations, checked by hand:

  • Person Case 14.
    • Person: pension_income adds taxable_pension_income, so 36,500. It has no SS or RRB, so no premium is deducted.
    • SSI: 36,500 / 12 − 20 = 3,021.67 > 994, so SSI is 0.
    • Medical deduction: the head is 70, so 100 × 12 = 1,200.
    • Countable: 36,500 − 1,200 = 35,300.
    • Limit: 2,840 × 12 = 34,080, and the floor is 1.1 × 15,650 = 17,215. 35,300 is above both, so ineligible, $0.
  • Person Case 15.
    • Person 1: 52,000 × (1 − 0.2) = 41,600.
    • Person 2: SS of 1,800 (social_security, not in unearned_sources, so not counted twice, as Count each Social Security benefit once in Pennsylvania CCW countable income #10408 requires). With takes_up_ssi_if_eligible false, ssi is 0. So max(1,800 − 2,434.80, 0) = 0.
    • Medical deduction 0: the head is 40, and the parent is a dependent, not head or spouse (V/income/mo_liheap_medical_deduction.py).
    • TANF and child support paid are 0, so countable is 41,600.
  • T/mo_liheap.yaml: the two edits are comment-only, and no expected value changed.
  • Case count: 10 + 4 + 3 + 13 + 2 + 7 + 15 + 10 = 64, as the body and #1899 say. Quick Feedback ran the person file: 15 passed.

The hand-resolved merge block is correct. It is the programs.yaml coverage line in dd863edc6c.

  • It is a24c9cb55d's line with MO between MD and MS. The head's states minus the base's are {MO}; the reverse is empty, and nothing is duplicated.
  • The LIHEAP state_implementations list has one MO entry.
  • source_conflicts/candidates.yaml merged without conflict. The merge changed no Missouri file.

Candidates. The five MO entries now list their code files instead of code: [], and drop "Code not yet on main".

  • All 9 listed paths are files at the head, which test_candidate_fields / _inside_repository require.
  • The fields are otherwise unchanged.
  • On the tree merged with current main, the 5 MO ids are unique among 322.

#10287. The new comments contain no conflict-sounding wording, and the guard tests passed in the head's CI run.

Main-side guards. The PR changes no formula, so the round-2 results stand:

Main's commits since the CI run's base:

  • MN, TN, NV, WI and VT LIHEAP;
  • SC TANF;
  • head-independent state taxes;
  • the SS PIA-ratio contrib lever;
  • student loans;
  • the MO property tax credit.

None of these touches a variable that Missouri LIHEAP reads.

Axiom

Valid: axiom: TheAxiomFoundation/rulespec-us#1899 queued.

  • State: OPEN, labelled pe-parity, updated 2026-10-30 02:09 UTC.
  • It describes head dd863edc6c, with f9336d119a and the merge of main at a24c9cb55d.
  • New companion row 15 maps person Cases 14-15:
    • (a) $36,500, no deduction, countable 35,300, $0;
    • (b) [41,600, 0] and countable 41,600, or 40,965.20 without the floor.
  • Interpretation 10 cites tests 15(a) (the gate) and 15(b) (the floor).
  • Interpretation 14 and test 14(c) keep UNSPECIFIED at $0 under Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754, which matches the N1 decline.
  • Case count: 64.
  • It is dispatch-ready, with module paths, corpus status and blockers, verbatim sources, and source-derived companion tests.

CI

33 of 33 checks pass. Run 40989084354 is the pull_request run on dd863edc6c (2026-10-30 01:59 UTC), re-checked at the end of this review.

  • It covers all Baseline and Contrib shards, Quick Feedback, Microsimulation, the six Rest Python shards, Household API Partners and PR validation (guard tests).
  • It includes the two new cases and countable Case 6's ssi: 0 fix.

Housekeeping: the body still says "CI has not yet run on the head", and the "64 YAML tests pass" box is unticked.

Branch

0 critical, 0 should and 1 suggestion are open at this head.

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. Both Medicare deduction guards are now pinned. Full report in the comment above. Branch note: more LIHEAP states merged to main, so re-resolve the coverage line (main's line plus MO) before landing.

…p-wave1d

# Conflicts:
#	policyengine_us/programs.yaml
@hua7450
hua7450 merged commit 083de63 into main Oct 30, 2026
1 check passed
@hua7450
hua7450 deleted the mo-liheap-wave1d branch October 30, 2026 16: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 Missouri LIHEAP Energy Assistance regular heating assistance

2 participants