Skip to content

Add Vermont Seasonal Fuel Assistance - #10436

Merged
hua7450 merged 6 commits into
mainfrom
vt-liheap-wave1d
Oct 30, 2026
Merged

hua7450 merged 6 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. Verification covers the 2025-26 heating season (model year 2026). Model year 2027 applies the published 2026-27 income limits and cost tables with the 2025-26 payment rate carried forward, so it is an unverified estimate. 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). P-2914D's 29% is a training figure. For 2026-27, DCF asked the Emergency Board on October 23, 2026 for $12.7 million to raise the seasonal benefit for oil and kerosene households to "approximately 62 percent purchasing power", against about 45% of estimated heating need in 2025-26; VTDigger reported that the board passed the proposal that day. DCF has not published the resulting rate, so the model carries 51% forward, and model year 2027 understates the benefit for oil and kerosene households. programs.yaml therefore lists verified_years: "2026".

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 farm, partnership and S corporation income, each floored at zero. Under 7 CFR 273.11(a)(2)(ii), a farmer with $1,000 or more of annual gross proceeds offsets a farm loss against other income; the model has no gross-proceeds input, so farm losses are floored like the other sources
Unearned income Rule 2911.2 SSI, the state AABD supplement (vt_aabd), General Assistance, Reach Up (tanf, once), pensions, Railroad Retirement benefits (annuities and retirement benefits, rule 2911.2(b)(2)-(4)), 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
2026-27 payment rate, including the larger oil and kerosene benefit Rule 2914(a)(8); Emergency Board materials, pages 3-5 DCF has not published it; 51% carries forward, so model year 2027 understates oil and kerosene benefits
December, January and February application reductions (80, 60, 40%) Rule 2914(b) No application-month input; 100% assumed
$50 minimum for an unsubsidized tenant with heat in rent and UNSPECIFIED fuel Rule 2914(c)(2)(i) heating_type is a required input and UNSPECIFIED pays $0 (#9754); a subsidized tenant with heat in rent keeps the $21 nominal benefit, which needs no fuel
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

All three entries in source_conflicts/candidates.yaml follow the statute and list this PR in prs.

  • us-vt:liheap-income-maximum-rounding: 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: 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).
  • us-vt:liheap-plan-income-items: plan item 1.9 leaves Strike Pay unchecked and plan item 2.1 reports 60% of state median income for all household sizes. Rule 2911.2(b)(10) counts strike benefits, and 33 V.S.A. 2604(a) and rule 2913(b) cap income at 185% of the guideline, so the model counts strike benefits and does not apply the plan's 60% SMI limit on its own. One person in 2026 with $24,000 of wages and $6,000 of strike benefits has $30,000 of income, above the $28,944 limit, and is ineligible; under the plan, the $24,000 of wages alone is within its $41,075 limit.

Shared parameter

The model reads gov.hhs.liheap.fpg_limit (150%, 42 U.S.C. 8624(b)(2)(B)(i)), which #10342 (North Dakota LIHEAP) added to main with the same path, value and 1981-10-01 start. Merging main took that file, so this PR adds no shared parameter.

Test plan

48 YAML cases in 9 files: vt_liheap.yaml 5, vt_liheap_fpg.yaml 2, vt_liheap_household_size.yaml 1, vt_liheap_income.yaml 8, integration.yaml 4, eligibility/vt_liheap_eligible.yaml 7, eligibility/vt_liheap_income_limit.yaml 4, payment/vt_liheap_heat_cost_percentage.yaml 6, payment/vt_liheap_heating_cost.yaml 11. They cover household size with a mixed-status member; every earned and unearned income source with a distinct amount, including a self-employment profit, monthly SSI and AABD, Railroad Retirement, reported unemployment and a military survivor annuity; the per-source floor for a self-employment loss and for a rental loss against wages; 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 first 45 cases passed locally on 2026-10-23 (policyengine-core test on the program folder, after merging main and adding vt_aabd) and in CI run 40032612243 on head d1c6ff4ffe. Review round 1 added income Cases 6-8 (every earned source, every unearned source and a rental loss); they were hand-derived and not run locally. CI validates them.

  • 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.

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

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

