From c1150614f27c9848a4192efd75881ce6730a265d Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 23 Oct 2026 18:11:13 -0400 Subject: [PATCH 1/3] Add Maine HEAP Fuel Assistance Co-Authored-By: Claude Opus 5.5 --- changelog.d/me-liheap-wave1d.added.md | 1 + .../parameters/gov/hhs/liheap/fpg_limit.yaml | 11 + .../gov/states/me/msha/heap/fpg_year_lag.yaml | 18 + .../heap/income/medical_expense_sources.yaml | 22 ++ .../me/msha/heap/income/minimum_age.yaml | 16 + .../me/msha/heap/income/sources/earned.yaml | 27 ++ .../me/msha/heap/income/sources/unearned.yaml | 45 +++ .../me/msha/heap/payment/nominal_benefit.yaml | 18 + .../me/msha/heap/payment/value_per_point.yaml | 25 ++ .../gov/states/me/msha/heap/points/base.yaml | 18 + .../states/me/msha/heap/points/county.yaml | 63 ++++ .../states/me/msha/heap/points/dwelling.yaml | 27 ++ .../gov/states/me/msha/heap/points/fuel.yaml | 38 ++ .../msha/heap/points/income_multiplier.yaml | 63 ++++ .../poverty_level_rounding_increment.yaml | 21 ++ .../me/msha/heap/points/priority/amount.yaml | 17 + .../heap/points/priority/child_age_limit.yaml | 14 + .../heap/points/priority/elderly_age.yaml | 15 + .../points/priority/older_child_amount.yaml | 19 + .../heap/points/priority/young_child_age.yaml | 15 + .../msha/heap/points/subsidized_heating.yaml | 16 + policyengine_us/programs.yaml | 10 +- .../me_heap_categorically_eligible.yaml | 46 +++ .../heap/eligibility/me_heap_eligible.yaml | 126 +++++++ .../eligibility/me_heap_income_limit.yaml | 60 ++++ .../gov/states/me/msha/heap/integration.yaml | 216 +++++++++++ .../gov/states/me/msha/heap/me_heap.yaml | 166 +++++++++ .../me/msha/heap/me_heap_household_size.yaml | 33 ++ .../states/me/msha/heap/me_heap_income.yaml | 313 ++++++++++++++++ .../payment/me_heap_income_multiplier.yaml | 74 ++++ .../me/msha/heap/payment/me_heap_points.yaml | 337 ++++++++++++++++++ .../me_heap_categorically_eligible.py | 19 + .../msha/heap/eligibility/me_heap_eligible.py | 55 +++ .../heap/eligibility/me_heap_income_limit.py | 35 ++ .../gov/states/me/msha/heap/me_heap.py | 51 +++ .../me/msha/heap/me_heap_dwelling_type.py | 26 ++ .../me/msha/heap/me_heap_household_size.py | 18 + .../gov/states/me/msha/heap/me_heap_income.py | 43 +++ .../heap/payment/me_heap_income_multiplier.py | 36 ++ .../me/msha/heap/payment/me_heap_points.py | 94 +++++ 40 files changed, 2266 insertions(+), 1 deletion(-) create mode 100644 changelog.d/me-liheap-wave1d.added.md create mode 100644 policyengine_us/parameters/gov/hhs/liheap/fpg_limit.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/fpg_year_lag.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/income/medical_expense_sources.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/income/minimum_age.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/income/sources/earned.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/income/sources/unearned.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/payment/nominal_benefit.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/payment/value_per_point.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/points/base.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/points/county.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/points/dwelling.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/points/fuel.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/points/income_multiplier.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/points/poverty_level_rounding_increment.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/points/priority/amount.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/points/priority/child_age_limit.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/points/priority/elderly_age.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/points/priority/older_child_amount.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/points/priority/young_child_age.yaml create mode 100644 policyengine_us/parameters/gov/states/me/msha/heap/points/subsidized_heating.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_categorically_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_income_limit.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/integration.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap_household_size.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/payment/me_heap_income_multiplier.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/payment/me_heap_points.yaml create mode 100644 policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_categorically_eligible.py create mode 100644 policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_eligible.py create mode 100644 policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_income_limit.py create mode 100644 policyengine_us/variables/gov/states/me/msha/heap/me_heap.py create mode 100644 policyengine_us/variables/gov/states/me/msha/heap/me_heap_dwelling_type.py create mode 100644 policyengine_us/variables/gov/states/me/msha/heap/me_heap_household_size.py create mode 100644 policyengine_us/variables/gov/states/me/msha/heap/me_heap_income.py create mode 100644 policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_income_multiplier.py create mode 100644 policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_points.py diff --git a/changelog.d/me-liheap-wave1d.added.md b/changelog.d/me-liheap-wave1d.added.md new file mode 100644 index 00000000000..e17445236b3 --- /dev/null +++ b/changelog.d/me-liheap-wave1d.added.md @@ -0,0 +1 @@ +Add Maine HEAP Fuel Assistance (LIHEAP regular heating) for the 2025-26 and 2026-27 heating seasons, including the June 29, 2026 points matrix. diff --git a/policyengine_us/parameters/gov/hhs/liheap/fpg_limit.yaml b/policyengine_us/parameters/gov/hhs/liheap/fpg_limit.yaml new file mode 100644 index 00000000000..452a783bcf2 --- /dev/null +++ b/policyengine_us/parameters/gov/hhs/liheap/fpg_limit.yaml @@ -0,0 +1,11 @@ +description: The US Department of Health and Human Services allows states to provide assistance to households with income up to this percentage of the poverty guideline, when greater than the state median income limit, under the Low Income Home Energy Assistance Program. +# Pub. L. 97-35, section 2605(b)(2)(B)(i), set this ceiling from fiscal year 1982. +values: + 1981-10-01: 1.5 +metadata: + period: year + unit: /1 + label: LIHEAP poverty guideline income limit + reference: + - title: 42 U.S.C. 8624(b)(2)(B)(i) + href: https://www.law.cornell.edu/uscode/text/42/8624#b_2_B diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/fpg_year_lag.yaml new file mode 100644 index 00000000000..9d48edace1d --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/fpg_year_lag.yaml @@ -0,0 +1,18 @@ +description: Maine uses the federal poverty guidelines from this many years before the model year for the income limit and the poverty level under the Low Income Home Energy Assistance Program. +# Model period 2026 (FFY2026, the 2025-26 season) uses the 2025 guidelines, +# which HHS made mandatory for LIHEAP from October 1, 2025, and 2027 (FFY2027) +# the 2026 guidelines. Earlier years are unverified backfill. +values: + 2024-10-01: 1 + +metadata: + unit: year + period: year + label: Maine HEAP poverty guideline year lag + reference: + - title: 99-346 CMR ch. 24, section 3(C) Income Eligibility + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=11 + - title: LIHEAP IM 2025-02, Attachment 2, poverty guidelines for mandatory use in FY26 + href: https://acf.gov/sites/default/files/documents/ocs/COMM_LIHEAP_IM2025-02_FPGSte-Table_Att2.pdf#page=1 + - title: LIHEAP IM 2026-01, poverty guidelines for optional use in FY26 and mandatory use in FY27 + href: https://acf.gov/ocs/policy-guidance/liheap-im2026-01-federal-poverty-guidelines-fpg-and-state-median-income-smi diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/income/medical_expense_sources.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/income/medical_expense_sources.yaml new file mode 100644 index 00000000000..7d9a05112ae --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/income/medical_expense_sources.yaml @@ -0,0 +1,22 @@ +description: Maine deducts these medical expenses, up to the amount needed to reach the income limit, from the income of a household over the limit under the Low Income Home Energy Assistance Program. +# Rule 3(C)(1) defines medical expenses by IRS Publication 502 and allows only +# the amount necessary to make the household eligible. Annual expenses stand in +# for those paid during the income period. +values: + 2024-07-03: + - medical_expense_health_insurance_premiums + - other_medical_expenses + +metadata: + unit: list + period: year + label: Maine HEAP medical expense sources + reference: + - title: 99-346 CMR ch. 24, section 3(C)(1) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx + - title: 99-346 CMR ch. 24, section 3(C)(1) (effective June 29, 2026) + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=15 + - title: MaineHousing HEAP Income Eligibility Guidelines, 2026-2027 season + href: https://www.mainehousing.org/charts/ + - title: MaineHousing Home Energy Assistance Program page, 2025-2026 season income limits (archived November 1, 2025) + href: https://web.archive.org/web/20251101091454id_/https://www.mainehousing.org/programs-services/energy/energydetails/liheap diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/income/minimum_age.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/income/minimum_age.yaml new file mode 100644 index 00000000000..5ea030e5efa --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/income/minimum_age.yaml @@ -0,0 +1,16 @@ +description: Maine counts the income of household members at or above this age under the Low Income Home Energy Assistance Program. +# Rule 3(C)(1) counts the income of "all Household Members over the age of 18" +# in both the 2025 edition and the June 29, 2026 replacement. The model counts +# members aged 18 and older. +values: + 2024-07-03: 18 + +metadata: + unit: year + period: year + label: Maine HEAP minimum income age + reference: + - title: 99-346 CMR ch. 24, section 3(C)(1) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx + - title: "99-346 CMR ch. 24, section 3(C)(1) (effective June 29, 2026) #page 11-12" + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=11 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/income/sources/earned.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/income/sources/earned.yaml new file mode 100644 index 00000000000..ec48544c52f --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/income/sources/earned.yaml @@ -0,0 +1,27 @@ +description: Maine counts these sources as earned income under the Low Income Home Energy Assistance Program. +# Rule 3(C)(1)(a)-(b): wages, salaries, commissions, tips and bonuses before +# taxes or deductions, and self-employment income. The model has only net +# business income, so each source is floored at zero and a loss in one source +# cannot offset another. Contract income counts when reported as wages or +# self-employment income. Earnings of a full-time high school student who is +# not the primary applicant are excluded in me_heap_income. +values: + 2024-07-03: + - employment_income + - self_employment_income + - sstb_self_employment_income + - farm_operations_income + # Partnership and S corporation income is self-employment income (item b). + - partnership_s_corp_income + +metadata: + unit: list + period: year + label: Maine HEAP earned income sources + reference: + - title: 99-346 CMR ch. 24, section 3(C)(1) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx + - title: "99-346 CMR ch. 24, section 3(C)(1) (effective June 29, 2026) #page 11-13" + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=11 + - title: "Maine FY2026 LIHEAP State Plan, Section 1.9 #page 6-7" + href: https://liheapch.acf.gov/docs/2026/state-plans/ME_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/income/sources/unearned.yaml new file mode 100644 index 00000000000..a148a264dfd --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/income/sources/unearned.yaml @@ -0,0 +1,45 @@ +description: Maine counts these sources as unearned income under the Low Income Home Energy Assistance Program. +# Rule 3(C)(1)(c)-(n). Social Security (retirement and SSDI) and SSI count +# gross, including Medicare deductions. financial_assistance stands for cash +# gifts from someone outside the household. disability_benefits stands for +# long-term disability payments, item (n) of the June 29, 2026 replacement; the +# 2025 edition's list "includes, but is not limited to" the items it names. +# Royalties, contract income and payments from mortgage or sales contracts +# have no specific inputs. +# Not counted: TANF and General Assistance, which the rule does not list and +# the FY2026 plan's Section 1.9 leaves unchecked (General Assistance vouchers +# are excluded non-cash income), and retirement_distributions, since assets +# drawn down from financial institutions are excluded. +values: + 2024-07-03: + - social_security + - ssi + - total_unemployment_compensation + - workers_compensation + - strike_benefits + - alimony_income + - child_support_received + - veterans_benefits + - financial_assistance + - pension_income + # Government pensions include military retired pay, a separate input from + # the pension inputs that pension_income adds. + - military_retirement_income + - disability_benefits + - interest_income + - ordinary_dividend_income + - estate_income + - rental_income + - farm_rent_income + +metadata: + unit: list + period: year + label: Maine HEAP unearned income sources + reference: + - title: 99-346 CMR ch. 24, section 3(C)(1) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx + - title: "99-346 CMR ch. 24, section 3(C)(1) (effective June 29, 2026) #page 11-13" + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=11 + - title: "Maine FY2026 LIHEAP State Plan, Section 1.9 #page 6-7" + href: https://liheapch.acf.gov/docs/2026/state-plans/ME_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/payment/nominal_benefit.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/payment/nominal_benefit.yaml new file mode 100644 index 00000000000..49a7e2c1a64 --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/payment/nominal_benefit.yaml @@ -0,0 +1,18 @@ +description: Maine provides this benefit to tenants of subsidized housing whose heat is included in rent under the Low Income Home Energy Assistance Program. +# Rule 4(C) of the 2025 edition and 4(B) of the June 29, 2026 replacement limit +# these tenants to a benefit that maximizes SNAP benefits; the FY2026 plan sets +# the nominal payment at $21. +values: + 2024-10-01: 21 + +metadata: + unit: currency-USD + period: year + label: Maine HEAP subsidized heat-in-rent benefit + reference: + - title: Maine FY2026 LIHEAP State Plan, Section 1.7 + href: https://liheapch.acf.gov/docs/2026/state-plans/ME_Plan_2026.pdf#page=5 + - title: Maine FY2026 Benefit Matrix, 99-346 CMR ch. 24, section 4(C) (2025 edition) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=2 + - title: 99-346 CMR ch. 24, section 4(B) (effective June 29, 2026) + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=17 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/payment/value_per_point.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/payment/value_per_point.yaml new file mode 100644 index 00000000000..13c991f9c2a --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/payment/value_per_point.yaml @@ -0,0 +1,25 @@ +description: Maine pays this amount per benefit point under the Low Income Home Energy Assistance Program. +# ESTIMATE. MaineHousing announces the dollar value of points after each federal +# award (rule 4(A)) and has published none for FFY2025 through FFY2027. $22 is +# inferred from the FFY2025 and FFY2026 plans' $1,012 maximum benefit: the +# 2025-edition maximum of 35 points times 130% is 45.5, rounded up to 46, and +# 46 x $22 = $1,012. Their $88 minimum (4 x $22) follows the plans' 4-point +# template; the matrix's smallest award is 3 points (6 + 2 + 4 - 9 = 3; +# 3 x 70% = 2.1, rounded up to 3), so the minimum does not support the value. +# The value is reused with the June 29, 2026 matrix until MaineHousing +# announces one. The last published values ($55 and $44 for FFY2023) belong +# to an older energy-cost matrix and are not used. +values: + 2024-10-01: 22 + +metadata: + unit: currency-USD + period: year + label: Maine HEAP dollar value per point + reference: + - title: Maine FY2025 LIHEAP State Plan, Section 2.6 + href: https://liheapch.acf.gov/docs/2025/state-plans/ME_Plan_2025.pdf#page=8 + - title: Maine FY2026 LIHEAP State Plan, Section 2.6 + href: https://liheapch.acf.gov/docs/2026/state-plans/ME_Plan_2026.pdf#page=10 + - title: 99-346 CMR ch. 24, section 4(A) (effective June 29, 2026) + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=15 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/points/base.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/points/base.yaml new file mode 100644 index 00000000000..ea7050ac409 --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/points/base.yaml @@ -0,0 +1,18 @@ +description: Maine assigns this number of base points to every eligible household under the Low Income Home Energy Assistance Program. +# The 2024 and 2025 editions have no base points; the June 29, 2026 +# replacement adds 8. +values: + 2024-07-03: 0 + 2026-06-29: 8 + +metadata: + unit: /1 + period: year + label: Maine HEAP base points + reference: + - title: Maine FY2026 Benefit Matrix, 99-346 CMR ch. 24, section 4(A) (2025 edition) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=1 + - title: 99-346 CMR ch. 24, section 4(A) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx + - title: 99-346 CMR ch. 24, section 4(A) (effective June 29, 2026) + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=16 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/points/county.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/points/county.yaml new file mode 100644 index 00000000000..fdb1ed4d6e1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/points/county.yaml @@ -0,0 +1,63 @@ +description: Maine assigns these points by county of residence under the Low Income Home Energy Assistance Program. +# The June 29, 2026 replacement sets county points by heating degree days. +ANDROSCOGGIN_COUNTY_ME: + 2024-07-03: 4 + 2026-06-29: 2 +AROOSTOOK_COUNTY_ME: + 2024-07-03: 7 + 2026-06-29: 6 +CUMBERLAND_COUNTY_ME: + 2024-07-03: 3 + 2026-06-29: 1 +FRANKLIN_COUNTY_ME: + 2024-07-03: 6 + 2026-06-29: 5 +HANCOCK_COUNTY_ME: + 2024-07-03: 4 + 2026-06-29: 4 +KENNEBEC_COUNTY_ME: + 2024-07-03: 2 + 2026-06-29: 1 +KNOX_COUNTY_ME: + 2024-07-03: 3 + 2026-06-29: 2 +LINCOLN_COUNTY_ME: + 2024-07-03: 3 + 2026-06-29: 2 +OXFORD_COUNTY_ME: + 2024-07-03: 5 + 2026-06-29: 4 +PENOBSCOT_COUNTY_ME: + 2024-07-03: 5 + 2026-06-29: 3 +PISCATAQUIS_COUNTY_ME: + 2024-07-03: 6 + 2026-06-29: 4 +SAGADAHOC_COUNTY_ME: + 2024-07-03: 2 + 2026-06-29: 3 +SOMERSET_COUNTY_ME: + 2024-07-03: 6 + 2026-06-29: 4 +WALDO_COUNTY_ME: + 2024-07-03: 3 + 2026-06-29: 3 +WASHINGTON_COUNTY_ME: + 2024-07-03: 2 + 2026-06-29: 4 +YORK_COUNTY_ME: + 2024-07-03: 3 + 2026-06-29: 2 + +metadata: + unit: /1 + period: year + label: Maine HEAP county points + propagate_metadata_to_children: true + reference: + - title: Maine FY2026 Benefit Matrix, 99-346 CMR ch. 24, section 4(A) (2025 edition) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=1 + - title: 99-346 CMR ch. 24, section 4(A) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx + - title: 99-346 CMR ch. 24, section 4(A) (effective June 29, 2026) + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=16 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/points/dwelling.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/points/dwelling.yaml new file mode 100644 index 00000000000..4d58db96073 --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/points/dwelling.yaml @@ -0,0 +1,27 @@ +description: Maine assigns these points by dwelling type under the Low Income Home Energy Assistance Program. +# Condominiums, duplexes and apartments have separate rows with the same +# points in both matrices, so they share one dwelling type. +STICK_BUILT_OR_MODULAR: + 2024-07-03: 9 + 2026-06-29: 3 +MOBILE_OR_MANUFACTURED: + 2024-07-03: 8 + 2026-06-29: 2 +CONDO_DUPLEX_OR_APARTMENT: + 2024-07-03: 6 + 2026-06-29: 1 + +metadata: + unit: /1 + period: year + label: Maine HEAP dwelling points + breakdown: + - me_heap_dwelling_type + propagate_metadata_to_children: true + reference: + - title: Maine FY2026 Benefit Matrix, 99-346 CMR ch. 24, section 4(A) (2025 edition) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=1 + - title: 99-346 CMR ch. 24, section 4(A) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx + - title: 99-346 CMR ch. 24, section 4(A) (effective June 29, 2026) + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=16 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/points/fuel.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/points/fuel.yaml new file mode 100644 index 00000000000..75a726086a0 --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/points/fuel.yaml @@ -0,0 +1,38 @@ +description: Maine assigns these points by primary heating fuel under the Low Income Home Energy Assistance Program. +# The wood row is cordwood. Bio-fuel (6), corn (5) and wood pellets (5) in the +# 2025 edition and wood pellets (4) and biobrick (3) in the June 29, 2026 +# replacement have no heating_type value and are not stored. +electricity: + 2024-07-03: 14 + 2026-06-29: 8 +lp_gas: + 2024-07-03: 10 + 2026-06-29: 5 +kerosene: + 2024-07-03: 8 + 2026-06-29: 5 +oil: + 2024-07-03: 7 + 2026-06-29: 4 +coal: + 2024-07-03: 5 + 2026-06-29: 4 +natural_gas: + 2024-07-03: 5 + 2026-06-29: 3 +wood: + 2024-07-03: 4 + 2026-06-29: 3 + +metadata: + unit: /1 + period: year + label: Maine HEAP fuel points + propagate_metadata_to_children: true + reference: + - title: "Maine FY2026 Benefit Matrix, 99-346 CMR ch. 24, section 4(A) (2025 edition) #page 1-2" + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=1 + - title: 99-346 CMR ch. 24, section 4(A) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx + - title: 99-346 CMR ch. 24, section 4(A) (effective June 29, 2026) + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=16 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/points/income_multiplier.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/points/income_multiplier.yaml new file mode 100644 index 00000000000..6cc879d9dcf --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/points/income_multiplier.yaml @@ -0,0 +1,63 @@ +description: Maine multiplies the household's points by this factor based on its poverty level, as a share of the federal poverty guideline, under the Low Income Home Energy Assistance Program. +# Thresholds are band starts as a share of the poverty guideline, applied +# after rounding the poverty level (poverty_level_rounding_increment). The +# 2025 edition's 70% band covers income above 150% up to the income limit. +# The eighth band starts with the June 29, 2026 replacement and is +# unreachable before. +brackets: + - threshold: + 2024-07-03: 0 + amount: + 2024-07-03: 1.3 + 2026-06-29: 1.8 + - threshold: + 2024-07-03: 0.26 + 2026-06-29: 0.11 + amount: + 2024-07-03: 1.2 + 2026-06-29: 1.6 + - threshold: + 2024-07-03: 0.51 + 2026-06-29: 0.34 + amount: + 2024-07-03: 1.1 + 2026-06-29: 1.4 + - threshold: + 2024-07-03: 0.76 + amount: + 2024-07-03: 1 + 2026-06-29: 1.2 + - threshold: + 2024-07-03: 1.01 + amount: + 2024-07-03: 0.9 + 2026-06-29: 1 + - threshold: + 2024-07-03: 1.26 + amount: + 2024-07-03: 0.8 + 2026-06-29: 0.9 + - threshold: + 2024-07-03: 1.51 + amount: + 2024-07-03: 0.7 + 2026-06-29: 0.8 + - threshold: + 2024-07-03: .inf + 2026-06-29: 1.76 + amount: + 2024-07-03: 0.7 + +metadata: + type: single_amount + threshold_unit: /1 + amount_unit: /1 + period: year + label: Maine HEAP income multiplier + reference: + - title: Maine FY2026 Benefit Matrix, 99-346 CMR ch. 24, section 4(A) (2025 edition) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=2 + - title: 99-346 CMR ch. 24, section 4(A) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx + - title: 99-346 CMR ch. 24, section 4(A) (effective June 29, 2026) + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=17 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/points/poverty_level_rounding_increment.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/points/poverty_level_rounding_increment.yaml new file mode 100644 index 00000000000..928c6699c19 --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/points/poverty_level_rounding_increment.yaml @@ -0,0 +1,21 @@ +description: Maine rounds the poverty level, as a share of the federal poverty guideline, to this increment under the Low Income Home Energy Assistance Program. +# The 2025 edition rounds to the whole percent ("income at 75.1% will be +# rounded to 75%; income at 100.6% will be rounded to 101%"), an increment of +# 0.01. The June 29, 2026 replacement prints bands such as 0-10.99% and +# 11-33.99%, so the poverty level is kept to hundredths of a percent, an +# increment of 0.0001. Rounding is half up. +values: + 2024-07-03: 0.01 + 2026-06-29: 0.0001 + +metadata: + unit: /1 + period: year + label: Maine HEAP poverty level rounding increment + reference: + - title: Maine FY2026 Benefit Matrix, 99-346 CMR ch. 24, section 4(A) (2025 edition) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=2 + - title: 99-346 CMR ch. 24, section 4(A) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx + - title: 99-346 CMR ch. 24, section 4(A) (effective June 29, 2026) + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=17 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/points/priority/amount.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/points/priority/amount.yaml new file mode 100644 index 00000000000..f878c406076 --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/points/priority/amount.yaml @@ -0,0 +1,17 @@ +description: Maine assigns these priority points to a household with a member aged 60 or older, a disabled member or a child aged 6 or younger under the Low Income Home Energy Assistance Program. +# The June 29, 2026 replacement awards the points once, for the first at-risk +# member in the house. +values: + 2024-07-03: 5 + +metadata: + unit: /1 + period: year + label: Maine HEAP at-risk priority points + reference: + - title: Maine FY2026 Benefit Matrix, 99-346 CMR ch. 24, section 4(A) (2025 edition) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=2 + - title: 99-346 CMR ch. 24, section 4(A) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx + - title: 99-346 CMR ch. 24, section 4(A) (effective June 29, 2026) + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=16 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/points/priority/child_age_limit.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/points/priority/child_age_limit.yaml new file mode 100644 index 00000000000..f8aacd6d9da --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/points/priority/child_age_limit.yaml @@ -0,0 +1,14 @@ +description: Maine assigns older child priority points only to a child below this age under the Low Income Home Energy Assistance Program. +# The 2025 edition covers a child aged 7 to 17. +values: + 2024-07-03: 18 + +metadata: + unit: year + period: year + label: Maine HEAP priority child age limit + reference: + - title: Maine FY2026 Benefit Matrix, 99-346 CMR ch. 24, section 4(A) (2025 edition) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=2 + - title: 99-346 CMR ch. 24, section 4(A) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/points/priority/elderly_age.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/points/priority/elderly_age.yaml new file mode 100644 index 00000000000..c9e80b5792b --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/points/priority/elderly_age.yaml @@ -0,0 +1,15 @@ +description: Maine assigns priority points to a household with a member at or above this age under the Low Income Home Energy Assistance Program. +values: + 2024-07-03: 60 + +metadata: + unit: year + period: year + label: Maine HEAP priority elderly age + reference: + - title: Maine FY2026 Benefit Matrix, 99-346 CMR ch. 24, section 4(A) (2025 edition) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=2 + - title: 99-346 CMR ch. 24, section 4(A) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx + - title: 99-346 CMR ch. 24, section 4(A) (effective June 29, 2026) + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=16 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/points/priority/older_child_amount.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/points/priority/older_child_amount.yaml new file mode 100644 index 00000000000..272fd32b9a2 --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/points/priority/older_child_amount.yaml @@ -0,0 +1,19 @@ +description: Maine assigns these priority points to a household whose only priority member is a child older than the young child age and younger than the child age limit under the Low Income Home Energy Assistance Program. +# The 2025 edition awards 3 points for a child aged 7 to 17. The June 29, 2026 +# replacement lists only members aged 60 or older, children aged 6 or younger +# and disabled members. +values: + 2024-07-03: 3 + 2026-06-29: 0 + +metadata: + unit: /1 + period: year + label: Maine HEAP older child priority points + reference: + - title: Maine FY2026 Benefit Matrix, 99-346 CMR ch. 24, section 4(A) (2025 edition) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=2 + - title: 99-346 CMR ch. 24, section 4(A) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx + - title: 99-346 CMR ch. 24, section 4(A) (effective June 29, 2026) + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=16 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/points/priority/young_child_age.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/points/priority/young_child_age.yaml new file mode 100644 index 00000000000..c00edcc7506 --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/points/priority/young_child_age.yaml @@ -0,0 +1,15 @@ +description: Maine assigns at-risk priority points to a household with a child at or below this age under the Low Income Home Energy Assistance Program. +values: + 2024-07-03: 6 + +metadata: + unit: year + period: year + label: Maine HEAP priority young child age + reference: + - title: Maine FY2026 Benefit Matrix, 99-346 CMR ch. 24, section 4(A) (2025 edition) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=2 + - title: 99-346 CMR ch. 24, section 4(A) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx + - title: 99-346 CMR ch. 24, section 4(A) (effective June 29, 2026) + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=16 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/points/subsidized_heating.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/points/subsidized_heating.yaml new file mode 100644 index 00000000000..6231c3b133e --- /dev/null +++ b/policyengine_us/parameters/gov/states/me/msha/heap/points/subsidized_heating.yaml @@ -0,0 +1,16 @@ +description: Maine adds these points for a household in subsidized housing that is responsible for its heating costs under the Low Income Home Energy Assistance Program. +values: + 2024-07-03: -9 + 2026-06-29: -3 + +metadata: + unit: /1 + period: year + label: Maine HEAP subsidized heating points + reference: + - title: Maine FY2026 Benefit Matrix, 99-346 CMR ch. 24, section 4(A) (2025 edition) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=2 + - title: 99-346 CMR ch. 24, section 4(A) (2025 edition, SOS filing 2025-135) + href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx + - title: 99-346 CMR ch. 24, section 4(A) (effective June 29, 2026) + href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=16 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 43fdd714b80..9fa468e2011 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -808,7 +808,7 @@ programs: category: Benefits agency: HHS status: partial - coverage: AL, DC, Riverside County, MA, FL, IL, IN, KS, KY, MD, MS, NC, NE, NJ, NY, OH, OK, OR, SD, RI, TX + coverage: AL, DC, Riverside County, MA, FL, IL, IN, KS, KY, MD, ME, MS, NC, NE, NJ, NY, OH, OK, OR, SD, RI, TX state_implementations: - state: AL status: partial @@ -898,6 +898,14 @@ programs: parameter_prefix: gov.states.md.dhs.meap verified_years: "2025-2027" notes: FY25-FY27 regular heating only, with the fuel tables, the Garrett County multiplier and the subsidized and nominal levels. The SNAP nominal payment, cooling, EUSP, arrearage, crisis and equipment benefits are out of scope. + - state: ME + status: partial + name: Maine HEAP + full_name: Maine Home Energy Assistance Program + variable: me_heap + parameter_prefix: gov.states.me.msha.heap + verified_years: "2026-2027" + notes: FY2026-FY2027 Fuel Assistance under both points matrices, with an estimated $22 per point because MaineHousing has not announced one. Crisis, the TANF fuel supplement, weatherization and CHIP are out of scope. - state: MS status: partial name: Mississippi LIHEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_categorically_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_categorically_eligible.yaml new file mode 100644 index 00000000000..59cae09cd06 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_categorically_eligible.yaml @@ -0,0 +1,46 @@ +# Rule 3(C)(2): adult members on a Maine DHHS Notice of Decision for TANF or SNAP +# have categorical income eligibility (2026 edition, page 15; the 2025 edition has +# the same text): +# https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=15 +# Rule 3(C)(2): members on a DHHS TANF or SNAP notice are income eligible; the model +# treats any SNAP or TANF received by the household as passing the income test. + +- name: Case 1, a family receiving TANF is categorically eligible. + period: 2026 + input: + people: + head: + age: 27 + ssi: 0 + child1: + age: 5 + ssi: 0 + child2: + age: 2 + ssi: 0 + spm_units: + spm_unit: + members: [head, child1, child2] + # Maine TANF maximum for three, 895 a month (10-144 CMR ch. 331, Appendix + # Table 2): 895 x 12 = 10,740. SNAP is set to 0 to isolate the TANF path. + tanf: 10_740 + snap: 0 + households: + household: + members: [head, child1, child2] + state_code: ME + output: + me_heap_categorically_eligible: true + +- name: Case 2, SNAP receipt is categorical and its absence is not. + period: 2026 + input: + state_code: [ME, ME] + age: [58, 58] + ssi: [0, 0] + tanf: [0, 0] + # SNAP of 100 a month from a DHHS Notice of Decision; any positive amount + # qualifies. + snap: [1_200, 0] + output: + me_heap_categorically_eligible: [true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_eligible.yaml new file mode 100644 index 00000000000..275f4c52700 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_eligible.yaml @@ -0,0 +1,126 @@ +# Eligibility for HEAP Fuel Assistance, 99-346 CMR ch. 24 (2026 edition; the 2025 +# edition has the same tests): +# https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=6 +# - Size counts citizens, nationals and qualified aliens (3(A)(1), page 6). +# - Income at or below the limit, or categorical eligibility through SNAP or TANF +# (3(C), pages 11-15). +# - A direct or indirect energy cost for heat and a functioning heating system +# (3(A)(4) and 3(B)(1), pages 10-11), with a fuel listed in the points matrix. +# Subsidized tenants with heat in their rent qualify for the nominal benefit +# whatever the fuel (3(B)(3) and 4(B), pages 11 and 17). +# Wages use Maine's 2025 minimum wage, $14.65 an hour (Maine Department of Labor): +# 14.65 x 40 hours x 52 weeks = 30,472. The 2026 one-person limit is 36,836. +# Heating bills: EIA's 2025-26 winter household expenditures (Short-Term Energy +# Outlook, October 2026, Table WF01): Northeast electricity 1,685, propane 1,979 and +# natural gas 1,023; U.S. average heating oil 1,745, also used for kerosene (the +# same input) and for coal, which neither EIA nor Maine prices. Maine Department of +# Energy Resources survey of January 5, 2026: cordwood 355 a cord, two cords (the +# survey's minimum purchase) = 710; wood pellets 357 for a one-ton purchase. + +- name: Case 1, threshold edge at the one-person limit, and SNAP passes the income test above it. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [ME, ME, ME] + age: [40, 40, 40] + employment_income: [36_836, 36_837, 36_837] + ssi: [0, 0, 0] + medical_expense_health_insurance_premiums: [0, 0, 0] + other_medical_expenses: [0, 0, 0] + tanf: [0, 0, 0] + # The third household receives SNAP of 100 a month. + snap: [0, 0, 1_200] + heating_type: [FUEL_OIL, FUEL_OIL, FUEL_OIL] + fuel_oil_expense: [1_745, 1_745, 1_745] + heat_expense_included_in_rent: [false, false, false] + receives_housing_assistance: [false, false, false] + is_in_public_housing: [false, false, false] + output: + # No medical expenses, so income stays gross. + me_heap_income: [36_836, 36_837, 36_837] + # 36,836 <= 36,836: eligible. 36,837 > 36,836: not eligible unless categorical. + me_heap_eligible: [true, false, true] + +- name: Case 2, every listed fuel qualifies while no heat, other fuels, unspecified fuel and other states do not. + period: 2026 + input: + state_code: [ME, ME, ME, ME, ME, ME, ME, ME, ME, ME, ME, NH] + age: [40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40] + employment_income: [30_472, 30_472, 30_472, 30_472, 30_472, 30_472, 30_472, 30_472, 30_472, 30_472, 30_472, 30_472] + ssi: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + snap: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + heating_type: [NONE, OTHER, UNSPECIFIED, ELECTRICITY, SOLAR, PROPANE, KEROSENE, FUEL_OIL, COAL, NATURAL_GAS, WOOD, FUEL_OIL] + # Each household with a fuel pays that fuel's bill; OTHER is a pellet stove. + other_heating_fuel_expense: [0, 357, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + pre_subsidy_electricity_expense: [0, 0, 0, 1_685, 1_685, 0, 0, 0, 0, 0, 0, 0] + bottled_gas_expense: [0, 0, 0, 0, 0, 1_979, 0, 0, 0, 0, 0, 0] + fuel_oil_expense: [0, 0, 0, 0, 0, 0, 1_745, 1_745, 0, 0, 0, 1_745] + coal_expense: [0, 0, 0, 0, 0, 0, 0, 0, 1_745, 0, 0, 0] + gas_expense: [0, 0, 0, 0, 0, 0, 0, 0, 0, 1_023, 0, 0] + wood_expense: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 710, 0] + heat_expense_included_in_rent: [false, false, false, false, false, false, false, false, false, false, false, false] + receives_housing_assistance: [false, false, false, false, false, false, false, false, false, false, false, false] + is_in_public_housing: [false, false, false, false, false, false, false, false, false, false, false, false] + output: + # Income 30,472 <= 36,836 throughout. NONE, OTHER and UNSPECIFIED have no row + # in the points matrix (OTHER fails even with its pellet bill); the eight listed + # fuels with a bill qualify; New Hampshire does not. + me_heap_eligible: [false, false, false, true, true, true, true, true, true, true, true, false] + +- name: Case 3, heat in rent qualifies subsidized tenants without a listed fuel but not unsubsidized ones. + period: 2026 + input: + state_code: [ME, ME, ME, ME, ME] + age: [40, 40, 40, 40, 40] + employment_income: [30_472, 30_472, 30_472, 30_472, 30_472] + ssi: [0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0] + snap: [0, 0, 0, 0, 0] + heating_type: [UNSPECIFIED, UNSPECIFIED, UNSPECIFIED, NATURAL_GAS, NONE] + heat_expense_included_in_rent: [true, true, true, true, true] + # Housing Choice Voucher, public housing, private rental, private rental, + # voucher. + receives_housing_assistance: [true, false, false, false, true] + is_in_public_housing: [false, true, false, false, false] + output: + # 1-2: subsidized with heat in rent qualify without a fuel. + # 3: an unsubsidized tenant needs a listed fuel; UNSPECIFIED has none. + # 4: an unsubsidized tenant whose building burns natural gas qualifies. + # 5: NONE means the home has no heating system, which 3(B)(1)(a) requires. + me_heap_eligible: [true, true, false, true, false] + +- name: Case 4, a household whose members are all ineligible noncitizens is not eligible. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 38 + immigration_status: TPS + ssi: 0 + spouse: + age: 31 + immigration_status: UNDOCUMENTED + ssi: 0 + spm_units: + spm_unit: + members: [head, spouse] + snap: 0 + tanf: 0 + heating_type: FUEL_OIL + fuel_oil_expense: 1_745 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [head, spouse] + state_code: ME + is_in_public_housing: false + output: + # Neither status is a qualified alien, so the size is 0 and the limit is 0. + # The household has no income, so 0 <= 0 and only the size gate fails. + me_heap_household_size: 0 + me_heap_income_limit: 0 + me_heap_income: 0 + me_heap_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_income_limit.yaml new file mode 100644 index 00000000000..6b8e189287b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_income_limit.yaml @@ -0,0 +1,60 @@ +# Income limit = the greater of 150% of the poverty guideline and 60% of state +# median income, the LIHEAP maximum in 42 U.S.C. 8624(b)(2)(B): +# https://www.law.cornell.edu/uscode/text/42/8624#b_2_B +# MaineHousing publishes the resulting annual limits for sizes 1-10: +# - 2025-26 season (period 2026), HEAP page captured 2025-11-01: +# https://web.archive.org/web/20251101091454id_/https://www.mainehousing.org/programs-services/energy/energydetails/liheap +# - 2026-27 season (period 2027): https://www.mainehousing.org/charts/ +# Guidelines lag one year: period 2026 uses the 2025 guideline (15,650 + 5,500 per +# additional person) and period 2027 the 2026 guideline (15,960 + 5,680). +# Maine four-person SMI: 118,065 (FFY2026) and 122,022 (FFY2027). 60% SMI is +# floor(floor(0.6 x SMI) x size factor), where the size factor is 0.52 + 0.16 per +# person for persons two to six, plus 0.03 per person above six (45 CFR 96.85(b)). +# 60% of the four-person SMI: floor(70,839.0) = 70,839 (2026); floor(73,213.2) = +# 73,213 (2027). + +- name: Case 1, one person in 2026 is limited by 60 percent of state median income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: ME + output: + # 150% x 15,650 = 23,475. 60% SMI = floor(70,839 x 0.52) = floor(36,836.28) + # = 36,836. max(23,475, 36,836) = 36,836, the 2025-26 chart's 12-month figure. + me_heap_income_limit: 36_836 + +- name: Case 2, ten members in 2026 are limited by state median income and eleven by the poverty guideline. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [ME, ME] + me_heap_household_size: [10, 11] + output: + # Size 10: guideline 15,650 + 9 x 5,500 = 65,150, 150% = 97,725. Size factor + # 0.52 + 5 x 0.16 + 4 x 0.03 = 1.44; floor(70,839 x 1.44) = floor(102,008.16) + # = 102,008, the chart figure. max = 102,008. + # Size 11: guideline 15,650 + 10 x 5,500 = 70,650, 150% = 105,975. Size factor + # 1.47; floor(70,839 x 1.47) = floor(104,133.33) = 104,133. max = 105,975. + me_heap_income_limit: [102_008, 105_975] + +- name: Case 3, one and four members in 2027 follow the 2026-27 chart. + period: 2027 + absolute_error_margin: 0.01 + input: + state_code: [ME, ME] + me_heap_household_size: [1, 4] + output: + # Size 1: 150% x 15,960 = 23,940; floor(73,213 x 0.52) = floor(38,070.76) = + # 38,070. max = 38,070. + # Size 4: 150% x (15,960 + 3 x 5,680 = 33,000) = 49,500; 73,213 x 1.00 = + # 73,213. max = 73,213. Both match the 2026-27 chart. + me_heap_income_limit: [38_070, 73_213] diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/integration.yaml new file mode 100644 index 00000000000..6b560bf5cde --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/integration.yaml @@ -0,0 +1,216 @@ +# End-to-end HEAP Fuel Assistance households. +# Sources for the expected values: +# - 99-346 CMR ch. 24, 2026 edition (period 2027 matrix, rule "#page 6,11-17"; the +# 2025 edition's eligibility and income text is the same): +# https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=6 +# - 2025 edition points matrix (period 2026), "#page 1-2": +# https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=1 +# - Income limits: the 2025-26 HEAP page (captured 2025-11-01) and the 2026-27 chart: +# https://web.archive.org/web/20251101091454id_/https://www.mainehousing.org/programs-services/energy/energydetails/liheap +# https://www.mainehousing.org/charts/ +# - $22 a point is an estimate from the FFY2026 plan's $1,012 maximum: +# https://liheapch.acf.gov/docs/2026/state-plans/ME_Plan_2026.pdf#page=10 +# Guidelines lag one year: 15,650 + 5,500 per additional person (2026) and 15,960 + +# 5,680 (2027). Incomes: Maine's minimum wage, $14.65 (2025) and $15.10 (2026), an +# hour (Maine Department of Labor); the 2025 average retired-worker Social Security +# benefit, $1,976 a month (SSA 2025 COLA fact sheet). SNAP, TANF and SSI are set to 0. +# Heating bills are EIA's 2025-26 winter household expenditures (Short-Term Energy +# Outlook, October 2026, Table WF01): heating oil 1,745 (U.S. average) and propane +# 1,979 (Northeast). Rule 3(A)(4) (page 10) requires a Direct Energy Cost or an +# Indirect Determinable Energy Cost such as heat included in rent. + +- name: Case 1, an older homeowner on Social Security heating with oil in Penobscot County. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 72 + # 1,976 x 12 = 23,712. + social_security_retirement: 23_712 + ssi: 0 + spm_units: + spm_unit: + members: [head] + snap: 0 + tanf: 0 + me_heap_dwelling_type: STICK_BUILT_OR_MODULAR + heating_type: FUEL_OIL + fuel_oil_expense: 1_745 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [head] + state_code: ME + county: PENOBSCOT_COUNTY_ME + is_in_public_housing: false + output: + me_heap_household_size: 1 + # Social Security counts in full. + me_heap_income: 23_712 + # 2025-26 chart, one person. + me_heap_income_limit: 36_836 + me_heap_categorically_eligible: false + # The oil bill, 1,745 > 0, with heat not in the rent is a direct energy cost. + has_heating_expense: true + me_heap_eligible: true + # 23,712 / 15,650 = 151.51% -> 152, above 150%: 0.70. + me_heap_income_multiplier: 0.7 + # Stick-built 9 + Penobscot 5 + oil 7 + age 60 or over 5 = 26. + # 26 x 0.70 = 18.2, rounded up to 19. + me_heap_points: 19 + # 19 x 22 = 418. + me_heap: 418 + +- name: Case 2, a working family renting an apartment with heat in the rent in Cumberland County. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + head: + age: 34 + # 15.10 x 40 hours x 52 weeks = 31,408. + employment_income: 31_408 + ssi: 0 + spouse: + age: 32 + # 15.10 x 20 hours x 52 weeks = 15,704. + employment_income: 15_704 + ssi: 0 + child1: + age: 8 + ssi: 0 + child2: + age: 4 + ssi: 0 + spm_units: + spm_unit: + members: [head, spouse, child1, child2] + snap: 0 + tanf: 0 + me_heap_dwelling_type: CONDO_DUPLEX_OR_APARTMENT + # The landlord heats the building with natural gas; the rent includes heat. + heating_type: NATURAL_GAS + heat_expense_included_in_rent: true + receives_housing_assistance: false + households: + household: + members: [head, spouse, child1, child2] + state_code: ME + county: CUMBERLAND_COUNTY_ME + is_in_public_housing: false + output: + me_heap_household_size: 4 + # 31,408 + 15,704 = 47,112. + me_heap_income: 47_112 + # 2026-27 chart, four people. + me_heap_income_limit: 73_213 + me_heap_categorically_eligible: false + # Heat in the rent is an indirect energy cost, not a separate heating bill. + has_heating_expense: false + # An indirect energy cost with a listed fuel qualifies an unsubsidized tenant. + me_heap_eligible: true + # 47,112 / (15,960 + 3 x 5,680 = 33,000) = 142.76%: 126-150.99% band, 0.9. + me_heap_income_multiplier: 0.9 + # Base 8 + apartment 1 + Cumberland 1 + natural gas 3 + child under 7 5 = 18; + # no subsidized-housing adjustment. 18 x 0.9 = 16.2, rounded up to 17. + me_heap_points: 17 + # 17 x 22 = 374. + me_heap: 374 + +- name: Case 3, a subsidized family paying its own propane bill in Aroostook County. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 29 + # 14.65 x 30 hours x 52 weeks = 22,854. + employment_income: 22_854 + # 200 a month. + child_support_received: 2_400 + ssi: 0 + child1: + age: 9 + ssi: 0 + child2: + age: 3 + ssi: 0 + spm_units: + spm_unit: + members: [head, child1, child2] + snap: 0 + tanf: 0 + me_heap_dwelling_type: CONDO_DUPLEX_OR_APARTMENT + heating_type: PROPANE + bottled_gas_expense: 1_979 + heat_expense_included_in_rent: false + # Housing Choice Voucher. + receives_housing_assistance: true + households: + household: + members: [head, child1, child2] + state_code: ME + county: AROOSTOOK_COUNTY_ME + is_in_public_housing: false + output: + me_heap_household_size: 3 + # 22,854 + 2,400 = 25,254. + me_heap_income: 25_254 + # 2025-26 chart, three people. + me_heap_income_limit: 59_504 + me_heap_categorically_eligible: false + # The propane bill, 1,979 > 0, with heat not in the rent is a direct energy cost. + has_heating_expense: true + me_heap_eligible: true + # 25,254 / (15,650 + 2 x 5,500 = 26,650) = 94.76% -> 95: 1.00. + me_heap_income_multiplier: 1 + # Apartment 6 + Aroostook 7 + LP gas 10 + child under 7 5 - subsidized 9 = 19. + # 19 x 1.00 = 19. + me_heap_points: 19 + # 19 x 22 = 418. + me_heap: 418 + +- name: Case 4, the Case 1 homeowner heats with oil but has no oil bill and no heat in the rent, so receives nothing. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 72 + # 1,976 x 12 = 23,712. + social_security_retirement: 23_712 + ssi: 0 + spm_units: + spm_unit: + members: [head] + snap: 0 + tanf: 0 + me_heap_dwelling_type: STICK_BUILT_OR_MODULAR + heating_type: FUEL_OIL + fuel_oil_expense: 0 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [head] + state_code: ME + county: PENOBSCOT_COUNTY_ME + is_in_public_housing: false + output: + me_heap_household_size: 1 + me_heap_income: 23_712 + me_heap_income_limit: 36_836 + me_heap_categorically_eligible: false + # FUEL_OIL reads fuel_oil_expense: 0. + heating_expense: 0 + # No bill and heat not in the rent: neither a direct nor an indirect energy cost. + has_heating_expense: false + # Income 23,712 <= 36,836 and oil is a listed fuel, but rule 3(A)(4) fails. + me_heap_eligible: false + # The multiplier and points do not depend on eligibility: as in Case 1, + # 0.70 and 26 x 0.70 = 18.2, rounded up to 19. + me_heap_income_multiplier: 0.7 + me_heap_points: 19 + me_heap: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap.yaml new file mode 100644 index 00000000000..d871f19826f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap.yaml @@ -0,0 +1,166 @@ +# HEAP Fuel Assistance benefit = points x the dollar value per point (rule 4(A)); +# subsidized tenants with heat in their rent receive a nominal benefit "to maximize +# benefits under SNAP" (rule 4(B), page 17 of the 2026 edition; 4(C) of the 2025 +# edition): +# https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=17 +# - $22 a point is an estimate: the FFY2025 and FFY2026 plans give a $1,012 maximum +# (section 2.6), which equals the 2025 edition's highest points, 35 x 1.30 = 45.5, +# rounded up to 46, times $22. The model reuses $22 for 2027: +# https://liheapch.acf.gov/docs/2026/state-plans/ME_Plan_2026.pdf#page=10 +# - $21 for subsidized heat in rent: FFY2026 plan section 1.7 and MaineHousing notice +# 2024-HEAP-003. +# Incomes use Maine's 2025 minimum wage, $14.65 an hour (Maine Department of Labor), +# the 2025 average Social Security disability benefit, $1,580 a month, and the +# 2026 average retired-worker benefit, $2,071 a month (SSA COLA fact sheets). +# Heating bills are EIA's winter household expenditures (Short-Term Energy Outlook, +# October 2026, Table WF01): 2025-26 Northeast electricity 1,685 and U.S. average +# heating oil 1,745; 2026-27 forecast Northeast natural gas 879. Rule 3(A)(4) +# requires a heating bill or heat included in rent. + +- name: Case 1, the 2026 plan maximum of 1,012 dollars. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 64 + # An early retiree living on withdrawals from savings in a traditional IRA + # before claiming Social Security. + taxable_ira_distributions: 9_000 + ssi: 0 + spm_units: + spm_unit: + members: [head] + snap: 0 + tanf: 0 + me_heap_dwelling_type: STICK_BUILT_OR_MODULAR + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_685 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [head] + state_code: ME + county: AROOSTOOK_COUNTY_ME + is_in_public_housing: false + output: + # Assets drawn down from financial institutions are not income: 0. + me_heap_income: 0 + me_heap_eligible: true + # 0% of the guideline -> 0%-25% band: 1.30. + me_heap_income_multiplier: 1.3 + # Stick-built 9 + Aroostook 7 + electricity 14 + age 60 or over 5 = 35. + # 35 x 1.30 = 45.5, rounded up to 46. + me_heap_points: 46 + # 46 x 22 = 1,012. + me_heap: 1_012 + +- name: Case 2, an older adult heating an apartment with natural gas in 2027. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + head: + age: 70 + # 2,071 x 12 = 24,852. + social_security_retirement: 24_852 + ssi: 0 + spm_units: + spm_unit: + members: [head] + snap: 0 + tanf: 0 + me_heap_dwelling_type: CONDO_DUPLEX_OR_APARTMENT + heating_type: NATURAL_GAS + gas_expense: 879 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [head] + state_code: ME + county: KENNEBEC_COUNTY_ME + is_in_public_housing: false + output: + # 24,852 <= 38,070. + me_heap_eligible: true + # 24,852 / 15,960 = 155.71%: 151-175.99% band, 0.8. + me_heap_income_multiplier: 0.8 + # Base 8 + apartment 1 + Kennebec 1 + natural gas 3 + age 60 or over 5 = 18. + # 18 x 0.8 = 14.4, rounded up to 15. + me_heap_points: 15 + # 15 x 22 = 330. + me_heap: 330 + +- name: Case 3, a subsidized tenant with heat in the rent receives the nominal benefit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 58 + is_disabled: true + # 1,580 x 12 = 18,960. + social_security_disability: 18_960 + ssi: 0 + spm_units: + spm_unit: + members: [head] + snap: 0 + tanf: 0 + me_heap_dwelling_type: CONDO_DUPLEX_OR_APARTMENT + # The building's heat is included in the rent and its fuel is unknown. + heating_type: UNSPECIFIED + heat_expense_included_in_rent: true + # Housing Choice Voucher. + receives_housing_assistance: true + households: + household: + members: [head] + state_code: ME + county: KENNEBEC_COUNTY_ME + is_in_public_housing: false + output: + # 18,960 <= 36,836, and subsidized heat in rent needs no listed fuel. + me_heap_eligible: true + me_heap: 21 + +- name: Case 4, a couple over the income limit receives nothing. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 38 + # 14.65 x 40 hours x 52 weeks = 30,472. + employment_income: 30_472 + medical_expense_health_insurance_premiums: 0 + other_medical_expenses: 0 + ssi: 0 + spouse: + age: 36 + employment_income: 30_472 + medical_expense_health_insurance_premiums: 0 + other_medical_expenses: 0 + ssi: 0 + spm_units: + spm_unit: + members: [head, spouse] + snap: 0 + tanf: 0 + heating_type: FUEL_OIL + fuel_oil_expense: 1_745 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [head, spouse] + state_code: ME + county: KENNEBEC_COUNTY_ME + is_in_public_housing: false + output: + # The oil bill meets the energy-cost test, so only income fails: + # 30,472 x 2 = 60,944 > 48,170, with no medical expenses or SNAP/TANF. + me_heap_eligible: false + me_heap: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap_household_size.yaml new file mode 100644 index 00000000000..676cece5151 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap_household_size.yaml @@ -0,0 +1,33 @@ +# Rule 3(A)(1) counts only citizens, nationals and qualified aliens in the HEAP +# household size. Members with another status remain in the household, and rule +# 3(C)(1) still counts their income (me_heap_income.yaml Case 7). +# 99-346 CMR ch. 24 (2026 edition), pages 6 and 15: +# https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=6 + +- name: Case 1, a mixed-status family counts its citizen and refugee members only. + period: 2026 + input: + people: + head: + age: 41 + immigration_status: CITIZEN + spouse: + age: 38 + immigration_status: UNDOCUMENTED + child1: + age: 15 + immigration_status: REFUGEE + child2: + age: 9 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [head, spouse, child1, child2] + households: + household: + members: [head, spouse, child1, child2] + state_code: ME + output: + # head (citizen), child1 (refugee, a qualified alien) and child2 (citizen) + # count; the undocumented spouse does not: 3. + me_heap_household_size: 3 diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap_income.yaml new file mode 100644 index 00000000000..69c8a26e61c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap_income.yaml @@ -0,0 +1,313 @@ +# Household income under rule 3(C)(1) of 99-346 CMR ch. 24 (2026 edition, pages +# 11-15; the 2025 edition has the same text): +# https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=11 +# - Gross income of all members over the age of 18 (modeled as age 18 and older), +# from the listed sources: wages, self-employment, Social Security and SSI +# "including Medicare deductions", child support, pensions and others. +# - Excluded: "Assets drawn down from financial institutions" and "Income earned by +# a Household Member who is a full time high school student, unless they are the +# Primary Applicant" (the tax unit head). TANF is not a listed source. +# - Each source is floored at zero, so a self-employment loss cannot offset wages. +# - Over-income households deduct documented medical expenses (IRS Publication +# 502), "only the amount necessary to make the Household eligible". +# - Ineligible members' income counts. +# Incomes use figures for applications filed in the season: Maine's 2025 minimum +# wage of $14.65 an hour (Maine Department of Labor, 26 M.R.S. 664(1)); the 2025 +# average retired-worker Social Security benefit of $1,976 a month (SSA, 2025 COLA +# fact sheet); and the 2025 standard Medicare Part B premium of $185.00 a month +# (CMS). The 2026 income limit is 36,836 (one person), 48,170 (two) and 59,504 +# (three); see eligibility/me_heap_income_limit.yaml. Cases 8 and 9 pay an oil bill +# of 1,745, EIA's 2025-26 U.S. average winter heating-oil expenditure (Short-Term +# Energy Outlook, October 2026, Table WF01), so only income decides eligibility. + +- name: Case 1, an 18-year-old high school student's and a 17-year-old's earnings are not counted. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 45 + # 14.65 x 40 hours x 52 weeks = 30,472. + employment_income: 30_472 + ssi: 0 + child1: + age: 18 + # A high school senior working 12 hours a week for 40 weeks: + # 14.65 x 12 x 40 = 7,032. + is_in_k12_school: true + employment_income: 7_032 + ssi: 0 + child2: + age: 17 + # Left school and works 14.65 x 30 hours x 52 weeks = 22,854. + is_in_k12_school: false + employment_income: 22_854 + ssi: 0 + spm_units: + spm_unit: + members: [head, child1, child2] + snap: 0 + tanf: 0 + households: + household: + members: [head, child1, child2] + state_code: ME + output: + # child1 is a full-time high school student who is not the applicant: excluded. + # child2 is under 18: excluded. 30,472 <= 59,504, so no medical adjustment. + me_heap_income: 30_472 + +- name: Case 2, threshold edge, an 18-year-old who has left school is counted. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 44 + # 14.65 x 20 hours x 52 weeks = 15,236. + employment_income: 15_236 + ssi: 0 + child1: + age: 18 + # Graduated; works 14.65 x 30 hours x 52 weeks = 22,854, the same job as + # the 17-year-old in Case 1. + is_in_k12_school: false + employment_income: 22_854 + ssi: 0 + spm_units: + spm_unit: + members: [head, child1] + snap: 0 + tanf: 0 + households: + household: + members: [head, child1] + state_code: ME + output: + # 15,236 + 22,854 = 38,090 <= 48,170, so no medical adjustment. + me_heap_income: 38_090 + +- name: Case 3, a high school student who is the applicant has earnings counted. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 19 + # A young parent finishing high school who works 14.65 x 20 hours x 52 + # weeks = 15,236. + is_in_k12_school: true + employment_income: 15_236 + ssi: 0 + child1: + age: 1 + ssi: 0 + spm_units: + spm_unit: + members: [head, child1] + snap: 0 + tanf: 0 + households: + household: + members: [head, child1] + state_code: ME + output: + # The student is the tax unit head (the primary applicant), so the student exclusion + # does not apply: 15,236. + me_heap_income: 15_236 + +- name: Case 4, S corporation income counts and a self-employment loss does not reduce other income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 52 + # 14.65 x 25 hours x 52 weeks = 19,045. + employment_income: 19_045 + # A small woodworking business that lost money. + self_employment_income: -4_200 + # Share of profit from a family business organized as an S corporation. + partnership_s_corp_income: 8_000 + ssi: 0 + spm_units: + spm_unit: + members: [head] + snap: 0 + tanf: 0 + households: + household: + members: [head] + state_code: ME + output: + # Self-employment income includes partnership and S corporation income + # (rule 3(C)(1)(b)). 19,045 + max(-4,200, 0) + 8,000 = 27,045, not 22,845 + # (the loss offset) or 19,045 (S corporation income omitted). + # 27,045 <= 36,836, so no medical adjustment. + me_heap_income: 27_045 + +- name: Case 5, Social Security counts in full and IRA withdrawals do not count. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 70 + # 1,976 x 12 = 23,712, gross of the Medicare premium. + social_security_retirement: 23_712 + # Withdrawals from savings in a traditional IRA. + taxable_ira_distributions: 6_000 + # Part B premium: 185 x 12 = 2,220. + medical_expense_health_insurance_premiums: 2_220 + other_medical_expenses: 0 + ssi: 0 + spm_units: + spm_unit: + members: [head] + snap: 0 + tanf: 0 + households: + household: + members: [head] + state_code: ME + output: + # Social Security 23,712 counts; the IRA withdrawal is an asset drawn down. + # 23,712 <= 36,836, so the medical premiums are not deducted: 23,712. + me_heap_income: 23_712 + +- name: Case 6, TANF is not counted but child support is. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 27 + # 200 a month. + child_support_received: 2_400 + ssi: 0 + child1: + age: 5 + ssi: 0 + child2: + age: 2 + ssi: 0 + spm_units: + spm_unit: + members: [head, child1, child2] + # Maine TANF maximum for three, 895 a month (10-144 CMR ch. 331, Appendix + # Table 2, from 2024-10-01): 895 x 12 = 10,740. + tanf: 10_740 + snap: 0 + households: + household: + members: [head, child1, child2] + state_code: ME + output: + # Child support 2,400; TANF is excluded: 2,400. + me_heap_income: 2_400 + +- name: Case 7, an ineligible member's income is counted. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + immigration_status: CITIZEN + # 14.65 x 40 hours x 52 weeks = 30,472. + employment_income: 30_472 + ssi: 0 + spouse: + age: 33 + immigration_status: UNDOCUMENTED + # 14.65 x 15 hours x 52 weeks = 11,427. + employment_income: 11_427 + ssi: 0 + child1: + age: 4 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [head, spouse, child1] + snap: 0 + tanf: 0 + households: + household: + members: [head, spouse, child1] + state_code: ME + output: + # Size 2 (the spouse is not counted), limit 48,170. + me_heap_household_size: 2 + # 30,472 + 11,427 = 41,899 <= 48,170. + me_heap_income: 41_899 + +- name: Case 8, medical expenses that cover the excess bring income to the limit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 66 + # 1,976 x 12 = 23,712. + social_security_retirement: 23_712 + # Part-time job: 14.65 x 20 hours x 52 weeks = 15,236. + employment_income: 15_236 + # Part B premium: 185 x 12 = 2,220. + medical_expense_health_insurance_premiums: 2_220 + other_medical_expenses: 0 + ssi: 0 + spm_units: + spm_unit: + members: [head] + snap: 0 + tanf: 0 + heating_type: FUEL_OIL + fuel_oil_expense: 1_745 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [head] + state_code: ME + is_in_public_housing: false + output: + # Gross 23,712 + 15,236 = 38,948 > 36,836. Deducting 2,220 gives 36,728, + # but only the amount needed counts: max(36,728, 36,836) = 36,836. + me_heap_income: 36_836 + me_heap_eligible: true + +- name: Case 9, medical expenses that do not cover the excess leave the household over the limit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 66 + # 1,976 x 12 = 23,712. + social_security_retirement: 23_712 + # Part-time job: 14.65 x 25 hours x 52 weeks = 19,045. + employment_income: 19_045 + # Part B premium: 185 x 12 = 2,220. + medical_expense_health_insurance_premiums: 2_220 + other_medical_expenses: 0 + ssi: 0 + spm_units: + spm_unit: + members: [head] + snap: 0 + tanf: 0 + heating_type: FUEL_OIL + fuel_oil_expense: 1_745 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [head] + state_code: ME + is_in_public_housing: false + output: + # Gross 23,712 + 19,045 = 42,757 > 36,836. 42,757 - 2,220 = 40,537 > 36,836: + # max(40,537, 36,836) = 40,537, still over the limit. + me_heap_income: 40_537 + me_heap_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/payment/me_heap_income_multiplier.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/payment/me_heap_income_multiplier.yaml new file mode 100644 index 00000000000..71985193de7 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/payment/me_heap_income_multiplier.yaml @@ -0,0 +1,74 @@ +# Income multiplier ("Percentage of Points") by poverty level, the household's +# income as a percent of the poverty guideline for its eligible members. +# - 2025 edition (period 2026), benefit matrix page 2: 0%-25% 130%, 26%-50% 120%, +# 51%-75% 110%, 76%-100% 100%, 101%-125% 90%, 126%-150% 80%, above 150% 70%. +# "income at 75.1% will be rounded to 75%; income at 100.6% will be rounded to +# 101%": https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=2 +# - 2026 edition (period 2027), rule page 17: 0-10.99% 1.8, 11-33.99% 1.6, +# 34-75.99% 1.4, 76-100.99% 1.2, 101-125.99% 1, 126-150.99% 0.9, 151-175.99% 0.8, +# above 176% 0.7, with percents to two decimals: +# https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=17 +# Percents round half up to whole numbers (period 2026) or two decimals +# (period 2027), then the band that starts at or below the percent applies. +# Guidelines lag one year: 2026 uses 15,650 for one person; 2027 uses 33,000 for +# four (15,960 + 3 x 5,680). Size and income are set directly to place the percent. + +- name: Case 1, threshold edge, 25.4 percent rounds to 25 and 25.5 percent rounds to 26 in 2026. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [ME, ME] + me_heap_household_size: [1, 1] + me_heap_income: [3_975.1, 3_990.75] + output: + # 3,975.10 / 15,650 = 25.4% -> 25 -> 0%-25% band: 1.30. + # 3,990.75 / 15,650 = 25.5% -> 26 -> 26%-50% band: 1.20. Without rounding, + # 25.5 would stay in the first band; rounding every fraction up would move + # 25.4 to the second. + me_heap_income_multiplier: [1.3, 1.2] + +- name: Case 2, each remaining 2026 band, through above 150 percent. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [ME, ME, ME, ME, ME] + me_heap_household_size: [1, 1, 1, 1, 1] + # SSI at the 2025 federal benefit rate (967 x 12); half-time, 30-hour and + # full-time work at Maine's 2025 minimum wage of $14.65 (14.65 x 20, 30 and + # 40 hours x 52 weeks); and the 2025 average Social Security disability + # benefit (1,580 x 12, SSA 2025 COLA fact sheet). + me_heap_income: [11_604, 15_236, 18_960, 22_854, 30_472] + output: + # 11,604 / 15,650 = 74.15% -> 74: 1.10. + # 15,236 / 15,650 = 97.35% -> 97: 1.00. + # 18,960 / 15,650 = 121.15% -> 121: 0.90. + # 22,854 / 15,650 = 146.03% -> 146: 0.80. + # 30,472 / 15,650 = 194.71% -> 195: 0.70. + me_heap_income_multiplier: [1.1, 1.0, 0.9, 0.8, 0.7] + +- name: Case 3, threshold edges at 11, 76 and 176 percent in 2027. + period: 2027 + absolute_error_margin: 0.01 + input: + state_code: [ME, ME, ME, ME, ME, ME] + me_heap_household_size: [4, 4, 4, 4, 4, 4] + me_heap_income: [3_626.7, 3_630, 25_076.7, 25_080, 58_076.7, 58_080] + output: + # 3,626.70 / 33,000 = 10.99%: 1.8. 3,630 / 33,000 = 11.00%: 1.6. Whole-percent + # rounding would put 10.99 in the 1.6 band. + # 25,076.70 / 33,000 = 75.99%: 1.4. 25,080 / 33,000 = 76.00%: 1.2. + # 58,076.70 / 33,000 = 175.99%: 0.8. 58,080 / 33,000 = 176.00%: 0.7. + me_heap_income_multiplier: [1.8, 1.6, 1.4, 1.2, 0.8, 0.7] + +- name: Case 4, zero income and the 1.0 and 0.9 bands in 2027. + period: 2027 + absolute_error_margin: 0.01 + input: + state_code: [ME, ME, ME] + me_heap_household_size: [4, 4, 4] + # Full-time work at Maine's 2026 minimum wage of $15.10 (15.10 x 40 x 52 = + # 31,408) plus a second earner's 10 or 20 hours a week (7,852 or 15,704). + me_heap_income: [0, 39_260, 47_112] + output: + # 0%: 1.8. 39,260 / 33,000 = 118.97%: 1.0. 47,112 / 33,000 = 142.76%: 0.9. + me_heap_income_multiplier: [1.8, 1.0, 0.9] diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/payment/me_heap_points.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/payment/me_heap_points.yaml new file mode 100644 index 00000000000..b1d6bfe065c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/payment/me_heap_points.yaml @@ -0,0 +1,337 @@ +# Benefit points under rule 4(A): dwelling, county, fuel, priority and subsidized +# housing points, times the income multiplier, prorated by eligible members over all +# members, with fractions rounded up. +# - 2025 edition (period 2026), benefit matrix pages 1-2: no base points; +# stick-built/modular 9, mobile/manufactured 8, condo/duplex and apartment 6; +# Aroostook 7, Penobscot 5, Cumberland 3, Kennebec 2; electricity 14, LP gas 10, +# kerosene 8, oil 7, coal 5, natural gas 5, wood 4; subsidized household +# responsible for heating -9; priority "60+, Disabled or child 6 and under" 5 +# "or" child 7-17 3: +# https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=1 +# - 2026 edition (period 2027), rule pages 16-17: base 8; stick-built/modular 3, +# mobile 2, condo/duplex and apartment 1; Aroostook 6, Penobscot 3, Cumberland 1, +# Kennebec 1; electric 8, LP gas 5, kerosene 5, oil 4, coal 4, natural gas 3, wood +# 3; at risk (60+, child 6 and under, disabled) 5, "Points for the first in the +# house"; heating subsidized -3: +# https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=16 +# Modeling assumptions: SOLAR uses the electricity row (a grid-tied home with electric +# heat) and WOOD the cordwood row. Unless stated, +# households are unsubsidized, heat is not in the rent, and the income multiplier +# is set to 1 so the points equal the matrix total. Cases 10 and 11 also assert the +# benefit, so they pay an oil bill: EIA's U.S. average winter heating-oil +# expenditure, 1,745 for 2025-26 and 2,115 forecast for 2026-27 (Short-Term Energy +# Outlook, October 2026, Table WF01). + +- name: Case 1, dwelling and county points in 2026. + period: 2026 + input: + state_code: [ME, ME, ME, ME] + age: [40, 40, 40, 40] + county: [KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, AROOSTOOK_COUNTY_ME] + me_heap_dwelling_type: [STICK_BUILT_OR_MODULAR, MOBILE_OR_MANUFACTURED, CONDO_DUPLEX_OR_APARTMENT, STICK_BUILT_OR_MODULAR] + heating_type: [FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL] + me_heap_income_multiplier: [1, 1, 1, 1] + output: + # Dwelling + county + oil 7: 9 + 2 + 7 = 18; 8 + 2 + 7 = 17; 6 + 2 + 7 = 15; + # Aroostook 9 + 7 + 7 = 23. + me_heap_points: [18, 17, 15, 23] + +- name: Case 2, dwelling and county points with base points in 2027. + period: 2027 + input: + state_code: [ME, ME, ME, ME] + age: [40, 40, 40, 40] + county: [KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, AROOSTOOK_COUNTY_ME] + me_heap_dwelling_type: [STICK_BUILT_OR_MODULAR, MOBILE_OR_MANUFACTURED, CONDO_DUPLEX_OR_APARTMENT, STICK_BUILT_OR_MODULAR] + heating_type: [FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL] + me_heap_income_multiplier: [1, 1, 1, 1] + output: + # Base 8 + dwelling + county + oil 4: 8 + 3 + 1 + 4 = 16; 8 + 2 + 1 + 4 = 15; + # 8 + 1 + 1 + 4 = 14; Aroostook 8 + 3 + 6 + 4 = 21. + me_heap_points: [16, 15, 14, 21] + +- name: Case 3, fuel points in 2026. + period: 2026 + input: + state_code: [ME, ME, ME, ME, ME, ME, ME, ME] + age: [40, 40, 40, 40, 40, 40, 40, 40] + county: [KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME] + heating_type: [ELECTRICITY, SOLAR, PROPANE, KEROSENE, FUEL_OIL, COAL, NATURAL_GAS, WOOD] + me_heap_income_multiplier: [1, 1, 1, 1, 1, 1, 1, 1] + output: + # Stick-built 9 + Kennebec 2 = 11, plus electricity 14, solar 14 (electricity + # row), LP gas 10, kerosene 8, oil 7, coal 5, natural gas 5, wood 4. + me_heap_points: [25, 25, 21, 19, 18, 16, 16, 15] + +- name: Case 4, fuel points in 2027. + period: 2027 + input: + state_code: [ME, ME, ME, ME, ME, ME, ME, ME] + age: [40, 40, 40, 40, 40, 40, 40, 40] + county: [KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME] + heating_type: [ELECTRICITY, SOLAR, PROPANE, KEROSENE, FUEL_OIL, COAL, NATURAL_GAS, WOOD] + me_heap_income_multiplier: [1, 1, 1, 1, 1, 1, 1, 1] + output: + # Base 8 + stick-built 3 + Kennebec 1 = 12, plus electric 8, solar 8, LP gas 5, + # kerosene 5, oil 4, coal 4, natural gas 3, wood 3. + me_heap_points: [20, 20, 17, 17, 16, 16, 15, 15] + +- name: Case 5, threshold edge at age 60, and a disabled adult, for priority points in 2026. + period: 2026 + input: + state_code: [ME, ME, ME] + age: [59, 60, 40] + is_disabled: [false, false, true] + county: [KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME] + heating_type: [FUEL_OIL, FUEL_OIL, FUEL_OIL] + me_heap_income_multiplier: [1, 1, 1] + output: + # Stick-built 9 + Kennebec 2 + oil 7 = 18; age 59: no priority; age 60 and the + # disabled adult: + 5 = 23. + me_heap_points: [18, 23, 23] + +- name: Case 6, threshold edges at ages 6 to 7 and 17 to 18 for child priority points in 2026. + period: 2026 + input: + people: + parent1: + age: 40 + child1: + age: 6 + parent2: + age: 40 + child2: + age: 7 + parent3: + age: 40 + child3: + age: 17 + parent4: + age: 40 + child4: + age: 18 + spm_units: + spm_unit1: + members: [parent1, child1] + heating_type: FUEL_OIL + me_heap_income_multiplier: 1 + spm_unit2: + members: [parent2, child2] + heating_type: FUEL_OIL + me_heap_income_multiplier: 1 + spm_unit3: + members: [parent3, child3] + heating_type: FUEL_OIL + me_heap_income_multiplier: 1 + spm_unit4: + members: [parent4, child4] + heating_type: FUEL_OIL + me_heap_income_multiplier: 1 + households: + household1: + members: [parent1, child1] + state_code: ME + county: KENNEBEC_COUNTY_ME + household2: + members: [parent2, child2] + state_code: ME + county: KENNEBEC_COUNTY_ME + household3: + members: [parent3, child3] + state_code: ME + county: KENNEBEC_COUNTY_ME + household4: + members: [parent4, child4] + state_code: ME + county: KENNEBEC_COUNTY_ME + output: + # Stick-built 9 + Kennebec 2 + oil 7 = 18, plus: child aged 6, 5 = 23; child + # aged 7, 3 = 21; child aged 17, 3 = 21; an 18-year-old is not a child, 0 = 18. + me_heap_points: [23, 21, 21, 18] + +- name: Case 7, in 2027 older children earn no priority points and the at-risk points are awarded once. + period: 2027 + input: + people: + parent1: + age: 40 + child1: + age: 10 + parent2: + age: 40 + child2: + age: 6 + head: + age: 70 + spouse: + age: 68 + spm_units: + spm_unit1: + members: [parent1, child1] + heating_type: FUEL_OIL + me_heap_income_multiplier: 1 + spm_unit2: + members: [parent2, child2] + heating_type: FUEL_OIL + me_heap_income_multiplier: 1 + spm_unit3: + members: [head, spouse] + heating_type: FUEL_OIL + me_heap_income_multiplier: 1 + households: + household1: + members: [parent1, child1] + state_code: ME + county: KENNEBEC_COUNTY_ME + household2: + members: [parent2, child2] + state_code: ME + county: KENNEBEC_COUNTY_ME + household3: + members: [head, spouse] + state_code: ME + county: KENNEBEC_COUNTY_ME + output: + # Base 8 + stick-built 3 + Kennebec 1 + oil 4 = 16, plus: child aged 10, 0 = + # 16; child aged 6, 5 = 21; two members over 60, 5 once = 21, not 26. + me_heap_points: [16, 21, 21] + +- name: Case 8, subsidized tenants paying their own heating bill lose nine points in 2026. + period: 2026 + input: + state_code: [ME, ME, ME] + age: [40, 40, 40] + county: [KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME] + me_heap_dwelling_type: [CONDO_DUPLEX_OR_APARTMENT, CONDO_DUPLEX_OR_APARTMENT, CONDO_DUPLEX_OR_APARTMENT] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY] + heat_expense_included_in_rent: [false, false, false] + # Housing Choice Voucher, public housing, private rental. + receives_housing_assistance: [true, false, false] + is_in_public_housing: [false, true, false] + me_heap_income_multiplier: [1, 1, 1] + output: + # Apartment 6 + Kennebec 2 + electricity 14 = 22; subsidized: 22 - 9 = 13. + me_heap_points: [13, 13, 22] + +- name: Case 9, subsidized tenants paying their own heating bill lose three points in 2027. + period: 2027 + input: + state_code: [ME, ME] + age: [40, 40] + county: [KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME] + me_heap_dwelling_type: [CONDO_DUPLEX_OR_APARTMENT, CONDO_DUPLEX_OR_APARTMENT] + heating_type: [ELECTRICITY, ELECTRICITY] + heat_expense_included_in_rent: [false, false] + # Housing Choice Voucher, private rental. + receives_housing_assistance: [true, false] + is_in_public_housing: [false, false] + me_heap_income_multiplier: [1, 1] + output: + # Base 8 + apartment 1 + Kennebec 1 + electric 8 = 18; subsidized: 18 - 3 = 15. + me_heap_points: [15, 18] + +- name: Case 10, proration to an exact whole number of points is not rounded up in 2026. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 36 + immigration_status: CITIZEN + # 14.65 x 30 hours x 52 weeks = 22,854 (Maine's 2025 minimum wage). + employment_income: 22_854 + ssi: 0 + spouse: + age: 34 + immigration_status: UNDOCUMENTED + ssi: 0 + child1: + age: 8 + ssi: 0 + child2: + age: 5 + ssi: 0 + child3: + age: 2 + ssi: 0 + spm_units: + spm_unit: + members: [head, spouse, child1, child2, child3] + snap: 0 + tanf: 0 + me_heap_dwelling_type: MOBILE_OR_MANUFACTURED + heating_type: FUEL_OIL + fuel_oil_expense: 1_745 + households: + household: + members: [head, spouse, child1, child2, child3] + state_code: ME + county: PENOBSCOT_COUNTY_ME + output: + # Four of five members are eligible. + me_heap_household_size: 4 + me_heap_income: 22_854 + # 22,854 / (15,650 + 3 x 5,500 = 32,150) = 71.09% -> 71: 1.10. + me_heap_income_multiplier: 1.1 + # Mobile 8 + Penobscot 5 + oil 7 + child under 7 5 = 25. 25 x 1.10 x 4/5 = 22 + # exactly; in binary floating point the product is 22.000000000000004, which + # must not round up to 23. + me_heap_points: 22 + # 22 x 22 = 484. + me_heap: 484 + +- name: Case 11, prorated fractional points are rounded up in 2027. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + immigration_status: CITIZEN + # 15.10 x 40 hours x 52 weeks = 31,408 (Maine's 2026 minimum wage). + employment_income: 31_408 + ssi: 0 + spouse: + age: 33 + immigration_status: UNDOCUMENTED + ssi: 0 + child1: + age: 4 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [head, spouse, child1] + snap: 0 + tanf: 0 + heating_type: FUEL_OIL + fuel_oil_expense: 2_115 + households: + household: + members: [head, spouse, child1] + state_code: ME + county: KENNEBEC_COUNTY_ME + output: + # Two of three members are eligible. + me_heap_household_size: 2 + # 31,408 / (15,960 + 5,680 = 21,640) = 145.14%: 0.9. + me_heap_income_multiplier: 0.9 + # Base 8 + stick-built 3 + Kennebec 1 + oil 4 + child under 7 5 = 21. + # 21 x 0.9 x 2/3 = 12.6, rounded up to 13. + me_heap_points: 13 + # 13 x 22 = 286. + me_heap: 286 + +- name: Case 12, a household whose county is not in the table earns no county points. + period: 2026 + input: + state_code: ME + age: 40 + # The county is recorded as unknown, which has no row in the table. + county: UNKNOWN + me_heap_dwelling_type: STICK_BUILT_OR_MODULAR + heating_type: FUEL_OIL + me_heap_income_multiplier: 1 + output: + # Stick-built 9 + no county row 0 + oil 7 = 16. + me_heap_points: 16 diff --git a/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_categorically_eligible.py b/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_categorically_eligible.py new file mode 100644 index 00000000000..9ae599a7e4c --- /dev/null +++ b/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_categorically_eligible.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class me_heap_categorically_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Maine HEAP categorical income eligibility" + defined_for = StateCode.ME + reference = ( + "https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=15", + "https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx", + "https://liheapch.acf.gov/docs/2026/state-plans/ME_Plan_2026.pdf#page=5", + ) + + def formula(spm_unit, period, parameters): + # Rule 3(C)(2): members on a Maine DHHS TANF or SNAP notice of decision + # are income eligible. + return add(spm_unit, period, ["snap", "tanf"]) > 0 diff --git a/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_eligible.py b/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_eligible.py new file mode 100644 index 00000000000..0f0d2c37c09 --- /dev/null +++ b/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_eligible.py @@ -0,0 +1,55 @@ +from policyengine_us.model_api import * + + +class me_heap_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Maine HEAP regular heating eligibility" + defined_for = StateCode.ME + reference = ( + # PDF pages 2-3, 6, 10-11, 15-17 + "https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=2", + "https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx", + # PDF pages 1-2 + "https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=1", + ) + + def formula(spm_unit, period, parameters): + size = spm_unit("me_heap_household_size", period) + income = spm_unit("me_heap_income", period) + limit = spm_unit("me_heap_income_limit", period) + categorical = spm_unit("me_heap_categorically_eligible", period) + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + subsidized = spm_unit("receives_housing_assistance", period) | ( + spm_unit.household("is_in_public_housing", period) + ) + # Rule 3(A)(4) requires a Direct Energy Cost (a heating bill the + # household pays) or an Indirect Determinable Energy Cost (heat + # included in rent), and the points matrix lists the fuels. + energy_cost = spm_unit("has_heating_expense", period) | heat_in_rent + # SOLAR with an electricity bill stands for a grid-tied home with + # electric heat and uses the electricity row; an off-grid solar-only + # home has no heating bill. NONE means the home has no heating. + # Subsidized tenants with heat in rent receive the nominal benefit + # without a listed fuel; OTHER and UNSPECIFIED are otherwise + # ineligible because heating_type is a required input. + fuel = spm_unit("heating_type", period) + fuels = fuel.possible_values + listed_fuel = ( + (fuel == fuels.ELECTRICITY) + | (fuel == fuels.SOLAR) + | (fuel == fuels.PROPANE) + | (fuel == fuels.KEROSENE) + | (fuel == fuels.FUEL_OIL) + | (fuel == fuels.COAL) + | (fuel == fuels.NATURAL_GAS) + | (fuel == fuels.WOOD) + ) + return ( + (size > 0) + & ((income <= limit) | categorical) + & energy_cost + & (fuel != fuels.NONE) + & (listed_fuel | (heat_in_rent & subsidized)) + ) diff --git a/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_income_limit.py b/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_income_limit.py new file mode 100644 index 00000000000..dd353165bf5 --- /dev/null +++ b/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_income_limit.py @@ -0,0 +1,35 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.hhs_smi import liheap_smi_limit +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class me_heap_income_limit(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Maine HEAP annual income limit" + defined_for = StateCode.ME + reference = ( + "https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=11", + "https://liheapch.acf.gov/docs/2026/state-plans/ME_Plan_2026.pdf#page=9", + "https://www.mainehousing.org/charts/", + "https://web.archive.org/web/20251101091454id_/https://www.mainehousing.org/programs-services/energy/energydetails/liheap", + "https://www.law.cornell.edu/uscode/text/42/8624#b_2_B", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.me.msha.heap + p_hhs = parameters(period).gov.hhs.liheap + size = spm_unit("me_heap_household_size", period) + state_group = spm_unit.household("state_group_str", period) + state = spm_unit.household("state_code_str", period) + guideline = fpg( + max_(size, 1), state_group, period, parameters, year_lag=p.fpg_year_lag + ) + # The federal maximum, the greater of 150% of the poverty guideline and + # 60% of state median income (42 U.S.C. 8624(b)(2)(B)), reproduces + # MaineHousing's published 2025-26 and 2026-27 income charts. + smi_limit = liheap_smi_limit(size, state, p_hhs.smi_limit, period, parameters) + limit = max_(guideline * p_hhs.fpg_limit, smi_limit) + return where(size > 0, limit, 0) diff --git a/policyengine_us/variables/gov/states/me/msha/heap/me_heap.py b/policyengine_us/variables/gov/states/me/msha/heap/me_heap.py new file mode 100644 index 00000000000..66d50a9c93b --- /dev/null +++ b/policyengine_us/variables/gov/states/me/msha/heap/me_heap.py @@ -0,0 +1,51 @@ +from policyengine_us.model_api import * + + +class me_heap(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Maine HEAP regular heating benefit" + defined_for = "me_heap_eligible" + reference = ( + # PDF pages 15-17 + "https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=15", + "https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx", + # PDF pages 1-2 + "https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=1", + # PDF pages 5, 10 + "https://liheapch.acf.gov/docs/2026/state-plans/ME_Plan_2026.pdf#page=5", + "https://liheapch.acf.gov/docs/2025/state-plans/ME_Plan_2025.pdf#page=8", + ) + documentation = ( + "Annual HEAP Fuel Assistance benefit: points for dwelling, county, fuel, " + "priority and subsidized housing (plus base points from June 29, 2026), " + "adjusted by the income multiplier, prorated for ineligible members, " + "rounded up and multiplied by the dollar value per point; subsidized " + "tenants with heat in rent receive the nominal benefit. The $22 value " + "per point is an estimate because MaineHousing has not announced one. " + "The TANF Fuel Supplemental Benefit, ECIP crisis assistance, " + "weatherization and CHIP are excluded. Pellet, corn, bio-fuel and " + "biobrick rows have no heating_type value, and WOOD uses the cordwood " + "row. Eligibility requires a heating bill or heat included in rent. " + "SOLAR uses the electricity row, as for a grid-tied home with electric " + "heat (heating_expense's assumption); an off-grid solar-only home has " + "no electricity bill, so no energy cost for heat. Roomers, " + "live-in attendants, the optional exclusion of college students and the " + "residency tests have no inputs. Verified for model years 2026 and " + "2027; earlier years are unverified backfill." + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.me.msha.heap.payment + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + subsidized = spm_unit("receives_housing_assistance", period) | ( + spm_unit.household("is_in_public_housing", period) + ) + points = spm_unit("me_heap_points", period) + return where( + heat_in_rent & subsidized, + p.nominal_benefit, + points * p.value_per_point, + ) diff --git a/policyengine_us/variables/gov/states/me/msha/heap/me_heap_dwelling_type.py b/policyengine_us/variables/gov/states/me/msha/heap/me_heap_dwelling_type.py new file mode 100644 index 00000000000..2aa1dc2869e --- /dev/null +++ b/policyengine_us/variables/gov/states/me/msha/heap/me_heap_dwelling_type.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class MEHEAPDwellingType(Enum): + STICK_BUILT_OR_MODULAR = "Stick-built or modular home" + MOBILE_OR_MANUFACTURED = "Mobile or manufactured home" + CONDO_DUPLEX_OR_APARTMENT = "Condominium, duplex or apartment" + + +class me_heap_dwelling_type(Variable): + value_type = Enum + entity = SPMUnit + possible_values = MEHEAPDwellingType + default_value = MEHEAPDwellingType.STICK_BUILT_OR_MODULAR + definition_period = YEAR + label = "Maine HEAP dwelling type" + documentation = ( + "Dwelling row of the Maine HEAP points matrix: a stick-built or modular " + "home; a mobile or manufactured home; or a condominium, duplex or " + "apartment, which share the same points. Defaults to a stick-built or " + "modular home." + ) + reference = ( + "https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=1", + "https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=16", + ) diff --git a/policyengine_us/variables/gov/states/me/msha/heap/me_heap_household_size.py b/policyengine_us/variables/gov/states/me/msha/heap/me_heap_household_size.py new file mode 100644 index 00000000000..df84d806d16 --- /dev/null +++ b/policyengine_us/variables/gov/states/me/msha/heap/me_heap_household_size.py @@ -0,0 +1,18 @@ +from policyengine_us.model_api import * + + +class me_heap_household_size(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Maine HEAP household size" + defined_for = StateCode.ME + reference = ( + # PDF pages 6, 15 + "https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=6", + "https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx", + ) + + # Rule 3(A)(1) leaves members who are not citizens, nationals or qualified + # aliens out of the size, and rule 3(C)(1) still counts their income. + adds = ["is_citizen_or_legal_immigrant"] diff --git a/policyengine_us/variables/gov/states/me/msha/heap/me_heap_income.py b/policyengine_us/variables/gov/states/me/msha/heap/me_heap_income.py new file mode 100644 index 00000000000..368260eef79 --- /dev/null +++ b/policyengine_us/variables/gov/states/me/msha/heap/me_heap_income.py @@ -0,0 +1,43 @@ +from policyengine_us.model_api import * + + +class me_heap_income(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Maine HEAP household income" + defined_for = StateCode.ME + reference = ( + # PDF pages 11-15 + "https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=11", + "https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx", + # PDF pages 6-7 + "https://liheapch.acf.gov/docs/2026/state-plans/ME_Plan_2026.pdf#page=6", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.me.msha.heap.income + person = spm_unit.members + # Gross income of members of every immigration status (rule 3(C)(1)). + # Each source is floored at zero, so a loss in one source cannot offset + # another. + earned = 0 + for source in p.sources.earned: + earned = earned + max_(person(source, period), 0) + unearned = 0 + for source in p.sources.unearned: + unearned = unearned + max_(person(source, period), 0) + # Earnings of a full-time high school student are excluded unless the + # student is the primary applicant, represented by the tax unit head. + excluded_student = person("is_in_k12_school", period) & ~person( + "is_tax_unit_head", period + ) + counted = where(excluded_student, 0, earned) + unearned + adult = person("age", period) >= p.minimum_age + gross = spm_unit.sum(counted * adult) + # A household over the income limit deducts medical expenses, but only + # the amount needed to reach the limit. + limit = spm_unit("me_heap_income_limit", period) + medical = add(spm_unit, period, p.medical_expense_sources) + return where(gross > limit, max_(gross - medical, limit), gross) diff --git a/policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_income_multiplier.py b/policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_income_multiplier.py new file mode 100644 index 00000000000..bce948285b0 --- /dev/null +++ b/policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_income_multiplier.py @@ -0,0 +1,36 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + +# Numerical tolerances, not policy values: results are rounded to this many +# decimals before the rule's rounding so floating-point error (for example +# 25 x 1.1 x 4/5 = 22.000000000000004) cannot cross a rounding or band +# boundary. +FLOAT_GUARD_DECIMALS = 6 + + +class me_heap_income_multiplier(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = "/1" + label = "Maine HEAP income multiplier" + defined_for = StateCode.ME + reference = ( + "https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=2", + "https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx", + "https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=17", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.me.msha.heap + size = spm_unit("me_heap_household_size", period) + state_group = spm_unit.household("state_group_str", period) + guideline = fpg( + max_(size, 1), state_group, period, parameters, year_lag=p.fpg_year_lag + ) + poverty_level = spm_unit("me_heap_income", period) / guideline + # Round half up to the edition's increment, then look up the band. + increment = p.points.poverty_level_rounding_increment + increments = np.round(poverty_level / increment, FLOAT_GUARD_DECIMALS) + rounded = np.floor(increments + 0.5) * increment + return p.points.income_multiplier.calc(np.round(rounded, FLOAT_GUARD_DECIMALS)) diff --git a/policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_points.py b/policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_points.py new file mode 100644 index 00000000000..e7db3ff2fe4 --- /dev/null +++ b/policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_points.py @@ -0,0 +1,94 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.me.msha.heap.payment.me_heap_income_multiplier import ( + FLOAT_GUARD_DECIMALS, +) + + +class me_heap_points(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Maine HEAP benefit points" + defined_for = StateCode.ME + reference = ( + # PDF pages 1-2 + "https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=1", + "https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx", + # PDF pages 15-17 + "https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=15", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.me.msha.heap.points + dwelling_points = p.dwelling[spm_unit("me_heap_dwelling_type", period)] + # A county outside the table, such as UNKNOWN or a county in another + # state, gets no county points. The formula runs for every household + # before defined_for applies, and the lookup raises on an unlisted key, + # so those counties read a listed key and are then zeroed. + county = spm_unit.household("county_str", period) + in_table = np.isin(county, list(p.county)) + listed_county = where(in_table, county, next(iter(p.county))) + county_points = where(in_table, p.county[listed_county], 0) + # WOOD is the cordwood row. SOLAR stands for a grid-tied home with + # electric heat. OTHER, NONE and UNSPECIFIED have no row. + fuel = spm_unit("heating_type", period) + fuels = fuel.possible_values + fuel_points = select( + [ + (fuel == fuels.ELECTRICITY) | (fuel == fuels.SOLAR), + fuel == fuels.PROPANE, + fuel == fuels.KEROSENE, + fuel == fuels.FUEL_OIL, + fuel == fuels.COAL, + fuel == fuels.NATURAL_GAS, + fuel == fuels.WOOD, + ], + [ + p.fuel.electricity, + p.fuel.lp_gas, + p.fuel.kerosene, + p.fuel.oil, + p.fuel.coal, + p.fuel.natural_gas, + p.fuel.wood, + ], + default=0, + ) + # Priority points are awarded once per household, for any member. + person = spm_unit.members + age = person("age", period) + priority = p.priority + at_risk = ( + (age >= priority.elderly_age) + | person("is_disabled", period) + | (age <= priority.young_child_age) + ) + older_child = (age > priority.young_child_age) & ( + age < priority.child_age_limit + ) + priority_points = select( + [spm_unit.any(at_risk), spm_unit.any(older_child)], + [priority.amount, priority.older_child_amount], + default=0, + ) + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + subsidized = spm_unit("receives_housing_assistance", period) | ( + spm_unit.household("is_in_public_housing", period) + ) + subsidized_points = where(subsidized & ~heat_in_rent, p.subsidized_heating, 0) + raw_points = ( + p.base + + dwelling_points + + county_points + + fuel_points + + priority_points + + subsidized_points + ) + # Points are adjusted by the income multiplier and prorated by the + # share of members who are eligible, then fractions are rounded up. + multiplier = spm_unit("me_heap_income_multiplier", period) + eligible_share = spm_unit("me_heap_household_size", period) / ( + spm_unit.nb_persons() + ) + adjusted_points = raw_points * multiplier * eligible_share + return np.ceil(np.round(adjusted_points, FLOAT_GUARD_DECIMALS)) From 7d4e4569a0c27e91f907ac11314ad00c670a6783 Mon Sep 17 00:00:00 2001 From: Ziming Date: Wed, 28 Oct 2026 20:10:31 -0400 Subject: [PATCH 2/3] Address review round 1 for Maine LIHEAP Apply DTrim99's round 1 review: tests that pin every income source, the rule and documentation fixes, and source-conflict candidates. Co-Authored-By: Claude Opus 5.5 --- .../heap/income/medical_expense_sources.yaml | 4 +- .../me/msha/heap/income/sources/unearned.yaml | 25 ++-- .../me/msha/heap/payment/value_per_point.yaml | 12 +- .../gov/states/me/msha/heap/points/fuel.yaml | 3 +- .../heap/eligibility/me_heap_eligible.yaml | 20 +-- .../gov/states/me/msha/heap/me_heap.yaml | 74 +++++++++++ .../states/me/msha/heap/me_heap_income.yaml | 118 +++++++++++++++++- .../me_heap_categorically_eligible.py | 5 +- .../msha/heap/eligibility/me_heap_eligible.py | 27 ++-- .../heap/eligibility/me_heap_income_limit.py | 2 +- .../gov/states/me/msha/heap/me_heap.py | 7 +- .../gov/states/me/msha/heap/me_heap_income.py | 6 +- .../me/msha/heap/payment/me_heap_points.py | 4 +- source_conflicts/candidates.yaml | 60 +++++++++ 14 files changed, 319 insertions(+), 48 deletions(-) diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/income/medical_expense_sources.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/income/medical_expense_sources.yaml index 7d9a05112ae..af15c984628 100644 --- a/policyengine_us/parameters/gov/states/me/msha/heap/income/medical_expense_sources.yaml +++ b/policyengine_us/parameters/gov/states/me/msha/heap/income/medical_expense_sources.yaml @@ -16,7 +16,7 @@ metadata: href: https://www.maine.gov/sos/sites/maine.gov.sos/files/inline-files/346c024.docx - title: 99-346 CMR ch. 24, section 3(C)(1) (effective June 29, 2026) href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=15 - - title: MaineHousing HEAP Income Eligibility Guidelines, 2026-2027 season - href: https://www.mainehousing.org/charts/ - title: MaineHousing Home Energy Assistance Program page, 2025-2026 season income limits (archived November 1, 2025) href: https://web.archive.org/web/20251101091454id_/https://www.mainehousing.org/programs-services/energy/energydetails/liheap + - title: MaineHousing Home Energy Assistance Program page, 2026-2027 season income limits (archived October 15, 2026) + href: https://web.archive.org/web/20261015145903id_/https://www.mainehousing.org/programs-services/energy/energydetails/liheap diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/income/sources/unearned.yaml index a148a264dfd..1faf500ed12 100644 --- a/policyengine_us/parameters/gov/states/me/msha/heap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/states/me/msha/heap/income/sources/unearned.yaml @@ -1,19 +1,23 @@ description: Maine counts these sources as unearned income under the Low Income Home Energy Assistance Program. -# Rule 3(C)(1)(c)-(n). Social Security (retirement and SSDI) and SSI count -# gross, including Medicare deductions. financial_assistance stands for cash -# gifts from someone outside the household. disability_benefits stands for +# Rule 3(C)(1)(c)-(n). Household Income is the total income from all sources +# unless excluded. Social Security (retirement and SSDI) and SSI count gross, +# including Medicare deductions. financial_assistance stands for cash gifts +# from someone outside the household. disability_benefits stands for # long-term disability payments, item (n) of the June 29, 2026 replacement; the # 2025 edition's list "includes, but is not limited to" the items it names. # Royalties, contract income and payments from mortgage or sales contracts # have no specific inputs. -# Not counted: TANF and General Assistance, which the rule does not list and -# the FY2026 plan's Section 1.9 leaves unchecked (General Assistance vouchers -# are excluded non-cash income), and retirement_distributions, since assets -# drawn down from financial institutions are excluded. +# TANF, which no exclusion names, is a household grant counted once in +# me_heap_income. Not counted: General Assistance, excluded as non-cash +# voucher income (exclusion j) and without an input, and +# retirement_distributions, since assets drawn down from financial +# institutions are excluded (exclusion a). values: 2024-07-03: - social_security - ssi + # Maine's state supplement to SSI. + - me_ssp - total_unemployment_compensation - workers_compensation - strike_benefits @@ -25,7 +29,14 @@ values: # Government pensions include military retired pay, a separate input from # the pension inputs that pension_income adds. - military_retirement_income + # Item (i) regular annuity payments: Railroad Retirement Board and + # non-Social Security survivor annuities, gross. + - railroad_benefits + - survivor_benefits - disability_benefits + # Paid family and medical leave benefits; Maine's program pays them from + # May 1, 2026. + - pfml_benefits - interest_income - ordinary_dividend_income - estate_income diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/payment/value_per_point.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/payment/value_per_point.yaml index 13c991f9c2a..7b427827482 100644 --- a/policyengine_us/parameters/gov/states/me/msha/heap/payment/value_per_point.yaml +++ b/policyengine_us/parameters/gov/states/me/msha/heap/payment/value_per_point.yaml @@ -7,8 +7,14 @@ description: Maine pays this amount per benefit point under the Low Income Home # template; the matrix's smallest award is 3 points (6 + 2 + 4 - 9 = 3; # 3 x 70% = 2.1, rounded up to 3), so the minimum does not support the value. # The value is reused with the June 29, 2026 matrix until MaineHousing -# announces one. The last published values ($55 and $44 for FFY2023) belong -# to an older energy-cost matrix and are not used. +# announces one. The draft FFY2027 plan in the April 2026 board packet +# (Section 2.6) repeats the $88 minimum and $1,012 maximum beside a reviewer +# note to obtain the figures, so it supplies no new value. At $22 the June 29, +# 2026 matrix pays from $110 (8 + 1 + 1 + 0 - 3 = 7 points for a subsidized +# apartment heated with OTHER fuel; 7 x 0.7 = 4.9, rounded up to 5) to $1,188 +# (8 + 3 + 6 + 8 + 5 = 30 points; 30 x 1.8 = 54). The last published values +# ($55 and $44 for FFY2023) belong to an older energy-cost matrix and are not +# used. values: 2024-10-01: 22 @@ -21,5 +27,7 @@ metadata: href: https://liheapch.acf.gov/docs/2025/state-plans/ME_Plan_2025.pdf#page=8 - title: Maine FY2026 LIHEAP State Plan, Section 2.6 href: https://liheapch.acf.gov/docs/2026/state-plans/ME_Plan_2026.pdf#page=10 + - title: MaineHousing April 2026 board packet, draft FFY2027 LIHEAP State Plan, Section 2.6 + href: https://www.mainehousing.org/docs/default-source/boarddocs/april-2026-board-packet.pdf#page=110 - title: 99-346 CMR ch. 24, section 4(A) (effective June 29, 2026) href: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=15 diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/points/fuel.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/points/fuel.yaml index 75a726086a0..838ca3a80d3 100644 --- a/policyengine_us/parameters/gov/states/me/msha/heap/points/fuel.yaml +++ b/policyengine_us/parameters/gov/states/me/msha/heap/points/fuel.yaml @@ -1,7 +1,8 @@ description: Maine assigns these points by primary heating fuel under the Low Income Home Energy Assistance Program. # The wood row is cordwood. Bio-fuel (6), corn (5) and wood pellets (5) in the # 2025 edition and wood pellets (4) and biobrick (3) in the June 29, 2026 -# replacement have no heating_type value and are not stored. +# replacement have no heating_type value and are not stored. Households +# heating with them report OTHER, which is eligible and scores no fuel points. electricity: 2024-07-03: 14 2026-06-29: 8 diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_eligible.yaml index 275f4c52700..0e7ee24a02e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_eligible.yaml @@ -5,9 +5,10 @@ # - Income at or below the limit, or categorical eligibility through SNAP or TANF # (3(C), pages 11-15). # - A direct or indirect energy cost for heat and a functioning heating system -# (3(A)(4) and 3(B)(1), pages 10-11), with a fuel listed in the points matrix. -# Subsidized tenants with heat in their rent qualify for the nominal benefit -# whatever the fuel (3(B)(3) and 4(B), pages 11 and 17). +# (3(A)(4) and 3(B)(1), pages 10-11). A fuel outside the points matrix (OTHER) +# qualifies with its bill; an unspecified fuel qualifies only for subsidized +# tenants with heat in their rent, who receive the nominal benefit (3(B)(3) and +# 4(B), pages 11 and 17). # Wages use Maine's 2025 minimum wage, $14.65 an hour (Maine Department of Labor): # 14.65 x 40 hours x 52 weeks = 30,472. The 2026 one-person limit is 36,836. # Heating bills: EIA's 2025-26 winter household expenditures (Short-Term Energy @@ -41,7 +42,7 @@ # 36,836 <= 36,836: eligible. 36,837 > 36,836: not eligible unless categorical. me_heap_eligible: [true, false, true] -- name: Case 2, every listed fuel qualifies while no heat, other fuels, unspecified fuel and other states do not. +- name: Case 2, every fuel with a bill qualifies, including other fuels, while no heat, unspecified fuel and other states do not. period: 2026 input: state_code: [ME, ME, ME, ME, ME, ME, ME, ME, ME, ME, ME, NH] @@ -63,12 +64,13 @@ receives_housing_assistance: [false, false, false, false, false, false, false, false, false, false, false, false] is_in_public_housing: [false, false, false, false, false, false, false, false, false, false, false, false] output: - # Income 30,472 <= 36,836 throughout. NONE, OTHER and UNSPECIFIED have no row - # in the points matrix (OTHER fails even with its pellet bill); the eight listed + # Income 30,472 <= 36,836 throughout. NONE means no heating system + # (3(B)(1)(a)); OTHER qualifies with its pellet bill and scores no fuel points; + # UNSPECIFIED fails because heating_type is a required input; the eight listed # fuels with a bill qualify; New Hampshire does not. - me_heap_eligible: [false, false, false, true, true, true, true, true, true, true, true, false] + me_heap_eligible: [false, true, false, true, true, true, true, true, true, true, true, false] -- name: Case 3, heat in rent qualifies subsidized tenants without a listed fuel but not unsubsidized ones. +- name: Case 3, heat in rent qualifies subsidized tenants with an unspecified fuel but not unsubsidized ones. period: 2026 input: state_code: [ME, ME, ME, ME, ME] @@ -85,7 +87,7 @@ is_in_public_housing: [false, true, false, false, false] output: # 1-2: subsidized with heat in rent qualify without a fuel. - # 3: an unsubsidized tenant needs a listed fuel; UNSPECIFIED has none. + # 3: an unsubsidized tenant needs a known fuel; UNSPECIFIED is not one. # 4: an unsubsidized tenant whose building burns natural gas qualifies. # 5: NONE means the home has no heating system, which 3(B)(1)(a) requires. me_heap_eligible: [true, true, false, true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap.yaml index d871f19826f..f09ecb57114 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap.yaml @@ -164,3 +164,77 @@ # 30,472 x 2 = 60,944 > 48,170, with no medical expenses or SNAP/TANF. me_heap_eligible: false me_heap: 0 + +- name: Case 5, a public housing tenant with heat in the rent receives the nominal benefit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + # 14.65 x 40 hours x 52 weeks = 30,472. + employment_income: 30_472 + ssi: 0 + spm_units: + spm_unit: + members: [head] + snap: 0 + tanf: 0 + me_heap_dwelling_type: CONDO_DUPLEX_OR_APARTMENT + # The building's heat is included in the rent and its fuel is unknown. + heating_type: UNSPECIFIED + heat_expense_included_in_rent: true + receives_housing_assistance: false + households: + household: + members: [head] + state_code: ME + county: KENNEBEC_COUNTY_ME + is_in_public_housing: true + output: + # 30,472 <= 36,836, and public housing with heat in rent needs no known fuel. + me_heap_eligible: true + # Apartment 6 + Kennebec 2 + no fuel points = 8. 30,472 / 15,650 = 194.71% + # -> 195%: 0.70. 8 x 0.7 = 5.6, rounded up to 6. + me_heap_points: 6 + # Public housing is subsidized housing, so the nominal 21 applies, not + # 6 x 22 = 132. + me_heap: 21 + +- name: Case 6, a homeowner heating with a fuel outside the matrix, wood pellets, scores no fuel points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + # 14.65 x 40 hours x 52 weeks = 30,472. + employment_income: 30_472 + ssi: 0 + spm_units: + spm_unit: + members: [head] + snap: 0 + tanf: 0 + me_heap_dwelling_type: STICK_BUILT_OR_MODULAR + heating_type: OTHER + # Maine Department of Energy Resources survey of January 5, 2026: wood + # pellets 357 for a one-ton purchase. + other_heating_fuel_expense: 357 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [head] + state_code: ME + county: KENNEBEC_COUNTY_ME + is_in_public_housing: false + output: + # 30,472 <= 36,836, and the pellet bill is a direct energy cost. + me_heap_eligible: true + # 30,472 / 15,650 = 194.71% -> 195%: above 150%, 0.70. + me_heap_income_multiplier: 0.7 + # Stick-built 9 + Kennebec 2 + OTHER 0 = 11. 11 x 0.7 = 7.7, rounded up to 8. + me_heap_points: 8 + # 8 x 22 = 176. + me_heap: 176 diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap_income.yaml index 69c8a26e61c..39236c85674 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap_income.yaml @@ -6,7 +6,8 @@ # "including Medicare deductions", child support, pensions and others. # - Excluded: "Assets drawn down from financial institutions" and "Income earned by # a Household Member who is a full time high school student, unless they are the -# Primary Applicant" (the tax unit head). TANF is not a listed source. +# Primary Applicant" (the tax unit head). No exclusion names TANF, so the +# household's TANF grant counts once. # - Each source is floored at zero, so a self-employment loss cannot offset wages. # - Over-income households deduct documented medical expenses (IRS Publication # 502), "only the amount necessary to make the Household eligible". @@ -175,7 +176,7 @@ # 23,712 <= 36,836, so the medical premiums are not deducted: 23,712. me_heap_income: 23_712 -- name: Case 6, TANF is not counted but child support is. +- name: Case 6, TANF and child support are counted. period: 2026 absolute_error_margin: 0.01 input: @@ -203,8 +204,9 @@ members: [head, child1, child2] state_code: ME output: - # Child support 2,400; TANF is excluded: 2,400. - me_heap_income: 2_400 + # Child support 2,400 + TANF 10,740 = 13,140 <= 59,504, so no medical + # adjustment. + me_heap_income: 13_140 - name: Case 7, an ineligible member's income is counted. period: 2026 @@ -311,3 +313,111 @@ # max(40,537, 36,836) = 40,537, still over the limit. me_heap_income: 40_537 me_heap_eligible: false + +- name: Case 10, every earned source not pinned elsewhere counts. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 45 + # A different amount for each source, so dropping any one changes the + # total (wages and S corporation income are pinned in Cases 1-4). + self_employment_income_before_lsr: 3_000 + sstb_self_employment_income_before_lsr: 2_000 + farm_operations_income: 1_000 + ssi: 0 + spm_units: + spm_unit: + members: [head] + snap: 0 + tanf: 0 + households: + household: + members: [head] + state_code: ME + output: + # 3,000 + 2,000 + 1,000 = 6,000 <= 36,836, so no medical adjustment. + # Dropping a source gives 3,000, 4,000 or 5,000. + me_heap_income: 6_000 + +- name: Case 11, every unearned source not pinned elsewhere counts, including Maine SSP. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 66 + # A different amount for each source, so dropping any one changes the + # total (Social Security and child support are pinned in Cases 5 and 6). + # SSI and Maine SSP are monthly: 100 and 120 a year (10 a month). + ssi: 100 + me_ssp: 120 + unemployment_compensation: 200 + workers_compensation: 300 + strike_benefits: 400 + alimony_income: 500 + veterans_benefits: 600 + financial_assistance: 700 + taxable_private_pension_income: 800 + military_retirement_pay: 900 + disability_benefits: 1_000 + taxable_interest_income: 1_100 + qualified_dividend_income: 1_200 + estate_income: 1_300 + rental_income: 1_400 + farm_rent_income: 1_500 + railroad_benefits: 1_600 + survivor_benefits: 1_700 + pfml_benefits: 1_800 + spm_units: + spm_unit: + members: [head] + snap: 0 + tanf: 0 + households: + household: + members: [head] + state_code: ME + output: + # 100 x (1 + 2 + ... + 18) = 100 x 171 = 17,100, plus SSP 12 x 10 = 120: + # 17,220. 17,220 <= 36,836, so the modeled Medicare Part B premium is not + # deducted. + me_heap_income: 17_220 + +- name: Case 12, other medical expenses bring the Case 9 household to the limit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 66 + # 1,976 x 12 = 23,712. + social_security_retirement: 23_712 + # Part-time job: 14.65 x 25 hours x 52 weeks = 19,045. + employment_income: 19_045 + # Part B premium: 185 x 12 = 2,220. + medical_expense_health_insurance_premiums: 2_220 + # A pair of hearing aids (IRS Publication 502). + other_medical_expenses: 4_000 + ssi: 0 + spm_units: + spm_unit: + members: [head] + snap: 0 + tanf: 0 + heating_type: FUEL_OIL + fuel_oil_expense: 1_745 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [head] + state_code: ME + is_in_public_housing: false + output: + # Gross 23,712 + 19,045 = 42,757 > 36,836. 42,757 - (2,220 + 4,000) = 36,537, + # and only the amount needed counts: max(36,537, 36,836) = 36,836. Without + # the hearing aids the income is 40,537, as in Case 9. + me_heap_income: 36_836 + me_heap_eligible: true diff --git a/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_categorically_eligible.py b/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_categorically_eligible.py index 9ae599a7e4c..aba49455f5c 100644 --- a/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_categorically_eligible.py +++ b/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_categorically_eligible.py @@ -15,5 +15,8 @@ class me_heap_categorically_eligible(Variable): def formula(spm_unit, period, parameters): # Rule 3(C)(2): members on a Maine DHHS TANF or SNAP notice of decision - # are income eligible. + # are income eligible. Their benefit is scored on actual income, which + # 3(C)(2) permits ("using actual vetted income"); the pre-established + # poverty percentages it also allows are in MaineHousing's HEAP Guide, + # which was posted and is no longer available. return add(spm_unit, period, ["snap", "tanf"]) > 0 diff --git a/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_eligible.py b/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_eligible.py index 0f0d2c37c09..d205da4bea5 100644 --- a/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_eligible.py +++ b/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_eligible.py @@ -26,30 +26,23 @@ def formula(spm_unit, period, parameters): ) # Rule 3(A)(4) requires a Direct Energy Cost (a heating bill the # household pays) or an Indirect Determinable Energy Cost (heat - # included in rent), and the points matrix lists the fuels. + # included in rent), and rule 3(B)(1)(a) a functioning heating system, + # so NONE is ineligible. energy_cost = spm_unit("has_heating_expense", period) | heat_in_rent # SOLAR with an electricity bill stands for a grid-tied home with - # electric heat and uses the electricity row; an off-grid solar-only - # home has no heating bill. NONE means the home has no heating. - # Subsidized tenants with heat in rent receive the nominal benefit - # without a listed fuel; OTHER and UNSPECIFIED are otherwise - # ineligible because heating_type is a required input. + # electric heat; an off-grid solar-only home has no heating bill. + # OTHER (pellets, corn, bio-fuel, biobricks) is eligible with an + # energy cost and scores no fuel points. UNSPECIFIED is ineligible + # because heating_type is a required input, except for subsidized + # tenants with heat in rent, who receive the nominal benefit without a + # known fuel. fuel = spm_unit("heating_type", period) fuels = fuel.possible_values - listed_fuel = ( - (fuel == fuels.ELECTRICITY) - | (fuel == fuels.SOLAR) - | (fuel == fuels.PROPANE) - | (fuel == fuels.KEROSENE) - | (fuel == fuels.FUEL_OIL) - | (fuel == fuels.COAL) - | (fuel == fuels.NATURAL_GAS) - | (fuel == fuels.WOOD) - ) + known_fuel = (fuel != fuels.UNSPECIFIED) | (heat_in_rent & subsidized) return ( (size > 0) & ((income <= limit) | categorical) & energy_cost & (fuel != fuels.NONE) - & (listed_fuel | (heat_in_rent & subsidized)) + & known_fuel ) diff --git a/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_income_limit.py b/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_income_limit.py index dd353165bf5..4784712ec47 100644 --- a/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_income_limit.py +++ b/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_income_limit.py @@ -13,8 +13,8 @@ class me_heap_income_limit(Variable): reference = ( "https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=11", "https://liheapch.acf.gov/docs/2026/state-plans/ME_Plan_2026.pdf#page=9", - "https://www.mainehousing.org/charts/", "https://web.archive.org/web/20251101091454id_/https://www.mainehousing.org/programs-services/energy/energydetails/liheap", + "https://web.archive.org/web/20261015145903id_/https://www.mainehousing.org/programs-services/energy/energydetails/liheap", "https://www.law.cornell.edu/uscode/text/42/8624#b_2_B", ) diff --git a/policyengine_us/variables/gov/states/me/msha/heap/me_heap.py b/policyengine_us/variables/gov/states/me/msha/heap/me_heap.py index 66d50a9c93b..4bfa976111b 100644 --- a/policyengine_us/variables/gov/states/me/msha/heap/me_heap.py +++ b/policyengine_us/variables/gov/states/me/msha/heap/me_heap.py @@ -27,8 +27,11 @@ class me_heap(Variable): "per point is an estimate because MaineHousing has not announced one. " "The TANF Fuel Supplemental Benefit, ECIP crisis assistance, " "weatherization and CHIP are excluded. Pellet, corn, bio-fuel and " - "biobrick rows have no heating_type value, and WOOD uses the cordwood " - "row. Eligibility requires a heating bill or heat included in rent. " + "biobrick rows have no heating_type value: OTHER (pellets, corn, " + "bio-fuel, biobricks) is eligible and scores no fuel points, and WOOD " + "uses the cordwood row. Eligibility requires a heating bill or heat " + "included in rent; UNSPECIFIED fuel is ineligible unless a subsidized " + "tenant has heat in rent. " "SOLAR uses the electricity row, as for a grid-tied home with electric " "heat (heating_expense's assumption); an off-grid solar-only home has " "no electricity bill, so no energy cost for heat. Roomers, " diff --git a/policyengine_us/variables/gov/states/me/msha/heap/me_heap_income.py b/policyengine_us/variables/gov/states/me/msha/heap/me_heap_income.py index 368260eef79..449738cd98a 100644 --- a/policyengine_us/variables/gov/states/me/msha/heap/me_heap_income.py +++ b/policyengine_us/variables/gov/states/me/msha/heap/me_heap_income.py @@ -35,7 +35,11 @@ def formula(spm_unit, period, parameters): ) counted = where(excluded_student, 0, earned) + unearned adult = person("age", period) >= p.minimum_age - gross = spm_unit.sum(counted * adult) + # Rule 3(C)(1) counts income from all sources unless excluded, and no + # exclusion names TANF. The annual tanf aggregate includes me_tanf and + # applies take-up; me_tanf alone is the entitlement a nonrecipient + # could get. The household grant is counted once, not once per member. + gross = spm_unit.sum(counted * adult) + spm_unit("tanf", period) # A household over the income limit deducts medical expenses, but only # the amount needed to reach the limit. limit = spm_unit("me_heap_income_limit", period) diff --git a/policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_points.py b/policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_points.py index e7db3ff2fe4..1fd1f0622e5 100644 --- a/policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_points.py +++ b/policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_points.py @@ -30,7 +30,9 @@ def formula(spm_unit, period, parameters): listed_county = where(in_table, county, next(iter(p.county))) county_points = where(in_table, p.county[listed_county], 0) # WOOD is the cordwood row. SOLAR stands for a grid-tied home with - # electric heat. OTHER, NONE and UNSPECIFIED have no row. + # electric heat. OTHER (pellets, corn, bio-fuel, biobricks) is + # eligible and scores no fuel points, since those rows have no + # heating_type value; NONE and UNSPECIFIED have no row. fuel = spm_unit("heating_type", period) fuels = fuel.possible_values fuel_points = select( diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 843ddcbe21c..0a57cd635c3 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -3150,6 +3150,66 @@ candidates: state: unreviewed note: Identify the instructions and years, then determine whether the gloss fills a silence or impermissibly narrows eligibility. +- id: us-me:liheap-size-ten-income-limit + program: liheap + years: + from: 2026 + to: 2026 + type: federal-fiscal + question: Is Maine's FFY2026 HEAP income limit for ten people the greater of 150% of the poverty guideline and 60% + of state median income ($102,008) or 150% of the poverty guideline ($97,725)? + sources: + - kind: regulation + citation: 99-346 CMR ch. 24, section 4(A), poverty-level table (2025 edition, effective July 1, 2025; archived + July 22, 2025) + url: https://web.archive.org/web/20250722045413/https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=15 + - kind: plan + citation: Maine FY2026 LIHEAP State Plan, Section 2.1 + url: https://liheapch.acf.gov/docs/2026/state-plans/ME_Plan_2026.pdf#page=9 + readings: + statute: The 70% band runs above 150% of the poverty guideline up to the greater of 150% of the guideline and + 60% of state median income, $102,008 for ten people; MaineHousing's 2025-26 income chart prints the same figure. + other: Section 2.1 sets the ten-person threshold at 150% of the HHS poverty guidelines, $97,725. + example: A ten-person household with $100,000 of income is income eligible under the regulation and over the limit + under the plan. + pe_us: + follows: statute + code: [] + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10437 + state: ruled + note: 'PE-US follows the regulation and the chart under the 2026-10-19 statute-governs rule. Code not yet on main: + me_heap_income_limit.' +- id: us-me:liheap-tanf-and-disability-income + program: liheap + years: + from: 2026 + to: 2027 + type: federal-fiscal + question: Does Maine HEAP count TANF and long-term disability payments as income from all sources under the rule, + although the FY2026 plan leaves TANF and insurance payments made directly to the insured unchecked? + sources: + - kind: regulation + citation: 99-346 CMR ch. 24, sections 1(EE) and 3(C)(1) (2025 edition and June 29, 2026 replacement), PDF pages + 3 and 12-13 + url: https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=12 + - kind: plan + citation: Maine FY2026 LIHEAP State Plan, Section 1.9 + url: https://liheapch.acf.gov/docs/2026/state-plans/ME_Plan_2026.pdf#page=6 + readings: + statute: Household Income is the total income from all sources before taxes and deductions unless excluded; no + exclusion names TANF or recurring insurance payments (only one-time insurance payments, exclusion h), and item + (n) of the June 29, 2026 replacement lists long-term disability payments. + other: Section 1.9 leaves TANF benefits and insurance payments made directly to the insured unchecked. + example: In FFY2026 a parent of two young children with $2,400 of child support and $10,740 of TANF, heating a Penobscot + County stick-built home with oil, counts $13,140 and receives $704; excluding TANF counts $2,400 and pays $748. + pe_us: + follows: statute + code: [] + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10437 + state: ruled + note: 'PE-US counts both under the 2026-10-19 statute-governs rule. Code not yet on main: me_heap_income, gov.states.me.msha.heap.income.sources.unearned.' - id: us-me:property-tax-credit-unknown-utilities program: income-tax years: From 64e3aeb5944083a7d390989db90842ecd12224c7 Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 29 Oct 2026 21:59:14 -0400 Subject: [PATCH 3/3] Address review round 2 for Maine LIHEAP Apply DTrim99's round 2 review: OTHER fuel scores the lowest row of the fuels it stands for, tests for it, and candidate code paths. Co-Authored-By: Claude Opus 5.5 --- .../me/msha/heap/payment/value_per_point.yaml | 7 +-- .../gov/states/me/msha/heap/points/fuel.yaml | 12 +++-- .../heap/eligibility/me_heap_eligible.yaml | 14 ++--- .../gov/states/me/msha/heap/me_heap.yaml | 54 +++++++++++++++++-- .../me/msha/heap/payment/me_heap_points.yaml | 46 ++++++++-------- .../msha/heap/eligibility/me_heap_eligible.py | 7 ++- .../gov/states/me/msha/heap/me_heap.py | 5 +- .../me/msha/heap/payment/me_heap_points.py | 7 +-- source_conflicts/candidates.yaml | 12 +++-- 9 files changed, 110 insertions(+), 54 deletions(-) diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/payment/value_per_point.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/payment/value_per_point.yaml index 7b427827482..89922a1452e 100644 --- a/policyengine_us/parameters/gov/states/me/msha/heap/payment/value_per_point.yaml +++ b/policyengine_us/parameters/gov/states/me/msha/heap/payment/value_per_point.yaml @@ -10,9 +10,10 @@ description: Maine pays this amount per benefit point under the Low Income Home # announces one. The draft FFY2027 plan in the April 2026 board packet # (Section 2.6) repeats the $88 minimum and $1,012 maximum beside a reviewer # note to obtain the figures, so it supplies no new value. At $22 the June 29, -# 2026 matrix pays from $110 (8 + 1 + 1 + 0 - 3 = 7 points for a subsidized -# apartment heated with OTHER fuel; 7 x 0.7 = 4.9, rounded up to 5) to $1,188 -# (8 + 3 + 6 + 8 + 5 = 30 points; 30 x 1.8 = 54). The last published values +# 2026 matrix pays from $154 (8 + 1 + 1 + 3 - 3 = 10 points for a subsidized +# apartment in Cumberland or Kennebec heated with natural gas, wood, biobricks +# or OTHER fuel; 10 x 0.7 = 7) to $1,188 (8 + 3 + 6 + 8 + 5 = 30 points; +# 30 x 1.8 = 54). The last published values # ($55 and $44 for FFY2023) belong to an older energy-cost matrix and are not # used. values: diff --git a/policyengine_us/parameters/gov/states/me/msha/heap/points/fuel.yaml b/policyengine_us/parameters/gov/states/me/msha/heap/points/fuel.yaml index 838ca3a80d3..a8f1a9f819e 100644 --- a/policyengine_us/parameters/gov/states/me/msha/heap/points/fuel.yaml +++ b/policyengine_us/parameters/gov/states/me/msha/heap/points/fuel.yaml @@ -1,8 +1,9 @@ description: Maine assigns these points by primary heating fuel under the Low Income Home Energy Assistance Program. -# The wood row is cordwood. Bio-fuel (6), corn (5) and wood pellets (5) in the -# 2025 edition and wood pellets (4) and biobrick (3) in the June 29, 2026 -# replacement have no heating_type value and are not stored. Households -# heating with them report OTHER, which is eligible and scores no fuel points. +# The wood row is cordwood. Bio-fuel including bio-bricks (6), wood pellets (5) +# and corn (5) in the 2025 edition and wood pellet (4) and biobrick (3) in the +# June 29, 2026 replacement have no heating_type value. Households heating +# with them report OTHER, which takes the lowest of those rows; the +# replacement has no corn row. electricity: 2024-07-03: 14 2026-06-29: 8 @@ -24,6 +25,9 @@ natural_gas: wood: 2024-07-03: 4 2026-06-29: 3 +other: + 2024-07-03: 5 + 2026-06-29: 3 metadata: unit: /1 diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_eligible.yaml index 0e7ee24a02e..67d763139aa 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/eligibility/me_heap_eligible.yaml @@ -5,10 +5,10 @@ # - Income at or below the limit, or categorical eligibility through SNAP or TANF # (3(C), pages 11-15). # - A direct or indirect energy cost for heat and a functioning heating system -# (3(A)(4) and 3(B)(1), pages 10-11). A fuel outside the points matrix (OTHER) -# qualifies with its bill; an unspecified fuel qualifies only for subsidized -# tenants with heat in their rent, who receive the nominal benefit (3(B)(3) and -# 4(B), pages 11 and 17). +# (3(A)(4) and 3(B)(1), pages 10-11). A fuel without a heating_type value +# (OTHER) qualifies with its bill; an unspecified fuel qualifies only for +# subsidized tenants with heat in their rent, who receive the nominal benefit +# (3(B)(3) and 4(B), pages 11 and 17). # Wages use Maine's 2025 minimum wage, $14.65 an hour (Maine Department of Labor): # 14.65 x 40 hours x 52 weeks = 30,472. The 2026 one-person limit is 36,836. # Heating bills: EIA's 2025-26 winter household expenditures (Short-Term Energy @@ -65,9 +65,9 @@ is_in_public_housing: [false, false, false, false, false, false, false, false, false, false, false, false] output: # Income 30,472 <= 36,836 throughout. NONE means no heating system - # (3(B)(1)(a)); OTHER qualifies with its pellet bill and scores no fuel points; - # UNSPECIFIED fails because heating_type is a required input; the eight listed - # fuels with a bill qualify; New Hampshire does not. + # (3(B)(1)(a)); OTHER qualifies with its pellet bill; UNSPECIFIED fails + # because heating_type is a required input; the eight listed fuels with a + # bill qualify; New Hampshire does not. me_heap_eligible: [false, true, false, true, true, true, true, true, true, true, true, false] - name: Case 3, heat in rent qualifies subsidized tenants with an unspecified fuel but not unsubsidized ones. diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap.yaml index f09ecb57114..352a9108b7c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/me_heap.yaml @@ -201,7 +201,7 @@ # 6 x 22 = 132. me_heap: 21 -- name: Case 6, a homeowner heating with a fuel outside the matrix, wood pellets, scores no fuel points. +- name: Case 6, a homeowner heating with wood pellets reported as OTHER scores the lowest pellet and corn row. period: 2026 absolute_error_margin: 0.01 input: @@ -234,7 +234,51 @@ me_heap_eligible: true # 30,472 / 15,650 = 194.71% -> 195%: above 150%, 0.70. me_heap_income_multiplier: 0.7 - # Stick-built 9 + Kennebec 2 + OTHER 0 = 11. 11 x 0.7 = 7.7, rounded up to 8. - me_heap_points: 8 - # 8 x 22 = 176. - me_heap: 176 + # Stick-built 9 + Kennebec 2 + OTHER 5 (the 2025 edition's wood pellet and + # corn rows, the lowest of the fuels OTHER stands for) = 16. + # 16 x 0.7 = 11.2, rounded up to 12. + me_heap_points: 12 + # 12 x 22 = 264. + me_heap: 264 + +- name: Case 7, a voucher tenant paying a wood pellet bill for an apartment keeps a positive benefit after the subsidized deduction. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + # 14.65 x 15 hours x 52 weeks = 11,427. + employment_income: 11_427 + ssi: 0 + spm_units: + spm_unit: + members: [head] + snap: 0 + tanf: 0 + me_heap_dwelling_type: CONDO_DUPLEX_OR_APARTMENT + heating_type: OTHER + # Maine Department of Energy Resources survey of January 5, 2026: wood + # pellets 357 for a one-ton purchase. + other_heating_fuel_expense: 357 + heat_expense_included_in_rent: false + # Housing Choice Voucher. + receives_housing_assistance: true + households: + household: + members: [head] + state_code: ME + county: KENNEBEC_COUNTY_ME + is_in_public_housing: false + output: + # 11,427 <= 36,836, and the pellet bill is a direct energy cost. + me_heap_eligible: true + # 11,427 / 15,650 = 73.02% -> 73%: 51%-75% band, 1.10. + me_heap_income_multiplier: 1.1 + # Apartment 6 + Kennebec 2 + OTHER 5 + subsidized household responsible for + # heating -9 = 4. 4 x 1.1 = 4.4, rounded up to 5. With no fuel points for + # OTHER, 6 + 2 + 0 - 9 = -1 would give -1 point and -22. + me_heap_points: 5 + # Heat is outside the rent, so the points apply, not the nominal 21: + # 5 x 22 = 110. + me_heap: 110 diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/payment/me_heap_points.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/payment/me_heap_points.yaml index b1d6bfe065c..f0685630ef2 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/payment/me_heap_points.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/me/msha/heap/payment/me_heap_points.yaml @@ -4,18 +4,20 @@ # - 2025 edition (period 2026), benefit matrix pages 1-2: no base points; # stick-built/modular 9, mobile/manufactured 8, condo/duplex and apartment 6; # Aroostook 7, Penobscot 5, Cumberland 3, Kennebec 2; electricity 14, LP gas 10, -# kerosene 8, oil 7, coal 5, natural gas 5, wood 4; subsidized household -# responsible for heating -9; priority "60+, Disabled or child 6 and under" 5 -# "or" child 7-17 3: +# kerosene 8, oil 7, bio-fuel including bio-bricks 6, coal 5, wood pellets 5, +# corn 5, natural gas 5, wood 4; subsidized household responsible for heating -9; +# priority "60+, Disabled or child 6 and under" 5 "or" child 7-17 3: # https://liheapch.acf.gov/docs/2026/benefits-matricies/ME_BenefitMatrix_2026.pdf#page=1 # - 2026 edition (period 2027), rule pages 16-17: base 8; stick-built/modular 3, # mobile 2, condo/duplex and apartment 1; Aroostook 6, Penobscot 3, Cumberland 1, -# Kennebec 1; electric 8, LP gas 5, kerosene 5, oil 4, coal 4, natural gas 3, wood -# 3; at risk (60+, child 6 and under, disabled) 5, "Points for the first in the -# house"; heating subsidized -3: +# Kennebec 1; electric 8, LP gas 5, kerosene 5, oil 4, coal 4, wood pellet 4, +# natural gas 3, wood 3, biobrick 3, and no corn row; at risk (60+, child 6 and +# under, disabled) 5, "Points for the first in the house"; heating subsidized -3: # https://www.mainehousing.org/docs/default-source/msha-rules/ch24-home-energy-assistance-program-rule.pdf#page=16 # Modeling assumptions: SOLAR uses the electricity row (a grid-tied home with electric -# heat) and WOOD the cordwood row. Unless stated, +# heat) and WOOD the cordwood row. Pellets, corn, bio-fuel and biobricks have no +# heating_type value, so OTHER stands for them and takes the lowest of their rows: +# 5 in 2026 (wood pellets and corn) and 3 in 2027 (biobrick). Unless stated, # households are unsubsidized, heat is not in the rent, and the income multiplier # is set to 1 so the points equal the matrix total. Cases 10 and 11 also assert the # benefit, so they pay an oil bill: EIA's U.S. average winter heating-oil @@ -53,28 +55,30 @@ - name: Case 3, fuel points in 2026. period: 2026 input: - state_code: [ME, ME, ME, ME, ME, ME, ME, ME] - age: [40, 40, 40, 40, 40, 40, 40, 40] - county: [KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME] - heating_type: [ELECTRICITY, SOLAR, PROPANE, KEROSENE, FUEL_OIL, COAL, NATURAL_GAS, WOOD] - me_heap_income_multiplier: [1, 1, 1, 1, 1, 1, 1, 1] + state_code: [ME, ME, ME, ME, ME, ME, ME, ME, ME] + age: [40, 40, 40, 40, 40, 40, 40, 40, 40] + county: [KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME] + heating_type: [ELECTRICITY, SOLAR, PROPANE, KEROSENE, FUEL_OIL, COAL, NATURAL_GAS, WOOD, OTHER] + me_heap_income_multiplier: [1, 1, 1, 1, 1, 1, 1, 1, 1] output: # Stick-built 9 + Kennebec 2 = 11, plus electricity 14, solar 14 (electricity - # row), LP gas 10, kerosene 8, oil 7, coal 5, natural gas 5, wood 4. - me_heap_points: [25, 25, 21, 19, 18, 16, 16, 15] + # row), LP gas 10, kerosene 8, oil 7, coal 5, natural gas 5, wood 4, OTHER 5 + # (wood pellets and corn, the lowest of its rows): 11 + 5 = 16. + me_heap_points: [25, 25, 21, 19, 18, 16, 16, 15, 16] - name: Case 4, fuel points in 2027. period: 2027 input: - state_code: [ME, ME, ME, ME, ME, ME, ME, ME] - age: [40, 40, 40, 40, 40, 40, 40, 40] - county: [KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME] - heating_type: [ELECTRICITY, SOLAR, PROPANE, KEROSENE, FUEL_OIL, COAL, NATURAL_GAS, WOOD] - me_heap_income_multiplier: [1, 1, 1, 1, 1, 1, 1, 1] + state_code: [ME, ME, ME, ME, ME, ME, ME, ME, ME] + age: [40, 40, 40, 40, 40, 40, 40, 40, 40] + county: [KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME, KENNEBEC_COUNTY_ME] + heating_type: [ELECTRICITY, SOLAR, PROPANE, KEROSENE, FUEL_OIL, COAL, NATURAL_GAS, WOOD, OTHER] + me_heap_income_multiplier: [1, 1, 1, 1, 1, 1, 1, 1, 1] output: # Base 8 + stick-built 3 + Kennebec 1 = 12, plus electric 8, solar 8, LP gas 5, - # kerosene 5, oil 4, coal 4, natural gas 3, wood 3. - me_heap_points: [20, 20, 17, 17, 16, 16, 15, 15] + # kerosene 5, oil 4, coal 4, natural gas 3, wood 3, OTHER 3 (biobrick, the + # lowest of its rows): 8 + 3 + 1 + 3 = 15. + me_heap_points: [20, 20, 17, 17, 16, 16, 15, 15, 15] - name: Case 5, threshold edge at age 60, and a disabled adult, for priority points in 2026. period: 2026 diff --git a/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_eligible.py b/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_eligible.py index d205da4bea5..43c37f6e19f 100644 --- a/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_eligible.py +++ b/policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_eligible.py @@ -32,10 +32,9 @@ def formula(spm_unit, period, parameters): # SOLAR with an electricity bill stands for a grid-tied home with # electric heat; an off-grid solar-only home has no heating bill. # OTHER (pellets, corn, bio-fuel, biobricks) is eligible with an - # energy cost and scores no fuel points. UNSPECIFIED is ineligible - # because heating_type is a required input, except for subsidized - # tenants with heat in rent, who receive the nominal benefit without a - # known fuel. + # energy cost. UNSPECIFIED is ineligible because heating_type is a + # required input, except for subsidized tenants with heat in rent, who + # receive the nominal benefit without a known fuel. fuel = spm_unit("heating_type", period) fuels = fuel.possible_values known_fuel = (fuel != fuels.UNSPECIFIED) | (heat_in_rent & subsidized) diff --git a/policyengine_us/variables/gov/states/me/msha/heap/me_heap.py b/policyengine_us/variables/gov/states/me/msha/heap/me_heap.py index 4bfa976111b..66889c927f0 100644 --- a/policyengine_us/variables/gov/states/me/msha/heap/me_heap.py +++ b/policyengine_us/variables/gov/states/me/msha/heap/me_heap.py @@ -28,8 +28,9 @@ class me_heap(Variable): "The TANF Fuel Supplemental Benefit, ECIP crisis assistance, " "weatherization and CHIP are excluded. Pellet, corn, bio-fuel and " "biobrick rows have no heating_type value: OTHER (pellets, corn, " - "bio-fuel, biobricks) is eligible and scores no fuel points, and WOOD " - "uses the cordwood row. Eligibility requires a heating bill or heat " + "bio-fuel, biobricks) is eligible and takes the lowest of those rows, " + "5 points in the 2025 edition and 3 from June 29, 2026, and WOOD uses " + "the cordwood row. Eligibility requires a heating bill or heat " "included in rent; UNSPECIFIED fuel is ineligible unless a subsidized " "tenant has heat in rent. " "SOLAR uses the electricity row, as for a grid-tied home with electric " diff --git a/policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_points.py b/policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_points.py index 1fd1f0622e5..2aca10e9701 100644 --- a/policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_points.py +++ b/policyengine_us/variables/gov/states/me/msha/heap/payment/me_heap_points.py @@ -30,9 +30,8 @@ def formula(spm_unit, period, parameters): listed_county = where(in_table, county, next(iter(p.county))) county_points = where(in_table, p.county[listed_county], 0) # WOOD is the cordwood row. SOLAR stands for a grid-tied home with - # electric heat. OTHER (pellets, corn, bio-fuel, biobricks) is - # eligible and scores no fuel points, since those rows have no - # heating_type value; NONE and UNSPECIFIED have no row. + # electric heat. OTHER (pellets, corn, bio-fuel, biobricks) takes the + # lowest of those fuels' rows; NONE and UNSPECIFIED have no row. fuel = spm_unit("heating_type", period) fuels = fuel.possible_values fuel_points = select( @@ -44,6 +43,7 @@ def formula(spm_unit, period, parameters): fuel == fuels.COAL, fuel == fuels.NATURAL_GAS, fuel == fuels.WOOD, + fuel == fuels.OTHER, ], [ p.fuel.electricity, @@ -53,6 +53,7 @@ def formula(spm_unit, period, parameters): p.fuel.coal, p.fuel.natural_gas, p.fuel.wood, + p.fuel.other, ], default=0, ) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 4b5f42bb92d..2cabf21f02f 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -3527,12 +3527,12 @@ candidates: under the plan. pe_us: follows: statute - code: [] + code: + - policyengine_us/variables/gov/states/me/msha/heap/eligibility/me_heap_income_limit.py prs: - https://github.com/PolicyEngine/policyengine-us/pull/10437 state: ruled - note: 'PE-US follows the regulation and the chart under the 2026-10-19 statute-governs rule. Code not yet on main: - me_heap_income_limit.' + note: PE-US follows the regulation and the chart under the 2026-10-19 statute-governs rule. - id: us-me:liheap-tanf-and-disability-income program: liheap years: @@ -3558,11 +3558,13 @@ candidates: County stick-built home with oil, counts $13,140 and receives $704; excluding TANF counts $2,400 and pays $748. pe_us: follows: statute - code: [] + code: + - policyengine_us/parameters/gov/states/me/msha/heap/income/sources/unearned.yaml + - policyengine_us/variables/gov/states/me/msha/heap/me_heap_income.py prs: - https://github.com/PolicyEngine/policyengine-us/pull/10437 state: ruled - note: 'PE-US counts both under the 2026-10-19 statute-governs rule. Code not yet on main: me_heap_income, gov.states.me.msha.heap.income.sources.unearned.' + note: PE-US counts both under the 2026-10-19 statute-governs rule. - id: us-me:property-tax-credit-unknown-utilities program: income-tax years: