Skip to content

Add New Hampshire LIHEAP regular heating assistance - #10343

Merged
hua7450 merged 18 commits into
mainfrom
nh-liheap-wave2b
Oct 27, 2026
Merged

hua7450 merged 18 commits into
mainfrom
nh-liheap-wave2b

Conversation

@hua7450

@hua7450 hua7450 commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Adds New Hampshire's Fuel Assistance Program (FAP), its LIHEAP regular heating benefit, for program year 2027 (PY2027). The model counts citizens and qualified noncitizens for household size, counts each member's income from the manual's countable sources that have model inputs, less child support paid and the Medicare Part B premium withheld from each member's Social Security or railroad retirement, and tests it against 60% of state median income (SMI), with the federal protection below 110% of the poverty guideline. It estimates annual heating cost with the manual's formula, using bedrooms (at least one room) as the room count, takes the larger of that estimate and the actual bill, and pays the Attachment D matrix amount for the cost tier and income level, 75% of it for electricity, solar and natural gas. In PY2027, two adults in Coos County with $45,120 of wages, living in a four-bedroom single-family home heated with fuel oil and reporting no fuel bill, receive $800: $45,120 / $21,640 = 208.5% of the 2026 guideline, above the 175% bound, gives income level F (the two-person limit is $63,702); 2,000 x 4 bedrooms x 7,672 degree days / 1,000,000 x $28.881 / 0.78 = $2,272.56 of estimated heating cost gives cost tier BB; and the BB/F deliverable-fuel amount is $800.

Fixes #10337

Regulatory authority

Eligibility

Requirement Source How modeled
New Hampshire residence Manual page 12 defined_for = StateCode.NH; nh_fap is defined for nh_fap_eligible
Household size counts U.S. citizens and qualified noncitizens; ineligible members' income still counts Manual pages 12 and 34 to 36 nh_fap_household_size adds is_citizen_or_legal_immigrant over the SPM unit; a household with no counted member is ineligible
Income at or below 60% of state median income for the household size Manual pages 17 and 74; plan section 2.1 nh_fap_income_limit calls the shared liheap_smi_limit helper at New Hampshire's 60% rate: it floors 60% of the FY2027 four-person SMI ($156,134 x 0.6 = $93,680.40, floored to $93,680), applies the federal household-size factor and floors again. This rounding order is inferred: it reproduces all eight published limits, from $48,713 for one person to $129,278 for eight. The limit is inclusive.
Federal protection below 110% of the poverty guideline 42 U.S.C. 8624(b)(2)(B) nh_fap_eligible also accepts income strictly below 110% of the poverty guideline with a one-year lag (the 2026 guideline for PY2027), rounded to cents, reusing gov.hhs.liheap.fpg_floor. The protection exceeds the SMI limit only for households of 28 or more counted members; see the source-conflict note below.
Responsibility for heating costs, directly or through rent Manual pages 12, 18 and 21 A positive annual heating cost (nh_fap_heating_cost, actual or estimated) or unsubsidized heat in rent
Subsidized housing with heat in rent is ineligible; subsidized tenants who pay their own heating bill qualify Manual page 21; plan section 2.3 Ineligible when heat_expense_included_in_rent is true and the household is subsidized (receives_housing_assistance or is_in_public_housing)
No asset test and no categorical eligibility Plan section 2.3; manual page 12 None applied
Heating fuel Manual pages 71 and 77 to 78 heating_type NONE is ineligible, with or without heat in rent. SOLAR uses the electricity row, because heating_expense reads a solar home's bill as its grid electricity bill. OTHER and UNSPECIFIED have no page 77 row and pay nothing.

Countable income

Item Source How modeled
All members, including ineligible noncitizens Manual pages 12, 15 and 34 to 36 nh_fap_countable_income sums the income of every SPM unit member; ineligible noncitizens are left out of household size only
Earned income Manual pages 15, 16 and 25 to 28 Wages (employment_income) plus self-employment (including SSTB), farm operations, partnership and S corporation income, each floored at zero so a business loss does not offset other income. The earnings of members who are full-time students in secondary school are excluded. Military basic pay is part of employment_income, the convention under which Virginia and Indiana subtract military_basic_pay from AGI, so it is not added again.
Unearned income Manual pages 15, 23 and 29 Social Security (gross), pensions and annuities, military retirement pay and military Survivor Benefit Plan annuities (military_retirement_income), retirement distributions, railroad retirement, survivor benefits, veterans benefits, total unemployment compensation, workers' compensation, disability benefits, the state supplement's APTD, ANB and OAA payments (nh_ssp), strike benefits, financial assistance, alimony, rental income, farm rental income, estate income and gambling winnings, each floored at zero
SSI Manual pages 15 to 16 Counted for members aged 18 or older (ssi_min_age)
FANF Manual page 15 Received tanf, counted once per SPM unit, so an unclaimed entitlement is not income
Interest and dividends Manual pages 15 to 17 Combined interest and ordinary dividends less one $50 annual exclusion per SPM unit, floored at zero (see the investment exclusion note below)
Excluded receipts Manual pages 15 and 16; plan section 1.9; RSA 165:1 Child support received, capital gains, SSI for children under 18, full-time high school students' earnings and general assistance are not counted. Page 15 counts only the state welfare payments APTD, ANB, FANF, OAA and RCA (RCA has no model input), plan section 1.9 leaves "General Assistance benefits" unchecked, and RSA 165:1 makes general assistance a town responsibility that is not paid in cash.
Deductions Manual pages 14, 15, 17 and 23; plan section 1.9 Child support paid and each member's Medicare Part B premium, up to that member's Social Security and railroad retirement benefits, are subtracted, and the result is floored at zero. Page 17 deducts "Medicare deductions from Social Security or Railroad Retirement benefits (Part B and Part D)", and plan section 1.9 counts Social Security "Excluding MediCare deduction". The premium is the model's medicare_part_b_premium: $202.90 x 12 = $2,434.80 in 2027 (the 2026 standard premium, which the model carries forward) for an enrollee who pays it, and zero for a member who is not enrolled or whose premium a Medicare Savings Program pays. medicare_part_b_premiums_reported is not read.

Benefit

  • Annual heating cost (manual pages 17 to 18 and 76 to 77): nh_fap_heating_cost takes the larger of the actual annual bill (heating_expense, floored at zero) and the estimate dwelling factor x rooms x agency heating degree days / 1,000,000 x fuel cost per MBTU / fuel efficiency. The manual lets the agency compare the two and use the one that gives the better benefit; because the matrix amount never falls as cost rises, the larger cost gives that benefit. The model uses bedrooms as the room count, with at least one room for a household that is not homeless (see the room-count note below).
  • Dwelling factors (page 77): 2,000 for a single-family or modular home, 1,800 for a mobile home and 1,400 for an apartment, condominium or duplex, through the new nh_fap_dwelling_type input, which defaults to single family or modular.
  • Agency heating degree days by county_str (page 77, with the county-to-agency map from Community Action Partnership of New Hampshire): Belknap and Merrimack 6,610; Hillsborough and Rockingham 5,993; Strafford 6,061; Cheshire and Sullivan 6,588; Carroll, Coos and Grafton 7,672. An explicit unknown or out-of-state county produces no estimate. Without a county input, the model's existing county fallback assigns Belknap County, the first New Hampshire county alphabetically, so the estimate uses 6,610 degree days.
  • Fuel cost, efficiency and benefit multiplier (page 77):
heating_type Cost per MBTU Efficiency Benefit multiplier
Fuel oil $28.881 0.78 1.000
Kerosene $35.185 0.78 1.000
Propane $42.497 0.78 1.000
Wood $21.650 0.63 1.000
Coal $14.286 0.75 1.000
Natural gas $15.000 0.82 0.750
Electricity $64.706 1.00 0.750
Solar (electricity row) $64.706 1.00 0.750
  • Cost tiers (page 77): DD under $1,000, CC $1,000 to $1,749.99, BB $1,750 to $3,499.99 and AA $3,500 or more.
  • Income levels (pages 17 and 74): A through E with inclusive tops at 75%, 100%, 125%, 150% and 175% of the 2026 poverty guideline; F above 175% up to the SMI limit.
  • Deliverable-fuel matrix (Attachment D, page 78):
Annual heating cost A B C D E F
AA: $3,500 or more $2,500 $2,150 $1,825 $1,500 $1,200 $825
BB: $1,750 to $3,499.99 $2,350 $2,040 $1,720 $1,410 $1,100 $800
CC: $1,000 to $1,749.99 $1,460 $1,270 $1,150 $950 $730 $525
DD: under $1,000 $750 $650 $550 $500 $440 $400
  • Benefit multiplier (pages 77 to 78): nh_fap multiplies the deliverable-fuel amount by the benefit_multiplier parameter for the household's heating_type and rounds half up to whole dollars. Electricity, solar and natural gas receive 0.75 of it, which reproduces all 24 published utility amounts (for example, $2,150 x 0.75 = $1,612.50, paid as $1,613). Other, unspecified and no heat have a multiplier of 0 and pay nothing.
  • The award is the scheduled amount, with no cap at the actual bill and no supplement. A single adult with $10,000 of wages and a $100 oil bill, with no bedrooms or county entered, has a one-room Belknap estimate of 2,000 x 1 x 6,610 / 1,000,000 x $28.881 / 0.78 = $489.50, above the bill, and receives the DD/A amount of $750. An eligible household with no positive actual or estimated cost, such as heat in rent in an unknown county, receives $0.

Years covered

Model year Program year Guideline One-person limit
2027 PY2027, the 2026-2027 heating season; priority intake opens in July, general intake in October, and applications close April 30 2026: $15,960 for one person plus $5,680 for each additional person $48,713, from the FY2027 SMI of $156,134 for four people

The registry entry records verified_years: "2027". Earlier model years reuse the PY2027 New Hampshire parameter values with the poverty guideline and state median income that apply to each year, and are unverified historical estimates. Period 2026 therefore applies the PY2027 matrix. The published PY26 matrix differs: it starts tier AA at $1,750 and tier BB at $1,500, pays up to $2,177 (AA/A deliverable), sets utility amounts at about 62.5% of the deliverable amounts and labels level F as 200% of the guideline. PY26 values are not encoded.

Not modeled and source conflicts

  • Room count: no room-count input exists, so bedrooms stand in for the manual's room count. Page 17 counts every room except non-room spaces such as hallways, unfinished basements, garages, attics and closets, and may count an oversized room as more than one room (pages 17 to 18); the PY2027 application asks for "Bedrooms ___ Bathrooms ___ Kitchen ___ Living Rm ___ Dining Rm ___" (page 71). Bedrooms therefore understate the agency's room count for most dwellings. The manual's page 76 example counts 7 rooms for a two-person oil household at 7,672 degree days, giving $3,976.99, tier AA and $825 at level F; the same household with four bedrooms has an estimated $2,272.56, tier BB and $800. A single adult with $10,000 of wages in a two-bedroom Hillsborough County home heated with oil has a $887.61 estimate, tier DD and $750; counting a kitchen and a living room (4 rooms) would give $1,775.22, tier BB and $2,350. A household that is not homeless counts at least one room, as page 18 counts a space smaller than the standard room size as one room, so a studio or a household that reports no bedrooms gets the one-room estimate. That estimate never exceeds $992.85 (electricity at 7,672 degree days), so it always falls in tier DD. A homeless household keeps its reported bedroom count. The model does not remove rooms used only by foster individuals, a business or a renter (pages 25 to 26, 31 and 34), and it uses the whole household's bedrooms for each SPM unit.
  • Dwelling type: nh_fap_dwelling_type defaults to single family or modular. Mobile homes and apartments, condominiums or duplexes must be supplied; the agency's separate room type (factor 1,400) has no value of its own.
  • Heat in rent: unsubsidized heat-in-rent households receive the ordinary schedule for their fuel once, including the 0.75 utility factor. The PY2025 manual printed separate heat-in-rent matrices (page 74): deliverable AA/A $1,361 against $2,177 for direct payers, and utility AA/A $851 against $1,361. The PY26 Clearinghouse matrix and the PY2027 Attachment D (page 78) print no heat-in-rent matrix, and page 76 shows the heat-in-rent multiplier only for a household without heat in rent (1.0). The FY2026 plan says "Renters who pay for heat included in their rent are eligible for a reduced benefit" (page 9); the FFY2027 draft changes this to "eligible for a benefit", although its section 2.3 (page 8) still says these renters' benefits are reduced compared with direct payers. With no bill, the household receives the award for its estimated cost: a single adult with $10,000 of wages and oil heat in rent, with no bedrooms entered in Belknap County, receives $750 on the $489.50 one-room estimate. A heat-in-rent household with an unsupported fuel or county has no estimate and receives $0.
  • Investment exclusion: the model applies one $50 annual exclusion to combined interest and ordinary dividends in each SPM unit, following page 17's allowable expense "$50 of earned interest and/or dividends annually". Page 15 counts interest and dividends "over $50 a year", and page 16 excludes the first $50 of each, which would allow up to $100 for a household with both. None of these pages says whether the amount applies per person or per household. With $15,950 of wages, $50 of interest, $50 of dividends and a $4,000 oil bill, the combined exclusion gives countable income of $16,000, level C and $1,825; separate exclusions would give $15,950, level B and $2,150.
  • Fuel subtypes: WOOD stands in for cordwood; wood pellets and bio-bricks ($21.650, 0.70) cannot be identified from heating_type. Winter blend ($32.033, 0.78) is a heating-oil blend entered as FUEL_OIL or KEROSENE, so its own cost per MBTU is not used. Page 77 and the application list no other fuel, so OTHER and UNSPECIFIED return $0 even when the household is otherwise eligible.
  • Income timing and deductions: annual inputs stand in for the application-period income the agency annualizes. The model has no Part D premium variable, so Part D premiums are not deducted. Railroad retirement does not count toward the model's Medicare Savings Program income test, so a railroad-only enrollee whose resources are within that program's limit is treated as having the Part B premium paid and gets no deduction. Social Security overpayment recoveries, mandatory nursing-home payments and employer mileage reimbursements are not deducted, and child support paid is subtracted without a regularity check. Business income uses the model's net inputs without adding back the depreciation, amortization and home-business deductions that the manual disallows. Veterans benefits, financial assistance and disability benefits count as reported: the inputs do not separate VA Aid and Attendance or educational benefits, support received for fewer than six months, or one-time injury and insurance payments. Adoption subsidies, royalties, tribal income, taxable grants and military family allotments have no model inputs and are not counted.
  • Guidelines and limits: the agency loads new poverty guidelines each February (page 10); the model uses the 2026 guideline for the whole season. Limits above eight people follow the federal SMI extension with unverified state rounding. Page 17 allows level F to drop to 200% of the poverty guideline if funds are limited; no reduction is applied.
  • Household composition: the SPM unit stands in for the economic unit that purchases energy in common. Shared custody, foster individuals, students living away, live-in care providers, boarders and nursing-home residents are not modeled.
  • Manual differences: the page 19 example matrix and the page 75 illustration show amounts that differ from the current-year Attachment D on page 78 (page 75 shows $940 for CC/D and $425 for DD/E, where page 78 shows $950 and $440); the model uses page 78. Page 78's utility row labels overlap at $1,750 and $3,500; the agency system table on page 77 sets the tier tops at $999.99, $1,749.99 and $3,499.99, and the model follows it. These are differences within the manual, so the PR adds no source-conflict marker.
  • Federal floor over the 60% SMI limit (statute governs): the FY2026 plan (section 2.1) sets the limit at 60% of SMI for every household size, and the manual prints it through eight persons (page 74); with the federal size factors that limit falls below 110% of the 2026 poverty guideline from 28 counted members. The model follows 42 U.S.C. 8624(b)(2)(B): a 28-person household with $186,000 of countable income and a $2,000 fuel oil cost is above the $185,486 limit but below the $186,252 floor, so it receives the BB/C amount of $1,720. This reading is recorded as the backlog item us-nh:fap-income-floor-vs-smi-limit in source_conflicts/candidates.yaml.
  • This PR models regular heating assistance only. Crisis assistance, cooling, weatherization and heating-equipment repair are out of scope, as are the option to apply up to $300 of the benefit to furnace repair (page 57), the supplemental benefit and Senior Energy Assistance Services (page 40).

Test plan

  • 6 YAML files with 181 cases under policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/ (nh_fap.yaml 88, nh_fap_countable_income.yaml 36, nh_fap_eligible.yaml 30, nh_fap_heating_cost.yaml 15, nh_fap_household_size.yaml 4, nh_fap_income_limit.yaml 8); expected values are hand computed from the manual's income table, heating cost formula and benefit matrix in case comments. nh_fap.yaml Case 85 pays $750 to a single adult with $10,000 of wages and oil heat included in unsubsidized rent, with no bill and no bedrooms, on the $489.50 one-room Belknap estimate, and $0 when the county is unknown. Case 86 deducts the $2,434.80 Part B premium from $28,000 of Social Security, giving $25,565.20, level E and $1,100. Case 87 leaves $1,000 of general assistance out of a $19,500 earner's income, giving level C and $1,720. Case 88 pays a solar household with a $1,200 electricity bill $1,095 (CC/A $1,460 x 0.75). Countable-income Case 35 counts S corporation, SSTB, rental, survivor, disability and gambling income ($6,300 in total); Case 36 caps the Part B deduction at $1,200 of Social Security and allows none to an enrollee whose only income is a pension; Case 27 deducts nothing when a Medicare Savings Program pays the premium. Eligibility Case 26 makes a Medicare enrollee with $50,000 of Social Security eligible ($47,565.20 against the $48,713 limit) while a non-enrollee with the same income stays ineligible, and Case 30 makes a household with heat in rent and no heating type ineligible. Heating-cost Case 15 gives a homeless household with no bedrooms no estimate. CI passed the six New Hampshire files at a7d76c39d8; the review fixes in 6e975b1 were not run locally, and 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: pending. The federal-floor reading is recorded only as the unreviewed backlog item us-nh:fap-income-floor-vs-smi-limit in source_conflicts/candidates.yaml; this PR adds no rulespec-us register entry and no marker yet. The marker follows once a register entry merges and the pinned copy is refreshed.
  • make format
  • Changelog fragment in changelog.d/

axiom: TheAxiomFoundation/rulespec-us#1903 queued

🤖 Generated with Claude Code

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
#	source_conflicts/candidates.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 (54d73ab).
⚠️ Report is 712 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main    #10343   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4         7    +3     
  Lines           76       146   +70     
  Branches         2         2           
=========================================
+ Hits            76       146   +70     
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 New Hampshire LIHEAP regular heating assistance

PR #10343 (author: hua7450), head a7d76c39d8. This is a first review. The PR adds New Hampshire's Fuel Assistance Program (FAP) regular heating benefit for program year 2027 (October 1, 2026 to September 30, 2027), modeled as period 2027. It includes:

  • 14 parameter files and 7 variables (one new input, nh_fap_dwelling_type);
  • 6 test files (173 cases: nh_fap 84, countable income 34, eligible 29, heating cost 14, household size 4, income limit 8);
  • a programs.yaml entry (coverage line and state block);
  • one source_conflicts/candidates.yaml entry (us-nh:fap-income-floor-vs-smi-limit);
  • 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/nh/doe/fap/
  • V/ = policyengine_us/variables/gov/states/nh/doe/fap/
  • T/ = policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/

Sources read for this review:

  • PY2027 Fuel Assistance Program Policy and Procedures Manual. The agency server refuses automated clients (HTTP 403), and no archive holds the PY2027 edition (the newest Wayback capture, May 23, 2025, is PY2025). I read the PY2027 text through the verbatim transcription in rulespec-us#1903: pages 7-8, 10-19, 21, 23-26, 29-31, 34-36, 40, 57 and 74-78, with pages 75-77 read from 300 DPI renders. I checked it against two other sources:
  • FY2026 state plan, pages 4-9. The check boxes have no text layer. Each box in item 1.9 is drawn with one of two images, and I read the state of each box from which image it uses. The result matches the renders in rulespec-us#1903: "Excluding MediCare deduction" is checked and "General Assistance benefits" is unchecked.
  • Other: 42 U.S.C. 8624(b)(2)(B), and on main the Medicare, heating, military-pay, SMI and FPG helpers and policyengine-core's defined_for masking.

Summary

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

  • Tables.
    • All 24 deliverable cells match page 78.
    • The 0.75 multiplier with half-up rounding reproduces all 24 printed utility cells (for example 2,150 × 0.75 = 1,612.50, paid as $1,613).
    • The cost tiers follow page 77's cent tops ($999.99, $1,749.99, $3,499.99).
    • All seven fuel costs and efficiencies, the five agency degree-day values and the dwelling factors match page 77.
  • Limits.
    • The shared liheap_smi_limit helper (floor 60% of $156,134, then floor after the size factor) reproduces page 74's eight limits and HHS's sizes 9-12.
    • The income levels are inclusive at 0.75-1.75 × the 2026 guideline, and the dyadic factors keep the edges exact.
    • The federal 110% floor is strict, as the statute's "less than" requires.
  • Income.
    • Wages count gross and pre-tax premiums are not netted, as the manual's gross-income definition (p. 8) requires.
    • Business losses are floored per source and per person (p. 26).
    • Adult SSI counts, while SSI under 18, child support received, capital gains and full-time high-school earnings are excluded.
    • FANF counts once through tanf, and APTD, ANB and OAA count through nh_ssp.
    • railroad_benefits, military_retirement_income and total_unemployment_compensation are listed.
  • Housing. Subsidized heat in rent is excluded (p. 21), and subsidized direct payers qualify.
  • Tests and CI. I hand-checked 40 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 (checked key by key below).

There are no critical findings.

There are four should-address items:

  • S1. Medicare. Medicare is deducted only as a reported Part B premium, although page 17 deducts "Medicare deductions from Social Security or Railroad Retirement benefits (Part B and Part D)" and plan 1.9 counts Social Security "Excluding MediCare deduction". Countable-income Case 31 and eligibility Case 26 pin the gap. An enrollee with $50,000 of Social Security gets $0 instead of $825.
  • S2. General assistance. general_assistance is counted, although plan 1.9 leaves "General Assistance benefits" unchecked and page 15 counts only the state's programs (APTD, ANB, FANF, OAA, RCA). Countable Case 23 pins it. In the example, $1,410 instead of $1,720.
  • S3. Untested entries. Six of the 24 income-source entries and two gates have no test that fails if they are removed.
  • S4. Descriptions and comments. Three parameter descriptions are off the house template, and several comments cite "the user" or an unattributed "approval".

There are also eight suggestions:

  • G1: the bedroom room count.
  • G2: heat in rent.
  • G3: SOLAR and fuel subtypes.
  • G4: the $50 investment exclusion.
  • G5: the military basic pay convention.
  • G6: the source-conflict process.
  • G7: references.
  • G8: period 2026.

Status.

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

Critical (must fix)

None.

Should address

S1. Medicare premiums are deducted only when a Part B amount is reported, although the manual deducts the Medicare premiums withheld from Social Security and railroad retirement, and two tests pin the gap

  • Where:
    • V/nh_fap_countable_income.py:58-64 subtracts only medicare_part_b_premiums_reported. The comment says that "automatic premium reconstruction remain[s] unmodeled".
    • T/nh_fap_countable_income.yaml Case 31 (line 887, "Medicare age does not create an unreported premium deduction") pins $24,000 for an enrollee aged 70.
    • T/nh_fap_eligible.yaml Case 26 (line 916), second unit: Social Security of $50,000 with no reported premium is pinned ineligible.
  • Sources:
    • Page 17, "Allowable Expenses (aka Income Disregards)": "Medicare deductions from Social Security or Railroad Retirement benefits (Part B and Part D)".
    • Page 15: "Railroad retirement (net after Medicare deductions)".
    • Page 14: "Expenses that are eligible as income deductions, such as Medicare deductions or child support paid, can be entered as an expense and will reduce the total income."
    • Plan 1.9 (p. 6) checks "Excluding MediCare deduction" for Social Security, and leaves "Including MediCare deduction" unchecked.
  • main:
    • medicare_part_b_premium is the enrollee's out-of-pocket premium net of MSP. For a Social Security or railroad beneficiary, that is the amount withheld from the check.
    • It is $202.90 × 12 = $2,434.80 in 2027, because the base premium has no 2027 value and is not uprated.
    • The merged LIHEAP states read it: AR, GA, IA, IN, KY, MD, NJ, NY and SD.
    • main has no Part D premium variable, so Part D can stay a documented gap.
  • Effect (hand-traced; 2027, one person aged 70, enrolled, not MSP-eligible, fuel oil):
Household Head With the derived Part B
Social Security $50,000, bill $4,000 (eligibility Case 26, second unit) 50,000 > 48,713: $0 47,565.20 ≤ 48,713; 298.0% of $15,960, so level F; AA: $825
Social Security $28,000, bill $2,000 175.44%, so F; BB: $800 25,565.20, 160.2%, so E; BB: $1,100

