Skip to content

Add Virginia EAP Fuel Assistance (LIHEAP regular heating) - #10303

Merged
hua7450 merged 11 commits into
mainfrom
va-liheap-wave1c
Oct 27, 2026
Merged

hua7450 merged 11 commits into
mainfrom
va-liheap-wave1c

Conversation

@hua7450

@hua7450 hua7450 commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Adds Virginia's Energy Assistance Program (EAP) Fuel Assistance, the state's regular LIHEAP heating benefit, for FFY2026. The model counts citizens and qualified noncitizens for household size and counts the gross income of every member, with each income source floored at zero. It subtracts each member's out-of-pocket Medicare Part A and Part B premiums and $50 a month for each member aged 60 or older or disabled. It tests that income against 150% of the 2025 poverty guideline, or waives the test when every member receives SSI. The household must heat with an eligible fuel and pay for heat directly or through unsubsidized rent. It then scores the manual's point tables and pays points times the statewide value per point. In 2026, a person aged 65 in Fairfax County with $11,736 of Social Security and a $1,200 gas bill has $11,136 of countable income, 47% of the $23,472 limit; at that income the model's Medicare Savings Program pays the Part B premium, so no premium is deducted. That scores 9 (size) + 15 (income) + 16 (Northern zone) + 20 (elderly) + 20 (living arrangement) + 10 (fuel) + 13 (energy burden) = 103 points, and 103 × $4.531665 = $466.76.

Fixes #10291

Regulatory authority

Eligibility

Requirement Source How modeled
Virginia residence Manual page 32 defined_for = StateCode.VA
At least one citizen or qualified noncitizen; others are left out of size but their income counts Manual pages 31 to 32; 22VAC40-680-20 va_eap_household_size adds is_citizen_or_legal_immigrant; size 0 is ineligible
Gross countable monthly income at or below 150% of the poverty guideline Manual page 28; plan 2.1; 22VAC40-680-20 va_eap_income_limit = floor(guideline × 1.5 / 12) with a one-year guideline lag, reproducing the published $1,956 to $15,018 table for sizes 1 to 20 and the $687 step above 20
Households in which all members receive SSI are not held to the income maximum Manual page 28 va_eap_income_eligible waives the test when every member has ssi > 0 or receives_ssi
Primary fuel is electricity, oil, kerosene, natural gas, LP gas, wood or coal Manual page 26 heating_type list parameter; UNSPECIFIED, SOLAR, OTHER and NONE are ineligible (callers send heating_type)
Heating expense billed or included in rent Manual pages 26 and 50 has_heating_expense or heat_expense_included_in_rent
Subsidized households with heat in rent are ineligible; subsidized households paying their own heat are eligible Manual pages 26 to 27; 22VAC40-680-20 heat_expense_included_in_rent with receives_housing_assistance or is_in_public_housing
Homeless households are ineligible Manual page 27 is_homeless
No asset test and no categorical eligibility Plan 1.4, 1.7 and 2.3 Not applied

Countable income

Item Source How modeled
Earned income Manual pages 37 to 40; plan 1.9 Wages and net self-employment, farm, SSTB and partnership or S corporation income, each floored at zero
Earnings of children under 18 Manual page 30, exemption 20 Exempt
Unearned income Manual pages 25, 28 and 40; plan 1.9 Unemployment, strike benefits, Social Security, railroad retirement, SSI, the Auxiliary Grant (va_ag), pensions, military retirement and survivor pay (military_retirement_income), retirement distributions, survivor benefits, general assistance, rental and farm rent income, alimony, child support received, recurring cash contributions from outside the household (financial_assistance), veterans benefits, interest, ordinary dividends, workers' compensation and disability benefits. The household's TANF (tanf) is added at the SPM unit.
Losses Manual pages 38 to 39, C.5.a.3 and C.5.a.4.b Every earned and unearned source is floored at zero per person, so a business, rental or farm rent loss offsets no other income. The manual counts the income received and self-employment profit, and lists rental property among the self-employment profit determinations.
Auxiliary Grant Manual page 25, B.5.a Counted: B.5.a names "auxiliary grants to the aged, blind and disabled" as public assistance, and none of the 27 exemptions covers them
Medicare premiums Manual page 30, exemption 15; plan 1.9 ("Excluding MediCare deduction") Each member's out-of-pocket medicare_part_a_premium and medicare_part_b_premium are subtracted from household gross income, whether withheld from Social Security or railroad retirement or paid by the member. Each premium is net of Medicare Savings Program coverage and zero for non-enrollees; the Part A premium is also zero for premium-free Part A, which about 99% of enrollees have.
Interest and dividends Manual page 28 versus plan 1.9 Counted: the manual counts all gross unearned income and none of its exemptions covers them, while the plan leaves the box unchecked
$50 a month per elderly (60+) or disabled member, once per person Manual pages 31 and 52 Subtracted after the Medicare premiums and before the income test, with the result floored at zero; disabled means is_disabled, Social Security disability, SSI receipt with is_ssi_disabled, or a 100% service-connected disabled veteran

For example, a person aged 66 in Fairfax County with $25,000 of Social Security pays the full 2026 Part B premium, 12 × $202.90 = $2,434.80. Countable income is 25,000 − 2,434.80 − 600 = $21,965.20, 93% of the $23,472 limit, so the household is eligible with 3 income points: 91 points, or $412.38. Without the premium deduction, $24,400 would exceed the limit.

Benefit

va_eap = points × value per point (22VAC40-680-30). Points (manual pages 65 to 66; matrix pages 6 to 7):

Factor Points
Household size 1 to 2: 9; 3 to 5: 12; 6 or more: 15
Income as a percent of the limit (whole percent) 0 to 19: 25; 20 to 29: 20; 30 to 39: 18; 40 to 49: 15; 50 to 59: 13; 60 to 69: 10; 70 to 79: 8; 80 to 89: 5; 90 to 94: 3; 95 to 100: 1; above 100 (all-SSI only): 0
Climate zone, derived from county (all 133 Virginia localities) Central Mountain 20; Southwestern Mountain 18; Northern 16; Western Piedmont 13; Eastern Piedmont 12; Tidewater 8
Vulnerability, highest one only Member 60 or older 20; disabled member 15; child under 6 12
Living arrangement (every eligible code) 20
Primary fuel 10, placeholder
Energy burden 13, placeholder
Season Value per point Source
FFY2023 $4.226444 FY2024 plan item 2.5
FFY2024 $4.957075 FY2025 plan item 2.5
FFY2025 $4.531665 FY2026 plan item 2.5
FFY2026 not yet published; model year 2026 uses $4.531665 VDSS sets it after the December calculation

Placeholders. VDSS sets the primary fuel points each year from a vendor price survey and a Virginia Tech consumption study: the most expensive fuel gets 20 and the others get less in proportion to cost. The energy burden is the survey's average fuel cost for the household's fuel divided by household income, scored from 0 to 25 points. VDSS publishes only the method and the burden bands, not the survey results. The model gives every household 10 fuel points (half of 20) and 13 burden points (the 50% to 69% band), which add $104.23 to each benefit at $4.531665 a point.

Plan item 2.5 reports that FFY2025 benefits ran from $181.26 to $634.43, which equal 40 and 140 points at $4.531665 (VDSS truncates to the cent). The five published factors sum to between 37 and 100 points, so the fuel and burden points totaled at most 3 for the lowest benefit and at least 40 for the highest. With the placeholders, the model's benefits run from $271.90 (60 points, an all-SSI household above the limit) to $557.39 (123 points): $90.64 above the published minimum and $77.04 below the published maximum. Both placeholders are single parameters, and both parameter files cite the plan 2.5 range, so published values can replace them directly.

Years covered

Model year Season Guideline One-person limit (monthly / annual) Value per point
2026 FFY2026 (October 1, 2025 to September 30, 2026) 2025 $1,956 / $23,472 $4.531665 (FFY2025, latest published)

The registry entry records verified_years: "2026", and its note names the placeholder fuel and burden points. This PR also adds IA and AZ to the LIHEAP coverage line, so the three wave 1C PRs do not edit the same line; merge it after #10292 and #10293. Earlier years use parameter backfilling and are unverified historical estimates.

Not modeled and source notes

  • Annual amounts stand for twelve times the prior calendar month's income; the weekly and biweekly conversions and per-member rounding are not modeled.
  • Medicare Part D premiums have no input and are not deducted. The model's out-of-pocket Part A and Part B premiums stand in for the applicant's statement of the amount.
  • Roomer and boarder income, live-in attendants, licensed facilities (where most Auxiliary Grant recipients live), group homes, one-room occupants, the SSN rule and the subsidized excess-usage-only arrangement have no inputs.
  • The vendor-payment limit to charges through March 31 is not applied, because payment method is not an input.
  • Crisis, cooling, weatherization, equipment repair and replacement, and security deposits are out of scope.
  • The June 2017 Appendix A page drops Salem city (775); the January 2003 page lists it in Central Mountain, which the model follows. Bedford City (now part of Bedford County) and Clifton Forge (now a town in Alleghany County) have no county values of their own.
  • The manual compilation still carries the 2003 Appendix B page based on 130% of the guideline; the 2021 matrix page (150%) controls.

