Skip to content

Add Maryland MEAP regular heating assistance - #9746

Merged
hua7450 merged 13 commits into
mainfrom
md-meap
Oct 8, 2026
Merged

hua7450 merged 13 commits into
mainfrom
md-meap

Conversation

@hua7450

@hua7450 hua7450 commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Adds Maryland's MEAP regular heating benefit for state FY25 to FY27, including income/categorical eligibility, fuel and poverty-level payments, housing treatment and Garrett County's supplement. A qualifying gas-heating household at 101–150% FPG receives $360 statewide in FY26.

Fixes #9938.

Implementation

  • Reuses the lagged federal poverty guideline helper: 200% FPG through size ten, then 60% SMI using federal size factors. The published FY26 annual limits reconcile, the FY25 band tops are twice the 2024 guideline, and the size 11–12 SMI dollars match ACF IM 2025-02 Attachment 4. FY27 limits follow the same rule with the 2026 guideline; no FY27 income-guidelines sheet was located.
  • Follows the May 2025 manual (page 108) for household size: every child under 18 counts regardless of immigration status, nonqualified adults do not, and every member's income counts in full. COMAR 07.03.21.04C(1) would exclude every nonqualified member; the manual is the operating rule. At least one qualified member is still required. No SNAP-specific immigration bars apply.
  • Uses parameterized income sources and existing net business inputs without extra business/work-expense deductions. Excludes child/student earnings from age 18, counts pension, military retirement and retirement distributions separately, uses ordinary_dividend_income, and deducts reported child support paid, assumed court-ordered. Social Security and railroad retirement count net of the computed Medicare Part B premium (COMAR .04D(3), D(26), E(20); plan 1.9). Every source is floored at zero, so a rental or estate loss does not offset other income (D(2); plan 1.8). Farm rental income counts with rental income; survivor benefits count under D(12), D(13) and D(18).
  • Uses the annual tanf receipt variable for TCA income and categorical eligibility so unclaimed entitlement does not count. SNAP, TANF or SSI receipt waives the income test but not the other denials; the nominal amount above 200% FPG is $25.
  • The sole new empty input is a state dwelling arrangement: standard (the default), submetered or assisted living. Submetered/subsidized homes use Level 6; assisted-living applicants are excluded. The over-200% nominal level takes precedence over housing categories. Existing housing inputs cover ordinary subsidy treatment.
  • Encodes the FY25, FY26 and FY27 matrices (2024-07-01, 2025-07-01, 2026-07-01), which a model year reads at January 1 inside the matching state fiscal year. Gas and wood/coal are separate rows because FY25 and FY27 print different amounts. Kerosene maps to oil (labeled Oil/Kerosene from FY27) and a grid-tied solar home to electricity, the bill heating_expense assigns it. Applies the plan's 1.1 Garrett multiplier with half-up rounding and a flat nominal grant; the FY27 Garrett table matches that in every cell except oil Level 4, which prints $1,210 (1.1 × the FY26 oil Level 1) where 1.1 × $980 is $1,078, and the multiplier is kept. Heat-in-rent uses the known building fuel.
  • Formulas are undated; verified_years: "2025-2027". Earlier years remain unverified estimates.

Source conflicts and remaining coverage

Topic Codified text or plan Manual and model behavior
Children under 18 who are not citizens or qualified aliens COMAR 07.03.21.04C(1): a nonqualified member's income is included "but may not be counted as a member of the applicant's household" Manual page 108, twice: "Children under 18 are always counted in the household, regardless of citizenship status." The model follows the manual: a citizen parent with a nonqualified child has size two.
Interest and dividends FY2026 plan 1.9 leaves "Interest, dividends, or royalties" unchecked COMAR .04D(5)–(7) and manual Attachment A count them; the model counts them.
Strike pay COMAR .04D(24) counts employee strike funds "if there is no employee contribution"; plan 1.9 checks "Strike Pay" Manual Attachment B excludes "Strike funds without employee contributions"; the model follows COMAR.
Lump sums and AmeriCorps/VISTA COMAR .04E excludes them The manual and plan checklists count them; the model follows COMAR.

No amendment or agency clarification reconciling these was found; the 2023 categorical-eligibility statute does not settle household membership.