S2. General assistance is counted, although plan 1.9 leaves it unchecked and the manual counts only the state's own welfare programs

  • Where:
    • P/income/unearned_sources.yaml:15 lists general_assistance. The comment at :28-29 says it "approximates other non-FANF state welfare".
    • T/nh_fap_countable_income.yaml Case 23 (line 664) pins $600 of it as countable (total $3,600).
  • Sources:
    • Page 15 counts "State welfare payments (APTD, ANB, FANF, OAA, RCA)", which are all state programs. APTD, ANB and OAA are nh_ssp, and FANF is tanf.
    • Page 16 excludes "One-time or irregular payments from a welfare agency to a family or person".
    • Plan 1.9 (p. 6) leaves "General Assistance benefits" unchecked, while it checks TANF, SSI and Social Security.
    • In New Hampshire, general assistance is municipal (town and city welfare under RSA 165), not one of the listed state programs.
    • The PR's own Axiom issue (interpretation 15) records that the plan leaves General Assistance unchecked, but the body and the code do not.
  • Effect (hand-traced; 2027, one adult, wages $19,500, general_assistance $1,000, fuel oil $2,000):
    • Head: 20,500 / 15,960 = 128.4%, so level D; BB/D: $1,410.
    • Without GA: 19,500 / 15,960 = 122.2%, so level C; BB/C: $1,720.
  • Precedent: Utah Add Utah LIHEAP regular heating assistance #10344 S3, where dividends were counted against both the rule and plan 1.9, was rated should.
  • Fix:
    • Drop general_assistance and its comment line, and re-pin countable Case 23 to $3,000.
    • If the intent is a stand-in for RCA (which has no input), say so and record a us-nh: candidate that gives plan 1.9 on the other side.
    • Update the body's unearned row and Axiom interpretation 15.

S3. Six of the 24 income-source entries and two gates have no test that would fail if they were removed

  • Untested entries:
    • Never set: s_corp_income, survivor_benefits, disability_benefits and gambling_winnings.
    • Set only below zero: sstb_self_employment_income (−$600, countable Case 13, line 379) and rental_income (−$300, Case 15, line 438). Both floor to $0, so removing either entry changes no result.
  • Untested gates:
    • The award gate. V/nh_fap.py:69 (where(expense > 0, benefit, 0)). No nh_fap case is eligible with zero cost. Eligibility Case 22 (line 814, heat in unsubsidized rent, no bedrooms) asserts only eligibility. Without the gate, that household would get DD/A $750, and no case would fail. See G2 for the policy question.
    • The NONE clause. V/nh_fap_eligible.py:44 (heating_type != NONE). Eligibility Case 19 (line 739) already fails the cost test, because a NONE household has no heating_expense and no estimate. The clause matters only for NONE with heat in rent, which no case sets.
  • Tested: wages, self-employment, farm operations, partnership, Social Security, adult SSI, pension, retirement distributions, railroad, both military components, veterans, unemployment (through total_unemployment_compensation), workers' compensation, nh_ssp, strike, financial assistance, alimony, farm rent, estate, interest, dividends, FANF, child support paid and reported Part B.
  • 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, Add Utah LIHEAP regular heating assistance #10344 S5).
  • Fix:
    • Add one countable-income case that gives the six sources distinct positive amounts for an adult who is not a high-school student.
    • Add an nh_fap case for an eligible heat-in-rent household with no bill and no bedrooms (now $0).
    • Add a NONE heat-in-rent eligibility case, or drop the redundant clause.
    • Add the cases to the rulespec-us#1903 companion tests.

S4. Three parameter descriptions are off the house template, and several comments cite "the user" or an unattributed approval

  • Descriptions (checklist item 6: " provides/limits ... under the ", with nothing after the program name):
    • P/income/investment_disregard.yaml:1 begins "PolicyEngine applies this annual exclusion once to each SPM unit's ...".
    • P/income/business_sources.yaml:1 ends "... under the Fuel Assistance Program, excluding each source's losses."
    • P/income/unearned_sources.yaml:1 ends "... under the Fuel Assistance Program before its source-specific SSI and investment income exclusions."
  • Comments:
    • P/income/investment_disregard.yaml:3 ("as requested by the user").
    • T/nh_fap_countable_income.yaml:437 ("user-directed nonnegative rental streams") and :492 ("User-approved convention").
    • T/nh_fap_heating_cost.yaml:4 ("a user-approved modeling approximation, not the statutory room definition"; the room definition is in the manual, not a statute).
    • "Approved" assumptions in V/nh_fap.py (documentation and line 63), V/nh_fap_heating_cost.py and the test headers.
    • The comments do not say who approved them or where. They read as session notes in a public repository.
  • Fix:
    • Rewrite the descriptions. For example: "New Hampshire counts these business income sources under the Fuel Assistance Program." "New Hampshire counts these unearned income sources under the Fuel Assistance Program." "New Hampshire excludes this amount of annual interest and dividend income under the Fuel Assistance Program."
    • Move the qualifications into comments.
    • Replace "user", "user-approved" and bare "approved" with the reason itself (for example, "no room-count input exists").

Suggestions

G1. Bedrooms understate the manual's room count, and the gap can be large.

  • The rule. Page 17 counts every room except non-room spaces (hallways, unfinished basements, garages, attics, closets). The application form counts "Bedrooms ___ Kitchen/Dining ___ Living/Family ___ Other ___ Total rooms" (PY2025 edition, p. 71). Page 18 says the system uses the estimate automatically.
  • Effect. One adult, wages $10,000, a two-bedroom single-family home in Hillsborough County, fuel oil, no bill:
    • Head (2 rooms): 2,000 × 2 × 5,993 / 1,000,000 × 28.881 / 0.78 = $887.61, tier DD: $750.
    • With a kitchen and a living room (4 rooms): $1,775.22, tier BB: $2,350.
    • The PR's own worked example falls from AA to BB ($825 to $800).
  • Precedent. Wisconsin Add Wisconsin WHEAP regular heating benefit #10434 G5 rated the same proxy a suggestion.
  • Fix. Add an nh_fap_rooms input that defaults to bedrooms, so that callers who know the room count can supply it. Consider a default that adds the kitchen and living room the form counts. Treat a studio (0 bedrooms) as at least one room once a dwelling is known.

G2. Heat in rent.

  • The evidence supports the PR's no-reduction choice.
    • The PY2025 manual printed separate heat-in-rent matrices (p. 74): deliverable AA/A $1,361 against $2,177 for direct payers, and utility $851 against $1,361.
    • The PY26 Clearinghouse matrix and PY2027 page 78 print none.
    • The FFY2027 draft plan changes "eligible for a reduced benefit" to "eligible for a benefit".
    • Fix: cite this in the body and the variable documentation in place of "may overestimate awards".
  • No estimate. An eligible heat-in-rent household with no bedrooms entered gets $0 (S3). Consider paying at least the DD cell, the tier for any cost below $1,000. The same applies to UNSPECIFIED heat in rent, which main's heating_type documentation calls the right input when the tenant does not know the fuel. That part is optional (Add Vermont Seasonal Fuel Assistance #10436 G1, Add Maine HEAP Fuel Assistance #10437 G1).