Test plan

  • 12 YAML files with 130 cases under policyengine_us/tests/policy/baseline/gov/states/va/dss/eap/, with expected values hand computed from the manual tables and the published limits in case comments. Every listed income source takes a nonzero value in at least one case.
  • va_eap.yaml Case 9 pins the Part B deduction for a Social Security retiree ($412.38), and Case 10 a rental loss that leaves wages over the limit ($0). va_eap_countable_income.yaml Case 20 pins a farm rent loss that offsets nothing, Case 21 every remaining source, including TANF and the Auxiliary Grant, Case 22 the premium deduction for a pensioner without Social Security, and Case 23 a Part A premium deducted along with Part B.
  • All 130 cases pass locally, as do the registry and code-health pytest checks.
  • YAML tests whose expected values come from an external source (statute, official form or worksheet, official calculator)
  • make format
  • Changelog fragment in changelog.d/

axiom: TheAxiomFoundation/rulespec-us#1900 queued

🤖 Generated with Claude Code

hua7450 and others added 5 commits October 20, 2026 01:55
Virginia Energy Assistance Program Fuel Assistance for FFY2026: 150% FPG
income test with the all-SSI exception, countable income with the $50
elderly or disabled disregard, climate zones from county, the manual's
point tables, placeholder fuel and energy-burden points, and the latest
published value per point.

Fixes #10291

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Count recurring cash contributions (financial_assistance) as unearned
income, treat 100% service-connected disabled veterans as disabled, move
the end-to-end cases into integration.yaml, add a two-person all-SSI case,
and tidy one reference and the income-points comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main now models military retirement and survivor pay as the separate
military_retirement_income total, so add it to Virginia's countable
unearned income with countable-income and benefit cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hua7450
hua7450 requested a review from DTrim99 October 21, 2026 16:30
@DTrim99

DTrim99 commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Add Virginia EAP Fuel Assistance (LIHEAP regular heating)

PR #10303 (author: hua7450), head 1396baf7fe. This is a first review. The PR adds Virginia's Energy Assistance Program (EAP) Fuel Assistance benefit for FFY2026: 26 parameters, 11 variables, 12 test files (124 cases), a programs.yaml entry and a changelog fragment. There are no comments on the PR yet. It touches no partner test file.

Path prefixes:

  • P/ = policyengine_us/parameters/gov/states/va/dss/eap/
  • V/ = policyengine_us/variables/gov/states/va/dss/eap/
  • T/ = policyengine_us/tests/policy/baseline/gov/states/va/dss/eap/

Sources read for this review:

Summary