# Conflicts:
#	policyengine_us/parameters/gov/hhs/liheap/fpg_limit.yaml
#	policyengine_us/programs.yaml
#	source_conflicts/candidates.yaml
@codecov

codecov Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (b0a8c09) to head (19ae70d).
⚠️ Report is 51 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##              main    #10436    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files            1         9     +8     
  Lines           15       166   +151     
  Branches         0         3     +3     
==========================================
+ Hits            15       166   +151     
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.

@hua7450
hua7450 requested a review from DTrim99 October 29, 2026 13:43
@hua7450

hua7450 commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, David. Round 1 is in e42e517721; 76e97ea4fa merges current main.

  • S1. income/sources/unearned.yaml now lists railroad_benefits after pension_income, citing rule 2911.2(b)(2)-(4). By hand, your 63-year-old with $21,600 of railroad annuity now gets $1,098 (was $1,433), and with $30,000 is ineligible and gets $0 (was $1,433). vt_liheap_income.yaml Case 7 pins the entry: without it, the case's total falls from $30,360 to $29,760.
  • S2. vt_liheap_income.yaml adds Cases 6-8 (48 cases in all):
    • Case 6 gives each earned source a distinct amount: wages $16,000, self_employment_income $1,000 (a profit), sstb_self_employment_income $2,000, farm_operations_income $4,000 and partnership_income (through partnership_s_corp_income) $8,000, for $31,000.
    • Case 7 gives the k-th of the 22 unearned sources 120 x k, so dropping any entry changes the total: 120 x 253 = $30,360. It sets ssi and vt_aabd as annual inputs of $120 and $240 (10 and 20 a month, summed back through the monthly add() path), railroad_benefits, a reported unemployment_compensation for total_unemployment_compensation and a military_retirement_pay_survivors annuity.
    • Case 8 pins the unearned floor: $18,746 of wages and a $3,600 rental loss give $18,746; without the floor, $15,146.
  • S3. I took option (a). programs.yaml now has verified_years: "2026", and its note says later years carry the estimated rate forward and omit the larger 2026-27 oil and kerosene benefit. payment/payment_rate.yaml keeps 0.51; its comment says so and cites the Emergency Board materials of October 23, 2026 (pp. 3-5) next to the September 16 memo. vt_liheap.py's documentation calls model year 2027 an unverified estimate. Those materials give "approximately 62 percent purchasing power" and average increases per household (oil $1,508, kerosene $1,924), but no payment rate, so option (b) has nothing to encode yet. Your 2027 example still pays $1,242, now labeled an estimate. The 2026-27 rate or rates can go in from 2026-10-01 once DCF publishes them.
  • G1. Declined. UNSPECIFIED pays $0 because callers must send heating_type; Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754 tracks the default. Eligible Case 4 stays not eligible. The $21 for a subsidized tenant with heat in rent (vt_liheap.yaml Case 4) stays, because that benefit needs no fuel. The PR body's Not modeled table now states the difference.
  • G2. income/sources/earned.yaml keeps the per-source floor. Its comment now says that under 7 CFR 273.11(a)(2)(ii) a farmer with $1,000 or more of annual gross proceeds offsets a farm loss against other income, and that the model has no gross-proceeds input; the references add 7 CFR 273.11. Axiom interpretation 7 states the approximation.
  • G3. The Axiom issue's gross-income row now lists vt_aabd and railroad_benefits among 22 unearned sources.
  • G4. In source_conflicts/candidates.yaml, both us-vt:liheap-income-maximum-* entries now list this PR in prs, with code: [] and notes ending "Code not yet on main: vt_liheap_income_limit." The new us-vt:liheap-plan-income-items (follows statute, state ruled) records both plan choices: plan 1.9 leaves Strike Pay unchecked while rule 2911.2(b)(10) counts strike benefits, and plan 2.1 reports 60% of state median income for every size. Its example: one person in 2026 with $24,000 of wages and $6,000 of strike benefits has $30,000, above the $28,944 limit, while the wages alone are within the plan's $41,075. I used one new entry for both items instead of adding the plan to the large-households entry, because plan 2.1 applies to every household size.
  • G5.
    • P-2914D: vt_liheap.py now cites PDF pages 6-9, with the href on page 6. The P-2914D lines in vt_liheap.yaml's header drop their page numbers.
    • Training ratable: payment/payment_rate.yaml notes that P-2914D's worked example uses 29% for training only.
    • Body: the test plan now says the first 45 cases passed locally on 2026-10-23 and in CI run 40032612243, and that Cases 6-8 were not run locally. "CI passes" stays unchecked until CI finishes on this head.
  • Branch. 76e97ea4fa merges current main. VT joins main's LIHEAP coverage line, the VA and VT registry entries both stay, and both candidate blocks stay. gov/hhs/liheap/fpg_limit.yaml now comes from main (North Dakota, Add North Dakota LIHEAP regular heating assistance #10342) with the same path and value, so the PR no longer adds it.