G3. SOLAR and fuel subtypes.

  • SOLAR. It gets $0 even with an electric bill. main treats solar homes as grid-tied with electric supplemental heat. Example: wages $10,000 and a $1,200 bill give $0, against CC/A × 0.75 = $1,095 as electricity. Map it to the electricity row, or keep $0 as a stated choice (Add Utah LIHEAP regular heating assistance #10344 G3, Add Wyoming LIHEAP regular heating assistance #10346 G3, Add Wisconsin WHEAP regular heating benefit #10434 G4).
  • OTHER. $0 is defensible here. Page 77 and the application list no "other" fuel.
  • Winter blend. P/payment/benefit_multiplier.yaml:17 says that WOOD "also represents pellets, bio-bricks and winter blends". Winter blend is a heating-oil blend (page 77: $32.033 at 0.78), which a caller would enter as FUEL_OIL or KEROSENE, not WOOD. Correct the comment.

G4. The $50 investment exclusion.

  • The sources. Pages 15-16 exclude "the first $50" of interest and of dividends separately. Page 17's "interest and/or dividends" is consistent with that reading. The PR applies one combined $50 per SPM unit, the least generous reading.
  • Effect. Wages $15,950, interest $50, dividends $50, fuel oil $4,000:
    • Head: 16,000, level C: $1,825.
    • Separate exclusions: 15,950, level B: $2,150.
  • Fix. Consider separate $50 exclusions, which pages 15 and 16 state in terms, or keep the documented convention.

G5. Military basic pay: the convention is defensible, so state the basis.

  • The PR's convention. The PR treats military pay as part of employment_income ("Military pay is part of wages and is not added again").
  • Why I do not raise this as Utah Add Utah LIHEAP regular heating assistance #10344 S2 did. On main, Virginia and Indiana subtract military_basic_pay from AGI. That only works if it is already inside employment_income. Iowa LIHEAP, Oregon ERDC, Georgia CAPS and Washington list it as a separate source instead.
  • Fix. Cite the tax-side convention in the comment, so that a later sweep does not add it and double count. The repo-wide meaning of military_basic_pay should be settled separately.

G6. The source-conflict process. The federal-floor reading is recorded only as a candidates.yaml backlog item. CONTRIBUTING "Source conflicts" asks a PR that takes such a reading to add a rulespec-us register entry under proposed and mark the deciding line (V/nh_fap_eligible.py:27). The floor binds only at 28 or more counted members, so this is optional (#10435 G2).

G7. References.

  • The manual. Its URL is re-issued each program year and refuses automated clients, and no capture of the PY2027 edition exists. Archive a copy, or name the corpus copy (SHA-256 48d77fdaed2b6b33...) in a comment, so that the #page anchors stay verifiable after PY2028.
  • The county map. It cites capnh.org/home-old. Add a capture or the five agencies' service-area pages.
  • The limit. Consider adding plan item 2.1 (p. 8) to P/smi_rate.yaml.

G8. Period 2026 (unclaimed and documented).

  • What it gets now. Period 2026 applies the PY2027 matrix.
  • The PY26 matrix. It is published: AA from $1,750 with a $2,177 maximum, utility at 0.625, and level F labelled 200% of the guideline.
  • Fix. None needed now (NJ S3). Adding PY26 values dated 2025-10-01 would be cheap.

Verified correct

Hand-checked expectations (40; all match the head, though S1 and S2 change three of them):

Test Hand computation Expected
nh_fap Cases 1-2 10,000 / 15,960 = 62.7%, A; $500: DD; 750 and 750 × 0.75 = 562.50 → 563 750; 563
nh_fap Case 24 40,000 = 250.6%, F; $1,200: CC; 525 × 0.75 = 393.75 → 394 394
nh_fap Case 36 F; $2,000: BB; 800 × 0.75 600
nh_fap Case 40 14,000 = 87.7%, B; $4,000: AA; 2,150 × 0.75 = 1,612.50 → 1,613 1,613
nh_fap Case 47 F; AA 825
nh_fap Cases 49-50 11,970 = 0.75 × 15,960 (exact), A, AA; one cent more, B 2,500; 2,150
nh_fap Case 65 kerosene, F, BB, multiplier 1 800
nh_fap Case 69 A; $100: DD; no cap at the bill 750
nh_fap Case 72 size 2, 30,000 / 21,640 = 138.6%, D; AA 1,500
nh_fap Case 73 undocumented member out of size, income in: 30,000 / 15,960 = 188.0%, F, within 48,713; AA 825
nh_fap Case 76 2,000 × 4 × 7,672 / 10^6 = 61.376 × 28.881 / 0.78 = 2,272.56, BB; 45,120 / 21,640 = 208.5%, F 2,272.564431; 800
nh_fap Case 77 30.688 × 28.881 / 0.78 = 1,136.28, CC/A; $4,000, AA/A [1,460, 2,500]
nh_fap Case 78 30.688 × 15 / 0.82 = 561.37, DD/A × 0.75 → 563; AA/A × 0.75 [563, 1,875]
nh_fap Case 79 no county, so Belknap: 2,000 × 3 × 6,610 / 10^6 × 64.706 = 2,566.24, BB; size 3, A; 2,350 × 0.75 → 1,763 1,763
nh_fap Case 81 50,000 > 48,713; 10,000, A, DD × 0.75 [0, 563]
nh_fap Case 82 size 2, 9,600 and 13,200 ≤ 16,230, A; AA [2,500, 2,500]
nh_fap Case 83 limit floor(93,680 × 1.98) = 185,486; floor 1.1 × 169,320 = 186,252; 186,000 = 109.85%, C; CC × 0.75 = 862.50 → 863 [863, 863, 0, 0]
nh_fap Case 84 SBP 20,000 = 125.3%, D; 2,272.56, BB 1,410
income limit Cases 1-8 floor(93,680 × {0.52, 0.68, 0.84, 1.00, 1.16, 1.32, 1.35, 1.38}) 48,713; 63,702; 78,691; 93,680; 108,668; 123,657; 126,468; 129,278
countable Case 7 18-year-old full-time high-school wages and self-employment excluded 10,000
countable Cases 17-19 (50 + 50) − 50; (51 + 51) − 50; (40 + 20 + 40 + 40) − 50 50; 52; 90
countable Case 22 2,400 + 1,200 + 1,200 + 600 + 600 + 600 6,600
countable Case 23 600 GA + 1,200 + 1,200 + 600 (S2) 3,600
countable Cases 27-28, 32 24,000 − 2,400; 18,000 − 2,400; [24,000 − 2,400 + 18,000 − 1,200, 12,000] 21,600; 15,600; [38,400, 12,000]
heating cost Case 2 (kerosene) 30.688 × 35.185 / 0.78 1,384.30
eligible Case 26 50,000 − 2,000 = 48,000 ≤ 48,713; 50,000 > 48,713 (S1) [true, false]
eligible Case 28 48,713 + (75 − 50) = 48,738 > 48,713 false

Batch checks:

  • 1. Plan, manual and matrix.
    • Cells. All 48 page 78 cells (24 stored, 24 reproduced), the four page 77 tiers, seven fuels, five agencies, the dwelling factors and page 74's limits and level tops reconcile.
    • Timing. PY2027 runs October 1, 2026 to September 30, 2027 (p. 8), with the 2026 guidelines loaded 1/16/26 and the FY2027 SMI loaded 5/28/26 (p. 74). The February guideline update is documented.
  • 2. Income.
    • Gross. Gross income (p. 8; plan 1.8) and the Social Security rule are right apart from S1.
    • Losses. They are floored per source and per person (p. 26).
    • Period. Annual amounts are used, as documented.
    • See S1, S2 and G4.
  • 3. Backdating. Only 2027 is claimed (verified_years: "2027", backed by the PY2027 values). See G8 for 2026.
  • 4. Arrangements.
    • Housing. Subsidized and public housing with heat in rent are excluded; subsidized direct payers qualify; unsubsidized heat in rent is eligible.
    • Eligibility rules. There is no categorical eligibility (p. 12) and no asset test (plan 2.3).
    • Members. A qualified member is required, and ineligible members' income counts (p. 36).
    • Fuel. NONE is ineligible, which fits a heating-only program. See G2 and G3.
  • 5. Tests. Periods are plain, and the level tops, tier cent edges, sizes 1-8 at the limit and one cent above, and the 28-person floor are covered. See S3.
  • 6. Descriptions. 11 of 14 follow the template; see S4.
  • 7. References. One entry per document, file-page anchors, and multi-page titles ending #page 15-17,23,29 and similar. The variable tuples carry # PDF pages comments. See G7.
  • 8. Wiring.
    • Registry. The programs.yaml block is complete.
    • Aggregate. No merged LIHEAP state feeds a household aggregate.
    • Gating. Every variable is gated by StateCode.NH or nh_fap_eligible, except the new input.
  • 11, Integrate modeled state unemployment insurance into income flows #9558. The list reads total_unemployment_compensation (countable Case 22), and no Python reads the raw input.
  • 12. IIJA. There is no supplement. The page 40 supplemental benefit is out of scope, as documented.
  • 13. Mutation. See S3. The level and tier edges, multipliers, floor, subsidized branches, high-school exclusion, SSI age, investment exclusion, FANF and child support paid are all pinned.
  • 14. Health premiums (Document disjoint health premium inputs and correct consumer treatment #10046). The manual's gross-income definition includes medical insurance (p. 8), so not netting pre_tax_health_insurance_premiums is right.
  • 15, NH Add New Hampshire state supplementation for 2026 #10156. Safe. policyengine-core runs the formula for every household when any household in the simulation is in New Hampshire, so I checked each key:
    • nh_fap_dwelling_type defaults to a valid key.
    • The county and fuel keys pass through np.isin with an UNAVAILABLE fallback.
    • benefit_multiplier has all 11 HeatingType members.
    • The tier and level keys come from select with string defaults.
    • The SMI and FPG helpers key on state_code_str and state_group_str, and use size (0 outside New Hampshire) only arithmetically.
    • No case runs two states together. Heating-cost Case 10 runs Massachusetts alone, which the all-false mask short-circuits.
  • 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 (nh_fap Cases 81 and 84).
  • 17. State SSP. nh_ssp is listed (countable Case 34), and 15 aged or disabled members pin it to 0.
  • 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. It is implemented and binds from 28 counted members (Case 83).
  • Changelog. changelog.d/nh-liheap-wave2b.added.md is a top-level fragment.
  • Local runs. None. CI ran all six New Hampshire YAML files green at this head, and the findings rest on source text and hand traces.

Axiom line assessment

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

  • The issue. It is OPEN and labelled pe-parity. It is dispatch-ready: proposed us-nh/ module paths with corpus status, verbatim manual, plan, HHS and statute text (including transcriptions of the image-only pages 75-77), required outputs, and 13 companion tests derived from the sources.
  • The blocker. Pages 75-77 need an admitted transcription; the CAPNH page, 42 U.S.C. 8624, 45 CFR 96.85 and the FFY2027 draft are not ingested; the signed release has not been checked; there is no reviewed oracle revision; and billed runs need approval.
  • The match. It describes head a7d76c39d8 and the 173 cases (84/34/29/14/4/8), with total_unemployment_compensation, military_retirement_income and nh_ssp in the lists. It was updated after the head.
  • After the fixes, refresh:
    • interpretation 13 and companion tests 12(h)-(i) (S1);
    • interpretation 15 and the countable-income required output (S2);
    • the new companion cases (S3);
    • the head SHA and case counts.

CI status

gh pr checks 10343 at head a7d76c39d8, re-checked at the end of this review: 34 pass, none failing or pending (run 40057976084, 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: 0 critical, 4 should (Medicare premiums deducted only when reported; general assistance counted against plan 1.9; untested income sources and gates; descriptions and comments). Full report in the comment above.

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

hua7450 commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the review, David. The fixes are in 6e975b1, and c0cda9e merges current main.

  • S1 (Medicare). Fixed. nh_fap_countable_income.py lines 66 to 78 now deduct each member's computed medicare_part_b_premium (zero when not enrolled or when a Medicare Savings Program pays it), capped at that member's social_security plus railroad_benefits, citing page 17 and plan item 1.9. The reported input medicare_part_b_premiums_reported is no longer read, consistent with the other wave PRs. Part D stays undeducted because main has no Part D premium variable. Countable Case 31 is now $21,565.20 ($24,000 - $2,434.80), and eligibility Case 26 makes the enrollee with $50,000 of Social Security eligible ($47,565.20) while a non-enrollee stays ineligible. Countable Cases 28, 29 and 32 now use the computed premium ($15,565.20, $19,565.20 and [$37,130.40, $12,000]), Case 30 still floors at $0, Case 27 now shows no deduction when a Medicare Savings Program pays the premium ($12,000), new Case 36 caps the deduction at $1,200 of Social Security ($30,000), and new nh_fap Case 86 is your $28,000 example ($25,565.20, level E, $1,100).
  • S2 (general assistance). Fixed. general_assistance is out of unearned_sources.yaml; the comment cites page 15's APTD, ANB, FANF, OAA and RCA, plan item 1.9 (now a reference) and RSA 165. Countable Case 23 is $3,000, and new nh_fap Case 87 is your example ($19,500, level C, $1,720).
  • S3 (untested entries). Fixed. New countable Case 35 gives s_corp_income, sstb_self_employment_income, rental_income, survivor_benefits, disability_benefits and gambling_winnings distinct amounts ($6,300 in total). New nh_fap Case 85 pins the award gate: heat in rent with no bill and no bedrooms pays $750 on the one-room Belknap estimate ($489.50) and $0 in an unknown county. New eligibility Case 30 pins the NONE clause (heat in rent, no heating type: ineligible).
  • S4 (descriptions and comments). Fixed. The three descriptions now read as you suggested, with the qualifications in comments. The "user", "user-approved" and "approved" wording is gone from the parameters, variables and tests; each comment states the reason.
  • G1 (room count). Partly adopted. Bedrooms stay the proxy, because a room-count input needs separate authorization. The one-room floor is in: nh_fap_heating_cost.py lines 17 to 25 count at least one room for a household that is not homeless (page 18 counts a space smaller than the standard room as one room), and the comment states that bedrooms understate the page 17 count and the application's kitchen, living and dining rooms. Eligibility Cases 20 and 27 (unit 2) are now eligible, heating-cost Cases 5, 12 and 13 now give the $568.14 one-room Coos estimate where no larger bill applies, and new heating-cost Case 15 keeps a homeless household at no estimate. The largest one-room estimate is $992.85, so no existing award changes.
  • G2 (heat in rent). Adopted. The nh_fap documentation and the body now cite the PY2025 separate heat-in-rent matrices (page 74), their absence from the PY26 matrix and PY2027 page 78, and the FFY2027 draft's "eligible for a benefit", in place of "may overestimate awards". The no-bedrooms case is covered by the floor (Case 85). UNSPECIFIED stays $0.
  • G3 (SOLAR, OTHER, winter blend). Adopted. SOLAR takes the electricity row in the cost, efficiency and multiplier parameters; new nh_fap Case 88 is your example ($1,095), and heating-cost Case 8 now gives $3,971.40 for four rooms in Coos. OTHER stays $0, with the page 77 and application basis in the comment. The winter blend comment now says it is entered as FUEL_OIL or KEROSENE.
  • G4 ($50 exclusion). Kept. The single combined $50 per SPM unit was an earlier deliberate choice. The investment_disregard.yaml comment now gives its basis (page 17's "$50 of earned interest and/or dividends annually") and the pages 15 to 16 alternative of $50 each, and the body states your $1,825 against $2,150 example.
  • G5 (military basic pay). Documented. nh_fap_countable_income.py lines 29 to 31 state the tax-side convention (Virginia and Indiana subtract military_basic_pay from AGI), so a later sweep does not add it again.
  • G6 (source-conflict process). Not taken now. The federal floor binds only at 28 or more counted members, so it stays a candidates.yaml backlog item.
  • G7 (references). Partly adopted. smi_rate.yaml adds plan item 2.1 (page 8), and heating_degree_days.yaml adds the June 13, 2026 Wayback capture of the CAPNH page. The Wayback Machine has no PY2027 manual: its newest successful capture of the URL (May 23, 2025) is the PY2025 edition, and the March 7, 2026 capture is a 403 page. The body records the SHA-256 of the PY2027 copy read. The candidate entry now lists its three code paths.
  • G8 (period 2026). Documented. The body's Years covered section says period 2026 applies the PY2027 matrix and lists how the PY26 matrix differs; PY26 values are not encoded.

The PR body and TheAxiomFoundation/rulespec-us#1903 are updated for c0cda9e. Tests now number 181 (nh_fap 88, countable income 36, eligible 30, heating cost 15, household size 4, income limit 8). I did not run the tests locally, so CI will check them.

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

DTrim99 commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator

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

PR #10343 (author: hua7450), head c0cda9e671. Round 1 reviewed a7d76c39d8 and requested changes (0 critical, 4 should, 8 suggestions). Since then the author has pushed:

  • one fix commit, 6e975b1605;
  • a merge of main, c0cda9e671, with one hand-resolved conflict in programs.yaml (two hunks: the LIHEAP coverage line and the state block).

The PR still touches no partner test file.

Path prefixes:

  • P/ = policyengine_us/parameters/gov/states/nh/doe/fap/
  • V/ = policyengine_us/variables/gov/states/nh/doe/fap/
  • T/ = policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/

Summary

All four should-address items are fixed, and a test would fail without each fix:

  • S1. Medicare. The deduction now uses each member's computed out-of-pocket medicare_part_b_premium (net of Medicare Savings Program coverage), capped at that member's Social Security plus railroad retirement. The reported input is no longer read.
  • S2. General assistance. general_assistance is off the unearned list, citing page 15, plan item 1.9 and RSA 165.
  • S3. Untested entries. The six untested sources, the award gate and the NONE clause are now pinned.
  • S4. Descriptions and comments. The three descriptions follow the template, and no "user" or "approved" wording remains.

The suggestions.

  • G2, G3, G5 and G8 were adopted or documented.
  • G1 and G7 were partly adopted.
  • G4 and G6 were declined with reasons I accept.

The merge. The hand-resolved programs.yaml conflict is correct: main's coverage line plus NH, and VA's block kept with NH appended once.

The rulespec-us issue. rulespec-us#1903 describes the new head.

No new critical or should-address items.

  • New conditions. The fix adds two: the homeless exception in the one-room floor and the benefit cap on the Part B deduction. Each is pinned (heating-cost Case 15; countable Case 36).
  • Lost pins. Two old pins changed meaning when the one-room floor arrived. Eligibility Case 27 unit 7 now pins the heating-cost test, and nh_fap Case 85 unit 2 pins the heat-in-rent route.
  • Precedent. This is the Utah and North Dakota round-2 check, and it finds nothing to raise.

Four new suggestions:

  • N-G1: railroad retirement in main's Medicare Savings Program income test.
  • N-G2: a backlog candidate for the heat-in-rent reading.
  • N-G3: the FFY2027 draft-plan URL.
  • N-G4: housekeeping.

Status.

  • CI: 35 of 35 pass at the head.
  • Branch: 17 commits ahead of main (3 of them merges) and 2 behind, with a clean merge.

Prior findings

Finding Status Evidence
S1. Medicare is deducted only as reported Part B Fixed Code: V/nh_fap_countable_income.py:66-78 subtracts, for each member, min_(medicare_part_b_premium, max_(social_security, 0) + max_(railroad_benefits, 0)). The comment cites page 17's "Medicare deductions from Social Security or Railroad Retirement benefits (Part B and Part D)". The reference list adds plan item 1.9 (p. 6). medicare_part_b_premiums_reported is no longer read.
Tests: countable Case 31 (line 905) is now 24,000 − 2,434.80 = 21,565.20. Eligibility Case 26 (line 922) makes the enrollee eligible (47,565.20 ≤ 48,713) and keeps the non-enrollee ineligible. nh_fap Case 86 (line 2815) is the round-1 $28,000 example: level E, $1,100. Countable Case 27 shows no deduction when MSP pays, and Case 36 shows the cap.
Mutations:
• Reverting to the reported input fails countable Cases 28-32 and 36, eligibility Case 26 and nh_fap Case 86.
• Removing the cap fails Case 36 (28,765.20 and 27,565.20 instead of 30,000 each).
• Dropping railroad retirement from the cap fails Case 28 (18,000) and Case 32 (39,565.20).
Scope: Part D stays a documented gap, since main has no Part D variable. Part A is not deducted, which follows page 17's list ("Part B and Part D").
S2. general_assistance is counted Fixed Code: P/income/unearned_sources.yaml drops it. Lines 30-32 cite page 15's APTD, ANB, FANF, OAA and RCA, the unchecked plan 1.9 box and RSA 165, and plan 1.9 (p. 6) is now a reference.
Tests: countable Case 23 (line 669) keeps $600 of general_assistance as an input and now expects 3,000. nh_fap Case 87 (line 2846) is the round-1 example: 19,500 is level C, $1,720.
Mutation: re-adding the source fails both cases (3,600; and 20,500, level D, $1,410).
S3. Six untested sources and two untested gates Fixed Sources: countable Case 35 (line 1062) gives s_corp_income 3,200, sstb_self_employment_income 1,600, rental_income 800, survivor_benefits 400, disability_benefits 200 and gambling_winnings 100, for a total of 6,300. The amounts are distinct, so dropping any entry changes the total.
Award gate: nh_fap Case 85 unit 2 (line 2768) is eligible through heat in rent, with an unknown county and so no estimate. It pays $0, and $750 (DD/A) without the gate.
NONE clause: eligibility Case 30 (line 1183) has heat in unsubsidized rent and NONE. It is ineligible, and eligible without the clause.
S4. Off-template descriptions; "user"/"approved" comments Fixed Descriptions:
• business_sources: "New Hampshire counts these business income sources under the Fuel Assistance Program."
• unearned_sources: the same wording, with "unearned".
• investment_disregard: "New Hampshire excludes this amount of annual interest and dividend income under the Fuel Assistance Program."
Template: all 14 descriptions now follow it, with the qualifications moved into comments.
Comments: a search of all 27 New Hampshire files finds no "user", "approv" or "requested" wording.
G1. Bedrooms understate rooms Partly adopted One-room floor: V/nh_fap_heating_cost.py:17-25 sets rooms to max_(bedrooms, where(~is_homeless, 1, 0)), citing page 18's one-room example. It is pinned by heating-cost Cases 5, 12, 13 and 15 and eligibility Cases 20 and 27.
DD tier: the largest one-room estimate is 2,000 × 7,672 / 10^6 × 64.706 = 992.85, so it always falls in tier DD.
Room-count input: declined, because it needs separate authorization. I accept that. The body gives the round-1 $750 against $2,350 example.
G2. Heat in rent Adopted Documentation: V/nh_fap.py and the body cite the PY2025 heat-in-rent matrices, their absence from PY26 and from PY2027 page 78, and the FFY2027 draft's wording. The no-bedrooms case is now paid (Case 85 unit 1, $750).
UNSPECIFIED: it stays $0, the wave's #9754 convention. See N-G2.
G3. SOLAR, OTHER, winter blend Adopted SOLAR: it takes the electricity cost, efficiency and 0.75 multiplier (P/payment/benefit_multiplier.yaml:26-27). nh_fap Case 88 pays $1,095, and heating-cost Case 8 gives 3,971.40.
OTHER: its basis (page 77 and the application) is stated.
Winter blend: the comment now says it is entered as FUEL_OIL or KEROSENE.
G4. Combined $50 exclusion Declined; documented Reason: P/income/investment_disregard.yaml now gives page 17's "$50 of earned interest and/or dividends annually" as the basis and states the pages 15-16 alternative. The body shows $1,825 against $2,150.
Assessment: it is a reading choice within one manual and is documented, so I accept it.
G5. Military basic pay Documented V/nh_fap_countable_income.py:29-31 cites the Virginia and Indiana tax-side convention.
G6. Source-conflict process Declined; reason accepted The floor binds only from 28 counted members. The candidate now lists its three code paths.
G7. References Partly adopted Plan and county map: P/smi_rate.yaml adds plan item 2.1 (p. 8). heating_degree_days.yaml adds the June 13, 2026 Wayback capture of the CAPNH page; I opened it, and it lists the five agencies' counties.
Manual: no PY2027 capture exists, so the body records the SHA-256 of the copy read. I accept that.
G8. Period 2026 Documented The body's "Years covered" section says period 2026 applies the PY2027 matrix and lists how the PY26 matrix differs.

New findings

Critical

None.

Should address

None.

Suggestions

  • N-G1. Railroad retirement in main's Medicare Savings Program income test.
    • The gap. msp_countable_income uses ssi_unearned_income, and main's SSI unearned list (gov.ssa.ssi.income.sources.unearned) has no railroad_benefits. Yet 20 CFR 416.1121(a) lists "railroad retirement annuities" as unearned income.
    • Who it affects. A railroad-only enrollee with resources within the MSP limit, including the default of zero assets, is treated as MSP-covered. That gives medicare_part_b_premium of 0, so no deduction.
    • Example (hand-traced; 2027, age 70, railroad retirement $24,000, no assets, fuel oil $2,000):
      • Head: 24,000 / 15,960 = 150.4%, level E: $1,100.
      • With railroad retirement counted: 1,980 a month after the $20 exclusion is above the $1,795.50 QI limit, so the premium is withheld. 21,565.20 is 135.1%, level D: $1,410.
    • Why only a suggestion. The PR documents this in the body, in Case 28's comment and in Axiom interpretation 13, and the fix belongs to main. No open issue covers it. Consider filing one.
  • N-G2. A backlog candidate for the heat-in-rent reading.
    • The tension. The new documentation records that the FY2026 plan (p. 9) and the FFY2027 draft's item 2.3 (p. 8) describe a reduced heat-in-rent benefit. Page 76's "Step 6 - Apply Heat Included in Rent Multiplier" also shows only the HIR = No factor of 1.0.
    • The PR's choice. It follows page 78's single schedule. Round 1 accepted that, and I still do.
    • Suggestion. A us-nh: entry in source_conflicts/candidates.yaml next to the federal-floor item would track it if the agency confirms a PY2027 multiplier. PY2025's was 0.625 (1,361 / 2,177). This is optional, as G6 was.
  • N-G3. The FFY2027 draft-plan URL.
    • The problem. V/nh_fap.py:34 cites energy.nh.gov's low-income-home-energy-assistance-detailed-model-plan.pdf#page=8. The agency reuses that URL every year: the Wayback Machine holds different documents there from February 2024, June 2024 and February 2025. The server refuses automated clients, and no capture of the FFY2027 draft exists.
    • Fix. Once the final FFY2027 plan is posted, cite it. The Clearinghouse path docs/2027/state-plans/NH_Plan_2027.pdf is a 404 today. Until then, record the draft's hash in the body, as was done for the manual.
  • N-G4. Housekeeping.
    • Stale CI note. The body's test plan and rulespec-us#1903 still say CI passed only at a7d76c39d8 and that round 2 was not run. CI is now green at c0cda9e671.
    • The 2027 premium. CMS publishes the 2027 Part B premium in November. When main adds it, the expectations pinned at $2,434.80 will need re-pinning: countable Cases 28, 29, 31, 32 and 36, eligibility Case 26, nh_fap Case 86, and the medicare_part_b_premium outputs. No action is needed now.

Verified

Hand-checked expectations. Every new or changed expectation is checked below. The guideline is $15,960 for one person (2026). The Part B premium is $202.90 × 12 = $2,434.80 (the 2026 standard carried forward; main has no 2027 value). The QI limit is 135% of the guideline: $1,795.50 a month at the 2026 figure, about $1,838 at the model's 2027 figure.

Case Arithmetic Expected
nh_fap 69 max(100, Belknap one room 2,000 × 6,610 / 10^6 × 28.881 / 0.78 = 489.50) → DD; 10,000 ≤ 11,970 → A; 750 > 489.50, so no cap 750 ✓
nh_fap 85 unit 1: 489.50, DD/A; unit 2: unknown county → no estimate, cost 0, eligible through heat in rent, gate → 0 [489.50, 0], [true, true], [750, 0] ✓
nh_fap 86 2,333.33 − 20 > QI → premium 2,434.80; 28,000 − 2,434.80 = 25,565.20 = 160.2% → E (23,940-27,930); $2,000 > 489.50 → BB 2,434.80; 25,565.20; 1,100 ✓
nh_fap 87 GA excluded; 19,500 = 122.2% → C (15,960-19,950); BB/C 19,500; 1,720 ✓
nh_fap 88 SOLAR estimate 2,000 × 6,610 / 10^6 × 64.706 = 855.41 < 1,200 → CC; CC/A 1,460 × 0.75 1,200; 1,095 ✓
countable 23 1,200 + 1,200 + 600; $600 GA not counted 3,000 ✓
countable 27 1,000 − 20 = 980 ≤ QI → MSP pays → premium 0 0; 12,000 ✓
countable 28 assets 20,000 > 9,950 → premium 2,434.80; 18,000 − 2,434.80 15,565.20 ✓
countable 29 24,000 − 2,000 − 2,434.80 19,565.20 ✓
countable 30 24,000 − 23,000 − 2,434.80 < 0 → floor 0 ✓
countable 31 1,980 > QI; 24,000 − 2,434.80 21,565.20 ✓
countable 32 42,000 − 2 × 2,434.80 = 37,130.40; person 3: 980 ≤ QI → 0 [2,434.80, 2,434.80, 0]; [37,130.40, 12,000] ✓
countable 35 3,200 + 1,600 + 800 + 400 + 200 + 100 6,300 ✓
countable 36 31,200 − min(2,434.80, 1,200) = 30,000; 30,000 − min(2,434.80, 0) = 30,000 [30,000, 30,000] ✓
eligible 20 no county → Belknap; 0 bedrooms → 1 room: 489.50 > 0 true ✓
eligible 21 unknown county → no estimate; $0.01 bill > 0 true ✓
eligible 26 50,000 > 48,713 and above the 17,556 floor; 47,565.20 ≤ 48,713 [0, 2,434.80]; [50,000, 47,565.20]; [false, true] ✓
eligible 27 unit 2: Coos, 1 room: 2,000 × 7,672 / 10^6 × 28.881 / 0.78 = 568.14 > 0; unit 7: unknown county, no bill → 0 [true, true, false × 6, true] ✓
eligible 30 heat in rent passes the cost and housing tests; NONE fails false ✓
heating cost 5 568.14 with no bill; 650 and 750 bills exceed it [568.14, 650, 750] ✓
heating cost 8 SOLAR: 2,000 × 4 × 7,672 / 10^6 × 64.706 / 1 = 3,971.40 > 650; OTHER bill 250; UNSPECIFIED and NONE 0 [250, 3,971.40, 0, 0] ✓
heating cost 12, 13 negative bill floors at 0; 500 bill; both below 568.14 568.14; 568.14 ✓
heating cost 15 homeless → 0 rooms → no estimate; bill 0 0 ✓

Mutation check of the fix commit (the Utah and North Dakota round-2 rule):

Condition What fails if it is removed
One-room floor Heating-cost Cases 5, 12 and 13; eligibility Cases 20 and 27; nh_fap Case 85 unit 1 (cost 0 → $0)
Homeless exception Heating-cost Case 15 (568.14)
Part B cap; railroad retirement in the cap Countable Case 36; Cases 28 and 32
heating_cost > 0 (was pinned by eligibility Case 20) Eligibility Case 27 unit 7
| heat_in_rent (was pinned by eligibility Case 22, which now has a Belknap estimate) nh_fap Case 85 unit 2
SOLAR rows nh_fap Case 88 (multiplier) and heating-cost Case 8 (cost and efficiency)

Three floors that no test pins are only defensive, because the floored values cannot go negative in practice:

  • max_(social_security, 0) and max_(railroad_benefits, 0) in the cap;
  • max_(heating_expense, 0), since the estimate is never negative.

Merge resolution. I checked the hand-resolved policyengine_us/programs.yaml conflict (two hunks):

  • Coverage line. It is main's line (AR … TX, VA, with AZ and IA) plus NH.
  • State block. It keeps VA and appends the NH block once.
  • Diff. git diff 86b04f3816 c0cda9e671 -- policyengine_us/programs.yaml shows only the NH additions.
  • Counts. LIHEAP state_implementations has 25 entries on the merged main and 26 at the head, so no sibling entry was dropped or duplicated.
  • Candidates. source_conflicts/candidates.yaml merged with additions only: the single us-nh:fap-income-floor-vs-smi-limit, sorted before us-nh:ssp-….

Guards on the merged tree (PolicyEngine/main + head, tree 5e7fc48593):

Other checks.

  • NH Add New Hampshire state supplementation for 2026 #10156 pattern: still safe. The fix adds no keyed lookup. SOLAR is in both fuel_cost_per_mbtu and fuel_efficiency, so the fuel key resolves in both. is_homeless and bedrooms are Household variables used only arithmetically.
  • References: the plan's item 1.9 is on p. 6 and item 2.1 on p. 8 (both checked against the plan text). The multi-page variable references carry # PDF pages comments.
  • Counts: 181 cases (88 / 36 / 30 / 15 / 4 / 8), matching the body and the issue.
  • Unchanged items: nh_ssp stays listed (item 17), the changelog fragment is unchanged, and test periods are plain.
  • No local runs: CI ran all six New Hampshire files green at this head.

Axiom

axiom: TheAxiomFoundation/rulespec-us#1903 queued: valid and current.

  • Status: the issue is OPEN, labelled pe-parity, and was updated 2026-10-27 at 19:48 UTC.
  • Head: it describes head c0cda9e671 (6e975b1605 on a7d76c39d8) with 181 cases.
  • Interpretations updated:
    • 1: the one-room floor.
    • 5: SOLAR at electricity values.
    • 13: the computed Part B premium net of MSP, capped at Social Security plus railroad retirement; the reported input unused; the railroad-retirement MSP gap.
    • 15: general assistance excluded under page 15, plan 1.9 and RSA 165:1.
  • Companion tests: they mirror every new case: 7 (SOLAR), 9(a), 12(h)/(i)/(l), 13(g) and 14(a)-(f). Their expected values come from the manual, the plan and CMS's 2026 premium fact sheet.
  • Remaining staleness: only the CI note (N-G4).

CI

gh pr checks 10343 at head c0cda9e671 (run 40572347640, started 2026-10-27 19:48 UTC): 35 pass, none failing or pending.

  • Coverage: all Baseline shards (including the four state shards that run the New Hampshire YAML files), the Contrib shards, Rest Python shards 1-6, Microsimulation, Household API Partners, PR validation, Quick Feedback and Codecov.
  • Known failure: Rest (Python shard 3) passed, so the known main-caused test_social_security_input_state_machine failure did not occur on this run.

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 2: 0 critical, 0 should. All four round-1 shoulds are fixed and pinned. Full report in the comment above.

@hua7450
hua7450 merged commit a733a78 into main Oct 27, 2026
35 checks passed
@hua7450
hua7450 deleted the nh-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 New Hampshire LIHEAP regular heating assistance

2 participants