What is right. The published FFY2026 parts are encoded correctly:

  • Income limits. floor(1.5 × 2025 guideline / 12) reproduces all 20 published monthly limits and the $687 step. I checked every size.
  • Point tables. Household size, income, climate zone, vulnerability and living arrangement all match.
  • Climate zones. All 133 localities match Appendix A, including Salem from the 01/03 page.
  • Value per point. The FFY2023–FFY2025 values match plan item 2.5.
  • Other rules. The $50 medical deduction, the eligible fuels, the subsidized and heat-in-rent rules, the homeless exclusion and the all-SSI waiver all match the manual and 22VAC40-680.
  • Tests. The tests are thorough. I hand-checked 15 cases below, and CI passed all 124.
  • Batch checks. The PR already uses total_unemployment_compensation (Integrate modeled state unemployment insurance into income flows #9558) and military_retirement_income (Wire military retirement pay and survivors into federal and state tax (#10181) #10182), and its comments pass the source-conflict ratchet on the merged tree.

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

  • S1. Medicare premiums. Social Security and railroad retirement count gross. Manual exemption 15, the application and plan item 1.9 all exclude the Medicare premium. The PR says there is "no premium input", but medicare_part_b_premium exists, and the merged KY, NJ, OH, RI and SD LIHEAP formulas subtract it. Example: a 66-year-old with $25,000 of Social Security gets $0 at the head and $412.38 under the rule.
  • S2. Rental losses. A rental or farm-rent loss offsets wages. The unearned list is summed with signs, although the PR's own earned-income rule says losses do not offset other income. Example: $30,000 of wages and an $8,000 rental loss get $303.62 instead of $0.
  • S3. Placeholder points. The fuel and energy-burden placeholders (10 + 13 points for everyone) cannot reproduce the published benefit range.
    • Published. Plan item 2.5 prints FFY2025's maximum and minimum benefits, $634.43 and $181.26. These are exactly 140 and 40 points.
    • Model. The model's range is 60–123 points ($271.90–$557.39).
    • Regulation. 22VAC40-680-30 requires fuel type and energy burden to vary the benefit.
  • S4. Untested income sources. 15 of the 26 income-source entries never take a nonzero value in any test. They include tanf and total_unemployment_compensation.
  • S5. Axiom line. axiom: needed is not valid for a staff author, and there is no rulespec-us issue for Virginia EAP.

There are also six suggestions. They include adding Virginia's Auxiliary Grant (va_ag, merged in #10172) to the unearned list (item 17) and the heat-in-rent UNSPECIFIED fuel convention.

Status.

  • CI: 15 checks pass and 3 are still pending; none has failed. The Virginia EAP files passed in states-shard-4.
  • Branch: 7 commits ahead of main and 2 behind, and it merges cleanly.
  • Siblings: the 11 open LIHEAP sibling PRs conflict only in programs.yaml.

Critical (must fix)

None.

Should address

S1. Medicare premiums are not exempted: Social Security and railroad retirement count gross, although exemption 15 excludes the premium and medicare_part_b_premium exists

  • Where:
    • P/income/sources/unearned.yaml:16-19: "Exemption 15 lets the worker deduct Medicare premiums from Social Security or Railroad Retirement; there is no premium input, so these benefits are counted gross."
    • V/va_eap_countable_income.py:41-50 subtracts only the $50 disregard.
    • The PR body's not-modeled list says "the Medicare premium exemption has no input".
  • Sources:
    • Manual B.6.b.15 (p. 30) exempts "Medicare Premiums when deducted from Social Security or Railroad Retirement checks or when the household member is responsible for payment of the premium. NOTE: This is not an automatic exemption and therefore must be manually deducted from gross income. The client's statement is accepted as to the amount of the premium."
    • Manual C.2.c (p. 36): "Deduct the Medicare premium listed on the application from the preprinted income amount." C.1.a (p. 35) accepts the applicant's statement for the "Medicare premium".
    • Application question 10 (p. 2): "Does anyone pay for Medicare Part B ___ or D ___ insurance? ... How much? $___".
    • FY2026 plan item 1.9 (p. 6, rendered): "Social Security Administration (SSA) benefits" is checked, with "Excluding MediCare deduction" checked. The PR's own comment at unearned.yaml:4-5 reads the box the same way.
  • Why "no premium input" does not hold:
    • medicare_part_b_premium (person, annual) is on main and at this head's base. It holds the out-of-pocket Part B premium, net of Medicare Savings Program (MSP) coverage, and it is zero for non-enrollees.
    • Merged KY, NJ, OH, RI and SD LIHEAP already subtract it.
    • Netting MSP coverage is what exemption 15 asks for: a premium that MSP pays is neither withheld from the check nor paid by the member.
  • Effect (hand-traced from the code for 2026). A single person aged 66 in Fairfax County has $25,000 a year of Social Security and natural gas heat with a bill. Their income is $2,083 a month; after the $20 disregard that is above 135% of the guideline, so MSP pays nothing. The premium is 12 × $202.90 = $2,434.80.
    • Head: 25,000 − 600 = 24,400 > 23,472, so $0.
    • Exemption 15: 25,000 − 2,434.80 − 600 = 21,965.20 ≤ 23,472, so the household is eligible. 93.58% gives 3 income points, and 9 + 3 + 16 + 20 + 43 = 91 points gives $412.38.
    • Points change within the limit too. With $22,800 of Social Security, the head gives 94% (3 points, $412.38). The rule gives 84% (5 points, $421.44).
    • Reach. Medicare enrollees whose premium MSP does not cover, at incomes from about 135% of the guideline up to the limit plus the premium. The existing retiree cases appear to be MSP-covered in the model, so their expectations would likely not change; CI will confirm.
  • Severity: should. Wave 1 rated the same pattern should: Add New Jersey LIHEAP regular heating assistance #9745 S1 (New Jersey, also a "not wired in" comment) and Add New York HEAP regular heating assistance #9744 S1.
  • Fix:
    • Subtract each member's medicare_part_b_premium from gross income before the floor, either as a deductions list like Ohio's or in the formula.
    • Replace the comment at unearned.yaml:16-18. Keep Part D as a not-modeled row, since it has no separate input.
    • Add a case like the one above.
    • Correct the body row and the countable-income documentation.

S2. Rental and farm-rent losses offset wages and other income

  • Where:
    • V/va_eap_countable_income.py:41 sums p.income.sources.unearned with add(), keeping signs. That list includes rental_income and farm_rent_income (P/income/sources/unearned.yaml:31-32).
    • Only the earned entries are floored (lines 35-36). The household total is floored only at the end (line 50).
    • The PR's own rule, at P/income/sources/earned.yaml:6-9, says "each source is floored at zero in the formula so a business loss does not offset other income. Rental income is counted with the unearned sources."
  • Sources:
    • Rental property is self-employment. Manual C.5.a.4.b (pp. 38-39) lists "Rental property" among the self-employment profit determinations. C.5.a.3.b says "Profit from self-employment is the countable income."
    • Countable income is income received. C.5.a.3: "Countable income must be the combined total monthly income received by all members of the household." A loss is not income received.
    • Nothing authorizes netting. 22VAC40-680-30 scores "Gross monthly income", and no source lets a loss in one source reduce another.
  • Effect (hand-traced for 2026). A single person aged 40 in Henrico County has $30,000 of wages and an $8,000 rental loss, with gas heat and a bill.
    • Head: countable income is 22,000, or 93.73% of the limit: 3 income points, 9 + 3 + 12 + 0 + 43 = 67 points, $303.62.
    • With the loss floored: 30,000 > 23,472, so $0.
  • Tests: no case sets a nonzero rental_income or farm_rent_income.
  • Severity: should, as in Add New Jersey LIHEAP regular heating assistance #9745 S2 (New Jersey).
  • Fix:
    • Floor rental_income and farm_rent_income per person. Either move them into the floored earned-style loop or floor each unearned entry.
    • Add a rental-loss case like the one above.

S3. The flat fuel and energy-burden placeholders cannot reproduce the published benefit range, and the registry marks 2026 verified

  • Where:

    • P/points/primary_fuel.yaml (10 points) and P/points/energy_burden.yaml (13 points).
    • V/va_eap_points.py:86-94 adds both to every household.
    • The programs.yaml VA entry (line 965) records verified_years: "2026".
  • The rule:

    • 22VAC40-680-30: "Benefit levels shall be established based on income in relation to household size, fuel type, and geographic area, with the highest benefit given to households with the least income and the highest energy need." The section lists "Primary heat type" and "Energy burden" as point factors.
    • 22VAC40-680-10 defines energy burden as "the average fuel cost for the primary fuel type used by a household divided by the income of the household".
    • Appendix B (matrix p. 7) scores the burden from 0 to 25 points and gives the costliest fuel 20.
  • Published check (not in the PR). Plan item 2.5 prints each season's extremes:

    Season Source Maximum Minimum Value per point Points
    FFY2025 FY2026 plan, p. 9 $634.43 $181.26 $4.531665 exactly 140 and 40
    FFY2024 FY2025 plan, p. 9 $703.90 $198.28 $4.957075 142 and 40
    FFY2023 FY2024 plan, p. 9 $600.15 $185.96 $4.226444 142 and 44
  • What the model can produce:

    • The five published factors (size, income, zone, vulnerability, living arrangement) sum to between 37 (9 + 0 + 8 + 0 + 20) and 100 (15 + 25 + 20 + 20 + 20).
    • So in FFY2025 the fuel and burden points were at most 3 for the lowest-benefit household and at least 40 for the highest.
    • The model gives every household 23. Its range is 60–123 points, or $271.90–$557.39; integration Case 7 is the model maximum.
    • Size of the gap. The model understates the top by at least 17 points (about $77) and overstates the bottom by at least 20 points (about $91).
  • Only one input is missing. The burden is driven by household income, which the PR already computes. Only VDSS's average fuel cost per fuel is unpublished. A flat 13 removes the income gradient the regulation requires ("the highest benefit given to households with the least income").

  • Severity: should, as in Add Ohio HEAP regular heating assistance #9975 S3 (Ohio). There, the 2026 coefficients could not reproduce the printed FY2026 range, and the fix was a labelled estimate fitted to that range.

  • Fix: either of these.

    • (a) Model the burden. Compute the burden as a labelled, estimated average seasonal fuel cost divided by monthly countable income, banded per Appendix B. Keep fuel points by fuel type if they can be estimated. Calibrate both so that the model's extremes reproduce the published 40- and 140-point households. Add cases that pin $634.43 (140 × $4.531665) and $181.27 (40 points; see G6).
    • (b) Document the gap. Cite the plan 2.5 extremes in both parameter files, the body and the registry note, and state the model's $271.90–$557.39 range. Drop verified_years: "2026", or limit the note to eligibility and the published tables, so the registry does not present estimated benefit amounts as verified.

S4. 15 of the 26 income-source entries have no test that would fail if they were dropped

  • Where: P/income/sources/earned.yaml and P/income/sources/unearned.yaml.
  • The untested entries. No case gives these a nonzero value:
    • Earned: sstb_self_employment_income, farm_operations_income and partnership_s_corp_income.
    • Unearned: total_unemployment_compensation, strike_benefits, railroad_benefits, retirement_distributions, survivor_benefits, general_assistance, tanf (every case sets tanf: 0), rental_income, farm_rent_income, alimony_income, workers_compensation and disability_benefits.
  • The tested entries: wages, self-employment, Social Security, SSI, pensions, military retirement, child support, cash contributions, veterans' benefits, interest and dividends.
  • Why it matters: this is checklist item 13. total_unemployment_compensation is the Integrate modeled state unemployment insurance into income flows #9558-guarded entry, and tanf is the take-up-gated aggregate the comment describes; neither is pinned.
  • Severity: should, as in Add Alabama LIHEAP regular heating assistance #9967 S3 (Alabama) and Add Oklahoma LIHEAP regular heating assistance #9970 S6 (Oklahoma).
  • Fix: add one countable-income case that sets every listed source to a distinct amount, as Alabama's Case 18 does. Include a positive tanf and reported unemployment compensation. The S2 rental-loss case can sit beside it.

S5. axiom: needed is not a valid line for a staff author

  • Where: the PR body's last line.
  • The rule: CLAUDE.md "Axiom Parity" reserves axiom: needed for external contributors. Ziming is staff.
  • The issue state:
    • rulespec-us has no Virginia EAP issue. I searched titles and the open pe-parity list.
    • rulespec-us main (5796861e58) has no Virginia EAP module: us-va/policies/ holds only cms and income_tax.
  • Precedent. The wave 2a siblings and this PR's wave 1c companions use queued lines: rulespec-us Add Oregon integration test #1532 (Alabama) through Remove complex pass-through reform #1540 (Ohio), Updated Oregon tax parameters for 2022 #1833 (Arizona, PR Add Arizona LIHEAP Standard benefit #10293) and Too many EITCs #1834 (Iowa, PR Add Iowa LIHEAP regular heating assistance #10292). Each is open, labelled pe-parity and dispatch-ready.
  • Fix:
    • File a dispatch-ready, pe-parity-labelled rulespec-us issue in the Too many EITCs #1834 format. It needs:
      • module paths and corpus status;
      • the verbatim 22VAC40-680-10, -20 and -30, manual B.5–B.7, C.5, D.4–D.5 and Appendices A–B, and FY2026 plan items 1.9, 2.1 and 2.5;
      • the required outputs;
      • companion tests taken from the manual tables and published limits, not from PE output;
      • the blockers.
    • Change the line to axiom: TheAxiomFoundation/rulespec-us#<n> queued.
    • Update the issue for S1–S3 once they are settled.

Suggestions

G1. Count Virginia's Auxiliary Grant (va_ag, checklist item 17).

  • What exists on main. Add Virginia state supplementation for 2026 #10172 (merged, and already in this head's base) added va_ag, Virginia's state supplement. It lists va_ag in the SNAP and HUD unearned lists, household_state_benefits and spm_unit_benefits. The EAP unearned list omits it.
  • The rule counts it.
    • Manual B.5.a (p. 25) names "auxiliary grants to the aged, blind and disabled" as public assistance.
    • None of the 27 exemptions covers it.
    • Exemption 25 follows SNAP, which counts it.
  • Example (hand trace, 2026). A person aged 70 in Norfolk gets the full $994 of SSI. They are certified for an Auxiliary Grant in supportive housing (outside Northern Virginia) and have a heating bill.
    • The grant. The care rate is 994 + 1,136 = $2,130. The need is $2,130 + $87 = $2,217, so the grant is $2,217 − $994 = $1,223 a month.
    • Head: 11,328 is 48% of the limit: 15 income points, 95 points, $430.51.
    • Counting va_ag: 26,004 is 111%. The all-SSI waiver keeps the household eligible, at 0 income points: 80 points, $362.53.
  • Why only a suggestion:
  • Fix: add va_ag beside ssi.

G2. A heat-in-rent household with UNSPECIFIED fuel.

  • The input convention. main's heating_type documentation says UNSPECIFIED "is also the right value when heat is included in rent and the fuel is unknown". The PR makes that household ineligible (V/va_eap_eligible.py:37-38; eligibility Case 14).
  • The rule. B.5.c.1.b admits unsubsidized heat-in-rent households without listing a fuel; only D.1.b asks the household to designate one.
  • Why it can be changed. The model's points do not depend on the fuel. The merged AL, OH and OK formulas treat UNSPECIFIED as eligible.
  • Fix: consider eligible_fuel | (heat_in_rent & UNSPECIFIED), or record the choice against the input convention in the body.

G3. Backfilled years (unclaimed and documented, as in NJ S3).

  • The income rate. P/eligibility/fpg_rate.yaml starts at 1.5 on 2021-05-01 and carries back. The 01/03 Appendix B (p. 65) bases the levels on "130% of the Poverty Income Guidelines".
  • The value per point. The FFY2023 value carries back to 2015–2022, and FFY2022 was an ARPA year.
  • Fix: consider a 2003-01-01: 1.3 entry, if an older plan confirms it, or keep the backfill and keep the body note.

G4. References polish.

  • The nine 22VAC40-680 entries point to the whole-chapter page. Section URLs (.../chapter680/section10/, section20/, section30/) would anchor the cited text.
  • If S3 takes option (b), add the plan 2.5 extremes as a reference.

G5. Optional tests.

  • A mixed-state case. No va_eap case runs a Virginia and a non-Virginia SPM unit in one simulation, as Alabama's Case 6 does. It is safe today: va_eap_climate_zone defaults to TIDEWATER, a valid key in p.climate_zone[zone]. A mixed case would keep it pinned (item 15).
  • The default zone. A Virginia household with an unlisted county would pin the TIDEWATER default of the select.

G6. Cent truncation. The printed benefits truncate to the cent rather than round:

  • 40 × 4.531665 = 181.2666 is printed as $181.26.
  • 142 × 4.226444 = 600.155 is printed as $600.15.

va_eap does not round, so the difference is under one cent. Flooring to the cent is optional.

Verified correct

Values against sources:

  • Income limits (manual B.6.a p. 28, Information Sheet 05/25, matrix p. 2). floor(1,956.25 + 687.5 × (n − 1)) gives 1,956, 2,643, 3,331, 4,018, 4,706, 5,393, 6,081, 6,768, 7,456, 8,143, 8,831, 9,518, 10,206, 10,893, 11,581, 12,268, 12,956, 13,643, 14,331 and 15,018. That matches all 20 published sizes. Above 20 the step is +687 (floor 687.5).
  • The lag. The one-year guideline lag matches "new income limits ... in June of each year" (matrix p. 1). fpg() reads the guideline at 2025-01-01 for period 2026.
  • Point tables (matrix pp. 6-7, rendered):
    • size: 9 / 12 / 15;
    • income: the ten bands from 25 down to 1;
    • zones: 20 / 18 / 16 / 13 / 12 / 8;
    • vulnerability: 20 / 15 / 12, not cumulative;
    • living arrangement: codes A, C, E, G and P at 20.
    • The 05/21 page says "150%", and the PR correctly sets aside the manual's 01/03 copy (130%).
  • Climate zones (matrix pp. 3-5 and manual pp. 62-64): all 133 localities (95 counties and 38 cities) match. Salem comes from the 01/03 page, Bedford City is a town since 2013, and Clifton Forge is a town since 2001. Climate-zone Case 10 pins all 133.
  • Value per point:
    • The three values match plan item 2.5 in FY2024, FY2025 and FY2026, each on p. 9.
    • Period 2026 carries the FFY2025 $4.531665. That is the latest published value, as disclosed.
    • No FY2027 plan or matrix is posted: the Clearinghouse returned 404 on 2026-10-21, and the VDSS page still links the 05/25 Information Sheet.
  • Eligibility:
    • Fuels and expense: the seven fuels (B.5.c.1.a, p. 26; application question 12), heat in rent for unsubsidized households (B.5.c.1.b), and subsidized households paying their own bill (B.5.c.1.c).
    • Exclusions: subsidized households with heat in rent (B.5.c.2.a; plan 2.3, p. 8), homeless households (B.5.c.2.g).
    • Income: the at-or-below test (22VAC40-680-20(B)(1)) and the all-SSI exception (B.6.a).
    • Not required: there is no asset test (plan 2.3), no categorical eligibility (plan 1.4 "No", rendered p. 5) and gross income applies (plan 1.8).
  • Income:
    • The deduction. $50 a month per elderly (60+) or disabled member, once per person (B.6.c, p. 31). The disability definition matches 22VAC40-680-10. Using is_disabled for railroad disability and Medicaid permanent-and-total certification follows merged Ohio and Florida.
    • Exempt earnings. Members under 18 (exemption 20; plan 1.9 box unchecked).
    • Household size. Ineligible noncitizens are left out of the size, but their income counts (B.7, p. 31; C.5.a, p. 41).
    • Gross wages (C.5.a.3.a).
    • Interest, dividends and cash contributions are counted under the manual, against the unchecked plan boxes. That is documented and defensible: C.5.a.4.c names "cash contributions", and exemption 19 excludes only one-time gifts.
  • Benefit: points × value per point (22VAC40-680-30(4)). There is no minimum or maximum other than the points.

Hand-checked expectations (15 cases). At $4.531665 a point and 43 points of living arrangement, fuel and burden:

  • Integration Case 1: 11,736 − 600 = 11,136, or 47.44%: 15 points. Fairfax is Northern (16). 9 + 15 + 16 + 20 + 43 = 103, so $466.76.
  • Integration Case 2: 26,562 + 3,600 = 30,162 against 39,972, or 75%: 8 points. Norfolk is Tidewater. 12 + 8 + 8 + 12 + 43 = 83, so $376.13. Heat in unsubsidized rent is eligible.
  • Integration Case 3: the 16-year-old's $4,000 is exempt. 52,000 against 72,972 is 71%: 8 points. 15 + 8 + 12 + 12 + 43 = 90, so $407.85.
  • Integration Case 4: 18,960 − 600 = 18,360, or 78%. The subsidized household with its own bill is eligible. 9 + 8 + 12 + 15 + 43 = 87, so $394.25.
  • Integration Case 5: SSI 438 (994 − 957.50 = 36.50 a month). 24,438 − 600 = 23,838, or 101%: 0 points. All-SSI keeps it eligible. 9 + 0 + 18 + 15 + 43 = 85, so $385.19.
  • Integration Case 6: size 2 (undocumented parent excluded). 25,000 against 31,716 is 78%. 9 + 8 + 16 + 12 + 43 = 88, so $398.79.
  • Integration Case 7: 11,400 against 64,716 is 17%: 25 points. Augusta is Central Mountain. 15 + 25 + 20 + 20 + 43 = 123, so $557.39 (the model maximum; S3).
  • va_eap.yaml Case 5 (2025): floor(15,060 × 1.5 / 12) = 1,882, or 22,584. 12,600 is 55%: 13 points. 9 + 13 + 12 + 20 + 43 = 97, so $439.57.
  • va_eap.yaml Case 7: the −60,000 loss floors to 0. 15,000 is 63%: 10 points, 74 points, $335.34.
  • va_eap.yaml Case 8: military retirement of 18,000 is 76%: 8 points, 76 points, $344.41.
  • Income-limit Cases 1 and 4: all listed sizes, including 21 (188,460) and 22 (196,704). For 2025, sizes 1–4 give 1,882, 2,555, 3,227 and 3,900; size 20 gives 14,660; size 21 adds 672.
  • Income-points Cases 1–2: 37,691 / 188,460 = 19.99947%, rounded to four places and floored, gives 19, so 25 points. Each exact band start lands in its band.
  • Countable Case 19: 9,600 (SBP annuity in military_retirement_pay_survivors) + 12,000 − 600 = 21,000.
  • Income-eligible Case 9: the couple's SSI is 17,892 − (34,000 − 240 − 780) / 2 = 1,402. 34,000 + 1,402 − 1,200 = 34,202 > 31,716, but both members receive SSI, so eligible.
  • Eligibility Cases 13–17: heat in unsubsidized rent is eligible. A voucher or public-housing household with heat in rent is not. A subsidized household with its own bill is.

Batch checks:

  • Integrate modeled state unemployment insurance into income flows #9558: the list reads total_unemployment_compensation. The guard's four tests passed in CI (Rest shard-1). It is unpinned by any case (S4).
  • Wire military retirement pay and survivors into federal and state tax (#10181) #10182 (item 16): the list reads military_retirement_income, and countable Case 19 pins the survivor input.
  • Document disjoint health premium inputs and correct consumer treatment #10046 (item 14): not applicable to payroll premiums. Virginia counts gross wages from employment_income and deducts no health premiums, apart from Medicare (S1). ssi counts the actual benefit.
  • State-gated keys (item 15): safe.
    • va_eap_climate_zone is an enum gated to Virginia whose default, TIDEWATER, is a valid key in p.climate_zone[zone].
    • va_eap_household_size (int, default 0) feeds only .calc() brackets and arithmetic.
    • The Microsimulation job passed. A mixed-state test is optional (G5).
  • Federal 110% floor: never binds. The limit uses the same lagged guideline as gov.hhs.liheap.fpg_floor, and floor(1.5g / 12) × 12 ≥ 1.5g − 12 is about 1.36 times 1.1g at every size. For size 1 in 2026 that is $23,472 against $17,215; above 20 members, +$8,244 a year against +$6,050. Not wiring the floor matches Alabama and Apply the federal 110% poverty floor to state LIHEAP programs (DC, MA, MS, NJ, OR) #10279's treatment of 150% states.
  • IIJA (item 12): Virginia pays no separate supplement; the value per point comes from the whole allocation.
  • Source-conflict ratchet (Tie source-conflict markers to the register shared with Axiom #10287):
    • I ran the comment readers and CONFLICT_COMMENT_RE from main's test_source_conflicts.py over the PR's 37 new parameter and variable files. They give 0 matching lines and no marker attempts. A control file (Florida's base_amount.yaml) gave its expected 1.
    • In CI, test_conflict_sounding_comments_do_not_grow passed on the merge with main at a6fafb06c2. main's two newer commits touch no code-health file.
  • Wiring:
    • Registry. The programs.yaml VA entry is in order after TX, and VA is added to the coverage line. test_programs_registry passed.
    • Aggregates. No aggregate wiring, which matches every merged LIHEAP state.
    • Gating. defined_for = StateCode.VA is on 10 variables, and va_eap is gated by va_eap_eligible.
    • Take-up. None beyond tanf's.
    • Changelog. changelog.d/va-liheap-wave1c.added.md is top level and typed added.
  • Descriptions (item 6): all 26 end "under the Energy Assistance Program", Virginia's program name, as Ohio's end "under the Home Energy Assistance Program".
  • References (item 7): every anchor I opened shows its value:
    • matrix pp. 1, 3-7;
    • plan pp. 5, 6, 8 and 9;
    • manual pp. 26, 28, 30, 31, 52 and 62-66;
    • application pp. 1-2.
    • Multi-page titles use #page 28,38-40 and #page 31,52, and variable tuples use # PDF pages comments.

Checklist results (LIHEAP batch):

  1. State plan and manual: pass for every published table. The value per point is the latest published; S3 covers the placeholders.
  2. Income definition: fail on two items. Medicare premiums are not exempted (S1), and rental losses net (S2). Gross wages and Social Security otherwise follow the manual.
  3. Backdating: pass for 2026. Earlier years are documented backfill (G3), and no FY2027 table is published.
  4. Household and arrangement rules: pass. Every eligibility branch has a test; heat in rent with UNSPECIFIED fuel is G2.
  5. Tests: 124 cases, 15 hand-checked, periods plain. Source coverage is S4.
  6. Descriptions: pass.
  7. References: pass (polish in G4).
  8. Wiring: pass.
  9. Axiom: invalid (S5).
  10. Branch and CI: a clean merge; 15 pass and 3 pending, none failed.
  11. Integrate modeled state unemployment insurance into income flows #9558: pass (pinning in S4).
  12. IIJA / FY2027: not applicable; nothing published for FY2027.
  13. Mutation check: every eligibility, limit, points and vulnerability branch is pinned. 15 source entries are not (S4).
  14. Document disjoint health premium inputs and correct consumer treatment #10046: not applicable (gross wages, no premium deduction other than S1).
  15. State-gated index defaults: safe (default key valid).
  16. Wire military retirement pay and survivors into federal and state tax (#10181) #10182: pass.
  17. Merged state SSPs: va_ag is missing (G1).
  • Federal 110% floor: never binds.

Axiom line assessment

Invalid: axiom: needed (S5).

  • The rule. CLAUDE.md allows needed only for external contributors; the author is staff.
  • No issue exists. rulespec-us has no issue for Virginia EAP, and main (5796861e58, 2026-10-20) has no us-va EAP module.
  • The companions are queued. Updated Oregon tax parameters for 2022 #1833 (Arizona) and Too many EITCs #1834 (Iowa) are open and labelled pe-parity. Each records its blockers (sources missing from the corpus, an unchecked signed release, encoder approval) and its companion tests.
  • What the new issue needs. File the Virginia issue in that format, matched to the head after S1–S4 are settled. Its required outputs should name medicare_part_b_premium (S1) and the per-source floors (S2). Its interpretations should record the placeholder or estimated fuel and burden points (S3), the plan-versus-manual choice for interest, dividends and cash contributions, and Salem's zone.

CI status

15 pass, 3 pending, 0 failed. Run 39635445749 tests merge commit 9f707dcb, which merges head 1396baf7fe into a6fafb06c2 (pull_request, started 2026-10-21 17:24 UTC). I re-checked gh pr checks 10303 at the end of this review.

  • Passed: all four states shards, household, irs, ssa-usda, rest-a and rest-b baseline shards, Rest (Python, shard-1), Microsimulation, Household API Partners, PR validation, Package and compatibility, and Quick Feedback.
  • Still pending: Baseline contrib-hhs, Baseline reform, and Rest (Python + variables). None of them runs the Virginia EAP files.
  • Where the Virginia tests ran. Quick Feedback deferred to the full suite: "12 files > 8" changed test files. States-shard-4 (batch 12/13) ran all 12 Virginia EAP files: 7 + 8 + 10 + 19 + 10 + 23 + 6 + 9 + 6 + 7 + 7 + 12 = 124 passed.
  • Guards. Rest shard-1 passed:
  • Known main-caused failure. Not applicable: the run started after 13:24 UTC, and the ratchet passed.
  • Locally: I ran no tests or probes. The examples in S1–S3 and G1 are hand traces of the code at the head.

Branch status

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

Verdict: REQUEST_CHANGES

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The FFY2026 limits, every Appendix B point table, all 133 climate zones and the eligibility rules check out, and #9558, #10182 and the ratchet pass. Five should-address items: (1) Social Security and RRB count gross, though VA excludes Medicare premiums (age 66 with $25k SS: $0 vs $412.38); (2) rental and farm-rent losses offset wages, against the PR's own no-offset rule; (3) the flat fuel and burden placeholders don't reproduce plan 2.5's range; (4) 15 of 26 income sources are never exercised in a test; (5) axiom: needed is invalid for staff, so file a pe-parity issue and mark it queued. Six suggestions. Full report in the comment above.

hua7450 and others added 2 commits October 23, 2026 20:03
- Deduct Medicare Part B premiums from gross income (manual exemption 15)
- Floor rental, farm-rent and other unearned sources per source
- Count the Auxiliary Grant (va_ag) as unearned income
- Document the placeholder points against plan item 2.5's range
- Cite 22VAC40-680 by section; test the remaining income sources

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

hua7450 commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, David. The fixes are in 9f0caea, and ae989be merges main. All 129 Virginia cases pass locally, as do the registry, source-conflict and unemployment-reader checks.

Should address

  • S1, Medicare premium: fixed. Exemption 15 (page 30) covers premiums "when deducted from Social Security or Railroad Retirement checks or when the household member is responsible for payment of the premium". It says they "must be manually deducted from gross income". va_eap_countable_income now subtracts the members' medicare_part_b_premium from household gross income, floored at zero. Your 66-year-old with $25,000 of Social Security receives $412.38 (va_eap Case 9). Without the premium as an input, the model computes the same $2,434.80 premium and $412.38. Countable income Case 22 covers a retiree with a pension and no Social Security. The "no premium input" wording is gone.
  • S2, rental losses: fixed. Every unearned source is now floored at zero per person, including rental and farm rent (manual C.5.a.4.b). Your Henrico example now gets $0 (va_eap Case 10). Countable income Case 20 shows that a farm-rent loss offsets neither rental income nor wages.
  • S3, placeholders: option (b). Both placeholder files now cite plan item 2.5 (page 9). They state the published FFY2025 range, $181.26 to $634.43 (40 and 140 points), next to the model's $271.90 to $557.39 (60 to 123 points). The PR body states both ranges too. I kept verified_years: "2026". The income limits, eligibility rules and published tables are verified for 2026, and the registry note already names the placeholder fuel and burden points.
  • S4, untested sources: fixed. Countable income Case 21 sets every remaining source to a distinct amount, including reported unemployment, a positive tanf and va_ag.
  • S5, Axiom: filed. LIHEAP wave 1c: encode Virginia EAP regular Fuel Assistance TheAxiomFoundation/rulespec-us#1900 is labelled pe-parity, and the body line now reads queued.

Suggestions

  • G1, Auxiliary Grant: taken. va_ag now counts beside ssi (manual B.5.a, page 25).
  • G2, UNSPECIFIED with heat in rent: declined. heating_type is a required input for every state LIHEAP program, and callers send the fuel. The default is tracked in Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754.
  • G3, backfilled years: kept as documented unverified estimates.
  • G4, references: taken. The 22VAC40-680 links now point to sections 10, 20 and 30, and the plan item 2.5 reference is added.
  • G5 and G6: not taken. The G6 difference is under one cent.

@hua7450
hua7450 requested a review from DTrim99 October 24, 2026 00:50
@DTrim99

DTrim99 commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 2): Add Virginia EAP Fuel Assistance (LIHEAP regular heating)

PR #10303 (author: hua7450). This round reviews head ae989becf3; round 1 reviewed 1396baf7fe. Two commits came in between:

  • 9f0caea3ca, "Fix issues from review: Medicare premium, rental floor, Auxiliary Grant" (22 files, +242/−46). It answers S1–S5 and G1–G6.
  • ae989becf3, a merge of main at 6a92d6a983. It had one hand-resolved conflict, the LIHEAP coverage line in programs.yaml (checked below). It touches no Virginia EAP file.

Ziming replied to every finding on 2026-10-24. The PR body now ends axiom: TheAxiomFoundation/rulespec-us#1900 queued. The PR still touches no partner test file.

Path prefixes, as in round 1: P/ = policyengine_us/parameters/gov/states/va/dss/eap/, V/ = policyengine_us/variables/gov/states/va/dss/eap/, T/ = policyengine_us/tests/policy/baseline/gov/states/va/dss/eap/.

Summary

Ziming fixed all five round-1 should items:

  • S1, Medicare premiums. V/va_eap_countable_income.py:64 subtracts each member's out-of-pocket medicare_part_b_premium from household gross income, as merged KY, NJ, OH, RI and SD do. va_eap.yaml Case 9 reproduces the round-1 example exactly: $412.38 instead of $0.
  • S2, rental losses. Every unearned source is now floored at zero per person (line 51). va_eap.yaml Case 10 reproduces the round-1 example ($0 instead of $303.62), and countable Case 20 covers a farm-rent loss.
  • S3, placeholder points. He took option (b). Both placeholder files and the body cite plan item 2.5's FFY2025 range ($181.26–$634.43, or 40–140 points) beside the model's $271.90–$557.39 (60–123 points). The registry note names the placeholder fuel and burden points, so verified_years: "2026" no longer reads as verified benefit amounts.
  • S4, untested sources. Countable Case 21 sets all 16 remaining sources to distinct amounts, including reported unemployment, a positive tanf and va_ag. Every earned and unearned entry is now positive in at least one case.
  • S5, Axiom. rulespec-us#1900 is open, labelled pe-parity, dispatch-ready, and describes this exact head.

Of the six suggestions, two are taken (G1 va_ag, G4 section URLs) and four are declined. I accept the reasons for G2, G3 and G6, and G5 was optional.

There are no new critical or should findings, and two new suggestions:

  • G7. Exemption 15 covers "Medicare Premiums" generally, and medicare_part_a_premium exists on main. It only matters for the roughly 1% of enrollees who buy Part A.
  • G8. The registry note could also carry the plan 2.5 range. This is optional polish left over from S3.

Batch checks:

I hand-checked every new or changed expectation: 5 new cases and 1 changed input. CI is 27/27 green at the head, and states-shard-4 passed all 129 Virginia EAP cases. The branch is 34 commits behind main and merges cleanly.

Prior findings status

Finding Status Evidence
S1: Social Security and railroad retirement counted gross, although exemption 15 excludes Medicare premiums and medicare_part_b_premium exists Fixed The code. V/va_eap_countable_income.py:64 sets medicare_premiums = add(spm_unit, period, ["medicare_part_b_premium"]), and line 72 returns max_(gross_income - medicare_premiums - disregard, 0). The comment at lines 56-63 quotes exemption 15 (manual p. 30) and plan 1.9 ("Excluding MediCare deduction"). The variable. medicare_part_b_premium is a person-level annual amount, "paid out of pocket by the enrollee, net of Medicare Savings Program coverage", and defined_for = "medicare_enrolled". That fits both prongs of exemption 15: a premium withheld from the check, or one the member pays. The tests. va_eap.yaml Case 9 is the round-1 example: $412.38, against $0 counted gross. Countable Case 22 is a pensioner with no Social Security ($20,965.20), which covers the "responsible for payment" prong. Income-eligible Case 3 now pins the premium at 0 to isolate the $50 deduction. The comments. The "no premium input" comment is gone. The documentation, the unearned-list comment and the body describe the deduction, and Part D stays not modeled (it has no input).
S2: rental and farm-rent losses offset wages Fixed The code. V/va_eap_countable_income.py:49-51 floors each unearned source per person: max_(person(source, period, options=[ADD]), 0). All 21 entries are person-level; ssi and va_ag are monthly and summed by ADD. tanf, an annual SPM-unit amount, moved out of the list and is added at line 54-55. The tests. va_eap.yaml Case 10 is the round-1 example: wages of $30,000 with an $8,000 rental loss give countable $30,000, not income eligible, $0. Countable Case 20: 24,000 + 4,800 + max(−2,500, 0) = $28,800. The comment. P/income/sources/earned.yaml:6-12 cites C.5.a.4.b (rental property among the self-employment profit determinations).
S3: the flat fuel (10) and burden (13) placeholders cannot reproduce plan 2.5's FFY2025 range Fixed (option b) The parameter files. P/points/primary_fuel.yaml:8-11 and P/points/energy_burden.yaml:8-11 state the published $181.26 (40 points) to $634.43 (140 points) range and the model's $271.90 (60 points) to $557.39 (123 points). Each adds a plan item 2.5 reference (p. 9). The body gives both ranges and the gaps: 271.90 − 181.26 = $90.64 and 634.43 − 557.39 = $77.04, which I re-derived. The registry keeps verified_years: "2026". Its note (line 1164) says the benefit uses "placeholder fuel-type and energy-burden points until VDSS publishes its survey". That is the "limit the note" alternative from round 1, so I accept it. Adding the range to the note would be polish (G8). The Axiom issue records the gap as interpretation 1.
S4: 15 of 26 income-source entries never took a nonzero value Fixed The new case. Countable Case 21 (T/va_eap_countable_income.yaml:446) sets SSTB self-employment $2,100, farm operations $1,300 and partnership $1,700. It also sets reported unemployment $3,100, strike $900, railroad $4,200, retirement distributions $1,500, survivor $2,400, general assistance $600, rental $3,600, farm rent $1,100, alimony $2,700, workers' compensation $3,300, disability benefits $2,900, va_ag $1,200 and SPM tanf $1,800. Each amount is distinct, so dropping any one entry changes the $34,400 total. Full coverage. Rental and farm rent are also positive in Case 20. The other 10 entries were already pinned in round 1, and self_employment_income is positive ($6,000) in Case 4.
S5: axiom: needed was not valid for a staff author Fixed rulespec-us#1900 is OPEN, labelled enhancement and pe-parity, and was created 2026-10-24 00:26 UTC. It names head ae989becf3 and commit 9f0caea3ca, and it is dispatch-ready (details under Axiom line assessment).
G1: count va_ag (item 17) Fixed P/income/sources/unearned.yaml:21-23 adds va_ag, citing B.5.a ("auxiliary grants to the aged, blind and disabled", manual p. 25). Case 21 pins it. The body records that most recipients live in licensed facilities, which have no input.
G2: a heat-in-rent household with UNSPECIFIED fuel Declined, accepted heating_type is a required input, and callers send the fuel. The body's eligibility row and the Axiom issue's interpretation 3 record the choice and #9754.
G3: backfilled years Declined, accepted These are documented, unclaimed estimates (body "Years covered"; Axiom interpretation 12), as in NJ S3.
G4: section URLs and the plan 2.5 reference Fixed No admincodefull link remains. All 6 parameter references and 9 variable references point to .../chapter680/section10/, section20/ or section30/, matching their titles. All three URLs return 200, and section 30 shows "point dollar value". The plan 2.5 reference is in both placeholder files.
G5: optional mixed-state and default-zone tests Declined (optional) Still safe: va_eap_climate_zone defaults to TIDEWATER, a valid key, and the Microsimulation job passed.
G6: cent truncation Declined, accepted The difference is under one cent. The Axiom issue's interpretation 11 records that benefits are not truncated.

New findings

Critical (must fix)

None.

Should address

None.

Suggestions

G7. Exemption 15 is not limited to Part B, and medicare_part_a_premium exists.

  • The rule. Manual B.6.b.15 (p. 30) exempts "Medicare Premiums when deducted from Social Security or Railroad Retirement checks or when the household member is responsible for payment of the premium". Application question 10 asks only about Parts B and D.
  • The input. main has medicare_part_a_premium: person-level, annual, "paid out of pocket by the enrollee, net of Medicare Savings Program coverage". It is $311 a month in 2026 with 30–39 quarters, $565 with fewer than 30, and $0 at the default of 40 quarters. The code documentation and the body say only that Part D has no premium variable, and they do not mention Part A.
  • Example (hand trace, 2026). A single person aged 66 in Fairfax County has $27,000 of Social Security, 35 quarters of coverage and a gas bill. The Medicare Savings Program pays neither premium at this income.
    • Head: 27,000 − 2,434.80 − 600 = 23,965.20 > 23,472, so $0.
    • With Part A: subtracting another 12 × 311 = 3,732 gives 20,233.20, or 86%. That is 5 income points, 9 + 5 + 16 + 20 + 43 = 93 points, and $421.44.
  • Why only a suggestion. About 99% of enrollees get premium-free Part A (the variable's own documentation), the application asks only about B and D, and no merged LIHEAP state deducts Part A.
  • Fix: add "medicare_part_a_premium" to the add() list at line 64 with a case, or list Part A under "Not modeled". If the code changes, update rulespec-us#1900's required output P.

G8. The registry note could carry the plan 2.5 range (S3 polish). Round 1's option (b) also asked for the extremes in the registry note. The note names the placeholders but not the $181.26–$634.43 published range against the model's $271.90–$557.39. One clause would let the coverage page show the gap.

Verified

The merge resolution (ae989becf3, programs.yaml).

  • The resolved coverage line is AR, AL, DC, GA, Riverside County, MA, FL, IL, IN, KS, KY, MD, MS, NC, NE, NJ, NY, OH, OK, OR, SD, RI, TX, VA. That is main's line (which gained AR and GA) plus VA. Nothing is dropped or duplicated.
  • state_implementations lists AR, GA and VA once each.
  • Against main, the PR's only programs.yaml changes are VA on the coverage line and the VA entry after TX.
  • test_programs_registry passed in CI.
  • The merge changed no Virginia EAP file: git diff 9f0caea3ca ae989becf3 over the EAP paths and the changelog is empty.

Hand-checks of every new or changed expectation. All use 2026, the $23,472 one-person limit (12 × $1,956), $4.531665 a point, and 43 points for living arrangement, fuel and burden.

  • va_eap.yaml Case 9 (age 66, Social Security $25,000, premium input $2,434.80 = 12 × $202.90, the 2026 base_premium; Fairfax; gas $1,200).
    • Income. 25,000 − 2,434.80 − 600 = 21,965.20 ≤ 23,472, so income eligible and eligible: gas with a bill, unsubsidized, not homeless.
    • Points. 21,965.20 / 23,472 = 93.58%, which floors to 93 and falls in "90 to 94": 3 income points. 9 + 3 + 16 (Northern) + 20 (elderly) + 43 = 91.
    • Benefit. 91 × 4.531665 = 412.381515, so $412.38.
    • Without the input the result is the same. After the $20 disregard, $2,063.33 a month is about 158% of the 2025 guideline, above the 135% QI limit, so the model charges the full premium, as the author says.
  • va_eap.yaml Case 10 (age 40, wages $30,000, rental −$8,000, Henrico). 30,000 + max(−8,000, 0) = 30,000 > 23,472, so not income eligible and $0. Not every member receives SSI, so the waiver does not apply.
  • Countable Case 20 (age 50, wages $24,000, rental $4,800, farm rent −$2,500). 24,000 + 4,800 + 0 = 28,800. The member is not Medicare-enrolled (under 65), not elderly, and has no SSI or TANF.
  • Countable Case 21 (head 45, child 10).
    • Unemployment. total_unemployment_compensation passes the reported $3,100 through (where(reported != 0, reported, modeled)), giving [3,100, 0].
    • Earned. 2,100 + 1,300 + 1,700 = 5,100.
    • Unearned. 3,100 + 900 + 4,200 + 1,500 + 2,400 + 600 + 1,800 + 3,600 + 1,100 + 2,700 + 3,300 + 2,900 + 1,200 = 29,300.
    • No deduction. va_eap_disabled reads is_disabled, social_security_disability, SSI with is_ssi_disabled and the 100% veteran flag, but not disability_benefits. The premium is pinned at 0.
    • Total: 34,400.
  • Countable Case 22 (age 67, taxable_private_pension_income $24,000, which feeds pension_income; premium $2,434.80). 24,000 − 2,434.80 − 600 = 20,965.20.
  • Income-eligible Case 3 (changed input: premium pinned at 0). 23,700 − 0 − 600 = 23,100 ≤ 23,472, so true. The model would otherwise charge $2,434.80 ($1,955 a month after the disregard is above the QI limit), and the case would still be true.
  • Case count. 7 + 10 + 10 + 22 + 10 + 23 + 6 + 9 + 6 + 7 + 7 + 12 = 129, as the body says. CI's per-file results match file by file.
  • Unchanged retiree cases. Integration Cases 1 and 7, va_eap.yaml Case 5 (2025) and countable Cases 5, 9, 10 and 12 keep their round-1 values with the premium now computed. In each, the Social Security income is under the QMB, SLMB or QI limit, so the model's Medicare Savings Program pays the premium. Case 12 floors to $0 in any case. CI confirms all of them.

New-code checks:

  • The formula. The S1 and S2 changes cannot form a cycle. medicare_part_b_premium reads the gross premium and MSP coverage, neither of which reads EAP.
  • Mutations.
    • Removing the premium deduction fails Cases 9 and 22.
    • Removing the per-source floor fails Cases 10 and 20.
    • Dropping va_ag, tanf or any listed source fails Case 21.
  • The documentation (V/va_eap_countable_income.py:10-24), the list comments and the body match the code.
  • The changelog changelog.d/va-liheap-wave1c.added.md is unchanged and still correct.

Checklist results (LIHEAP batch):

  1. State plan and manual: pass. The published tables are unchanged from round 1, and the placeholders are documented against plan 2.5 (S3).
  2. Income definition: pass. The Part B premium is deducted (S1), every source has a per-source floor (S2), wages count gross, and va_ag counts. Part A is G7.
  3. Backdating: pass for 2026. Earlier years are documented backfill (G3), and no FY2027 plan or matrix is posted.
  4. Household and arrangement rules: pass. The rules are unchanged, every branch is tested, and G2 is declined with a documented reason.
  5. Tests: 129 cases, all periods plain. The 5 new cases and 1 changed case all check by hand.
  6. Descriptions: pass, unchanged.
  7. References: pass. The section URLs resolve, and the multi-page titles use the #page 25,28-31,38,40 form with the href on the first page.
  8. Wiring: pass. The registry, the resolved coverage line and state gating are correct, and there is no aggregate wiring, as in every merged LIHEAP state.
  9. Axiom: valid (SSI Not Including Both Incomes for Married and Both Eligible #1900).
  10. Branch and CI: a clean merge with main and 27/27 green.
  11. Integrate modeled state unemployment insurance into income flows #9558: pass. The guard passes, and Case 21 pins reported unemployment.
  12. IIJA / FY2027: not applicable; there is no separate supplement and nothing published for FY2027.
  13. Mutation check: every new branch and every source entry is pinned.
  14. Document disjoint health premium inputs and correct consumer treatment #10046: not applicable. Wages come from gross employment_income, and the only premium deducted is Medicare.
  15. State-gated index defaults: safe, unchanged. The TIDEWATER default is a valid key, and va_eap_household_size feeds only brackets and arithmetic.
  16. Wire military retirement pay and survivors into federal and state tax (#10181) #10182 / Count military SBP annuities in state benefit income lists #10317: pass. The list reads military_retirement_income, and the Count military SBP annuities in state benefit income lists #10317 guard passes.
  17. Merged state SSPs: pass. va_ag counts (G1).

Federal 110% floor: never binds. The limits are unchanged from round 1, at about 1.36 times the floor.

Axiom line assessment

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

  • State: OPEN, labelled enhancement and pe-parity, created 2026-10-24 00:26 UTC.
  • It matches the head. It names head ae989becf3, review commit 9f0caea3ca and merge base 6a92d6a983, and lists 26 parameters, 11 variables and 129 cases by file. The required output for countable income, max(E + U + tanf − P − $600 × D, 0), names all 5 earned and 21 unearned sources, each floored per person, and P as the members' medicare_part_b_premium. That is the head's formula.
  • It is dispatch-ready in the rulespec-us#1467 / Too many EITCs #1834 format:
    • module paths with corpus status for each source;
    • verbatim text of 22VAC40-680-10, -20 and -30, manual B.5–B.8, Chapter C (including C.1.a, C.2.c and C.5.a.4.b), Chapter D, Appendices A–B, and FY2026 plan items 1.4, 1.8, 1.9, 2.1, 2.3, 2.5 and 2.6;
    • a required-outputs table;
    • 15 companion-test rows derived from the sources, not from PE output;
    • 12 interpretations, including the placeholders, the Part B-only deduction and the per-source floors;
    • its blockers: sources missing from the corpus, bundle scope, unpublished point values, an unchecked signed release, billed-run approval, and no reviewed oracle revision.
  • Note. If G7 is adopted, update output P and interpretation 4.

CI status

27 pass, 0 pending, 0 failed. Run 40043366372 ran on head ae989becf3 (pull_request, started 2026-10-24 00:07 UTC, after #10287 and the 2026-10-21 cutoff). I re-checked gh pr checks 10303 at the end of this review.

  • Virginia EAP tests. states-shard-4, batch 12/13, ran all 12 Virginia EAP files: 7 + 10 + 10 + 22 + 10 + 23 + 6 + 9 + 6 + 7 + 7 + 12 = 129 passed.
  • Guards (Rest shard-1):
  • Also passing: Microsimulation, Household API Partners, PR validation, Package and compatibility, and every baseline and contrib shard.
  • Known main-caused failure: not applicable.
  • Locally: I ran no tests or probes. The new expectations and G7 are hand traces of the code at the head.

Branch status

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.

Round 2: all five should items are fixed. The Medicare Part B premium is subtracted (Case 9 now $412.38), unearned sources are floored per person (Case 10 now $0), the placeholder gap against plan 2.5 is documented, the 16 remaining income sources are pinned, and rulespec-us#1900 is valid. The hand-resolved programs.yaml merge is correct, and all 129 VA cases pass. Two optional suggestions. Full report in the comment above.

- Subtract Medicare Part A premiums with Part B (manual exemption 15)
- Add IA, AZ and VA to the LIHEAP coverage line in this PR only, so the
  three wave 1C PRs do not edit the same line; merge after 10292 and 10293

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

hua7450 commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, David. 898d7cc takes G7: va_eap_countable_income now deducts medicare_part_a_premium together with medicare_part_b_premium, under exemption 15's "Medicare Premiums". The Part A premium is 0 for premium-free Part A, the default, so no existing case changes. All 129 cases pass locally.

G8 is declined. Registry notes stay at one or two sentences, and the plan item 2.5 range is already in both placeholder parameter files and the body.

This PR now also adds IA and AZ to the LIHEAP coverage line, so the three wave 1C PRs do not edit the same line. Please merge it after #10292 and #10293. TheAxiomFoundation/rulespec-us#1900 is updated to this head.

@DTrim99

DTrim99 commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 3): Add Virginia EAP Fuel Assistance (LIHEAP regular heating)

Summary

Head 898d7cc4d7, one commit after our round-2 approval (ae989becf3). It applies our round-2 suggestion G7, subtracting Medicare Part A premiums along with Part B, and it moves the shared programs.yaml LIHEAP coverage line into this PR.

The change

New finding

Should 1: the new Part A branch is unpinned. No test exercises a member with a Part A premium, so removing medicare_part_a_premium from the list would pass every case. Per the LIHEAP batch mutation rule, unpinned new logic is should-address. Add one case: a Medicare enrollee without premium-free Part A (e.g. fewer than 40 quarters), where the Part A premium changes countable income or the award.

CI and branch

At review time, 7 checks passed and 15 were pending. Let CI finish green, including test_programs_registry, which checks the coverage and state lists. Then merge after #10292 and #10293, as the commit message says.

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.

Round 3: the Part A change is correct (out-of-pocket and net of MSP under manual exemption 15), and the coverage-line move is fine. One should-address item: the new Part A branch has no test, so add a Medicare enrollee with a Part A premium. Then let CI finish, and merge after #10292 and #10293. Full note in the comment above.

- Countable income Case 23: a retiree's Part A and Part B premiums both come off
  gross income under exemption 15

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

hua7450 commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, David. 41c2063 adds countable income Case 23. A 67-year-old with a $24,000 pension pays the 2026 reduced Part A premium (311 x 12 = 3,732) and the 2,434.80 Part B premium. Countable income is 24,000 - 3,732 - 2,434.80 - 600 elderly deduction = 17,233.20, and it would be 20,965.20 without the Part A branch.

All 130 cases pass locally. TheAxiomFoundation/rulespec-us#1900 and the body are updated to this head.

@DTrim99

DTrim99 commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 4): Add Virginia EAP Fuel Assistance (LIHEAP regular heating)

Summary

This round reviews head 41c2063915. Round 3 reviewed 898d7cc4d7, and one author commit came in between: "Test the Part A premium deduction" (+25 lines, one test file). It adds countable income Case 23, which fixes the only item round 3 left open: the Part A branch had no test. No code, parameter, registry, changelog or partner test file changed.

  • G8 declined. On 2026-10-26 Ziming also declined G8 (putting the plan 2.5 range in the registry note). I accept that.
  • Axiom issue updated. rulespec-us#1900 now describes this head: 130 cases, with the deduction P covering the Part A and Part B premiums.

This round:

  • Findings: no new critical or should findings, and one new suggestion. The PR body's opening paragraph still says the deduction covers only "Medicare Part B".
  • CI: 32/32 green at the head.
  • Branch: 51 commits behind main, but it merges cleanly, and main's guards hold on the merged tree.
  • Merge-order note: this PR adds AZ to the LIHEAP coverage line, but Add Arizona LIHEAP Standard benefit #10293 (AZ) is still open. Hold this PR until Add Arizona LIHEAP Standard benefit #10293 merges, or drop AZ from the line (see Branch status).

Prior findings status

Finding Status Evidence
Round 3 Should 1: no test covered the new Part A branch Fixed The case. Countable income Case 23 sets up a 67-year-old with a $24,000 private pension who pays the 2026 reduced Part A premium (medicare_part_a_premium: 3_732) and the Part B premium (2_434.80). The expected value is 17,233.20. Mutation check. If "medicare_part_a_premium" is dropped from the add() list in va_eap_countable_income.py, the result is 20,965.20 (Case 22's value). That misses by 3,732 against a 0.01 margin, so the case fails. CI ran it green (see below).
G7 (round 2): exemption 15 covers Part A as well as Part B Fixed (in 898d7cc4d7) The code. medicare_premiums = add(spm_unit, period, ["medicare_part_a_premium", "medicare_part_b_premium"]). The documentation, the formula comment, the parameter comment and the body's Medicare row all say Part A and Part B. The test. Case 23 now covers it.
G8 (round 2): put the plan 2.5 range in the registry note Declined, accepted The registry notes stay at one or two sentences. Both placeholder parameter files and the body already state the published $181.26–$634.43 range next to the model's $271.90–$557.39. This was optional polish.
S1 (round 1): Medicare premiums counted gross Fixed in round 2; unchanged The deduction now also covers Part A (G7). va_eap.yaml Case 9 ($412.38) and countable Case 22 still pass.
S2: rental and farm-rent losses offset other income Fixed in round 2; unchanged The per-source max_(…, 0) floors on both lists are untouched. Case 10 and Case 20 pass.
S3: placeholder fuel and burden points Fixed in round 2 (option b); unchanged The placeholder files, the body and the registry note are unchanged.
S4: income sources that no test exercised Fixed in round 2; unchanged Case 21 still pins every remaining source.
S5: axiom line Fixed; updated to this head See Axiom line assessment.
G1 (va_ag) and G4 (section URLs) Fixed in round 2; unchanged Neither file has changed since ae989becf3.
G2, G3, G5, G6 Declined, accepted in round 2; unchanged The reasons still apply.

Since ae989becf3 (round 2), the PR has changed only va_eap_countable_income.py (G7), the programs.yaml coverage line, and va_eap_countable_income.yaml (Case 23). The fixes recorded above are otherwise untouched.

New findings

Critical (must fix)

None.

Should address

None.

Suggestions

G9. The body's opening paragraph is stale after G7.

  • The problem. The body's first paragraph still says the model "subtracts each member's out-of-pocket Medicare Part B premium". The Medicare premiums row in the table, the "Not modeled" list and the Tests section already say Part A and Part B, and so do the code and the Axiom issue.
  • Fix: change it to "Medicare Part A and Part B premiums". This is a body-only edit.

Verified

Hand check of the one new expectation (Case 23, 2026).

  • Premiums. gov.hhs.medicare.part_a.reduced_premium is $311 a month from 2026-01-01; the full premium is $565, and the reduced premium applies with 30–39 quarters. 311 × 12 = 3,732. Part B is 202.90 × 12 = 2,434.80, the 2026 standard premium.
  • Gross income. taxable_private_pension_income feeds pension_income, so gross income is 24,000, as in Case 22. Every other source is zero: ssi is pinned at 0, there is no Social Security, and a single adult with no children gets no tanf.
  • Disregard. Age 67 ≥ 60 (p.age.elderly), and the head is not disabled, so the deduction is one member × $50 × 12 = 600.
  • Countable income: max(24,000 − 3,732 − 2,434.80 − 600, 0) = 17,233.20. This matches the expected value.
  • Inputs. Case 23 sets the premium directly, although medicare_part_a_premium is defined_for = "medicare_enrolled". A direct input bypasses the formula, so the case tests the Virginia deduction on its own, without depending on quarters-of-coverage or MSP logic.
  • Realism. A $2,000 monthly pension is above the QI limit, so a Medicare Savings Program would not pay either premium.
  • Comments. The case comments (30–39 quarters, 311 × 12, "paid by the member", exemption 15's "Medicare Premiums") match the parameters and the manual.

Case count. 7 + 10 + 10 + 23 + 10 + 23 + 6 + 9 + 6 + 7 + 7 + 12 = 130, which matches the body and the Axiom issue. va_eap_countable_income.yaml went from 22 to 23 cases.

Main-side guards on the merged tree (git merge-tree of PolicyEngine/main and the head):

Axiom line assessment

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

  • State: OPEN, labelled enhancement and pe-parity, last updated 2026-10-26 18:55 UTC.
  • It matches the head.
    • It names head 41c2063915 and describes commits 9f0caea3ca, 898d7cc4d7 and 41c2063915, including Case 23's arithmetic.
    • It lists 130 cases by file, with 23 in va_eap_countable_income.yaml.
    • The required output for countable income gives P as "the members' medicare_part_a_premium and medicare_part_b_premium", and interpretation 4 covers both parts and notes the premium-free default.
  • Dispatch-readiness: it is still dispatch-ready, as assessed in round 2.

CI status

32 pass, 0 pending, 0 failed. Run 40381871894 ran on head 41c2063915 (a pull_request run, created 2026-10-26 18:54 UTC, success). I re-checked gh pr checks 10303 at the end of this review.

Branch status

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

Verdict: APPROVE

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving round 4: 0 critical, 0 should. Full report in the comment above. Merge after #10293 (this PR's coverage line lists AZ).

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 Virginia Energy Assistance Program Fuel Assistance (LIHEAP)

2 participants