The PR body and TheAxiomFoundation/rulespec-us#1897 now describe this head.

I did not run the tests locally; CI passed every check at 76e97ea4fa.

@DTrim99

DTrim99 commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 2): Add Vermont Seasonal Fuel Assistance

PR #10436 (author: hua7450), head 76e97ea4fa. Round 1 reviewed d1c6ff4ffe on 2026-10-27 and requested changes: 0 critical, 3 should and 5 suggestions. Since then the author has pushed one fix commit, e42e517721, and a merge of main at c55b806541 that resolved three conflict blocks by hand. They replied item by item and re-requested review. The PR 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/

Summary

All three should items are fixed, and so are four of the five suggestions; the author declined G1, which follows the wave convention.

  • S1 (fixed). railroad_benefits is now in the unearned list. A test would fail without it.
  • S2 (fixed). Three new income cases give every one of the 5 earned and 22 unearned sources a distinct positive amount, run monthly SSI and AABD through add(), and pin the unearned floor with a rental loss.
  • S3 (fixed, option (a)). The registry now lists verified_years: "2026". The parameter comment, the variable documentation and the body all call model year 2027 an unverified estimate that leaves out the larger 2026-27 oil and kerosene benefit. I read the Emergency Board materials the PR now cites. They give a purchasing-power target and average increases, but no payment rate, so option (b) cannot be done yet.

Merge resolutions. All three hand-resolved blocks are correct:

  • fpg_limit.yaml now comes from main, unchanged.
  • The programs.yaml coverage line keeps all 31 of main's entries and adds VT; the VA and VT entries are both kept.
  • candidates.yaml keeps main's Virginia entry and all three Vermont entries, with no duplicate ids.

Axiom, CI and branch.

  • Axiom. rulespec-us#1897 was updated to this head.
  • CI. 35/35 checks pass at this head, and states-shard-1 ran all 48 Vermont cases.
  • Branch. Alaska Add Alaska LIHEAP regular heating assistance #10341 has merged since. Its coverage line conflicts with this PR's, a one-line union (branch note).

New findings: 0 critical, 0 should and 2 suggestions.

  • G-N1. No follow-up issue tracks the 2026-27 rate, and Fixes #10426 will close the issue whose scope included 2026-27.
  • G-N2. Housekeeping: one comment's source, and the "CI passes" box.

Prior findings

