Skip to content

Add Vermont Seasonal Fuel Assistance - #10436

Open
hua7450 wants to merge 3 commits into
mainfrom
vt-liheap-wave1d
Open

hua7450 wants to merge 3 commits into
mainfrom
vt-liheap-wave1d

Conversation

@hua7450

@hua7450 hua7450 commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Adds Vermont Seasonal Fuel Assistance, the state's LIHEAP regular heating benefit administered by the Department for Children and Families (DCF) Economic Services Division, for the 2025-26 and 2026-27 heating seasons (model years 2026 and 2027). The model counts citizens and qualified noncitizens in household size (all members' income still counts), tests monthly gross income against the lowest of the printed 185% monthly limit, exactly 185% of the poverty guideline and the LIHEAP maximum, and pays the heat cost percentage for the household's percentage of poverty (read from the monthly dollar bands of the procedures table) times the standard heating cost for its fuel, dwelling type and bedrooms, times the payment rate, adjusted for subsidized housing and heat included in rent, rounded down to whole dollars.

Fixes #10426

The payment rate is an estimate. DCF sets it by procedure each season and does not publish it; 51% is inferred from the FFY2026 plan's $2,089 maximum (floor(4,552 x 0.90 x 0.51), the only whole percent that reproduces it) and is reused for 2026-27 until DCF publishes that season's rate.

Regulatory authority

Eligibility

Requirement Source How modeled
Vermont residence Rule 2903 defined_for = StateCode.VT
At least one citizen, national or qualified immigrant Rules 2903(a)(2), 2907; P-2910 vt_liheap_household_size sums is_citizen_or_legal_immigrant; size 0 is ineligible
Gross income at most 185% FPG and the LIHEAP maximum 33 V.S.A. 2604(a); rules 2913(b), 2914(a)(3); P-2913 vt_liheap_income_limit = min(12 x the P-2913 monthly 185% amount, 185% of the annual guideline, greater of 150% FPG and 60% SMI); one person in 2026: $2,412 a month ($28,944 a year, the printed amount); two people: $39,127.50 a year (the statute's 185%, below the printed $3,262 a month); the LIHEAP maximum binds from size 10 in FFY2026 (see source conflicts)
Primary heating fuel listed in the cost tables Rules 2901(a)(14), 2916(b) Natural gas, propane, oil, kerosene, electricity, wood and coal; SOLAR uses the electricity row (grid-tied home with electric heat); NONE is ineligible; OTHER and UNSPECIFIED pay nothing except the $21 subsidized heat-in-rent benefit, which needs no fuel
Categorical (SNAP) eligibility Plan 1.4 Changes certification periods only; nothing to model

Countable income

Item Source How modeled
Earned income Rules 2911.1, 2911.3 Wages and net self-employment, including partnership and S corporation income, each floored at zero
Unearned income Rule 2911.2 SSI, the state AABD supplement (vt_aabd), General Assistance, Reach Up (tanf, once), pensions, military retirement and survivor annuities, retirement distributions, veterans, disability, workers' and unemployment compensation, Social Security (gross), other survivor benefits, strike benefits, rental income, child support, alimony, interest, dividends, trust income and other direct money payments (miscellaneous_income)
Child student earnings Rule 2912(a)(12) Excluded for a dependent under 18 in K-12 school
No deductions Rule 2910; P-2910 Gross income

Benefit

  • Heat cost percentage by percentage of poverty: 90% up to 74%, then 87, 84, 81, 78, 75, 72, 69, 66, 33, 30 and 27% for each further 10-point band up to the limit (rule 2915(d)). Rule 2915(b) takes the percentage of poverty from the P-2914A table, so monthly income is read against its monthly dollar band tops; income at or below a top stays in that band.
  • Standard heating cost by fuel, dwelling type (single-family or mobile home; multi-family) and bedrooms (1 to 4+; zero bedrooms use the 1-bedroom column), $923 to $4,552 (P-2916; unchanged for 2026-27). New input vt_liheap_dwelling_type, defaulting to single-family or mobile home.
  • Payment rate 0.51 (estimate, see above).
  • Housing: 100% when paying a supplier; 55% in Section 8 or other public housing; 30% with a $50 minimum when heat is in an unsubsidized rent; $21 when heat is in a subsidized rent (rule 2914(c); 33 V.S.A. 2605(e)-(f)).
  • Rounded down to whole dollars (rule 2914(d)).

Not modeled

Item Source Treatment
December, January and February application reductions (80, 60, 40%) Rule 2914(b) No application-month input; 100% assumed
Roomer or boarder households ($21), excludable members, 150-night residency Rules 2903, 2914(c)(5) No inputs
30-day income lookback with 4.3 and 2.15 conversions Rule 2911(c) Annual income divided by 12, compared with the monthly tables
Heat and Eat $21 for SNAP households 33 V.S.A. 2605(h) SNAP nominal payment, outside regular heating
2026-27 percentage-of-poverty table P-2914A Not yet published; the 2025-26 construction is applied to the 2026 guideline. The 2026-27 limits are the published DCF amounts
Crisis fuel, weatherization, mid-season rate changes Rules 2917, 2920 Out of scope

Monthly tables from the guideline

P-2913 and P-2914A print monthly dollars for sizes 1-15 but do not state how they are derived. The model builds them from the federal guideline instead of storing the tables (vt_liheap_fpg, new parameter fpg_table_size):

  • Monthly guideline: the HHS guideline divided by 12 and rounded half up to the dollar for sizes 1-8 (the sizes HHS lists), plus the additional-person amount divided by 12 and rounded half up for each person above eight (2025: $458).
  • Income limit: 185% of the monthly guideline, rounded half up to the dollar. This reproduces all 15 P-2913 amounts (2025-26) and all 15 DCF amounts (2026-27). The model then caps it at exactly 185% of the annual guideline, because 33 V.S.A. 2604(a) sets 185% as a ceiling (see source conflicts). The cap binds where the printed amount rounds above 185%: in 2026, sizes 2, 3, 5, 6, 8 and 10; in 2027, sizes 1, 3, 4, 6 and 7.
  • Band tops: each cutoff (74%, 84%, ... 174%) times the monthly guideline, rounded up to the dollar. This reproduces all 165 printed P-2914A tops for sizes 1-15.

The construction is a numerical inference from the printed tables, not a stated rule. Rounding each size's own guideline instead would miss 53 of the 55 band tops for sizes 11-15 by $1-$4, and some limits for sizes 9-15.

Source conflicts

  • us-vt:liheap-income-maximum-rounding (added to source_conflicts/candidates.yaml): P-2913 prints 185% of the monthly guideline rounded half up to the dollar, which is above 185% of the guideline for some sizes. 33 V.S.A. 2604(a) requires income "no greater than 185 percent of the federal poverty level", so the model applies the exact 185% where it is lower (two people in 2026: $39,127.50 a year rather than $3,262 x 12 = $39,144) and the printed amount elsewhere (one person: $2,412 a month against $2,412.71).
  • us-vt:liheap-income-maximum-large-households (added to source_conflicts/candidates.yaml): P-2913 prints 185% limits for sizes 10-15 that exceed the LIHEAP maximum in 33 V.S.A. 2604(a) and rule 2913(b); the model follows the statute (size 10 in FFY2026: $113,748 a year rather than $120,528). The plan's 60% SMI field is below the rule and is not followed on its own.

Shared parameter

Adds gov.hhs.liheap.fpg_limit (150%, 42 U.S.C. 8624(b)(2)(B)(i)) next to smi_limit. Its value starts 1981-10-01, because Pub. L. 97-35 set the 150% ceiling from fiscal year 1982. The Maine HEAP branch adds a byte-identical copy.

Test plan

45 YAML cases in 9 files cover household size with a mixed-status member, income sources, the per-source floor, the child-student exclusion and a 16-year-old student who is not a tax dependent and whose earnings count, the monthly guideline (a half-dollar rounding and an eleven-person household), the income limit at sizes 1, 10 and 13 in 2026 and size 10 in 2027, the printed monthly limit and $1 a month above it, a two-person income under the printed limit but above the statute's 185%, a printed band top and $1 a month above it, the 66% to 33% drop, every fuel row and both dwelling types, the bedroom bounds, all four housing treatments with the $50 minimum, rounding down, and four end-to-end households. Expected values are hand-computed from the printed rule and procedure tables. The cases were not re-run locally after the review fixes or the monthly-table change; CI validates them.

  • 45 YAML tests pass locally (policyengine-core test on the program folder, 2026-10-23, after merging main and adding vt_aabd)
  • CI passes
  • Changelog fragment in changelog.d/

axiom: TheAxiomFoundation/rulespec-us#1897 queued

🤖 Generated with Claude Code

hua7450 and others added 3 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 27, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Add Vermont Seasonal Fuel Assistance

PR #10436 (author: hua7450), head d1c6ff4ffe. This is a first review. The PR adds Vermont Seasonal Fuel Assistance, the state's LIHEAP regular heating benefit run by DCF's Economic Services Division, for the 2025-26 and 2026-27 seasons (model years 2026 and 2027). It adds 16 parameters (one of them the shared federal gov.hhs.liheap.fpg_limit), 9 variables, 9 test files (45 cases), a programs.yaml entry, two source_conflicts/candidates.yaml entries and a changelog fragment. The PR has no comments or reviews yet. It touches no partner test file.

Path prefixes:

  • P/ = policyengine_us/parameters/gov/states/vt/dcf/liheap/
  • V/ = policyengine_us/variables/gov/states/vt/dcf/liheap/
  • T/ = policyengine_us/tests/policy/baseline/gov/states/vt/dcf/liheap/

Sources read for this review:

Summary

What is right. The published 2025-26 rules are encoded faithfully, and the PR is unusually careful:

  • Tables. All 48 standard heating cost cells match P-2916 and the 2026-27 matrix. The 12-row heat cost percentage table matches rule 2915(d), P-2915 and the matrix. The housing factors (100%, 55%, 30% with a $50 minimum, and $21) and the round-down match rule 2914(c)-(d).
  • Monthly construction. The whole-dollar monthly guideline rebuilds the printed tables. I spot-checked P-2913 at sizes 1, 2, 10, 13 and 15, the DCF 2026-27 limits at sizes 1, 2, 3, 4, 10 and 15, and P-2914A tops at sizes 1, 4, 13, 14 and 15 across several bands. Every one matches.
  • Income. The model counts gross income with no deductions (rule 2910, P-2910). Social Security counts gross: plan item 1.9 checks "Including MediCare deduction". Each source is floored per person, and ineligible members' income counts while they are left out of the size (P-2910).
  • Statute-first choices. The exact 185% cap and the LIHEAP maximum for large households follow 33 V.S.A. 2604(a) and rule 2913(b) over the P-2913 table. Both choices are recorded as backlog candidates.
  • Tests and guards. I hand-checked 15 expectations, and CI ran all 45 cases green. The PR reads total_unemployment_compensation (Integrate modeled state unemployment insurance into income flows #9558), military_retirement_income (Count military SBP annuities in state benefit income lists #10317) and vt_aabd (item 17). Its comments pass the Tie source-conflict markers to the register shared with Axiom #10287 ratchet on the merged tree, and its state-gated keys are safe (the Add New Hampshire state supplementation for 2026 #10156 pattern).

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

  • S1. Railroad Retirement annuities are not counted. Rule 2911.2(b)(2)-(4) counts annuities, pensions and retirement benefits, and 3SquaresVT counts them too, but railroad_benefits is missing from the unearned list. Example: a 63-year-old with $21,600 of railroad annuity gets $1,433 instead of $1,098; with $30,000, $1,433 instead of $0.
  • S2. Untested sources. 22 of the 26 income-source entries never take a positive value in any test. The unearned per-source floor and the monthly add() path are never exercised either.
  • S3. The 2026-27 payment rate is stale. The model reuses the inferred 51% for model year 2027, and the registry lists 2027 as verified. Since then, Vermont has funded a larger 2026-27 benefit for oil and kerosene households, about 62% of need against the usual 45%. Those households are over half of recipients.

There are also five suggestions:

  • the $50 floor for unsubsidized heat-in-rent households whose fuel is UNSPECIFIED;
  • farm losses under the 3SquaresVT method;
  • one stale row in the Axiom issue;
  • the candidates' prs field and two plan-vs-rule choices;
  • small documentation fixes.

Status.

  • CI: 27 pass and 3 skip; none failed. States-shard-1 ran all 45 Vermont cases.
  • Branch: 3 commits ahead of main and 57 behind. The only conflict is in programs.yaml, against the merged VA/AZ/IA LIHEAP entries (a branch note).

Critical (must fix)

None.

Should address

S1. Railroad Retirement annuities are not counted as income

  • Where: P/income/sources/unearned.yaml:11-43 lists pension_income, military_retirement_income, retirement_distributions, social_security and survivor_benefits, but not railroad_benefits. V/vt_liheap_income.py:30-32 sums only that list.
  • Sources:
    • Rule 2911.2(b) (p. 28): unearned income includes "2. Annuities; 3. Pensions; 4. Retirement benefits; ... 9. Survivors or social security benefits". A Railroad Retirement Board annuity is a retirement annuity.
    • Rule 2910(b) (p. 25) computes gross income "based on the same rules used to calculate income in the 3SquaresVT program". P-2910 adds: "Countable vs. excludable income follows 3SquaresVT." SNAP counts annuities, pensions and retirement benefits (7 CFR 273.9(b)(2)(ii)).
    • Plan item 1.9 (p. 6) checks "Retirement / pension benefits".
  • Why the input is the right one:
    • On main, railroad_benefits is the person-level annual amount of RRB benefits. It is not part of pension_income, social_security or retirement_distributions, so adding it double counts nothing.
    • Fifteen merged LIHEAP lists include it (AL, AR, AZ, FL, GA, IA, KY, MS, NY, OH, OK, OR, RI, SD, VA).
    • The same author's Maine sibling has the same gap, rated should (Add Maine HEAP Fuel Assistance #10437 S1).
    • Under plan 1.9's "Including MediCare deduction", the annuity counts gross, like Social Security.
  • Effect (hand trace of the head for 2026). Take a single person aged 63, not disabled, so SSI and AABD are zero. Railroad annuity $21,600 ($1,800 a month). Fuel oil, single-family home, 2 bedrooms, buys from a dealer.
    • Head: countable income is 0, so the household is in the first band (90%): floor(3,123 × 0.90 × 0.51) = $1,433.
    • Rule: $1,800 a month is above the size-1 top of $1,748 (ceil(1.34 × 1,304)) and at or below $1,878 (ceil(1.44 × 1,304)), so 135%-144% gives 69%: floor(3,123 × 0.69 × 0.51) = floor(1,098.98) = $1,098.
    • With $30,000 of railroad annuity: $2,500 a month is above P-2913's $2,412, so the household is ineligible and gets $0. The head still gives $1,433.
  • Severity: should, as in Add Maine HEAP Fuel Assistance #10437 S1 (a countable source missing from the list).
  • Fix: add railroad_benefits to the unearned list next to pension_income, cite rule 2911.2(b)(2)-(4) in the comment, and add a test like the example above. Update the body's income table and the Axiom issue.

S2. 22 of the 26 income-source entries, the unearned per-source floor and the monthly add() path have no test that would fail if they were dropped

  • Where: P/income/sources/earned.yaml:8-13 (5 entries), P/income/sources/unearned.yaml:12-43 (21 entries), and V/vt_liheap_income.py:28 and :32.
  • Untested entries. No case gives these a positive value:
    • Earned: self_employment_income appears only as −$4,200 in T/vt_liheap_income.yaml Case 2. That floors to 0, which is the same result as dropping the entry. sstb_self_employment_income, farm_operations_income and partnership_s_corp_income never appear.
    • Unearned:
      • ssi (pinned at 0 in 11 cases, positive in none);
      • vt_aabd (the item 17 supplement, added in the last commit);
      • general_assistance, pension_income, military_retirement_income, veterans_benefits, disability_benefits, workers_compensation and total_unemployment_compensation;
      • survivor_benefits, strike_benefits, rental_income, farm_rent_income and alimony_income;
      • interest_income, ordinary_dividend_income, estate_income and miscellaneous_income.
  • Tested entries: wages, Social Security, retirement distributions (through taxable_ira_distributions in income Case 4) and child support, plus tanf.
  • The floors.
    • Earned. The earned floor (line 28) is pinned: income Case 2 expects 18,746, not 14,546.
    • Unearned. The unearned floor (line 32) is not. No case has a negative unearned source, such as a rental, farm-rent or estate loss, so removing that max_ fails nothing. That floor is what keeps a rental loss from offsetting wages (the wave's rental-loss item).
  • The monthly path. Line 31's comment says add() "also sums monthly sources such as vt_aabd", but no case gives a monthly source a value.
  • Severity: should, as in Add Iowa LIHEAP regular heating assistance #10292 S4, Add Virginia EAP Fuel Assistance (LIHEAP regular heating) #10303 S4 and Add New Mexico LIHEAP regular heating assistance #10431 S3 (the mutation rule).
  • Fix:
    • Every source. Add one income case that gives every listed source a distinct positive amount. Include unemployment_compensation (so total_unemployment_compensation is exercised), military_retirement_pay_survivors, a monthly vt_aabd and an ssi amount, and, after S1, railroad_benefits.
    • Rental loss. Add a case with wages and a rental loss that expects the wages alone.
    • Self-employment. Give self_employment_income a positive profit somewhere.

S3. Model year 2027 reuses the 2025-26 payment rate, but Vermont has since funded a larger 2026-27 benefit for oil and kerosene households

  • Where:
    • P/payment/payment_rate.yaml has one value, 2025-10-01: 0.51, which "is reused for the 2026-27 season until DCF publishes that season's rate".
    • programs.yaml:1163 (head) sets verified_years: "2026-2027", and the body claims both seasons.
  • Sources:
    • Rule 2914(a)(8) (p. 39) sets the payment rate "by procedure based on the number of eligible households and the amount of funds available".
    • P-2914B: "This payment rate changes every year based on funding availability."
    • The DCF memo of September 16, 2026 (pp. 1 and 4): "the benefit amounts for the 2026-2027 heating season have not yet been determined"; DCF "expects to have the 2026-2027 benefit amounts determined later in October". It also reports that SFY26 benefits covered "approximately 45% of estimated heating need". Oil and kerosene prices are up 20-24% since April. Oil and kerosene households are 52% of recipients.
    • VTDigger, October 23, 2026 (article): a $15.3 million state package increases "seasonal aid that Vermont can provide this year for households heating with oil and kerosene". The program "can now cover about 62% of fuel needs this year for Vermonters using oil or kerosene", against about 45% before.
    • Federal funding. IIJA LIHEAP supplemental funding ended with FFY2026 (checklist item 12), so the federal share of 2026-27 funding also differs from 2025-26.
  • Why it matters:
    • The 51% is already an inference for 2025-26: floor(4,552 × 0.90 × r) = 2,089 pins r between 50.99% and 51.01%. That is a reasonable, documented estimate.
    • For 2026-27, the newer evidence points to a different, fuel-specific rate for the largest group of recipients. A flat 51% therefore understates their benefit. The registry nonetheless lists 2027 as verified.
    • DCF must issue fuel dealer benefits by November 30 (P-2914C), so the actual rate should be known soon.
  • Effect (illustrative). Take integration Case 1's household (Social Security of $1,400 a month, oil, single-family, 2 bedrooms) in 2027. On the 2026 guideline ($1,330 a month) it falls in the 105%-114% band (78%), and the head pays floor(3,123 × 0.78 × 0.51) = $1,242. If the oil rate rises in proportion to the reported coverage (62 / 45 ≈ 1.38), the benefit is about $1,710. The exact figure depends on the rate DCF sets.
  • Severity: should. The 2027 claim rests on a carried-forward estimate that newer authoritative reporting contradicts for most recipients, and the registry and body overstate how verified 2027 is.
  • Fix: I recommend (a).
    • (a) Narrow the claim for now. Set verified_years: "2026". Say in the registry note, the payment_rate.yaml comment and the body that the 2026-27 rate is not yet known and that the state funded a larger oil and kerosene benefit. Open a follow-up issue to add the 2026-27 rate or rates from 2026-10-01. That may need a fuel-group breakdown of payment_rate.
    • (b) Encode the actual rate. If DCF publishes the 2026-27 rate before merge, encode it, fuel-specific if needed, keep 2027, and add a 2027 oil case.
    • Either way, update Axiom interpretation 1.

Suggestions

G1. Unsubsidized heat-in-rent households with UNSPECIFIED fuel get $0, although the $50 floor needs no fuel.

  • The head.
    • V/eligibility/vt_liheap_eligible.py:34 makes a household eligible when heating_cost > 0 or heat is in a subsidized rent.
    • So a subsidized tenant with heat in rent and UNSPECIFIED fuel gets $21 (T/vt_liheap.yaml Case 4).
    • An unsubsidized tenant in the same position gets $0 (T/eligibility/vt_liheap_eligible.yaml Case 4).
  • The rule and the input.
    • Rule 2914(c)(2)(i) (p. 40) guarantees unsubsidized heated renters "a minimum of $50.00".
    • The heating_type documentation on main says UNSPECIFIED "is also the right value when heat is included in rent and the fuel is unknown".
    • The head gives the lower-need subsidized tenant more than the unsubsidized one.
  • Example. Eligible Case 4's renter ($1,300 a month of Social Security) gets $0. With any listed fuel, the renter would get at least $50, for example floor(967 × 0.81 × 0.51 × 0.30) = $119 for multi-family electricity.
  • Precedent. The wave accepted "UNSPECIFIED pays nothing" for Arizona (Add Arizona LIHEAP Standard benefit #10293 G2), so this is optional.
  • Fix: pay at least heat_in_rent_minimum to unsubsidized heat-in-rent households whose fuel is UNSPECIFIED, treating them like the subsidized ones. Otherwise, record the asymmetry in the body.

G2. Farm losses under the 3SquaresVT method.

  • The rules. Rule 2911.3(b) (p. 29) computes self-employment income "using the same rules and procedures used to calculate self-employment income for the 3SquaresVT program". Under 7 CFR 273.11(a)(2)(ii), a farmer with $1,000 or more of gross proceeds offsets a farm loss against other income.
  • The head. P/income/sources/earned.yaml floors farm_operations_income at zero, so the loss offsets nothing. That is stricter than the rule. The input has no gross-proceeds field.
  • Fix: keep the floor, but state the farm-loss exception in the comment (lines 2-5) and in Axiom interpretation 7 as a known approximation.

G3. One row of rulespec-us#1897 lags the head. The issue names head d1c6ff4ffe, and its PR summary mentions the vt_aabd commit. Its required-outputs row for vt_liheap_income still lists the unearned sources without vt_aabd. Add it when you update the issue for S1-S3.

G4. Backlog candidates.

  • The prs field. Both us-vt:liheap-income-maximum-* entries have prs: [] and the note "Code not yet on main". Most entries list their PR. Add https://github.com/PolicyEngine/policyengine-us/pull/10436.
  • Two more plan-vs-rule choices, as in Add New Mexico LIHEAP regular heating assistance #10431 G2:
    • Strike pay. Plan item 1.9 leaves "Strike Pay" unchecked, but rule 2911.2(b)(10) counts strike benefits. The PR follows the rule.
    • The income limit. Plan item 2.1 (p. 9) reports 60% of SMI for all household sizes. The body notes this, but neither candidate lists the plan as a source.
  • Fix: record the strike pay choice, and add the plan to the large-households candidate. Both follow the statute-first rule.

G5. Small documentation fixes.

  • P-2914D anchors. The worked example runs over pages 6-9 ("Drop cents = $473" is on p. 9). T/vt_liheap.yaml:9-11 says "#page 6-8", and V/vt_liheap.py:15 says "PDF pages 7-9". Use 6-9 in both.
  • The training ratable. P-2914D uses "29% as the ratable reduction" for training (p. 8). Axiom interpretation 14 notes this. A one-line note in P/payment/payment_rate.yaml would stop readers from taking 29% for the real rate.
  • The body. It still has "[ ] CI passes" unchecked; CI is green. It says both that the cases "were not re-run locally after the review fixes" and that 45 tests passed locally after merging main. Reconcile the two.

Verified correct

Values against sources:

  • Heating costs. All 48 cells in P/payment/heating_cost.yaml match P-2916 (2025-26) and the 2026-27 matrix, pages 2-3: 6 fuel rows × 4 bedroom columns × 2 dwelling groups. The 4+ column, the shared firewood, pellets and coal row, and the dwelling groups match P-2927B's housing codes, with modular homes counted as single-family.
  • Heat cost percentages. The 12 bands (90 down to 27) match rule 2915(d) (p. 42), P-2915 and matrix page 1. The 90% cap of rule 2915(c) holds by construction.
  • Housing factors. 100%, 30% with a $50 minimum, 55% and $21 match rule 2914(c)(1)-(4) (pp. 40-41). The round-down matches 2914(d). 33 V.S.A. 2605(e)-(f) carries the $50 and $21.
  • The income rate. 185% matches 33 V.S.A. 2604(a), rule 2913(b) (p. 38) and P-2910.
  • The child exclusion. Under 18, half-time K-12 and living with a parent (rule 2912(a)(12), p. 34). Income Cases 3 and 5 pin all three conditions.
  • The payment rate. floor(4,552 × 0.90 × 0.51) = floor(2,089.37) = 2,089, the plan 2.6 maximum (p. 10); the minimum is $21. 50% gives $2,048 and 52% gives $2,130. The rate is disclosed as an estimate (S3 concerns only 2027).
  • Monthly guideline construction.
    • 2025 guideline (sizes 1-8 and the +$458 increment): 1,304; 1,763; 2,221; 2,679; 4,513.
    • P-2913 at 185% half up: size 1 2,412, size 2 3,262, size 10 10,044 (4,513 + 2 × 458 = 5,429), size 13 12,586 (6,803), size 15 14,280 (7,719). All match.
    • DCF 2026-27 at 185%: size 1 2,461, size 2 3,336, size 3 4,212, size 4 5,088, size 10 10,340 (4,643 + 2 × 473 = 5,589), size 15 14,715. All match.
    • P-2914A tops (each cutoff × M, rounded up): size 1 965 / 1,357 / 1,878 / 2,009 / 2,139; size 4 1,983; size 13 9,797 / 10,477 / 11,157; size 14 11,182 / 11,909; size 15 5,713. All match the printed table.

Income definition:

  • Gross income. Rule 2910(a) counts gross income, and P-2910 says "Fuel has no deductions for income". Plan 1.8 checks "Gross Income".
  • Social Security. Plan item 1.9 (rendered p. 6) checks "Including MediCare deduction" and leaves "Excluding MediCare deduction" unchecked. So Social Security counts gross, and no Medicare netting is due, unlike Iowa and Virginia. 3SquaresVT counts it gross too.
  • Household size. P-2910: "if the member is ineligible, e.g., undocumented immigrant, then that member's income is counted, but the person is not counted in the total number of household members". vt_liheap_household_size adds is_citizen_or_legal_immigrant (8 U.S.C. 1641), matching rule 2907(e).
  • Reach Up. It is counted once through tanf, whose state list on main includes vt_reach_up.
  • Per-source floors. Each source is floored per person. This matches SNAP's treatment of non-farm self-employment and rental losses (see G2 for farm losses). Rule 2911.2(b)(13) counts rent "minus costs".
  • Wage and premium handling (OK Add Oklahoma state supplementation for 2026 #10165 / checklist item 14): not applicable. Vermont has no deductions, and employment_income is gross wages.

Hand-checked expectations (15, all match):

  1. Integration Case 1 (SS 16,800, size 1, 2026). $1,400 a month falls between the $1,357 and $1,487 tops, so 78%. Cost 3,123. floor(3,123 × 0.78 × 0.51) = 1,242. Limit 28,944: min(2,412 × 12, 28,952.50, max(23,475, floor(78,992 × 0.52) = 41,075)).
  2. Integration Case 2 (wages 44,990.40, size 4). $3,749.20 falls between $3,590 and $3,858, so 69%. Cost 1,148 (multi-family gas). floor(1,148 × 0.69 × 0.51 × 0.30) = floor(121.19) = 121. Limit 59,472 (4,956 × 12, below 59,477.50 and 78,992).
  3. Integration Case 3 (income 23,695.20, size 3). $1,974.60 falls between $1,866 and $2,088, so 84%. Cost 3,379. floor(3,379 × 0.84 × 0.51 × 0.55) = floor(796.16) = 796. Limit 49,302.50: the 185% cap beats the printed 4,109 × 12 = 49,308.
  4. Integration Case 4 (2027, SS 28,200, size 2). M = 1,803. $2,350 falls between $2,236 and $2,417, so 72%. Cost 1,658. floor(1,658 × 0.72 × 0.51) = 608. Limit 40,032 (3,336 × 12, below 40,034 and floor(81,195 × 0.68) = 55,212).
  5. vt_liheap.yaml Case 1 (wages 20,995.52, size 3). $1,749.63 falls between $1,644 and $1,866, so 87%. floor(2,347 × 0.87 × 0.51) = 1,041.
  6. Case 2 (SS 13,800, public housing). $1,150, so 84%. floor(967 × 0.84 × 0.51 × 0.55) = floor(227.84) = 227. The is_in_public_housing arm is pinned.
  7. Case 3 (wages 28,119). $2,343.25 is above $2,269, so 27%. 967 × 0.27 × 0.51 × 0.30 = 39.95 < 50, so 50.
  8. Case 4 (Section 8, heat in rent, UNSPECIFIED). Eligible through the subsidized heat-in-rent arm, so 21.
  9. Income limit Case 2 (size 10, 2026). Guideline 65,150, 185% 120,527.50, size factor 0.52 + 5 × 0.16 + 4 × 0.03 = 1.44, 60% SMI floor(78,992 × 1.44) = 113,748.
  10. Income limit Case 3 (size 13). 150% × 81,650 = 122,475 beats floor(78,992 × 1.53) = 120,857, so 122,475.
  11. Income limit Case 4 (size 10, 2027). floor(81,195 × 1.44) = 116,920, below 124,080 and 124,098.
  12. Eligible Case 7 (size 2, wages 39,132). Limit min(39,144, 39,127.50, 53,714) = 39,127.50, and 39,132 is above it, so not eligible.
  13. Heat cost percentage Cases 2 and 3 (size 2). The top is ceil(0.74 × 1,763) = 1,305. $1,305 gives 90% and $1,306 gives 87%.
  14. Household size Case 1. Size 2 with the undocumented spouse excluded. Income 31,493.28 ($2,624.44) falls between $2,539 and $2,716, so 66%.
  15. vt_liheap_fpg.yaml Case 2 (size 11). 4,513 + 3 × 458 = 5,887, × 12 = 70,644.

Case count. 7 + 4 + 4 + 6 + 11 + 5 + 2 + 1 + 5 = 45, as the body says. CI's per-file results match.

Mutation check (beyond S2). Each branch has a case that would fail if it were removed:

  • has_heat (eligible Case 6: NONE with subsidized heat in rent);
  • size > 0 (Case 3);
  • both subsidized arms (integration Case 3 and vt_liheap.yaml Case 2);
  • the heat-in-rent 30% rate (integration Case 2) and its $50 floor (Case 3);
  • the statute cap (eligible Case 7) and both LIHEAP-maximum arms (income limit Cases 2 and 3);
  • the size-9+ increment (fpg Case 2);
  • both bedroom clips, the dwelling default, and the SOLAR, WOOD, COAL, OTHER, NONE and UNSPECIFIED rows;
  • the earned floor and all three child-exclusion conditions.

Federal LIHEAP limits:

  • The 110% floor (42 U.S.C. 8624(b)(2)(B)) never binds. The limit is at least min(185% rounded, 185%, max(150%, 60% SMI)), which is at least 150% of the guideline. For size 2 that is 39,127.50 against 23,265.
  • The LIHEAP maximum. gov.hhs.liheap.fpg_limit (150%) and liheap_smi_limit give the federal maximum. Plan 2.1's 60% SMI for all sizes is not followed on its own, consistent with the statute (see G4).

Batch checks:

  • Add New Hampshire state supplementation for 2026 #10156 pattern (item 15). No state-gated int or enum is used as a parameter key without a guard.
    • vt_liheap_household_size (0 outside Vermont) reaches fpg() only through clip(size, 1, 8) and max_(size, 1), and reaches liheap_smi_limit only arithmetically.
    • bedrooms is clipped to 1-4.
    • vt_liheap_dwelling_type is not state-gated and defaults to a valid key.
    • The band index is an integer from 0 to 11.
  • Item 16. military_retirement_income includes SBP annuities.
  • Item 17. vt_aabd (Vermont's AABD supplement, the state SSP on main) is in the list; rule 2911.2(b)(1) counts "other assistance based on need". It is untested (S2).
  • Integrate modeled state unemployment insurance into income flows #9558 and Count military SBP annuities in state benefit income lists #10317. The lists name total_unemployment_compensation and military_retirement_income. Both guards are unchanged on main since the merge base, and both passed in CI.
  • Count each Social Security benefit once in Pennsylvania CCW countable income #10408 (open, not on main). The list names social_security and none of its components, so nothing is counted twice.
  • Tie source-conflict markers to the register shared with Axiom #10287 ratchet. I applied main's CONFLICT_COMMENT_RE to every added line in the PR's parameter and variable files. It found 0 matches, so no marker or KNOWN_UNMARKED entry is needed.
    • Since the merge base, main has changed only KNOWN_UNMARKED entries (CA premium subsidy, QBI, local sales tax).
    • Both candidates pass the schema: valid kinds, states, years, follows: statute, and a code path that exists on the merged tree.
    • Neither id is in the vendored register (rulespec-us ad03b325).
  • Wiring.
    • programs.yaml has an entry with a valid variable and parameter_prefix, and test_programs_registry passed.
    • No merged LIHEAP program (IA, AZ, VA, OR, NY and the rest) is in a household benefit aggregate on main, so not adding one is consistent.
    • Every new variable is gated to Vermont, except the vt_liheap_dwelling_type input.
  • Fuel handling.
    • The electricity row covers ELECTRICITY, and SOLAR follows rule 2901(a)(14) (p. 6).
    • OTHER and NONE have no row (rule 2916(b)).
    • UNSPECIFIED pays nothing except subsidized heat in rent, consistent with merged AZ, IA and KY (see G1).
  • References. Rule anchors are correct: 2903 p. 10, 2907 p. 16, 2910 p. 25, 2911.1 p. 27, 2911.2 pp. 27-29, 2911.3 p. 29, 2912(a)(12) p. 34, 2913 p. 38, 2914 pp. 39-41, 2915 p. 42, 2916 p. 43. So are plan pp. 6-7 and 10, matrix pp. 1-3, P-2914A pp. 1-3, 90 FR 5917 p. 1 and 91 FR 1798 p. 2. Multi-page titles are quoted and use the #page a,b-c form.
  • Descriptions follow the house template.
  • Changelog. changelog.d/vt-liheap-wave1d.added.md is top-level, with type added.
  • Backfill (item 3). Years before 2026 use backfilled values. They are unclaimed and documented in vt_liheap's documentation and the registry (suggestion-level under NJ S3, so not listed separately).
  • Main's 57 newer commits. None touches a dependency of this PR: heating inputs, housing inputs, SMI, tanf, SSI income or the code-health guards. Uprate poverty guidelines over the 9902(2) interval and fix the Trustees wage base #10453 changed only projected (2027+) poverty guidelines; the 2025 and 2026 values this PR uses are unchanged.

Axiom line assessment

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

  • State: OPEN, labelled pe-parity. It was created 2026-10-23 22:16 UTC and has not been updated since.
  • It matches the head, with one lagging row (G3).
    • It names head d1c6ff4ffe and merge base 6a92d6a983.
    • It lists the 45 cases by file.
    • Its required outputs (monthly guideline, limit, bands, heating cost, eligibility, benefit) describe the head's code. The exception is the vt_aabd row noted in G3.
  • It is dispatch-ready in the rulespec-us#1467 format:
    • proposed us-vt/ module paths, with corpus status per source;
    • verbatim text of the rules, statute, procedures and plan items;
    • 14 companion tests derived from the sources, not from PE output;
    • 15 interpretations, including the 51% estimate, the statute cap, the large-household conflict and the P-2914D training figure;
    • its blockers: sources missing from the corpus, the unpublished payment rate, an unchecked signed release and billed-run approval.
  • Note. The fixes for S1-S3 change the gross-income row, interpretation 1 and the companion tests. Update the issue to the head that resolves them.

CI status

27 pass, 3 skipping, 0 failed. Run 40032612243 tested head d1c6ff4ffe (pull_request, started 2026-10-23 22:17 UTC, after #10287 and the 2026-10-21 cutoff). The three skipping entries are the parent workflows of an earlier run, 40032375829, that did not execute. I re-checked gh pr checks 10436 at the end of this review.

  • Vermont tests. Baseline states-shard-1 ran all 9 files: 7 + 4 + 4 + 6 + 11 + 5 + 2 + 1 + 5 = 45 passed.
  • Guards (Rest shard-1: 2,576 passed, 11 skipped):
    • test_unemployment_compensation_readers.py;
    • test_military_retirement_readers.py;
    • test_source_conflicts.py, including the ratchet and the candidate checks;
    • test_aggregate_list_integrity.py;
    • 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 and S3 examples are hand traces of the code at the head. The only local scripts were the static comment-pattern scan and rendering the plan's checkbox pages.

Branch status

  • Position: 3 commits ahead of main (d92649c6da) and 57 behind. The merge base is 6a92d6a983.
  • Merge: one conflict, in policyengine_us/programs.yaml only (branch note). git merge-tree --write-tree PolicyEngine/main d1c6ff4ffe reports two hunks:
    • The coverage line. main now reads "... TX, VA" with AZ and IA added (from Add Virginia EAP Fuel Assistance (LIHEAP regular heating) #10303). This PR adds VT to the old line. The resolution is the union, with VT added to main's line.
    • The entry. main's VA entry and this PR's VT entry both follow TX. Keep both, VA then VT.
    • source_conflicts/candidates.yaml auto-merges.
  • Shared parameter. The body says the Maine branch (Add Maine HEAP Fuel Assistance #10437) adds a byte-identical gov/hhs/liheap/fpg_limit.yaml. An identical add/add merges cleanly in either order.
  • Sibling conflicts (branch note). The other open LIHEAP PRs also edit the coverage line and append entries, so whichever lands first, the rest need a one-line resolution.

0 critical, 3 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.

Requesting changes: 0 critical, 3 should (railroad benefits missing from countable income; untested income sources; 2027 benefit rate and verified-years claim). Full report in the comment above.

This branch has not been deployed

No deployments
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 Vermont Seasonal Fuel Assistance regular heating assistance

2 participants