Medicare Part D premiums are not deducted (no variable). Means-tested VA eligibility cannot be inferred from generic veterans income. Other limits include detailed subsidy exceptions, the court-order cap on child support, some income sources and the 30-day observation window. No announced regular-heating medical-expense waiver was found. The renter fuel assignment remains an explicit inference. Other and unspecified fuels return zero as a coverage gap (#9754); an unknown county receives the statewide amount. The $21 SNAP nominal payment, cooling, EUSP, arrearage, crisis and equipment benefits are excluded.

Microsimulation: the datasets carry no heating inputs, so population totals are $0 until the default heating type changes. That change affects API partners and is tracked separately in #9754; callers must send heating_type with the matching fuel bill.

Validation

50 program YAML cases cover every FY26 statewide matrix cell, the FY25 and FY27 rows at Levels 1 and 6, the Garrett multiplier in all three years including the FY27 oil Level 4 cell, nominal payments, submetered/assisted-living treatment with and without categorical eligibility, the subsidized heat-in-rent denial with categorical eligibility, poverty boundaries, all FY26 limits for sizes 1–12 and the FY25 limits, the age-18 and student arms, the Medicare deduction (pinned and model-computed), a rental and an estate loss, every listed unearned source, the household-size rule for four mixed-status households, farm rental income over the limit, TANF take-up, model-computed SNAP conferring categorical eligibility, and integrated calculations. Every program case specifies heating_type; a two-state case covers a mixed simulation.

Official sources

axiom: TheAxiomFoundation/rulespec-us#1482 queued

@codecov

codecov Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (909176a) to head (91a4276).
⚠️ Report is 242 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #9746   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4        10    +6     
  Lines           76       153   +77     
  Branches         2         1    -1     
=========================================
+ Hits            76       153   +77     
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 and others added 8 commits October 3, 2026 01:52
Floor the payment level before the matrix lookup so units outside Maryland index a valid key in mixed-state simulations, and add a two-state case. Name the Medicare premium variables that are not wired in and cite COMAR 07.03.21.04D(3). Place Maryland between IL and TX in programs.yaml and shorten the note. Bring the nine test files to the shared conventions with fuel bills, realistic amounts and derivation comments, and add end-to-end cases for multi-member, Garrett, over-income, public-housing, size 11+ and heat-in-rent households.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
COMAR 07.03.21.04D(12) pensions, D(13) annuity and retirement
disbursements and D(18) veteran's pension benefits cover every
component of survivor_benefits, and .04E excludes none of them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Write parameter date keys unquoted, wrap each variable's documentation
string across lines, and assert the published income limits at the odd
household sizes only; the integration cases cover the even sizes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@hua7450
hua7450 marked this pull request as ready for review October 6, 2026 12:51
@hua7450
hua7450 requested a review from DTrim99 October 6, 2026 12:51
@DTrim99

DTrim99 commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Maryland MEAP regular heating assistance

PR #9746: Add Maryland MEAP regular heating assistance (author: hua7450, head 91a4276fac)

Scope: Maryland's LIHEAP heating program, run by the DHS Office of Home Energy Programs as the Maryland Energy Assistance Program (MEAP). The PR covers the regular heating benefit only. EUSP, EARA/GARA arrearages, crisis, cooling, equipment and the medical-expense waiver are out of scope. The PR changes 37 files: 16 parameters, 10 variables (9 computed and the new md_meap_dwelling_type input), 9 test files (40 cases), programs.yaml and a changelog fragment. Every parameter has a single 2025-07-01 value, the first day of state FY26. Every test uses period: 2026 except one FPG-lag case at 2022, and the registry lists verified_years: "2026".

Overall: the FY2026 encoding is accurate. All 35 FY26 matrix cells, the eight printed annual income limits, the size 11-12 limits from 60% SMI, the Garrett County multiplier, the band edges and all 40 test expectations match the sources. The problems are in how the parameters are dated and in a few income and fuel rules:

  • C1: period 2025 carries the FY26 amounts back over the published FY25 matrix, which is mostly lower. This is the same pattern as Add Indiana EAP regular heating assistance #9728 C1 and Add Oregon LIHEAP regular heating assistance #9743 C1.
  • S1: period 2027 keeps the FY26 amounts, although the FY27 matrix is published and has been in force since 2026-07-01.
  • S2-S4: Social Security and railroad benefits count gross of Medicare, military retirement pay is not counted, and rental and estate losses offset other income while self-employment losses are floored.
  • S5: SOLAR-heat households are eligible but get $0 by construction.
  • S6-S7: boundary-test gaps and parameter descriptions.

No finding was downgraded or dropped after re-verification. Two suggestions from the first pass were raised to should-address for consistency with this batch: the rental-loss asymmetry (S4, as #9743 S4) and the boundary-test gaps (S6, as #9743 S5 and #9744 S4). One proposed fix was corrected: netting Medicare does not change integration Case 5, because the model gives that couple a $0 out-of-pocket Part B premium, so a new case is needed to pin the deduction (S2). Duplicate findings from the regulatory and code passes are merged.

Path prefixes used below:

  • P/ = policyengine_us/parameters/gov/states/md/dhs/meap/
  • V/ = policyengine_us/variables/gov/states/md/dhs/meap/
  • T/ = policyengine_us/tests/policy/baseline/gov/states/md/dhs/meap/

Line numbers refer to head 91a4276fac. The benefit amounts below were reproduced by running the PR's code on single households.

Source Documents

# Document Link Use
1 FY2026 MD LIHEAP State Plan (Revision #1, accepted; 53 pp) MD_Plan_2026.pdf 1.4 CE (p5); 1.7b SNAP nominal, 1.8 gross income, 1.9 SSA "Excluding MediCare deduction" (p6-7); 2.1 limits (p9); 2.5 Garrett, 2.6 min/max (p11)
2 FY26 MEAP Benefit Matrix (1 p) FY26 matrix All 35 FY26 cells
3 FY2026 OHEP Income Guidelines (updated 7.9.2025; 1 p) Income Guidelines 200% FPG limits, sizes 1-8; "Households of 11+ must be based on 60% of the State Median Income"
4 OHEP Operations Manual (May 2025; 163 pp) Manual §1.1.1 (p6); eligibility (p11-12, 16); denials (p39); CE and nominal benefit (p62-63); members (p107-108); Att. A countable (p111); Att. B excluded (p112)
5 FY27 MEAP Benefit Matrices (statewide and Garrett) FY27 matrices Cited in the PR for rounding and kerosene only. Governs period 2027 (S1)
6 FY2025 MEAP Benefit Matrix MD_BenefitMatrix_2025.pdf Not cited in the PR. Governs period 2025 (C1)
7 FY2025 MD LIHEAP State Plan MD_Plan_2025.pdf Garrett 1.1, min $25 / max $750 (p11)
8 ACF LIHEAP IM 2025-02, Attachment 4 (FY2026 SMI table) Att. 4 MD SMI4 $154,117 (p2); 60% SMI for sizes 7-12, incl. $135,930 / $138,705 (p5)
9 COMAR 07.03.21 .04, .06, full chapter .02B(6) program year; .03 eligibility; .04 income and members; .06D(3) denials; .07 levels

Year map: which matrix each period reads

COMAR .02B(6): "'Current program year' means the State fiscal year from July 1 through June 30." .07B(1): "The Administration shall determine annually the benefit payment levels and include the levels in each year's approved State plan."

The PR maps period Y to state FY Y (V/md_meap.py:19-20: "verified for state FY26 (July 2025-June 2026), modeled in period 2026"). A YEAR formula reads parameters at January 1 of the period. The system backdates each parameter's earliest value to 2015 and carries the last value forward (main's #9628 moves that step before reforms but keeps it), so every period reads the 2025-07-01 values.

Period Instant Program year in force Amounts the PR returns Published amounts Limits (FPG lag, gov.hhs.smi)
2025 2025-01-01 FY25 (Jul 2024-Jun 2025) FY26 (backdated) FY25 matrix, mostly lower 2 x 2024 FPG ($30,120 at size 1), matching the FY25 band table's $2,510/month top; FFY25 SMI. Correct
2026 2026-01-01 FY26 FY26 FY26 matrix 2 x 2025 FPG; SMI $154,117. Correct
2027 2027-01-01 FY27 (Jul 2026-Jun 2027) FY26 (carried forward) FY27 matrix, mostly higher 2 x 2026 FPG ($31,920 at size 1, $66,000 at size 4); FFY27 SMI $158,187. Follows the rule; FY27 income guidelines not collected

Critical (Must Fix)

C1. Period 2025 pays FY26 amounts, but the published FY25 matrix is different and mostly lower

  • Code: P/payment/amount/electricity.yaml:2-15, gas_wood_coal.yaml:2-15, oil.yaml:2-15 and propane.yaml:2-15. Every cell has only 2025-07-01, so period 2025 (instant 2025-01-01, inside FY25) reads the backdated FY26 values.

  • What the PR says: V/md_meap.py:20 ("Earlier results are unverified backfilled estimates") and programs.yaml:856 (verified_years: "2026"). Labelling the values as estimates does not help, because the governing FY25 schedule is published and differs.

  • Sources:

    • FY2025 MEAP Benefit Matrix, p. 1, Levels 1-7:
      • Electric: "$102" at Levels 1-6, "$25" at Level 7
      • Gas: "$301 $241 $210 $181 $150 $102 $25"
      • Oil: "$750 $600 $525 $450 $375 $102 $25"
      • Propane: "$644 $515 $450 $386 $321 $102 $25"
      • Wood/Coal: "$418 $334 $292 $250 $209 $102 $25"
      • Labelled "*Flat Rates"; Level 7 is "Over 200%FPL CE Only". The bands are the same five FPL bands as FY26.
    • FY2025 plan 2.6, p. 11: "Minimum Benefit $25 Maximum Benefit $750". That is the FY25 oil Level 1 cell. The same page keeps the Garrett "1.1 multiplier".
    • In FY25, Gas ($301) and Wood/Coal ($418) are different rows, so the shared gas_wood_coal parameter cannot hold FY25. FY27 splits them too (S1).
  • Head trace, period 2025, Baltimore County unless noted:

    Household PR FY25 matrix Error
    Zero income, oil, Level 1 $1,100 $750 +$350
    Zero income, gas, Level 1 $550 $301 +$249
    Zero income, wood, Level 1 $550 $418 +$132
    Zero income, propane, Level 1 $1,000 $644 +$356
    Zero income, electric, Level 1 $100 $102 -$2
    Subsidized, oil, Level 6 $225 $102 +$123
    Garrett, oil, Level 1 $1,210 $825 (750 x 1.1) +$385
    $20,000 wages, gas (133% of the 2024 FPG, Level 4) $360 $181 +$179

    The limits and bands are right for 2025. Only the dollar amounts are wrong.

  • Precedent: Add Indiana EAP regular heating assistance #9728 C1 and Add Oregon LIHEAP regular heating assistance #9743 C1 had the same setup (single-dated FY2026 values, verified_years: "2026", and a published prior-year matrix that the backdated values contradict). Both were rated critical.

  • Older periods: 2024 and earlier get the same FY26 values. The FY24 matrix was not collected, so that tier is not rated separately; either fix below covers it.

  • Fix, either option is acceptable:

    1. Encode FY25 (preferred).
      • Split gas_wood_coal into gas and wood_coal; FY25 and FY27 both need the split. Update the select at V/md_meap.py:35-51 (NATURAL_GAS to gas; WOOD and COAL to wood_coal).
      • Add 2024-07-01 values from the FY25 matrix to every row, including electricity $102 at Levels 1-6 and $102 at Level 6 for every fuel. Cite the FY25 matrix and MD_Plan_2025.pdf#page=11.
      • Add a period-2025 test: zero-income oil $750; $20,000 wages with gas $181; Garrett oil Level 1 $825.
    2. Start the program in FY26 explicitly. Add an in_effect boolean (false before, true from 2025-07-01) and apply it in md_meap_eligible, so period 2025 and earlier return $0. Say so in the documentation and the registry notes, and add a period-2025 test that expects 0.

Should Address

S1. Period 2027 pairs stale FY26 amounts with FY27, whose matrix is published and in force

  • Code: the same four amount files. V/md_meap.py:16 already cites FY27-MEAP-Benefit-Matrices.pdf#page=1, but only for rounding and the kerosene label. The PR body says "FY27 dollar amounts are not used."

  • Source: FY27 MEAP Benefit Matrices, p. 1, statewide Levels 1-7:

    • Electric: "$100" at Levels 1-6, "$25" at Level 7
    • Gas: "$700 $665 $630 $560 $455 $315 $25"
    • Oil/Kerosene: "$1,225 $1,160 $1,080 $980 $795 $550 $25"
    • Propane: "$1,325 $1,255 $1,190 $1,060 $860 $595 $25"
    • Wood/Coal: "$550 $520 $495 $440 $355 $245 $25"
    • Level 7 header: "Over 200% CE"
  • FY27 began 2026-07-01, so this matrix is in force today. Level 6 is now fuel-specific, and Gas and Wood/Coal differ.

  • Head trace, period 2027:

    Household PR FY27 Error
    Oil, Level 1 $1,100 $1,225 -$125
    Gas, Level 1 $550 $700 -$150
    Propane, Level 1 $1,000 $1,325 -$325
    Wood or coal, Level 2 $475 $520 -$45
    Subsidized, oil, Level 6 $225 $550 -$325
    Garrett, oil, Level 1 $1,210 $1,348 (FY27 Garrett table) -$138
    $20,000 wages, gas (125% of the 2026 FPG, Level 4) $360 $560 -$200

    Electric and Wood/Coal Level 1 ($550) are unchanged.

  • Why should and not critical: carrying values forward is the normal state until a parameter update, and the registry claims only 2026. FY27 is the current program year and the PR is not merged yet, so this is the same rating as Add Oregon LIHEAP regular heating assistance #9743 S1.

  • Fix:

    • Add 2026-07-01 values from the FY27 matrix to the split gas / wood_coal rows and to oil, propane and electricity.
    • Keep the 1.1 multiplier rather than reading the FY27 Garrett table. Its Oil Level 4 cell, "$1,210", does not equal 1.1 x $980 = $1,078 (likely a typo); every other Garrett cell matches half-up rounding of 1.1 x statewide.
    • Add a period-2027 case, for example $20,000 wages with gas giving $560 and Garrett oil Level 1 giving $1,348.
    • Widen verified_years to the range actually encoded (for example "2025-2027") and update the md_meap documentation. If FY27 is deferred, say in the registry notes and the PR body that 2027 uses FY26 amounts.

S2. Social Security and railroad retirement are counted gross of the Medicare premium; COMAR, the plan and the manual all net it

  • Code:
    • P/income/sources/unearned.yaml:4-9 lists social_security and railroad_benefits, with the comment "medicare_part_b_premium and medicare_part_b_premiums_reported are not wired in yet, so both count gross".
    • V/income/md_meap_countable_income.py:19 ("Medicare-premium deductions remain deferred") and :25-32.
  • Sources:
    • COMAR 07.03.21.04D(3): "Social Security income less Medicare payment deduction"; D(26): "Railroad retirement benefits less Medicare payment deduction"; E(20) excludes "Medicare payments deducted from Social Security grants".
    • FY2026 plan 1.9, p. 6: "Social Security Administration (SSA) benefits" is checked, with "Including MediCare deduction" unchecked and "Excluding MediCare deduction" checked.
    • Manual Att. B, p. 112, excluded income: "Medicare payments and premiums deducted from Social Security or pensions".
  • Why "not wired in yet" does not hold: medicare_part_b_premium exists and is computed (the out-of-pocket premium, net of Medicare Savings Program coverage). Merged policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap_income.py:36-45 already subtracts it, as does IN EAP. Add New York HEAP regular heating assistance #9744 S1 raised the same gap.
  • Head trace, period 2026: a single 70-year-old with $32,000 of Social Security retirement and a gas bill, not on SNAP, TCA or SSI.
    • PR: $32,000 is over the $31,300 limit, so $0.
    • The model's premium for this person is $2,434.80. Net income is $29,565.20, under the limit and 189% of FPG: Level 5, gas, $300.
    • Every enrolled Social Security recipient within one premium of the limit is affected.
  • Fix:
    • Per person, count max_(social_security + railroad_benefits - medicare_part_b_premium, 0), as KY does, and remove the two sources from the plain list (or subtract the premium in the formula).
    • Integration Case 5 (T/integration.yaml:184-222) keeps its values: the model gives both spouses a $0 out-of-pocket premium at that income, so countable income stays $26,400 and the benefit $798. Update its "counted gross" comment, and the one in T/income/md_meap_countable_income.yaml Case 1, whose recipient is not Medicare-enrolled.
    • Add a case that pins the deduction, such as the $32,000 example above ($300).
    • Update rulespec-us#1482, which encodes "Medicare deduction not wired".

S3. Military retirement pay is not counted as a pension

  • Code: P/income/sources/unearned.yaml:15 lists pension_income, which adds only taxable_pension_income and tax_exempt_pension_income. military_retirement_pay is a separate input and is in neither.
  • Sources:
  • Head trace, period 2026: a 60-year-old with $40,000 of military_retirement_pay and a gas bill. The PR counts $0 (pension_income is $0), so Level 1 and $550. $40,000 is over the $31,300 limit, so the correct result is $0.
  • Precedent: merged KY lists both, with the note "Military retirement pay is a separate input from the pension inputs that pension_income adds, so both are listed" (ky/dcbs/liheap/unearned_income_sources.yaml). Add Oregon LIHEAP regular heating assistance #9743 S3 raised the same omission.
  • Fix: add military_retirement_pay to the unearned list, with a test. Its documentation says it includes survivor benefits, so add a comment against double counting with survivor_benefits.

S4. Rental and estate losses reduce countable income, but self-employment losses are floored, although COMAR treats self-employment and rental income as one item

  • Code:
    • V/income/md_meap_countable_earned_income.py:21-22 floors each earned source: max_(person(source, period), 0). Its documentation (:14-15) calls flooring "a modeling convention".
    • V/income/md_meap_countable_income.py:30-32 adds the unearned list unfloored. That list includes estate_income, rental_income and farm_rent_income (P/income/sources/unearned.yaml:14, 24-25), all signed inputs.
    • The documentation at :19-20 says "Rental income is assumed nonnegative", but nothing enforces it.
  • Source: COMAR 07.03.21.04D(2) puts both in one item: "Self-employment and rental income, less operating expenses except for depreciation necessary to produce the income". Plan 1.8, p. 6 checks "Gross Income". Nothing in COMAR, the plan or the manual supports flooring one half of D(2) and not the other.
  • Head trace, period 2026, single adult, gas:
    • $33,000 wages and rental_income: -3_000: countable income $30,000, eligible, Level 5, $300.
    • The same -$3,000 as self_employment_income: $33,000, ineligible, $0.
  • Why should: this matches Add Oregon LIHEAP regular heating assistance #9743 S4 (the Oregon manual sends landlords to the self-employment form; the PR floored self-employment only). The code also contradicts the PR's own documented assumption.
  • Fix: loop over p.sources.unearned with max_(person(source, period), 0), matching the earned loop and ky_liheap_income.py ("a loss in one source cannot offset another"). Replace the sentence at :19-20, and add a rental-loss case like the one above.

S5. SOLAR-heat households are eligible but paid $0 by construction

  • Code: V/md_meap.py:35-51. The select has no SOLAR branch, so SOLAR falls to default=0. T/md_meap.yaml:96-109 (Case 7) asserts SOLAR → $0.
  • Why the household is eligible: the shared heating_expense reads pre_subsidy_electricity_expense for ELECTRICITY | SOLAR ("Solar-primary homes are assumed grid-tied with electric supplemental heat"). So has_heating_expense is true and md_meap_eligible is true.
  • Source: Manual §1.1.1, p. 6: MEAP issues "energy benefits that can be applied toward the cost of electricity, utility gas, oil, kerosene, propane, coal, wood, or solar energy subscriptions (MEAP only)."
  • Head trace, period 2026: SOLAR heat, a $900 pre-subsidy electric bill and zero income: eligible, Level 1, $0. The electricity row, for the same bill, gives $100.
  • Why should and not critical (unlike Add Oregon LIHEAP regular heating assistance #9743 C2): the payable item MD names is a solar subscription (community-solar credits on an electric account), not solar-primary heating. COMAR .02B(13) lists suppliers of "electricity, natural gas, propane, kerosene, coal, or wood", and no matrix row covers solar. The Oregon case was critical because that manual is fuel blind and the PR's own test said solar is billed as electricity; neither holds here. Merged KY and NE pay SOLAR $0, while DC, IL, MA and KS map it to the electric row. Reach is small.
  • Fix: map SOLAR to the electricity row, consistent with heating_expense and DC/IL/MA/KS, and update Case 7 to [1_100, 550, 100, 0, 0, 0]. Otherwise make SOLAR (and OTHER) ineligible, so the model does not report eligible-but-$0.

S6. Four boundaries have no test that would fail if they broke

Coverage is otherwise strong: every fuel branch, every band edge and edge + $1, Levels 6 and 7, Garrett at Levels 1, 6 and 7, the size-11 SMI limit at and $1 above, and the two-state case. These are not pinned:

  • (a) The switch from 200% FPG to 60% SMI.
    • T/eligibility/md_meap_income_limit.yaml:10 tests sizes 1, 3, 5, 7 and 9, and Case 2 tests 11 and 12. Setting P/eligibility/smi_min_size.yaml to 10 would pass every test (size 10 would get $133,156 instead of $130,300).
    • The comment at :17-18, "Even sizes are exercised by the integration cases", does not hold: integration asserts only sizes 2 and 4, so sizes 6, 8 and 10 are never checked.
    • Add sizes 8 and 10 ($108,300 and $130,300) and correct the comment.
  • (b) The age-18 boundary. V/income/md_meap_countable_earned_income.py:23 uses age >= p.earned_income_min_age. The only 18-year-old (T/income/md_meap_countable_earned_income.yaml:11-12) is a full-time student, so their $0 comes from the student flag; > would pass. The rule is COMAR .04E(3) ("employment income of a child younger than 18 or a full-time student"). Add an 18-year-old non-student whose earnings count, and an adult full-time student.
  • (c) survivor_benefits. It is set in no MD test, so removing it from the list would pass. The PR body claims "Survivor benefits count under D(12), D(13) and D(18)", and commit 1d83d76f54 added it. T/income/md_meap_countable_income.yaml:5 is named "every modeled unearned source counts" but omits it. Add it to that case, or rename the case.
  • (d) Categorical eligibility with a non-income denial. The subsidized heat-in-rent denial (T/eligibility/md_meap_eligible.yaml:40-56) and the assisted-living denial (T/md_meap.yaml:111-132, row 3) are tested only for households that are not categorically eligible. V/eligibility/md_meap_eligible.py:34-40 ANDs them correctly, but a rewrite that let CE bypass them would pass. Manual p. 38: categorical eligibility applications "cannot be denied for over income ... but may be denied for other eligibility issues." Add one CE row to each.

S7. Parameter descriptions use verbs outside the template

The same pattern was flagged in #9744 S5, #9728 S5 and #9726 S8. Merged LIHEAP parameters use provides/sets/limits/uses/excludes. Each description below ends correctly with the program name:

File:line Current Suggested
P/income/sources/unearned.yaml:1 "counts these available unearned income sources" "Maryland uses these unearned income sources to compute countable income under ..."
P/payment/garrett_multiplier.yaml:1 "multiplies regular heating grants in Garrett County by this factor" "Maryland sets this multiplier for regular heating payments in Garrett County under ..."
P/payment/nominal_level.yaml:1 "assigns categorically eligible households above the highest income band to this payment level" "Maryland sets this payment level for categorically eligible households above 200 percent of the poverty guideline under ..."
P/payment/subsidized_level.yaml:1 "assigns subsidized or sub-metered households to this payment level" "Maryland sets this payment level for subsidized or submetered households under ..."
P/payment/regional_counties.yaml:1 "applies its regional heating multiplier in these counties" "Maryland uses the regional heating multiplier in these counties under ..."

Two smaller wording issues:

  • P/income/sources/earned.yaml:1 says "these existing earnings sources". "Existing" is model jargon; move the business-expense note to a YAML comment.
  • P/payment/amount/gas_wood_coal.yaml:1 and its label (:19) read "gas wood coal" with no punctuation. The C1/S1 split replaces this row anyway.

Source: repository parameter-description convention; there is no regulatory page.

Suggestions

G1. Household size follows COMAR .04C, not manual p. 108 (documented choice)

  • V/eligibility/md_meap_household_size.py:15-22. COMAR .04C(1): a nonqualified member "shall have the member's income included on the application, but may not be counted as a member of the applicant's household." Manual p. 108: "Children under 18 are always counted in the household, regardless of citizenship status".
  • In integration Case 1 (citizen parent, undocumented child, $21,000), COMAR gives size 1, Level 4, $360; the manual gives size 2, Level 3, $400.
  • Following the regulation is defensible and documented. Settle it with the owner, and remove the review-process language before merge: "this draft follows the regulation" (md_meap_household_size.py:19) and "that conflict awaits the owner's call" (T/integration.yaml:49-50).

G2. The SNAP nominal payment is neither modeled nor listed as excluded, and the two exclusion lists differ

  • Plan p. 6: 1.7a nominal payment for SNAP households "Yes"; 1.7b "Amount of Nominal Assistance: $21.00"; 1.7c "Once Per Year".
  • V/md_meap.py:25-26 excludes "Cooling, EUSP, arrearages, crisis and equipment benefits"; programs.yaml:857 omits arrearages. Neither names the $21 SNAP payment. Align the two lists and add it to both.

G3. Two source conflicts are not disclosed (the PR follows COMAR, which is correct)

  • Interest and dividends. Plan p. 7 leaves "Interest, dividends, or royalties" unchecked. COMAR .04D(5)-(7) and manual Att. A (p. 111) count them.
  • Strike pay. Manual Att. B, p. 112 excludes "Strike funds without employee contributions". COMAR .04D(24) counts "Employee strike funds if there is no employee contribution", and plan p. 6 checks "Strike Pay".
  • Add both to the md_meap_countable_income documentation next to the lump-sum and AmeriCorps conflicts it already lists.

G4. Point the unknown-fuel $0 at #9754 in code and tests

  • V/md_meap.py:23-24 ("Unknown fuels return zero"); T/md_meap.yaml:107-108 and T/integration.yaml:381-382 both say "heating_type is a required input", but it defaults to UNSPECIFIED.
  • The PR body defers this to Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754, which the batch accepts. The heating_type enum makes UNSPECIFIED the value for heat-in-rent tenants who do not know the building's fuel, so every such household gets $0 although COMAR .07A(2) pays the landlord.
  • Add a # See #9754 note in md_meap.py and both cases, and reword the comments so the $0 reads as a coverage gap, not a legal result.

G5. A published table already gives the large-household SMI dollars

  • V/eligibility/md_meap_income_limit.py:20-22 and :31-32 say the amounts are "calculated from the stated SMI rule, not independently published FY26 dollar rows".
  • ACF IM 2025-02 Att. 4, p. 5 prints Maryland's 60% SMI for sizes 7-12, including "$135,930" and "$138,705" for 11 and 12, which match the PR exactly. Cite it and reword both comments.

G6. Reference polish

None of these references is wrong. They could be more complete:

  • COMAR subsections. The regs.maryland.gov pages carry subsection anchors (verified: #D(2), #D(3), #E(3), #D(3)(i), #D(3)(k)). The md_meap_dwelling_type href 07.03.21.06#D(3)(k) is valid. Parameter titles such as "COMAR 07.03.21.04, countable income and exclusions" could cite and anchor the operative item, e.g. earned_income_min_age → #E(3).
  • Missing citations:
    • V/payment/md_meap_level.py:10-14: add plan #page=9 (the subsidized "lower benefit level" its tests cite).
    • P/payment/nominal_level.yaml:8-10: add manual #page=63 (the CE nominal-benefit rule).
    • P/eligibility/fpg_year_lag.yaml:8-10: the Income Guidelines never state a lag. Add a comment that it is inferred from the limits equalling 2 x the 2025 FPG, as md_meap_fpg.yaml already does.
    • V/income/md_meap_countable_income.py:11: add manual #page=111 and #page=112 and plan #page=6, which its documentation relies on.
  • Test headers cite several COMAR sections with one URL: T/eligibility/md_meap_eligible.yaml:9-10, T/md_meap.yaml:12-13 and T/integration.yaml:17-18. Add each section's URL.

G7. Non-Maryland units divide 0/0 in md_meap_level

  • V/payment/md_meap_level.py:24-26 divides by md_meap_fpg, which is defined_for = StateCode.MD. Core evaluates the formula on the full arrays and masks afterwards, so non-MD rows compute 0/0 = NaN with a RuntimeWarning in mixed simulations and microsim. The result is masked, so nothing breaks.
  • Drop defined_for from md_meap_fpg (fpg() is positive for every state at size 1 or more), or guard the division.

G8. Level 7 is encoded twice

  • The top bracket of P/payment/income_level.yaml:23-26 returns 7, and P/payment/nominal_level.yaml:3 is also 7. V/payment/md_meap_level.py:35 and V/md_meap.py:53 rely on the two being equal.
  • Add a cross-reference comment to both, or test "over 200%" directly and use nominal_level only as the output.

G9. Digit separators

P/payment/amount/oil.yaml:3 (1100) and propane.yaml:3 (1000). Use 1_100 and 1_000, as merged MS and the PR's own tests do.

G10. No integration case lets SNAP or SSI compute

Every integration case pins snap: 0, tanf: 0 and ssi (stated at T/integration.yaml:19-20). The CE path runs only from directly set values or TCA take-up. Add one low-income case without pinned snap, so the SNAP → CE → MEAP chain runs end to end. No SNAP variable reads a LIHEAP variable, so there is no cycle risk.

G11. Document the dwelling-type default

V/md_meap_dwelling_type.py:21-25 lacks the sentence NE and IN use ("If omitted, the model assumes ..."). Add it, with the microsim consequence: every household defaults to STANDARD, so submetered homes are paid at their income level instead of Level 6, and assisted-living residents are not denied.

G12. Naming and duplicated logic

  • P/payment/regional_counties.yaml has a generic name, but the multiplier it gates is garrett_multiplier. Use one convention (regional_* or garrett_*).
  • receives_housing_assistance | is_in_public_housing appears in both V/eligibility/md_meap_eligible.py:27-29 and V/payment/md_meap_level.py:29-31. Optionally share it through one small variable.

G13. PR body and registry hygiene

  • "PR remains draft" is stale (isDraft is false), and "not re-run locally after the review fixes" is superseded by green CI.
  • The survivor-benefits claim has no test (S6c).
  • After C1 and S1, update verified_years and the programs.yaml notes, and replace "Earlier results are unverified backfilled estimates" in V/md_meap.py:20.

Verified correct

Check Result
FY26 matrix (source 2) 35 of 35 cells: Electric 100 x6 / 25; Gas 550/475/400/360/300/225/25; Oil 1,100/990/880/770/650/225/25; Propane 1,000/930/830/725/600/225/25; Wood/Coal = Gas. Gas and Wood/Coal are identical in FY26, so the shared row is valid for FY26 only
Income limits, sizes 1-10 2 x FPG with a one-year lag. The eight printed annual limits ($31,300 ... $108,300) reproduce, and sizes 9-10 give $119,300 / $130,300 (the IG "+$11,004" line gives $4 more, which the PR documents). The plan's %SMI table (2.1, p. 9) is a truncated restatement; the published IG dollars and COMAR .03A(2)'s poverty-percent wording control
Income limits, sizes 11+ floor(factor x floor(0.6 x SMI4)): SMI4 $154,117 (gov.hhs.smi 2025-10-01; ACF Att. 4 p. 2), giving $135,930 and $138,705 (Att. 4 p. 5). 200% FPG at size 10 ($130,300) stays under 60% SMI ($133,156), so switching at 11 is the federal cap at work
Categorical eligibility snap + tanf + ssi > 0 matches plan 1.4 (p. 5) and manual p. 62. tanf carries md_tca take-up. CE households over 200% get Level 7, $25 (the manual's "Level 6" on p. 63 is a mislabel; the FY25-FY27 matrices all put it at Level 7). Means-tested VA is a documented gap
Household and immigration is_citizen_or_legal_immigrant matches COMAR .02B(20)(a)-(g); at least one qualified member (manual p. 16); nonqualified members' income counted (.04C(2))
Countable income Child and full-time-student earnings excluded (.04E(3)); court-ordered child support paid deducted (.04E(13), F); lump sums excluded per .04E(16) (documented conflict); veterans_benefits breadth supported by manual Att. A; pension_income and retirement_distributions disjoint. The lists are self-contained, with no SNAP list reuse and no snap_rental_income double count
Denials .06D(3)(i) subsidized heat-in-rent (manual p. 39) and .06D(3)(k) assisted living; no asset test (plan 2.3)
Levels and bands Thresholds 0.25/0.5/1/1.5/2 with right=True match the inclusive FY26 headers; the FY25 band table confirms inclusive tops (size 1 "$0 - 314" for $313.75). Subsidized, public-housing and submetered households go to Level 6 unless at Level 7
Fuel mapping Kerosene → oil (FY27 "Oil/Kerosene"); wood/coal → the gas row (FY26 only, see C1/S1); OTHER and NONE → $0, consistent with the COMAR .02B(13) supplier list
Garrett County Plan 2.5 (p. 11) 1.1 multiplier. Half-up rounding and the flat Level 7 match the FY27 Garrett table (665 x 1.1 = 731.5 → "$732"; 595 → "$655"; every Level 7 cell "$25"). The plan's $1,100 maximum is the statewide oil Level 1 (FY25 likewise: $750), so $1,210 for FY26 Garrett oil Level 1 is consistent
Code patterns defined_for throughout (md_meap → md_meap_eligible → MD); SPMUnit/YEAR, Person/YEAR earnings; no hard-coded policy values; vectorized; level floored at 1 so mixed-state runs do not raise (Case 9); all 16 parameters read; helpers reused (fpg(year_lag=), smi(), has_heating_expense, heat_expense_included_in_rent); no SNAP-to-MEAP cycle
Tests 40 cases, plain 2026/2022 periods. Hand-checked: integration Cases 1-5, 7 and 10 (e.g. Case 5: 26,400 / 21,150 = 125% → Level 4 → 725 x 1.1 = 797.50 → $798; Case 10: 138,228 / 70,650 = 196% → Level 5 → $300), md_meap Case 6 ($523/$798/$248/$1,210/$25), countable income $29,440, FPG Case 1 ($17,420)
Microsimulation Datasets carry no heating inputs, so MD totals are about $0 until #9754 lands, as the PR body says. md_meap feeds no household aggregate

Checklist results (LIHEAP batch 2)

  1. SNAP income-list reuse: pass on reuse; the lists are self-contained and use rental_income, so main's snap_rental_income change cannot double count. Fail on source treatment: Social Security and railroad benefits gross of Medicare (S2), military retirement pay missing (S3), rental and estate losses unfloored (S4).
  2. Backfill and carry-forward: period 2025 carries FY26 amounts back over the published FY25 matrix (C1); period 2027 carries them forward although FY27 is published (S1).
  3. Operative state rule: COMAR .03A residence and qualified member, .04C, .06D(3)(i) and (k), manual p. 16 heating responsibility and p. 62 CE are encoded. The .04C versus manual p. 108 conflict is documented (G1). SOLAR is eligible but unpaid (S5).
  4. UNSPECIFIED and microsim defaults: UNSPECIFIED → $0 is deferred to Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754, which is acceptable; add the pointer in code (G4).
  5. Axiom line: valid queued line pointing to a dispatch-ready pe-parity issue; the head pin is stale (see below).
  6. References: every PDF href is a single #page=N; no page text in titles; plan #page=5/9/11 and manual #page=11/16/39/62/63/107 land correctly; editions are current (FY26 plan Revision Basic prototype #1, manual May 2025). Polish in G5-G6.
  7. programs.yaml: the MD entry is well formed and sorted between KY and MS. The coverage-line conflict with main is expected (see Branch status). Not a finding.
  8. Rounding and boundaries: half-up rounding is tested at .50 products ($523/$798/$248) with a flat Level 7; every band top is tested at the edge and $1 above; limits are tested at the edge and $1 above for sizes 1 and 11; periods are plain. Gaps: the size 10/11 switch and the age-18 boundary (S6).

CI status

37/37 checks pass at 91a4276fac (gh pr checks 9746, 2026-10-06).

Branch status

Axiom line

axiom: TheAxiomFoundation/rulespec-us#1482 queued: valid format, and the issue meets the bar.

  • rulespec-us#1482 is open and labelled pe-parity (plus enhancement).
  • It has module paths (us-md/regulations/comar/07.03.21/{02,03,04,06,07}, us-md/statutes/human-services/5-5A-07; corpus marked "needs ingest"), verbatim COMAR, manual and plan text, a required-outputs table covering every variable, and 21 companion tests derived from the matrix, Income Guidelines and FPG rather than from PE output. It is dispatch-ready.
  • It is stale:
    • It records head 1d83d76f54, so it misses 7ede048e5a (the tidy commit) and two main merges, and it calls the PR a draft.
    • It encodes "Medicare deduction not wired" (S2) and SOLAR → $0 (S5), and its year scope will change with C1 and S1.
  • None of this blocks the PR by itself. Update the issue when the fixes land.

Next steps

  1. C1: split gas_wood_coal and add FY25 (2024-07-01) amounts, or gate the program to start in FY26. Add a period-2025 test either way.
  2. S1: add FY27 (2026-07-01) amounts and a period-2027 test, then widen verified_years.
  3. S2-S4: net Social Security and railroad benefits of medicare_part_b_premium, add military_retirement_pay, floor each unearned source, and add the three cases above.
  4. S5: map SOLAR to the electricity row (or make it ineligible) and update Case 7.
  5. S6-S7: add the size 8/10, age-18, survivor-benefit and CE-denial tests, and align the five parameter descriptions.
  6. Rebase on main (coverage line as above), refresh the PR body and rulespec-us#1482, and re-run CI.

1 critical and 7 should-address findings, plus 13 suggestions.

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 per the review comment above: 1 critical (period 2025 carries the FY26 amounts back over the published, mostly lower FY25 matrix) and 7 should-address items (2027 keeps FY26 amounts though the FY27 matrix is in force, Social Security/RRB counted gross of Medicare, military retirement pay not counted, rental/estate losses offsetting income while self-employment losses are floored, SOLAR households eligible but paid $0, boundary-test gaps, parameter descriptions). All 35 FY26 matrix cells, income limits and the Garrett multiplier match the sources.

hua7450 and others added 2 commits October 6, 2026 17:07
- Encode the FY25 and FY27 benefit matrices beside FY26, splitting the
  gas and wood/coal rows; verified_years "2025-2027"; document the FY27
  Garrett oil Level 4 cell.
- Count every child under 18 regardless of status and exclude only
  nonqualified adults, following the OHEP manual; require a qualified
  member; share adult_age with the earned-income rule.
- Count Social Security and railroad retirement net of the Medicare
  Part B premium; add military retirement pay; floor every unearned
  source.
- Read the electricity row for solar heat; guard the FPG ratio divisor.
- Add cases for the FY25 and FY27 rows and Garrett amounts, mixed-status
  household size, the Medicare deduction, rental and estate losses,
  categorical eligibility with assisted living and heat in rent, and
  model-computed SNAP conferring categorical eligibility.
- Re-template descriptions and add manual, plan, matrix and COMAR
  references.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
# Conflicts:
#	policyengine_us/programs.yaml
@hua7450
hua7450 requested a review from DTrim99 October 6, 2026 21:10
@DTrim99

DTrim99 commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 2): Maryland MEAP regular heating assistance

PR #9746: Add Maryland MEAP regular heating assistance (author: hua7450). Round 1 reviewed head 91a4276fac and requested changes for 1 critical and 7 should-address findings, plus 13 suggestions. This round reviews head 906c7855b0: fix commit 9423ea0487 ("Address review round 1 on Maryland MEAP") plus a merge of main. The author left no reply comment; the commit message and the rewritten PR body serve as the response.

Summary: the fix commit addresses the critical finding and all seven should-address findings.

  • C1 (FY25): the FY25 matrix is encoded at 2024-07-01, with gas split from wood/coal. All 35 FY25 cells match the matrix.
  • S1 (FY27): the FY27 statewide matrix is encoded at 2026-07-01, including the fuel-specific Level 6. All 35 cells match. Garrett County keeps the 1.1 multiplier, and the FY27 table's oil Level 4 cell ($1,210) is documented as the one cell that disagrees.
  • S2-S4 (income): Social Security and railroad benefits now count net of medicare_part_b_premium, military_retirement_pay is counted, and each unearned source is floored at zero.
  • S5 (solar): SOLAR is paid from the electricity row.
  • S6 (tests): all four boundaries now have a test that fails if they break.
  • S7 (descriptions): the five descriptions follow the template.

I re-ran the round-1 examples on the new head, and each now gives the source's result. All 50 MEAP YAML cases pass locally. Every round-1 suggestion is addressed except G12 (optional naming) and part of G6.

There are no new critical or should-address findings. There are five new suggestions. The largest is that the FY27 OHEP Income Guidelines are published, although the PR says none were found (G14): the model reproduces their sizes 1-12, but not the 13-15 rows. CI has not finished on this head (see CI status).

Path prefixes as in round 1:

  • P/ = policyengine_us/parameters/gov/states/md/dhs/meap/
  • V/ = policyengine_us/variables/gov/states/md/dhs/meap/
  • T/ = policyengine_us/tests/policy/baseline/gov/states/md/dhs/meap/

Line numbers refer to head 906c7855b0. I reproduced the amounts by running the PR's code locally.

Sources added this round

# Document Link Use
10 FY27 OHEP Income Guidelines (1 p.) FY27-OHEP-Income-Guidelines.pdf Annual 200% FPL limits for sizes 1-10 ($31,920 ... $134,160) and "MEAP Guidelines for HH of 11+ 60% SMI" for sizes 11-15 ($139,520, $142,368, $145,690, $149,013, $152,335). Not cited in the PR (G14)

Sources 1-9 are as in round 1. The FY2025 matrix (source 6), the FY27 matrices (source 5), the FY2025 plan (source 7) and ACF Att. 4 (source 8) are now cited in the PR.

Year map at the new head

A YEAR formula reads parameters at January 1 of the period, which falls inside the state fiscal year of the same number (COMAR .02B(6): July 1 to June 30).

Period Instant Program year Amounts read Limits
2024 and earlier 2024-01-01 FY24 or earlier 2024-07-01 (FY25), backdated 2 x the prior year's FPG. Not claimed; documented as unverified estimates (V/md_meap.py:21-23)
2025 2025-01-01 FY25 2024-07-01 (FY25) 2 x 2024 FPG ($30,120 at size 1), matching the FY25 band table; FFY25 SMI
2026 2026-01-01 FY26 2025-07-01 (FY26) 2 x 2025 FPG; FFY26 SMI $154,117
2027 2027-01-01 FY27 2026-07-01 (FY27), or the FY26 value where FY27 is unchanged 2 x 2026 FPG ($31,920 at size 1); FFY27 SMI $158,187 (gov.hhs.smi, 2026-10-01)

Each claimed period now pairs its own matrix with its own limits.

Round-1 items

Item Status Evidence
C1 Period 2025 paid FY26 amounts ADDRESSED
  • P/payment/amount/gas.yaml:2-27 and wood_coal.yaml:2-26 replace gas_wood_coal. V/md_meap.py:44-62 maps NATURAL_GAS to gas and WOOD/COAL to wood_coal.
  • Every row has 2024-07-01 values (electricity.yaml:2-21, oil.yaml:2-27, propane.yaml:2-27), checked cell by cell against the FY2025 matrix image: Electric $102 x6 / $25; Gas 301/241/210/181/150/102/25; Oil 750/600/525/450/375/102/25; Propane 644/515/450/386/321/102/25; Wood/Coal 418/334/292/250/209/102/25. 35 of 35 match.
  • The level, multiplier, county, poverty-rate and lag parameters now start at 2024-07-01 and cite the FY25 matrix and plan.
  • Tests: T/md_meap.yaml Case 11 (line 200, Levels 1 and 6 for each fuel) and Case 12 (line 211, Garrett: $825 / $165 / $25); T/eligibility/md_meap_income_limit.yaml Case 3 (line 43: $30,120 / $62,400 / $105,440).
  • Re-run results below.
S1 Period 2027 kept FY26 amounts ADDRESSED
  • 2026-07-01 values at Levels 1-6 of gas, oil and propane, and Levels 2-6 of wood_coal. Electric (all levels) and Wood/Coal Level 1 are unchanged in FY27 ($100, $550), so carrying the FY26 value forward is correct. Level 7 is $25 in all three years. All 35 FY27 statewide cells match, including the fuel-specific Level 6 (315 / 550 / 595 / 245 / 100).
  • Garrett: the 1.1 multiplier with half-up rounding reproduces 34 of the 35 FY27 Garrett cells. The oil Level 4 cell prints $1,210, against 1.1 x $980 = $1,078. The model keeps $1,078, and V/md_meap.py:27-30, T/md_meap.yaml:239-256 (Case 14) and the PR body say why. That is the handling round 1 recommended.
  • programs.yaml:861 reads verified_years: "2025-2027".
  • Tests: T/md_meap.yaml Cases 13-14 (lines 228, 239). The levels are pinned in both, so period 2027 has no end-to-end case (G14).
S2 Social Security gross of Medicare ADDRESSED
  • V/income/md_meap_countable_income.py:41-48 counts max_(social_security + railroad_benefits - medicare_part_b_premium, 0) per person, and P/income/sources/unearned.yaml:2-3 drops both sources from the list. This is the merged KY pattern.
  • Tests: T/income/md_meap_countable_income.yaml Case 4 (line 74: 29,565 / 32,000), and T/integration.yaml Case 11 (line 451) with the computed premium ($2,434.80, giving 29,565.20, Level 5, $300).
  • Integration Cases 5 and 10 keep their values, and their comments now explain the $0 out-of-pocket premium.
S3 Military retirement pay ADDRESSED P/income/sources/unearned.yaml:12-15 adds military_retirement_pay, with the requested double-counting comment. T/income/md_meap_countable_income.yaml Case 1 includes $1,800 of it (31,780). Re-run: $40,000 of military retirement pay now gives $0 (was $550).
S4 Rental and estate losses offset other income ADDRESSED
  • V/income/md_meap_countable_income.py:54-56 floors each unearned source, and the documentation (:27-28) replaces "assumed nonnegative".
  • Test: T/income/md_meap_countable_income.yaml Case 5 (line 91: a -$3,000 rental and -$1,000 estate loss give 33,000).
  • Re-run: $33,000 wages with a -$3,000 rental loss now gives $0 (was $300), the same as a -$3,000 self-employment loss.
  • The floor applies to each source's unit total, so a residual cross-member offset remains (new suggestion G15). Add Oregon LIHEAP regular heating assistance #9743 round 2 rated the same residual a suggestion.
S5 SOLAR eligible but $0 ADDRESSED V/md_meap.py:42-46 pays SOLAR from the electricity row, citing manual §1.1.1 and the heating_expense pairing. T/md_meap.yaml Case 7 (line 107) now expects [1_100, 550, 100, 0, 0, 0].
S6a 200% FPG to 60% SMI switch ADDRESSED T/eligibility/md_meap_income_limit.yaml Case 1 (line 11) asserts sizes 1-10, including $108,300 and $130,300. Setting smi_min_size to 10 would now fail (size 10 would give $133,156). The "even sizes" comment is gone.
S6b Age-18 boundary ADDRESSED T/income/md_meap_countable_earned_income.yaml Case 1 (line 6) adds an 18-year-old non-student ($14,800) and a 40-year-old full-time student ($0). Changing >= to > would now fail.
S6c survivor_benefits untested ADDRESSED T/income/md_meap_countable_income.yaml Case 1 sets survivor_benefits: 300, and its expectation (34,180 - 2,400 = 31,780) depends on it.
S6d CE with a non-income denial ADDRESSED T/eligibility/md_meap_eligible.yaml Case 3 (line 45) adds a categorically eligible subsidized heat-in-rent household (false). T/md_meap.yaml Case 8 (line 124) adds a categorically eligible assisted-living household (false, $0). Both cite manual p. 38.
S7 Description verbs ADDRESSED Line 1 of unearned.yaml and earned.yaml now says "uses these sources as ... income"; garrett_multiplier, nominal_level and subsidized_level say "sets"; regional_counties says "uses". "Existing" is gone, and the business-expense note is a comment (earned.yaml:2-3). The amount rows say "provides this annual regular heating payment for ...". The new adult_age.yaml description uses "treats" (G17).
G1 Household size: COMAR .04C vs manual p. 108 ADDRESSED The author chose the manual: V/eligibility/md_meap_household_size.py:24-32 counts every child under 18 regardless of status and excludes nonqualified adults. The documentation (:15-22), the PR body's conflict table and rulespec-us#1482 record the COMAR .04C(1) conflict. The review-process wording is gone. V/eligibility/md_meap_eligible.py:37-39 keeps the qualified-member requirement, so a household whose only counted member is an undocumented child is denied (T/eligibility/md_meap_household_size.yaml, line 19). Integration Case 1 now gives size 2, Level 3, $400, the manual's result.
G2 SNAP nominal payment and exclusion lists ADDRESSED V/md_meap.py:31-33 and programs.yaml:862 both list the $21 SNAP nominal payment, cooling, EUSP, arrearage, crisis and equipment benefits.
G3 Interest/dividends and strike pay conflicts ADDRESSED V/income/md_meap_countable_income.py:20-24 discloses both, next to the lump-sum and AmeriCorps conflicts.
G4 #9754 pointers ADDRESSED V/md_meap.py:59-60, T/md_meap.yaml:121, T/integration.yaml:387-389. The comments now call the $0 a coverage gap.
G5 Large-household SMI dollars ADDRESSED ACF Att. 4 is cited in V/eligibility/md_meap_income_limit.py:15-16 and P/eligibility/smi_rate.yaml:11-12, and both comments are reworded (:22-24, :33-34).
G6 Reference polish PARTIALLY
  • Done: COMAR subsection anchors (P/income/sources/earned.yaml:16, P/adult_age.yaml:12, V/income/md_meap_countable_earned_income.py:11); plan #page=9 in md_meap_level (:14); manual #page=63 in nominal_level; the fpg_year_lag comment (:2-3); manual #page=111 and plan #page=6 in md_meap_countable_income; one URL per COMAR section in the three test headers.
  • Open: manual p. 112 is only in a # PDF pages 111, 112 comment (V/income/md_meap_countable_income.py:13), not an entry of its own. unearned.yaml:32 anchors #D(1), which is wages (G17).
G7 0/0 in md_meap_level ADDRESSED V/payment/md_meap_level.py:27-29 divides by where(fpg > 0, fpg, 1).
G8 Level 7 encoded twice ADDRESSED Cross-reference comments at P/payment/income_level.yaml:25-26, P/payment/nominal_level.yaml:2 and V/payment/md_meap_level.py:31.
G9 Digit separators ADDRESSED oil.yaml uses 1_100 and 1_225, and propane.yaml uses 1_000, 1_325 and so on.
G10 No case lets SNAP compute ADDRESSED T/integration.yaml Case 12 (line 483): nothing is pinned, the model's SNAP confers categorical eligibility, TCA is $0, and the result is Level 3, $400.
G11 Dwelling-type default ADDRESSED V/md_meap_dwelling_type.py:24-25: "If omitted, the model assumes a standard residence." The microsim consequence is not spelled out, which is optional.
G12 Naming and duplicated logic NOT ADDRESSED regional_counties / garrett_multiplier and the duplicated subsidy expression (V/eligibility/md_meap_eligible.py:28-30, V/payment/md_meap_level.py:33-35) are unchanged. This was optional.
G13 PR body and registry hygiene ADDRESSED The body no longer says draft. It describes 50 cases, cites the FY25 and FY27 sources, and updates the year scope. programs.yaml:861-862 has the new range and notes. One sentence in the body is now inaccurate: "no FY27 income-guidelines sheet was located" (G14).

Re-run of the round-1 households at the new head (single adult, Baltimore County unless noted, no SNAP, TCA or SSI):

Item Household Period Round 1 Now Source
C1 Zero income, oil, Level 1 2025 $1,100 $750 FY25 matrix
C1 Zero income, gas / wood / propane / electric 2025 $550 / $550 / $1,000 / $100 $301 / $418 / $644 / $102 FY25 matrix
C1 Subsidized, oil, Level 6 2025 $225 $102 FY25 matrix
C1 Garrett, oil, Level 1 2025 $1,210 $825 750 x 1.1
C1 $20,000 wages, gas (133% of the 2024 FPG, Level 4; limit $30,120) 2025 $360 $181 FY25 matrix
S1 Zero income, oil / gas / propane 2027 $1,100 / $550 / $1,000 $1,225 / $700 / $1,325 FY27 matrix
S1 $5,000 wages, wood (Level 2) 2027 $475 $520 FY27 matrix
S1 Subsidized, oil, Level 6 2027 $225 $550 FY27 matrix
S1 Garrett, oil, Level 1 2027 $1,210 $1,348 FY27 Garrett table
S1 $20,000 wages, gas (125% of the 2026 FPG, Level 4; limit $31,920) 2027 $360 $560 FY27 matrix
S2 Age 70, $32,000 Social Security, gas (computed premium $2,434.80; 29,565.20, Level 5) 2026 $0 $300 COMAR .04D(3); plan 1.9
S3 Age 60, $40,000 military retirement pay, gas 2026 $550 $0 (over $31,300) COMAR .04D(12)
S4 $33,000 wages and a -$3,000 rental loss, gas 2026 $300 $0, the same as a -$3,000 self-employment loss COMAR .04D(2)
S5 SOLAR, zero income 2026 $0 $100 (eligible) Electric row

Critical (Must Fix)

None.

Should Address

None.

Suggestions

G14. The FY27 OHEP Income Guidelines are published: cite them and test period 2027. Their 13-15 rows differ from the model

  • Where: the PR body ("no FY27 income-guidelines sheet was located"), P/eligibility/{fpg_rate,fpg_year_lag,smi_rate,smi_min_size}.yaml and V/eligibility/md_meap_income_limit.py:12-17, none of which cites a FY27 limit source. rulespec-us#1482 says the same thing.
  • Source: FY27 OHEP Income Guidelines:
    • "EUSP and MEAP Guidelines - 200% FPL", annual: "$31,920", "$43,280", "$54,640", "$66,000", "$77,360", "$88,720", "$100,080", "$111,440", "$122,800", "$134,160" for sizes 1-10.
    • "200% of the Federal Poverty Level exceeds 60% of state median income for households of 11 or more. Therefore MEAP eligibility for households of 11 or more is determined based on 60% State Median Income below."
    • "MEAP Guidelines for HH of 11+ 60% SMI", annual: "$139,520", "$142,368", "$145,690", "$149,013", "$152,335" for sizes 11-15.
  • What the model gives for period 2027 (reproduced, size forced): $31,920, $43,280, $54,640, $66,000, $77,360, $88,720, $100,080, $111,440, $122,800 and $134,160 for sizes 1-10, then $139,520 and $142,368 for sizes 11-12.
    • Sizes 1-12 match the sheet exactly. This confirms the one-year FPG lag, the switch at 11 and FFY27 SMI, so the "2025-2027" claim holds for the limits that matter.
    • Sizes 13-15 do not match. The sheet steps by $3,322 a member above 12, which is 3.5 points of 60% SMI ($94,912). 45 CFR 96.85(b) adds 3 points, which is what gov.hhs.smi uses: the model gives $145,215, $148,062 and $150,910, which is $475, $951 and $1,425 lower.
  • Why a suggestion: the sheet's 13-15 rows exceed 60% of SMI as 45 CFR 96.85(b) computes it, which is the ceiling the sheet itself cites. They look like an extrapolation error, like the FY27 Garrett oil Level 4 cell. Households of 13 or more are very rare.
  • Fix:
    • Cite the FY27 sheet in the four eligibility parameters and in md_meap_income_limit. Correct the PR body and rulespec-us#1482.
    • Add a period-2027 case to T/eligibility/md_meap_income_limit.yaml, for example sizes 1, 10, 11 and 12: [31_920, 134_160, 139_520, 142_368].
    • Add one end-to-end period-2027 case, since Cases 13-14 pin the level. For example, $20,000 of wages with gas heat is 125% of the 2026 guideline, Level 4, $560.
    • Document the 13-15 difference in md_meap_income_limit, as md_meap documents the oil Level 4 cell. Alternatively, follow the sheet for those sizes if the owner prefers the printed rows.

G15. The unearned floor applies to the unit total of each source, so one member's loss still offsets another member's income from the same source

  • Where: V/income/md_meap_countable_income.py:54-56 computes max_(add(spm_unit, period, [source]), 0), which sums each source across members and then floors the total. The earned formula floors each source per person (V/income/md_meap_countable_earned_income.py:21-22). The comment at :49-53 says unearned sources are floored "like the earned sources".
  • Example (reproduced, period 2026, gas): one adult earns $30,000 and has $20,000 of net rental income; the other adult in the unit has a -$20,000 rental loss on a separate property.
    • Rental income nets to $0 for the unit, so countable income is $30,000, 142% of the $21,150 two-person guideline: Level 4, $360.
    • The same amounts as self_employment_income count $50,000, over the $42,300 limit: $0.
    • COMAR .04D(2) lists self-employment and rental income as one item, which was the basis of round-1 S4.
  • Fix: floor the person-level sources per member before summing, as merged ky_liheap_income.py:33-35 does. tanf is SPM-level, so it keeps the unit sum. Alternatively, document the unit-level reading and add a two-member case. Add Oregon LIHEAP regular heating assistance #9743 round 2 raised the same point (N1).

G16. The rule parameters still start in FY26, although the registry claims FY25

  • Where: P/adult_age.yaml:3, P/eligibility/smi_rate.yaml:3, P/eligibility/smi_min_size.yaml:3, P/income/sources/earned.yaml:5 and P/income/sources/unearned.yaml:5 keep a single 2025-07-01 value and cite only FY2026 sources or COMAR. The amounts, levels, multiplier and the poverty-rate and lag parameters were re-dated to 2024-07-01 and cite the FY25 matrix and plan.
  • Effect: none on values. Backdating gives period 2025 the same numbers, and the age, the 60% SMI rate and the COMAR source lists did not change. This is documentation only, as in Add New York HEAP regular heating assistance #9744 round 2 (G15).
  • Fix: date these five parameters 2024-07-01 and add a FY2025 citation where one exists (for example FY2025 plan section 2.1 for the 60% SMI rule).

G17. Reference titles, one anchor and one description

  • Page text in titles (new this round; the template asks for "no page number in title"):
    • "PDF page 1" in the FY27 matrix titles at P/payment/amount/{electricity,gas,oil,propane}.yaml:33/39/39/39, wood_coal.yaml:38, P/payment/income_level.yaml:40, nominal_level.yaml:14, subsidized_level.yaml:13 and garrett_multiplier.yaml:13.
    • "PDF pages 2, 5" at P/eligibility/smi_rate.yaml:11.
    • "PDF pages 111, 112" and "PDF pages 6, 7" at P/income/sources/unearned.yaml:33, 35.
    • Keep the section in the title, and give each page its own entry (for example manual Att. A at #page=111 and Att. B at #page=112).
  • Anchor: P/income/sources/unearned.yaml:31-32 titles "COMAR 07.03.21.04D, countable income, and E, exclusions" but anchors #D(1), which is "Gross wages, tips, and commissions", an earned item. Use #D, and add #E as a second entry. (The 07.03.21.06#D(3)(k) anchor in V/md_meap_dwelling_type.py:19 is valid: the page has id="D(3)(k)", "Applicant resides in an assisted living facility.")
  • Description: P/adult_age.yaml:1 uses "treats ... as children, who count in the household regardless of immigration status and whose earnings are excluded". Suggested: "Maryland sets this age as the threshold below which members count as children under the Maryland Energy Assistance Program", with the two uses in a comment.

G18. FY25 and FY27 Levels 2-5 are pinned by only four cells

  • T/md_meap.yaml Cases 11 and 13 test Levels 1 and 6 of each fuel. Of the 40 FY25 and FY27 statewide cells at Levels 2-5, only four are exercised, and only through the Garrett multiplier (FY25 gas Level 5; FY27 gas Level 2, wood/coal Level 5 and oil Level 4).
  • FY26 has a case per fuel across all seven levels (Cases 1-5). The PR body describes the new coverage accurately ("the FY25 and FY27 rows at Levels 1 and 6"), and I checked every cell against the matrices, so this does not block.
  • Fix: add a seven-level row per fuel for FY25 and FY27, mirroring Cases 1-5, or at least the round-1 examples ($20,000 wages with gas: FY25 $181, FY27 $560).

Carried over: G6 (manual p. 112 entry, now in G17) and G12 (optional), above.

New code in 9423ea0487: other checks

  • Code patterns:
    • Nothing is hard-coded; the formulas are vectorized. Each of the 17 parameters is read, and the removed gas_wood_coal and earned_income_min_age are referenced nowhere.
    • adult_age replaces earned_income_min_age and serves both the child count (manual p. 108, "under 18") and the earnings exclusion (COMAR .04E(3), "younger than 18"). The two rules use the same age.
    • medicare_part_b_premium follows the merged KY and IN pattern. The computed premium runs without a cycle in the full calculation (integration Cases 11 and 12).
    • The breakdown keys changed from '1' to 1. The parameters load and every lookup returns the right cell.
    • Half-up rounding: floor(amount x 1.1 + 0.5) matches exact half-up rounding for every Level 1-6 cell in all three years (90 products, 0 mismatches).
  • Period logic: see the year map. The md_meap.yaml header (lines 22-23) and V/md_meap.py:21-22 state the January 1 reading correctly. Main's Backdate parameters before applying reforms #9628 backdates each parameter's earliest value to 2015 before reforms apply, so periods before 2025 read the FY25 values, which are documented as unverified estimates.
  • Tests: 50 cases (up from 40), all passing locally. I hand-checked these new or changed expectations:
    • Countable income Case 1: 10,000 + 12,000 + 600 + 1,200 + 1,800 + 300 + 2,400 + 120 + 240 + 360 + 1,200 + 240 + 600 + 480 + 720 + 840 + 960 + 120 = 34,180, less 2,400 = 31,780.
    • Income limit Case 3 (FY25): 2 x (15,060 + 5,380 x (n - 1)) = 30,120 / 62,400 / 105,440, whose twelfths are the band table's $2,510, $5,200 and $8,786.67 ("$8,787").
    • Integration Case 1: size 2, 21,000 / 21,150 = 99.3%, Level 3, Gas $400. Case 11: 32,000 - 2,434.80 = 29,565.20, 29,565.20 / 15,650 = 189%, Level 5, $300. Case 12: 18,000 / 21,150 = 85%, Level 3, $400.
    • md_meap Case 12 (FY25 Garrett): 750 x 1.1 = 825, 150 x 1.1 = 165, Level 7 25. Case 14 (FY27 Garrett): 100 x 1.1 = 110; 665 x 1.1 = 731.5, 732; 355 x 1.1 = 390.5, 391; 980 x 1.1 = 1,078; 25.
    • Household size Case 1: [2, 1, 1, 0] with eligibility [true, true, false, false], per manual p. 108.
  • References:
    • Every new href is a single URL with at most one #page=N.
    • New anchors open on their content: FY27 matrices #page=1 (both tables), ACF Att. 4 #page=5 (sizes 7-12), manual #page=108 (children under 18), #page=111 (Att. A), #page=63 (nominal benefit), FY2025 plan #page=11 (2.5 Garrett, 2.6 min/max), FY2026 plan #page=6 (1.8, 1.9) and #page=9 (2.3, renters in subsidized housing), and COMAR 07.03.21.04#D(1) and #E(3) (both ids exist).
    • The remaining items are page text in titles, one anchor and the missing p. 112 entry (G17).
  • Descriptions: all parameters follow the template, except the new adult_age wording (G17).
  • Changelog: changelog.d/md-meap.added.md is a top-level fragment: "Add Maryland MEAP regular heating assistance for state FY25 to FY27."
  • Main merge:
    • Among the PR's files, the merge changed only programs.yaml.
    • The LIHEAP coverage line resolves as round 1 suggested: DC, Riverside County, MA, IL, IN, KS, KY, MD, MS, NC, NE, TX (programs.yaml:811). The OR block stays deleted, as on main.
    • The MD entry (programs.yaml:855-862) is intact and sits between KY and MS.
  • Main's guards:

CI status

CI has not finished on 906c7855b0.

  • The Pull request workflow (run 37531692385) was queued at 21:08 UTC. At 22:20 UTC, gh pr checks 9746 shows 35 checks: 4 pass (changelog fragment, registry lock, release lock guard, final release wheel) and 31 pending.
  • None has failed. The full suites, Lint, the smoke imports and Quick Feedback are still queued.
  • Locally, the 50 MEAP YAML cases pass on this head. This approval assumes the run passes; please confirm it is green before merging.

The last completed CI run, on 91a4276fac, passed 37/37.

Branch status

Axiom line

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

  • The issue is open and labelled pe-parity (plus enhancement).
  • It was refreshed on 2026-10-06 at 21:38 UTC:
    • It now describes head 906c7855b0, "open and under review"; it no longer calls the PR a draft.
    • It covers FY25-FY27, with the FY25 and FY27 matrices quoted verbatim.
    • The required outputs now net the Part B premium, list military_retirement_pay, map SOLAR to Electric, describe the unit-level unearned floor and record the manual's household rule.
    • New companion tests cover the FY25/FY27 rows, the Garrett tables, the Medicare deduction (pinned and computed) and the losses.
  • It is still dispatch-ready. When G14 lands, replace "no FY27 OHEP income-guidelines sheet was found" with the FY27 sheet, add its 1-12 rows as companion values, and record the 13-15 difference. If G15 changes the floor, update the countable-income row.

Next steps

  1. Confirm that CI run 37531692385 (or the next push) is green.
  2. Optional:
    • G14: cite the FY27 Income Guidelines, add the period-2027 limit and end-to-end cases, and document sizes 13-15.
    • G15: floor person-level unearned sources per member.
    • G16: date the five rule parameters 2024-07-01.
    • G17: drop page text from titles, anchor #D, add the p. 112 entry, and reword adult_age.
    • G18: test the FY25 and FY27 Levels 2-5.
    • G12: naming and the shared subsidy expression.

0 critical and 0 should-address findings open; 5 new suggestions (G14-G18), plus G6 (partial) and G12 carried over.

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: the round-1 critical and all seven should items are addressed and verified (FY25 and FY27 matrices encoded with gas split from wood/coal, Social Security/RRB net of Part B, military retirement pay, per-source loss floors, SOLAR on the electricity row, new boundary tests, descriptions). The remaining items are suggestions only (notably citing the FY27 OHEP Income Guidelines and adding period-2027 limit tests). CI on 906c785 was still running at review time with no failures; please confirm it is green before merging.

@hua7450
hua7450 merged commit 7bb9b64 into main Oct 8, 2026
36 of 48 checks passed
@hua7450
hua7450 deleted the md-meap branch October 8, 2026 01:19
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 Maryland MEAP (Maryland Energy Assistance Program) regular heating benefit

2 participants