Finding Status Evidence
S1. Railroad Retirement annuities not counted Fixed P/income/sources/unearned.yaml:18-20 lists railroad_benefits after pension_income and cites rule 2911.2(b)(2)-(4). T/vt_liheap_income.yaml Case 7 gives it $600 (line 204). Without the entry, the case total falls from 30,360 to 29,760 and the case fails. Round-1 example traced at the head: an annuity of $21,600 is $1,800 a month, which falls between the size-1 tops for 134% ($1,748) and 144% ($1,878), so the band is 69%: floor(3,123 × 0.69 × 0.51) = floor(1,098.98) = $1,098 (was $1,433). An annuity of $30,000 is $2,500 a month, above $2,412, so the benefit is $0. Nothing is counted twice: neither social_security nor pension_income adds railroad_benefits, and Case 7's exact total confirms it.
S2. 22 of 26 sources, the unearned floor and the monthly add() path were untested Fixed Case 6 (lines 159-185) gives the five earned sources 16,000, 1,000 (a self-employment profit), 2,000, 4,000 and 8,000, total 31,000. Case 7 (lines 187-240) gives the k-th unearned source 120 × k, total 30,360. Case 8 (lines 242-264) has wages of 18,746 and a rental loss of 3,600, and expects 18,746. Dropping any list entry changes Case 6 or 7. Removing the unearned max_ (V/vt_liheap_income.py:32) makes Case 8 give 15,146. Case 7 enters ssi and vt_aabd (both monthly variables) as 120 and 240 a year, and add() sums them back.
S3. 2027 reused the inferred 51% while the registry claimed 2027 verified Fixed (option (a)) programs.yaml:1211 now sets verified_years: "2026". The note (line 1212), P/payment/payment_rate.yaml:7-12, V/vt_liheap.py:18-28 and the body all say model year 2027 carries 51% forward and leaves out the larger oil and kerosene benefit. The new reference, the Emergency Board binder of October 23, 2026, pp. 3-5, gives: $12.7 million for the seasonal increase; "approximately 62 percent purchasing power for households that heat with oil or kerosene"; average increases of $1,508 for oil (5,695 households) and $1,924 for kerosene (2,135 households); and propane "level". It has no payment rate, and I found no DCF publication of one as of 2026-10-29, so option (b) is not yet possible. This is a documentation fix, so the mutation rule does not apply. The follow-up issue I asked for under option (a) was not opened (see G-N1).
G1. UNSPECIFIED fuel with heat in an unsubsidized rent gets $0 Declined; accepted The author cites #9754. Paying $0 for UNSPECIFIED is the wave convention, and AZ, ND, AK, WA and VA declined the same item. The body's Not modeled table now records the gap between subsidized ($21) and unsubsidized ($0) tenants. Eligible Case 4 and vt_liheap.yaml Case 4 still pin both outcomes.
G2. Farm losses under the 3SquaresVT method Fixed P/income/sources/earned.yaml:6-8 states the exception, and lines 29-30 add 7 CFR 273.11. I checked the citation: the loss offset and the definition of a farmer ($1,000 or more of annual gross proceeds) are both in 273.11(a)(2)(ii). Axiom interpretation 7 calls the floor a known approximation.
G3. One Axiom row lagged the head Fixed The issue's gross-income row now lists vt_aabd and railroad_benefits among the 22 unearned sources.
G4. Backlog candidates Fixed Existing entries. Both us-vt:liheap-income-maximum-* entries now list this PR in prs. They have code: [] and a note ending "Code not yet on main: vt_liheap_income_limit", which is how other entries for unmerged code are written in that file. New entry. us-vt:liheap-plan-income-items (follows statute, state ruled) records both plan choices: strike pay (plan 1.9 vs rule 2911.2(b)(10)) and plan 2.1's 60% of state median income (SMI). I checked its example: 24,000 + 6,000 = 30,000, above 2,412 × 12 = 28,944, and the plan's limit is floor(78,992 × 0.52) = 41,075. One combined entry instead of adding the plan to the large-households entry is reasonable, since plan 2.1 covers every size.
G5. Small documentation fixes Fixed (one nit left) Anchors. V/vt_liheap.py:15-16 now reads "# PDF pages 6-9" with the href at #page=6. The P-2914D lines in the vt_liheap.yaml header drop their page numbers. Training ratable. payment_rate.yaml:7-8 notes that P-2914D's 29% is for training only. Body. The test plan now separates the 45 cases run locally and in CI from Cases 6-8, which were hand-derived. The "CI passes" box is still unchecked although CI is green (see G-N2).

New findings

Critical

None.

Should address

None.

Suggestions

G-N1. Nothing tracks the 2026-27 payment rate once this merges.

  • Why it matters. Under option (a), round 1 asked for a follow-up issue to add the 2026-27 rate or rates from 2026-10-01. None exists, and the body's Fixes #10426 will close the issue whose scope included the 2026-27 season.
  • The rate should be knowable soon. The binder says the larger oil and kerosene benefit "goes out in mid-November". Its footnote 1 says the 2026-27 wood benefit "was issued earlier this year", so DCF has already set at least one 2026-27 amount.
  • Effect. Integration Case 1's oil household still gets $1,242 in 2027: income of $1,400 a month falls between the 104% top ($1,384) and the 114% top ($1,517), so floor(3,123 × 0.78 × 0.51) = 1,242. The binder's average increase for oil households is $1,508.
  • Fix. Open a follow-up issue linked to the binder, or change Fixes #10426 to Part of #10426.

G-N2. Housekeeping.

  • The comment's source. P/payment/payment_rate.yaml:8-12 says the Emergency Board "funded" the larger benefit, but the cited binder is DCF's request. The approval is in the VTDigger report, which only the body cites. Add that reference, or write "approved DCF's proposal".
  • The body. Tick "CI passes": 35/35 checks are green at this head.

Verified

Hand-checks of every new expectation (the fix commit changed no existing expectation):

  1. Income Case 6. 16,000 + 1,000 + 2,000 + 4,000 + 8,000 = 31,000. The amounts are distinct powers of two, so dropping any entry gives 30,000, 29,000, 27,000, 23,000 or 15,000. partnership_s_corp_income adds partnership_income. The self_employment_income input overrides its adds.
  2. Income Case 7. 120 × (1 + 2 + … + 22) = 120 × 253 = 30,360. I checked each input against its list position, 1 to 22: ssi 120, vt_aabd 240, GA 360, private pension 480, railroad 600, SBP annuity 720, 401(k) 840, veterans 960, disability 1,080, workers' compensation 1,200, unemployment 1,320, Social Security retirement 1,440, survivor 1,560, strike 1,680, rental 1,800, farm rent 1,920, child support 2,040, alimony 2,160, interest 2,280, dividends 2,400, estate 2,520, miscellaneous 2,640. I also checked each routing on the head tree:
    • pension_income adds taxable_pension_income, which adds taxable_private_pension_income.
    • military_retirement_income adds military_retirement_pay_survivors.
    • retirement_distributions adds taxable_retirement_distributions, which adds taxable_401k_distributions.
    • social_security adds social_security_retirement.
    • interest_income adds taxable_interest_income, and ordinary_dividend_income adds non_qualified_dividend_income.
    • total_unemployment_compensation reads the reported unemployment_compensation.
    • ssi and vt_aabd are MONTH variables.
  3. Income Case 8. 14.42 × 25 × 52 = 18,746; 18,746 + max(−3,600, 0) = 18,746. Without the floor, 15,146.
  4. S1 trace (in the table above): $1,098 and $0.
  5. S3 / G-N1 trace (2027): M = round(15,960 / 12) = 1,330, so the band is 78% and the benefit $1,242.
  6. New candidate's example: 30,000 > 28,944, and 41,075 = floor(78,992 × 0.52).

Fix-commit checks:

Merge resolutions (all three blocks):

  • gov/hhs/liheap/fpg_limit.yaml. The head takes main's file from North Dakota (Add North Dakota LIHEAP regular heating assistance #10342): the same path and the same 1.5 from 1981-10-01, identical to main byte for byte. V/eligibility/vt_liheap_income_limit.py:43 reads it unchanged, and the PR no longer adds the file.
  • programs.yaml.
    • The coverage line (line 990) is the merge base's 31 entries plus VT.
    • state_implementations has the merge base's 30 states, with VT between VA and WA. Against the merge base, the only changes are the coverage line and the new VT entry, so no sibling was dropped or duplicated.
  • source_conflicts/candidates.yaml.
    • Both sides are kept: main's us-va:tanf-up-standards-below-tanf and the three us-vt:liheap-* entries (lines 7375, 7407 and 7439).
    • No line is removed against the merge base, and there are no duplicate ids. The same holds on the tree merged with current main.

Other checks:

  • Main since the merge base. None of main's 8 newer commits (Alaska LIHEAP, the Maine EITC age gate, the owners-draw SECA contributed reform, the SEHI deduction limit, ss_aime) touches an input or guard this PR depends on.
  • Case count. 5 + 2 + 1 + 8 + 4 + 7 + 4 + 6 + 11 = 48, matching the body and CI.
  • Body. It describes this head: the S3 framing, the Not modeled row for UNSPECIFIED, the farm-loss note, three candidates and the shared parameter from Add North Dakota LIHEAP regular heating assistance #10342.

Axiom

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

  • State. OPEN, labelled pe-parity, last updated 2026-10-29 13:43 UTC, after the fix commit.
  • It matches the head:
    • It names head 76e97ea4fa, merge base c55b806541 and fix commit e42e517721, and it lists the 48 cases by file.
    • The gross-income row has the 22 unearned sources, including railroad_benefits and vt_aabd.
    • Interpretation 1 verifies only period 2026, treats 2027 as a declared estimate and summarizes the binder (62 percent, nothing for propane, no rate).
    • Interpretation 7 covers the farm-loss approximation and the strike-pay candidate.
    • Companion test 12 covers income Cases 1-8.
  • Dispatch-ready.
    • It has module paths with corpus status and verbatim source text.
    • Its blockers are listed: no reviewed oracle revision, corpus gaps (now including the binder), the unpublished rate, the unchecked signed release, and billed-run approval.

CI

35 pass, 0 fail, 0 pending. Run 40792588889 (pull_request) tested head 76e97ea4fa, which includes main at c55b806541. 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 + 8 = 48 cases, all passing.
  • Guards. Rest (Python shard 3) passed 3,082 tests and skipped 11. Its guard files passed 934 tests, skipped 10 and failed none:
    • test_source_conflicts.py, including the ratchet and all four candidate checks;
    • test_unemployment_compensation_readers.py;
    • test_military_retirement_readers.py;
    • test_aggregate_list_integrity.py;
    • test_programs_registry.py.
  • Main-caused failures. None on this run.
  • Locally. I ran no tests or model probes. The examples are hand traces of the head. The only local work was the static comment-pattern scan and reading the binder PDF.

Branch

  • Position. 5 commits ahead of main (1976c4b600) and 8 behind; the merge base is c55b806541. GitHub reports the PR as conflicting.
  • Merge: one conflict, the programs.yaml LIHEAP coverage line (branch note). git merge-tree --write-tree PolicyEngine/main 76e97ea4fa reports only this hunk. main added AK there (Add Alaska LIHEAP regular heating assistance #10341), and this PR adds VT. The resolution is the union: AK, AR, AL, AZ, DC, …, UT, VA, VT, WA, WY.
  • Auto-merges. The state_implementations entries merge cleanly, with AK after AL and VT after VA. So does candidates.yaml, with no duplicate ids.
  • Siblings. Other open LIHEAP PRs also edit the coverage line, so whichever lands next needs the same one-line union.

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

Verdict: APPROVE

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving round 2: 0 critical, 0 should. All three round-1 shoulds are fixed and pinned. Full report in the comment above. Branch note: AK #10341 merged, so the coverage line needs AK + VT before landing.

…p-wave1d

# Conflicts:
#	policyengine_us/programs.yaml
@hua7450
hua7450 merged commit b3d3bbc into main Oct 30, 2026
67 of 68 checks passed
@hua7450
hua7450 deleted the vt-liheap-wave1d branch October 30, 2026 09:27
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