From 8b382eaaafdf7a76116d48cbeb826d5eff7b8251 Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 23 Oct 2026 18:11:08 -0400 Subject: [PATCH 1/2] Add New Mexico LIHEAP regular heating assistance Co-Authored-By: Claude Opus 5.5 --- changelog.d/nm-liheap-wave1d.added.md | 1 + .../nm/hca/liheap/eligibility/fpg_rate.yaml | 27 + .../hca/liheap/eligibility/fpg_year_lag.yaml | 21 + .../hca/liheap/income/earned_income_age.yaml | 17 + .../nm/hca/liheap/income/sources/earned.yaml | 26 + .../hca/liheap/income/sources/unearned.yaml | 52 ++ .../nm/hca/liheap/payment/elderly_age.yaml | 18 + .../liheap/payment/energy_burden_points.yaml | 42 ++ .../payment/energy_standard_allowance.yaml | 24 + .../liheap/payment/income_points/high.yaml | 15 + .../hca/liheap/payment/income_points/low.yaml | 15 + .../income_points/low_income_fpg_rate.yaml | 24 + .../nm/hca/liheap/payment/point_value.yaml | 25 + .../nm/hca/liheap/payment/propane_points.yaml | 17 + .../hca/liheap/payment/vulnerable_points.yaml | 18 + .../liheap/payment/young_child_max_age.yaml | 18 + policyengine_us/programs.yaml | 10 +- .../eligibility/nm_liheap_eligible.yaml | 251 +++++++++ .../eligibility/nm_liheap_income_limit.yaml | 161 ++++++ .../income/nm_liheap_countable_income.yaml | 189 +++++++ .../gov/states/nm/hca/liheap/nm_liheap.yaml | 528 ++++++++++++++++++ .../liheap/eligibility/nm_liheap_eligible.py | 53 ++ .../eligibility/nm_liheap_income_limit.py | 38 ++ .../income/nm_liheap_countable_income.py | 49 ++ .../gov/states/nm/hca/liheap/nm_liheap.py | 114 ++++ .../nm/hca/liheap/nm_liheap_household_size.py | 23 + source_conflicts/candidates.yaml | 31 + 27 files changed, 1806 insertions(+), 1 deletion(-) create mode 100644 changelog.d/nm-liheap-wave1d.added.md create mode 100644 policyengine_us/parameters/gov/states/nm/hca/liheap/eligibility/fpg_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/nm/hca/liheap/eligibility/fpg_year_lag.yaml create mode 100644 policyengine_us/parameters/gov/states/nm/hca/liheap/income/earned_income_age.yaml create mode 100644 policyengine_us/parameters/gov/states/nm/hca/liheap/income/sources/earned.yaml create mode 100644 policyengine_us/parameters/gov/states/nm/hca/liheap/income/sources/unearned.yaml create mode 100644 policyengine_us/parameters/gov/states/nm/hca/liheap/payment/elderly_age.yaml create mode 100644 policyengine_us/parameters/gov/states/nm/hca/liheap/payment/energy_burden_points.yaml create mode 100644 policyengine_us/parameters/gov/states/nm/hca/liheap/payment/energy_standard_allowance.yaml create mode 100644 policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/high.yaml create mode 100644 policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/low.yaml create mode 100644 policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/low_income_fpg_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/nm/hca/liheap/payment/point_value.yaml create mode 100644 policyengine_us/parameters/gov/states/nm/hca/liheap/payment/propane_points.yaml create mode 100644 policyengine_us/parameters/gov/states/nm/hca/liheap/payment/vulnerable_points.yaml create mode 100644 policyengine_us/parameters/gov/states/nm/hca/liheap/payment/young_child_max_age.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/eligibility/nm_liheap_income_limit.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/income/nm_liheap_countable_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/nm_liheap.yaml create mode 100644 policyengine_us/variables/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.py create mode 100644 policyengine_us/variables/gov/states/nm/hca/liheap/eligibility/nm_liheap_income_limit.py create mode 100644 policyengine_us/variables/gov/states/nm/hca/liheap/income/nm_liheap_countable_income.py create mode 100644 policyengine_us/variables/gov/states/nm/hca/liheap/nm_liheap.py create mode 100644 policyengine_us/variables/gov/states/nm/hca/liheap/nm_liheap_household_size.py diff --git a/changelog.d/nm-liheap-wave1d.added.md b/changelog.d/nm-liheap-wave1d.added.md new file mode 100644 index 00000000000..8039d88691e --- /dev/null +++ b/changelog.d/nm-liheap-wave1d.added.md @@ -0,0 +1 @@ +Add New Mexico LIHEAP regular heating assistance. diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/eligibility/fpg_rate.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/eligibility/fpg_rate.yaml new file mode 100644 index 00000000000..89486567f9a --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/eligibility/fpg_rate.yaml @@ -0,0 +1,27 @@ +description: New Mexico limits household income to this multiple of the federal poverty guideline under the Low Income Home Energy Assistance Program. +# The FFY2026 guide prints monthly limits of $1,956 to $6,769 for household +# sizes 1-8 (150% of the 2025 guideline over 12 months, rounded to the nearest +# dollar), and the FFY2027 guide $1,995 to $6,965 (2026 guideline). The 110% +# floor of 42 U.S.C. 8624(b)(2)(B) lies below this limit for every size. +values: + 2025-10-01: 1.5 + +metadata: + unit: /1 + period: year + label: New Mexico LIHEAP poverty guideline income limit rate + reference: + - title: 8.150.100.9 D NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0100.html + - title: 8.150.520.14 and 8.150.520.15 NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0520.html + - title: New Mexico FY2026 LIHEAP State Plan, Section 2.1 + href: https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=9 + - title: New Mexico FY2027 LIHEAP State Plan, Section 2.1 + href: https://www.hca.nm.gov/wp-content/uploads/DETAILED-MODEL-PLAN-LIHEAP_10_01_2026-4.pdf#page=8 + - title: New Mexico LIHEAP FFY2026 Heating and Cooling Point and Income Guide, income eligibility guidelines + href: https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf + - title: New Mexico LIHEAP FFY2027 Point and Income Guidelines, income guidelines + href: https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf + - title: New Mexico ISD 017 Income Eligibility Guidelines, October 1, 2025 to September 30, 2026, LIHEAP 111%-150% FPG + href: https://www.hca.nm.gov/wp-content/uploads/ISD-017-FPG-Cards-FY-2026-10.01.25-to-09.30.2026-final-1.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/eligibility/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/eligibility/fpg_year_lag.yaml new file mode 100644 index 00000000000..a37fb941e8a --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/eligibility/fpg_year_lag.yaml @@ -0,0 +1,21 @@ +description: New Mexico uses the federal poverty guidelines from this many years before the model year for the income limit and income points under the Low Income Home Energy Assistance Program. +# HHS made the 2025 guidelines mandatory for LIHEAP from October 1, 2025, so +# model period 2026 (FFY2026) uses the 2025 guidelines and 2027 (FFY2027) the +# 2026 guidelines. Both income tables of the FFY2026 and FFY2027 guides +# reproduce from these guideline years. +values: + 2025-10-01: 1 + +metadata: + unit: year + period: year + label: New Mexico LIHEAP poverty guideline year lag + reference: + - 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: 8.150.520.15 NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0520.html + - title: New Mexico LIHEAP FFY2026 Heating and Cooling Point and Income Guide, income eligibility guidelines + href: https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf + - title: New Mexico LIHEAP FFY2027 Point and Income Guidelines, income guidelines + href: https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/income/earned_income_age.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/income/earned_income_age.yaml new file mode 100644 index 00000000000..acbc449e6d5 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/income/earned_income_age.yaml @@ -0,0 +1,17 @@ +description: New Mexico counts the earned income of household members older than this age under the Low Income Home Energy Assistance Program. +# 8.150.520.12 B NMAC counts "the gross earned income of all household members +# over the age of 18", so the earnings of a member aged 18 or younger are not +# counted. The FY2026 plan leaves "Earned income of a child under the age of +# 18" unchecked (Section 1.9). +values: + 2025-10-01: 18 + +metadata: + unit: year + period: year + label: New Mexico LIHEAP earned income age + reference: + - title: 8.150.520.12 B NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0520.html + - title: New Mexico FY2026 LIHEAP State Plan, Section 1.9 + href: https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/income/sources/earned.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/income/sources/earned.yaml new file mode 100644 index 00000000000..f4290fb665e --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/income/sources/earned.yaml @@ -0,0 +1,26 @@ +description: New Mexico counts these sources as earned income of household members over the earned income age under the Low Income Home Energy Assistance Program. +# Wages, pay for irregular work and self-employment income, with the net +# income of a business counted as gross income. The existing net business +# inputs get no further deduction, and each source is floored at zero, so a +# loss in one source cannot offset another. Rental income counts as earned +# income for a landlord working 20 or more hours a week; no input records +# landlord hours, so rental income is listed with unearned income. +values: + 2025-10-01: + - employment_income + - self_employment_income + - sstb_self_employment_income + - farm_operations_income + - partnership_s_corp_income + +metadata: + unit: list + period: year + label: New Mexico LIHEAP earned income sources + reference: + - title: 8.150.520.8 A, 8.150.520.9 C and 8.150.520.12 NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0520.html + - title: "New Mexico FY2026 LIHEAP State Plan, Sections 1.8-1.9 #page 5-6" + href: https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=5 + - title: "New Mexico FY2027 LIHEAP State Plan, Sections 1.8-1.9 #page 5-6" + href: https://www.hca.nm.gov/wp-content/uploads/DETAILED-MODEL-PLAN-LIHEAP_10_01_2026-4.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/income/sources/unearned.yaml new file mode 100644 index 00000000000..500f015d1ff --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/income/sources/unearned.yaml @@ -0,0 +1,52 @@ +description: New Mexico counts these sources as unearned income of all household members under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: + # Gross OASDI benefits, including any Medicare premium deducted. + - social_security + - ssi + # Retirement, disability and survivor payments. + - pension_income + # IRA, 401(k) and 403(b) withdrawals; the annual input cannot separate + # nonrecurring lump-sum withdrawals, so all are counted as recurring. + - retirement_distributions + - military_retirement_income + - railroad_benefits + - survivor_benefits + - veterans_benefits + - disability_benefits + - workers_compensation + - total_unemployment_compensation + - general_assistance + - child_support_received + - alimony_income + - interest_income + - ordinary_dividend_income + # Net rental income, including the rent of a landlord working 20 or more + # hours a week, which 8.150.520.8 A NMAC counts as earned income. + - rental_income + - farm_rent_income + # Periodic receipts from estates or trusts. + - estate_income + # Contributions from family or friends. + - financial_assistance +# TANF (New Mexico Works) is counted at the SPM unit level in +# nm_liheap_countable_income. Lump sums, loans, earned income tax credits, +# SNAP, in-kind benefits and vendor payments are excluded, and strike pay is +# not counted. Royalties, tribal benefits, real-estate contract payments, +# legal settlements, foster-care funds and AmeriCorps payments have no +# inputs. FY2026 plan Section 1.9 checks one-time lump-sum payments and Social +# Security "Excluding MediCare deduction" and leaves cash gifts unchecked; +# 8.150.520.13 NMAC counts gross OASDI including the Medicare premium and +# contributions from family or friends and excludes lump sums, and the rule +# is followed for these items. +metadata: + unit: list + period: year + label: New Mexico LIHEAP unearned income sources + reference: + - title: 8.150.520.8 B, 8.150.520.12 and 8.150.520.13 NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0520.html + - title: "New Mexico FY2026 LIHEAP State Plan, Sections 1.8-1.9 #page 5-6" + href: https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=5 + - title: "New Mexico FY2027 LIHEAP State Plan, Sections 1.8-1.9 #page 5-6" + href: https://www.hca.nm.gov/wp-content/uploads/DETAILED-MODEL-PLAN-LIHEAP_10_01_2026-4.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/elderly_age.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/elderly_age.yaml new file mode 100644 index 00000000000..3949f406a0d --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/elderly_age.yaml @@ -0,0 +1,18 @@ +description: New Mexico includes household members at least this age in the elderly vulnerable group under the Low Income Home Energy Assistance Program. +# "Age 60 and over." +values: + 2025-10-01: 60 + +metadata: + unit: year + period: year + label: New Mexico LIHEAP elderly vulnerable group age + reference: + - title: 8.150.620.9 C(1) NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0620.html + - title: New Mexico LIHEAP FFY2026 Heating and Cooling Point and Income Guide, vulnerable members + href: https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf + - title: New Mexico LIHEAP FFY2027 Point and Income Guidelines, vulnerable members + href: https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf + - title: New Mexico FY2026 LIHEAP State Plan, Section 2.3 + href: https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/energy_burden_points.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/energy_burden_points.yaml new file mode 100644 index 00000000000..ab9ba20c78d --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/energy_burden_points.yaml @@ -0,0 +1,42 @@ +description: New Mexico assigns these energy points by the household's monthly energy bill as a share of monthly countable income under the Low Income Home Energy Assistance Program. +# The rule prints whole-percent bands: zero to five, six to ten, eleven to +# fifteen, and sixteen percent or more. The unrounded burden uses +# lower-inclusive cutoffs at 6%, 11% and 16%. Households with zero income +# receive the top band in nm_liheap. +brackets: + # Zero to five percent. + - threshold: + 2025-10-01: 0 + amount: + 2025-10-01: 0 + # Six to ten percent. + - threshold: + 2025-10-01: 0.06 + amount: + 2025-10-01: 1 + # Eleven to fifteen percent. + - threshold: + 2025-10-01: 0.11 + amount: + 2025-10-01: 2 + # Sixteen percent or more. + - threshold: + 2025-10-01: 0.16 + amount: + 2025-10-01: 3 + +metadata: + type: single_amount + threshold_unit: /1 + amount_unit: /1 + period: year + label: New Mexico LIHEAP energy burden points + reference: + - title: 8.150.620.9 A(1) NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0620.html + - title: New Mexico LIHEAP FFY2026 Heating and Cooling Point and Income Guide, energy points + href: https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf + - title: New Mexico LIHEAP FFY2027 Point and Income Guidelines, energy burden + href: https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf + - title: New Mexico FY2026 LIHEAP State Plan, Section 2.3 + href: https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=10 diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/energy_standard_allowance.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/energy_standard_allowance.yaml new file mode 100644 index 00000000000..37b0597b462 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/energy_standard_allowance.yaml @@ -0,0 +1,24 @@ +description: New Mexico uses this monthly energy standard allowance as the minimum monthly energy bill for the energy burden under the Low Income Home Energy Assistance Program. +# 8.150.620.9 A(4) NMAC applies the allowance when the applicant's monthly +# utility costs are less than the standard or are not available. The FFY2027 +# guide labels the $298 amount the Standard Utility Allowance, and the FFY2027 +# ISD 017 card lists it as the LIHEAP Energy Standard Allowance (ESA). +values: + 2025-10-01: 291 + 2026-10-01: 298 + +metadata: + unit: currency-USD + period: month + label: New Mexico LIHEAP energy standard allowance + reference: + - title: 8.150.620.9 A(4) NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0620.html + - title: New Mexico LIHEAP FFY2026 Heating and Cooling Point and Income Guide, energy points + href: https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf + - title: New Mexico LIHEAP FFY2027 Point and Income Guidelines, energy burden + href: https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf + - title: New Mexico ISD 017 Income Eligibility Guidelines, October 1, 2025 to September 30, 2026, LIHEAP information + href: https://www.hca.nm.gov/wp-content/uploads/ISD-017-FPG-Cards-FY-2026-10.01.25-to-09.30.2026-final-1.pdf#page=1 + - title: New Mexico ISD 017 FFY 2027 Income Eligibility Guidelines for SNAP and Cash, deductions + href: https://www.hca.nm.gov/wp-content/uploads/ISD-017-FPG-Form-FY-2027-10.01.26-to-09.30.2027-SNAP-and-Cash.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/high.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/high.yaml new file mode 100644 index 00000000000..527781c6a8d --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/high.yaml @@ -0,0 +1,15 @@ +description: New Mexico assigns these income points to households with monthly countable income above the low-income cutoff and within the income limit under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 2 + +metadata: + unit: /1 + period: year + label: New Mexico LIHEAP income points above the low-income cutoff + reference: + - title: 8.150.620.9 B(2) NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0620.html + - title: New Mexico LIHEAP FFY2026 Heating and Cooling Point and Income Guide, income points + href: https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf + - title: New Mexico LIHEAP FFY2027 Point and Income Guidelines, household income points + href: https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/low.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/low.yaml new file mode 100644 index 00000000000..518adc9e686 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/low.yaml @@ -0,0 +1,15 @@ +description: New Mexico assigns these income points to households with monthly countable income at or below the low-income cutoff under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 3 + +metadata: + unit: /1 + period: year + label: New Mexico LIHEAP income points at or below the low-income cutoff + reference: + - title: 8.150.620.9 B(1) NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0620.html + - title: New Mexico LIHEAP FFY2026 Heating and Cooling Point and Income Guide, income points + href: https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf + - title: New Mexico LIHEAP FFY2027 Point and Income Guidelines, household income points + href: https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/low_income_fpg_rate.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/low_income_fpg_rate.yaml new file mode 100644 index 00000000000..804bfc647be --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/low_income_fpg_rate.yaml @@ -0,0 +1,24 @@ +description: New Mexico sets the low-income cutoff for income points at this multiple of the federal poverty guideline under the Low Income Home Energy Assistance Program. +# 8.150.620.9 B NMAC gives three points up to 100% of the guideline; HCA's +# FFY2025-FFY2027 point and income guides and the FFY2026 ISD 017 card use +# 110%, which is followed; the choice is listed in the conflict register +# backlog (candidates.yaml) as us-nm:liheap-three-income-points. The FFY2026 +# guide prints monthly cutoffs of $1,435 to $4,964 for household sizes 1-8 +# and the FFY2027 guide $1,463 to $5,108, each 110% of the guideline over 12 +# months rounded to the nearest dollar. +values: + 2025-10-01: 1.1 + +metadata: + unit: /1 + period: year + label: New Mexico LIHEAP income points low-income cutoff poverty guideline rate + reference: + - title: 8.150.620.9 B NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0620.html + - title: New Mexico LIHEAP FFY2026 Heating and Cooling Point and Income Guide, income points + href: https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf + - title: New Mexico LIHEAP FFY2027 Point and Income Guidelines, income guidelines and point thresholds + href: https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf + - title: New Mexico ISD 017 Income Eligibility Guidelines, October 1, 2025 to September 30, 2026, LIHEAP 110% FPG or below + href: https://www.hca.nm.gov/wp-content/uploads/ISD-017-FPG-Cards-FY-2026-10.01.25-to-09.30.2026-final-1.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/point_value.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/point_value.yaml new file mode 100644 index 00000000000..1de2f5e430f --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/point_value.yaml @@ -0,0 +1,25 @@ +description: New Mexico multiplies the household's point total by this amount to compute the benefit under the Low Income Home Energy Assistance Program. +# 8.150.620.10 C NMAC lets the HCA secretary adjust the point value each year. +# The resulting FFY2026 benefits run from $70 to $490 (2 to 14 points) and +# the FFY2027 benefits from $80 to $560. +values: + 2025-10-01: 35 + 2026-10-01: 40 + +metadata: + unit: currency-USD + period: year + label: New Mexico LIHEAP point value + reference: + - title: 8.150.620.10 NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0620.html + - title: New Mexico LIHEAP FFY2026 Heating and Cooling Point and Income Guide, point values + href: https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf + - title: New Mexico LIHEAP FFY2027 Point and Income Guidelines, total points and benefit + href: https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf + - title: New Mexico ISD 017 Income Eligibility Guidelines, October 1, 2025 to September 30, 2026, LIHEAP information + href: https://www.hca.nm.gov/wp-content/uploads/ISD-017-FPG-Cards-FY-2026-10.01.25-to-09.30.2026-final-1.pdf#page=1 + - title: New Mexico FY2026 LIHEAP State Plan, Section 2.6 + href: https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=10 + - title: New Mexico FY2027 LIHEAP State Plan, Section 2.6 + href: https://www.hca.nm.gov/wp-content/uploads/DETAILED-MODEL-PLAN-LIHEAP_10_01_2026-4.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/propane_points.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/propane_points.yaml new file mode 100644 index 00000000000..dae16567052 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/propane_points.yaml @@ -0,0 +1,17 @@ +description: New Mexico adds these energy points when the household's energy burden is for propane under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 2 + +metadata: + unit: /1 + period: year + label: New Mexico LIHEAP propane points + reference: + - title: 8.150.620.9 A(2) NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0620.html + - title: New Mexico LIHEAP FFY2026 Heating and Cooling Point and Income Guide, energy points + href: https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf + - title: New Mexico LIHEAP FFY2027 Point and Income Guidelines, energy burden + href: https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf + - title: New Mexico FY2026 LIHEAP State Plan, Sections 2.3-2.5 + href: https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=10 diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/vulnerable_points.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/vulnerable_points.yaml new file mode 100644 index 00000000000..6e0af8c8ea1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/vulnerable_points.yaml @@ -0,0 +1,18 @@ +description: New Mexico adds these points for each vulnerable group (aged five or younger, aged 60 or older, or disabled) with at least one household member under the Low Income Home Energy Assistance Program. +# Each group counts once per household, however many members are in it. +values: + 2025-10-01: 2 + +metadata: + unit: /1 + period: year + label: New Mexico LIHEAP vulnerable population points + reference: + - title: 8.150.620.9 C NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0620.html + - title: New Mexico LIHEAP FFY2026 Heating and Cooling Point and Income Guide, vulnerable members + href: https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf + - title: New Mexico LIHEAP FFY2027 Point and Income Guidelines, vulnerable members + href: https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf + - title: "New Mexico FY2026 LIHEAP State Plan, Sections 2.3-2.4 #page 9-10" + href: https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/young_child_max_age.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/young_child_max_age.yaml new file mode 100644 index 00000000000..39b0860b760 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/young_child_max_age.yaml @@ -0,0 +1,18 @@ +description: New Mexico includes household members no older than this age in the young-child vulnerable group under the Low Income Home Energy Assistance Program. +# "Age five and under." +values: + 2025-10-01: 5 + +metadata: + unit: year + period: year + label: New Mexico LIHEAP young child vulnerable group maximum age + reference: + - title: 8.150.620.9 C(2) NMAC + href: https://www.srca.nm.gov/parts/title08/08.150.0620.html + - title: New Mexico LIHEAP FFY2026 Heating and Cooling Point and Income Guide, vulnerable members + href: https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf + - title: New Mexico LIHEAP FFY2027 Point and Income Guidelines, vulnerable members + href: https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf + - title: New Mexico FY2026 LIHEAP State Plan, Section 2.3 + href: https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=10 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 43fdd714b80..b5dffc6495e 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, MS, NC, NE, NJ, NM, NY, OH, OK, OR, SD, RI, TX state_implementations: - state: AL status: partial @@ -930,6 +930,14 @@ programs: parameter_prefix: gov.states.nj.dca.liheap verified_years: "2026-2027" notes: FY2026-FY2027 regular heating only, covering eligibility, countable income and the benefit grid, including the one renters cell that changes in FY2027; the program is not modeled before FY2022. Crisis, cooling and equipment assistance are out of scope. + - state: NM + status: partial + name: New Mexico LIHEAP + full_name: New Mexico Low Income Home Energy Assistance Program + variable: nm_liheap + parameter_prefix: gov.states.nm.hca.liheap + verified_years: "2026-2027" + notes: FY2026-FY2027 annual benefit from the point system, with three income points at 110% of the poverty guideline as HCA's guides apply. Crisis assistance and weatherization are out of scope. - state: NY status: partial name: New York HEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.yaml new file mode 100644 index 00000000000..d74f4056be2 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.yaml @@ -0,0 +1,251 @@ +# Countable income must not exceed 150% of the poverty guideline (8.150.100.9 D and +# 8.150.520.14 NMAC; FFY2026 size-one limit 1,956 a month = 23,472 a year). The +# household must incur a heating cost (8.150.410.9 A): wood-gathering households have a +# heating responsibility without a bill (8.150.410.9 C); heat included in non-subsidized +# rent qualifies, while subsidized households with heat in rent qualify only with an +# additional out-of-pocket cost (8.150.410.11 A-B). At least one member must be a citizen +# or qualified non-citizen (8.150.410.13). Wages of 12,000 are about 19 hours a week at +# the 2026 New Mexico minimum wage of 12.00; gas of 900 is 75 a month, within common +# New Mexico winter gas bills of 60 to 120 a month. + +- name: Case 1, income exactly at the size-one limit is eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 23_472 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 23,472 <= 1,956 x 12 + nm_liheap_eligible: true + +- name: Case 2, income one dollar a year above the size-one limit is ineligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 23_473 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + nm_liheap_eligible: false + +- name: Case 3, a home with no heating is ineligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NONE + households: + household: + members: [person1] + state_code: NM + output: + nm_liheap_eligible: false + +- name: Case 4, an unspecified heating fuel without heat in rent is ineligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: UNSPECIFIED + heat_expense_included_in_rent: false + households: + household: + members: [person1] + state_code: NM + output: + nm_liheap_eligible: false + +- name: Case 5, an unspecified heating fuel with heat included in non-subsidized rent is eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: UNSPECIFIED + heat_expense_included_in_rent: true + receives_housing_assistance: false + households: + household: + members: [person1] + state_code: NM + is_in_public_housing: false + output: + nm_liheap_eligible: true + +- name: Case 6, a household heating with gathered wood and no wood bill is eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: WOOD + wood_expense: 0 + households: + household: + members: [person1] + state_code: NM + output: + nm_liheap_eligible: true + +- name: Case 7, a household with rental assistance and heat in rent but no out-of-pocket heating cost is ineligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + households: + household: + members: [person1] + state_code: NM + output: + nm_liheap_eligible: false + +- name: Case 8, a public housing household with heat in rent but no out-of-pocket heating cost is ineligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: false + households: + household: + members: [person1] + state_code: NM + is_in_public_housing: true + output: + nm_liheap_eligible: false + +- name: Case 9, a household with rental assistance and heat in rent that pays an extra heating cost is eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + # Out-of-pocket electric heating cost of 30 a month beyond the subsidy. + pre_subsidy_electricity_expense: 360 + heat_expense_included_in_rent: true + receives_housing_assistance: true + households: + household: + members: [person1] + state_code: NM + output: + nm_liheap_eligible: true + +- name: Case 10, a household with rental assistance that pays its own gas bill is eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + heat_expense_included_in_rent: false + receives_housing_assistance: true + households: + household: + members: [person1] + state_code: NM + output: + nm_liheap_eligible: true + +- name: Case 11, a gas-heated household with no heating bill and heat outside rent is ineligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: false + households: + household: + members: [person1] + state_code: NM + output: + nm_liheap_eligible: false + +- name: Case 12, a household of only undocumented members with no income is ineligible. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # Size 0; zero income would otherwise pass the income test + nm_liheap_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/eligibility/nm_liheap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/eligibility/nm_liheap_income_limit.yaml new file mode 100644 index 00000000000..02ac7318305 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/eligibility/nm_liheap_income_limit.yaml @@ -0,0 +1,161 @@ +# The income standard is 150% of the poverty guideline as a monthly amount rounded to +# the nearest dollar (8.150.100.9 D and 8.150.520.15 NMAC; FFY2026 and FFY2027 Point +# and Income Guides); the annual limit is 12 times that monthly amount. FFY2026 uses +# the 2025 guidelines (15,650 + 5,500 per added person) and FFY2027 the 2026 +# guidelines (15,960 + 5,680). Household size counts citizens and qualified +# non-citizens only (8.150.410.13 and 8.150.410.15 NMAC). + +- name: Case 1, an undocumented member is excluded from household size so the FFY2026 size-one limit applies. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + person2: + age: 42 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1, person2] + state_code: NM + output: + nm_liheap_household_size: 1 + # round(15,650 x 1.5 / 12) = round(1,956.25) = 1,956; 1,956 x 12 + nm_liheap_income_limit: 23_472 + +- name: Case 2, an eight-person household uses the FFY2026 6,769 monthly limit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: {age: 40} + person2: {age: 38} + person3: {age: 16} + person4: {age: 14} + person5: {age: 12} + person6: {age: 10} + person7: {age: 8} + person8: {age: 6} + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + tanf: 0 + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + state_code: NM + output: + # round(54,150 x 1.5 / 12) = round(6,768.75) = 6,769; 6,769 x 12 + nm_liheap_income_limit: 81_228 + +- name: Case 3, a nine-person household extends the FFY2026 limit through the poverty guideline formula. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: {age: 42} + person2: {age: 40} + person3: {age: 17} + person4: {age: 16} + person5: {age: 14} + person6: {age: 12} + person7: {age: 10} + person8: {age: 8} + person9: {age: 7} + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9] + tanf: 0 + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9] + state_code: NM + output: + # round((15,650 + 5,500 x 8) x 1.5 / 12) = round(7,456.25) = 7,456; 7,456 x 12 + nm_liheap_income_limit: 89_472 + +- name: Case 4, an eight-person household uses the FFY2027 6,965 monthly limit. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: {age: 40} + person2: {age: 38} + person3: {age: 16} + person4: {age: 14} + person5: {age: 12} + person6: {age: 10} + person7: {age: 8} + person8: {age: 6} + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + tanf: 0 + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + state_code: NM + output: + # 55,720 x 1.5 / 12 = 6,965; 6,965 x 12 + nm_liheap_income_limit: 83_580 + +- name: Case 5, a nine-person household extends the FFY2027 limit through the poverty guideline formula. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: {age: 42} + person2: {age: 40} + person3: {age: 17} + person4: {age: 16} + person5: {age: 14} + person6: {age: 12} + person7: {age: 10} + person8: {age: 8} + person9: {age: 7} + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9] + tanf: 0 + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9] + state_code: NM + output: + # (15,960 + 5,680 x 8) x 1.5 / 12 = 7,675; 7,675 x 12 + nm_liheap_income_limit: 92_100 + +- name: Case 6, a household with no citizen or qualified non-citizen member has size zero and no limit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + nm_liheap_household_size: 0 + nm_liheap_income_limit: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/income/nm_liheap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/income/nm_liheap_countable_income.yaml new file mode 100644 index 00000000000..a407c7b3726 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/income/nm_liheap_countable_income.yaml @@ -0,0 +1,189 @@ +# Gross income before deductions (8.150.520.12 NMAC): earned income of members over the +# age of 18 and unearned income of all members (8.150.520.12 B), including OASDI before +# the Medicare premium deduction, contributions from family or friends and TANF +# (8.150.520.13 A-C). An ineligible non-citizen's income is prorated by the share of +# eligible members (8.150.520.10 B). Self-employment counts at its net (8.150.520.9 C). +# Wages of 24,960 are full-time work (2,080 hours) at the 2026 New Mexico minimum wage of +# 12.00 an hour; gas of 900 is 75 a month, within common winter gas bills of 60 to 120. + +- name: Case 1, an undocumented member's wages are prorated by the share of eligible members. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 24_960 + person2: + age: 8 + person3: + age: 42 + immigration_status: UNDOCUMENTED + employment_income: 18_000 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1, person2, person3] + state_code: NM + output: + # 24,960 + 18,000 / 3 x 2 = 24,960 + 12,000 + nm_liheap_countable_income: 36_960 + +- name: Case 2, an 18-year-old's wages are excluded while a 19-year-old's wages and the 18-year-old's interest are counted. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + employment_income: 24_960 + person2: + age: 19 + employment_income: 6_000 + person3: + age: 18 + employment_income: 4_000 + interest_income: 200 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1, person2, person3] + state_code: NM + output: + # 24,960 + 6,000 + 200; the 18-year-old's 4,000 wages are excluded + nm_liheap_countable_income: 31_160 + +- name: Case 3, Social Security counts at its gross amount before the Medicare Part B premium. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 67 + ssi: 0 + social_security_retirement: 24_000 + # 2026 standard Part B premium of 202.90 a month. + medicare_part_b_premium: 2_434.80 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # Gross 24,000; the 2,434.80 premium is not deducted (8.150.520.13 B) + nm_liheap_countable_income: 24_000 + +- name: Case 4, SSI and contributions from relatives count as unearned income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + is_disabled: true + # 2026 federal SSI benefit rate of 994 a month. + ssi: 11_928 + # Help of 100 a month from a relative. + financial_assistance: 1_200 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 994 x 12 + 100 x 12 = 11,928 + 1,200 + nm_liheap_countable_income: 13_128 + +- name: Case 5, New Mexico Works cash assistance received by the household is counted. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + person2: + age: 4 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + # New Mexico Works three-person payment standard of 549 a month. + tanf: 6_588 + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1, person2, person3] + state_code: NM + output: + # 549 x 12 = 6,588 + nm_liheap_countable_income: 6_588 + +- name: Case 6, child support is counted and capital gains are not. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 35 + employment_income: 24_960 + # Child support of 300 a month. + child_support_received: 3_600 + long_term_capital_gains: 1_000 + person2: + age: 8 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1, person2] + state_code: NM + output: + # 24,960 + 3,600; the 1,000 capital gain is not an income source + nm_liheap_countable_income: 28_560 + +- name: Case 7, a business loss is floored at zero and does not offset wages. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 24_960 + self_employment_income: -5_000 + interest_income: 300 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 24,960 + max(-5,000, 0) + 300 = 25,260 + nm_liheap_countable_income: 25_260 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/nm_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/nm_liheap.yaml new file mode 100644 index 00000000000..ad943c84e9b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/nm_liheap.yaml @@ -0,0 +1,528 @@ +# Benefit = total points x point value (8.150.620.10 A NMAC): 35 a point in FFY2026 and +# 40 in FFY2027 (Point and Income Guides). Energy burden = monthly heating bill (annual +# bill / 12, raised to the energy standard allowance of 291 in FFY2026 and 298 in FFY2027 +# when lower) / monthly countable income: 0 points below 6%, 1 from 6%, 2 from 11%, 3 +# from 16%; zero income receives 3; no energy points with heat in rent or gathered wood +# (8.150.620.9 A). Propane adds 2 (8.150.620.9 A(2)). Income points: 3 at or below 110% +# of the poverty guideline, otherwise 2 (Point and Income Guides; FFY2026 size-one cutoff +# 1,435 a month). Two points per category for a counted member 5 or younger, 60 or older, +# or disabled (8.150.620.9 C). Wages are at the 2026 New Mexico minimum wage of 12.00 an +# hour (24,960 full time); gas of 900 is 75 a month, within common winter gas bills of 60 +# to 120; electricity of 1,080 is about the EIA 2024 New Mexico average residential bill +# of 90 a month. + +- name: Case 1, a bill below the allowance uses the allowance and a burden of exactly 16 percent receives 3 energy points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + # About 35 hours a week at 12.00. + employment_income: 21_825 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_080 + households: + household: + members: [person1] + state_code: NM + output: + # max(1,080 / 12, 291) / (21,825 / 12) = 291 / 1,818.75 = 0.16 -> 3 energy + # 1,818.75 > 1,435 -> 2 income; 5 points x 35 + nm_liheap: 175 + +- name: Case 2, a burden just below 16 percent receives 2 energy points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 21_826 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_080 + households: + household: + members: [person1] + state_code: NM + output: + # 291 / (21,826 / 12) = 0.15999 -> 2 energy; 2 income; 4 points x 35 + nm_liheap: 140 + +- name: Case 3, a propane bill above the allowance at a burden of exactly 11 percent receives 2 energy and 2 propane points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 35 + employment_income: 36_000 + person2: + age: 7 + person3: + age: 9 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + heating_type: PROPANE + # A propane-heated home whose bills average 330 a month, above the allowance. + bottled_gas_expense: 3_960 + households: + household: + members: [person1, person2, person3] + state_code: NM + output: + # max(3,960 / 12, 291) / (36,000 / 12) = 330 / 3,000 = 0.11 -> 2 energy; 2 propane + # 3,000 > 2,443 (size three) -> 2 income; 6 points x 35 + nm_liheap: 210 + +- name: Case 4, a burden just below 11 percent receives 1 energy point. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 35 + employment_income: 36_012 + person2: + age: 7 + person3: + age: 9 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + heating_type: PROPANE + bottled_gas_expense: 3_960 + households: + household: + members: [person1, person2, person3] + state_code: NM + output: + # 330 / 3,001 = 0.10996 -> 1 energy; 2 propane; 2 income; 5 points x 35 + nm_liheap: 175 + +- name: Case 5, a six-person household with a burden of exactly 6 percent receives 1 energy point. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 38 + # Full time at about 16 an hour. + employment_income: 33_240 + person2: + age: 36 + employment_income: 24_960 + person3: {age: 7} + person4: {age: 9} + person5: {age: 11} + person6: {age: 13} + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6] + tanf: 0 + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1, person2, person3, person4, person5, person6] + state_code: NM + output: + # 291 / (58,200 / 12) = 291 / 4,850 = 0.06 -> 1 energy + # 4,850 > 3,955 (size six) -> 2 income; 3 points x 35 + nm_liheap: 105 + +- name: Case 6, a burden just below 6 percent receives no energy points and the 2-point minimum. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 38 + employment_income: 33_252 + person2: + age: 36 + employment_income: 24_960 + person3: {age: 7} + person4: {age: 9} + person5: {age: 11} + person6: {age: 13} + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6] + tanf: 0 + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1, person2, person3, person4, person5, person6] + state_code: NM + output: + # 291 / (58,212 / 12) = 291 / 4,851 = 0.05999 -> 0 energy; 2 income; 2 points x 35 + nm_liheap: 70 + +- name: Case 7, a household with zero income receives 3 energy points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # Zero income -> 3 energy; 0 <= 1,435 -> 3 income; 6 points x 35 + nm_liheap: 210 + +- name: Case 8, a propane-heated household with heat in rent receives no energy or propane points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + # About 19 hours a week at 12.00. + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + bottled_gas_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: false + households: + household: + members: [person1] + state_code: NM + output: + # 0 energy and 0 propane (8.150.620.9 A(3)(a)); 1,000 <= 1,435 -> 3 income; 3 points x 35 + nm_liheap: 105 + +- name: Case 9, a household heating with gathered wood receives no energy points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: WOOD + wood_expense: 0 + households: + household: + members: [person1] + state_code: NM + output: + # 0 energy (8.150.620.9 A(3)(b)); 3 income; 3 points x 35 + nm_liheap: 105 + +- name: Case 10, a household heating with purchased wood receives energy points from the allowance. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: WOOD + # Firewood bought for 600 a year. + wood_expense: 600 + households: + household: + members: [person1] + state_code: NM + output: + # max(600 / 12, 291) / 1,000 = 0.291 -> 3 energy; 3 income; 6 points x 35 + nm_liheap: 210 + +- name: Case 11, income exactly at the 110 percent cutoff receives 3 income points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 17_220 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 17,220 / 12 = 1,435 <= 1,435 -> 3 income; 291 / 1,435 = 0.203 -> 3 energy + # 6 points x 35 + nm_liheap: 210 + +- name: Case 12, income one dollar a year above the 110 percent cutoff receives 2 income points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 17_221 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 17,221 / 12 = 1,435.08 > 1,435 -> 2 income; 3 energy; 5 points x 35 + nm_liheap: 175 + +- name: Case 13, a five-year-old adds vulnerable points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 24_960 + person2: + age: 5 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1, person2] + state_code: NM + output: + # 291 / 2,080 = 0.140 -> 2 energy; 2,080 > 1,939 (size two) -> 2 income + # 2 vulnerable; 6 points x 35 + nm_liheap: 210 + +- name: Case 14, a six-year-old and a 59-year-old add no vulnerable points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 59 + employment_income: 24_960 + person2: + age: 6 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1, person2] + state_code: NM + output: + # 2 energy; 2 income; 0 vulnerable; 4 points x 35 + nm_liheap: 140 + +- name: Case 15, a 60-year-old adds vulnerable points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 60 + employment_income: 20_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 291 / 1,666.67 = 0.175 -> 3 energy; 1,666.67 > 1,435 -> 2 income + # 2 vulnerable; 7 points x 35 + nm_liheap: 245 + +- name: Case 16, a disabled member adds vulnerable points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + is_disabled: true + ssi: 0 + # Disability insurance benefit of 1,500 a month. + social_security_disability: 18_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 291 / 1,500 = 0.194 -> 3 energy; 1,500 > 1,435 -> 2 income + # 2 vulnerable; 7 points x 35 + nm_liheap: 245 + +- name: Case 17, an undocumented 65-year-old adds no vulnerable points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 18_000 + person2: + age: 65 + immigration_status: UNDOCUMENTED + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1, person2] + state_code: NM + output: + # Size one: 291 / 1,500 = 0.194 -> 3 energy; 1,500 > 1,435 -> 2 income + # 0 vulnerable; 5 points x 35 + nm_liheap: 175 + +- name: Case 18, the FFY2026 maximum of 14 points pays 490 and counts each vulnerable category once. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 62 + person2: + age: 35 + is_disabled: true + ssi: 0 + # Disability insurance benefit of 1,200 a month. + social_security_disability: 14_400 + person3: + age: 3 + person4: + age: 1 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + tanf: 0 + heating_type: PROPANE + # Propane of 150 a month, below the allowance. + bottled_gas_expense: 1_800 + households: + household: + members: [person1, person2, person3, person4] + state_code: NM + output: + # max(150, 291) / 1,200 = 0.243 -> 3 energy; 2 propane + # 1,200 <= 2,947 (size four) -> 3 income + # 2 for the children (once) + 2 for age 62 + 2 for disability = 6; 14 points x 35 + nm_liheap: 490 + +- name: Case 19, income above the limit pays nothing. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 23_473 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 23,473 > 1,956 x 12 + nm_liheap: 0 + +- name: Case 20, FFY2027 uses the 298 allowance and 40 a point. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + # About 36 hours a week at 12.00. + employment_income: 22_350 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # max(75, 298) / (22,350 / 12) = 298 / 1,862.50 = 0.16 -> 3 energy + # 1,862.50 > 1,463 -> 2 income; 5 points x 40 + nm_liheap: 200 + +- name: Case 21, IRA withdrawals count as unearned income and put a retiree over the FFY2026 limit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + ssi: 0 + # Social Security of 1,200 a month. + social_security_retirement: 14_400 + # Traditional IRA withdrawals of about 833 a month. + taxable_ira_distributions: 10_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 14,400 + 10,000 retirement distributions (8.150.520.13 A NMAC) + nm_liheap_countable_income: 24_400 + # 24,400 > 1,956 x 12 = 23,472; without the withdrawals 14,400 would be + # eligible for 3 energy + 3 income + 2 elderly = 8 points x 35 = 280 + nm_liheap: 0 diff --git a/policyengine_us/variables/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.py b/policyengine_us/variables/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.py new file mode 100644 index 00000000000..f3b8b45428f --- /dev/null +++ b/policyengine_us/variables/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.py @@ -0,0 +1,53 @@ +from policyengine_us.model_api import * + + +class nm_liheap_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "New Mexico LIHEAP regular heating eligibility" + defined_for = StateCode.NM + reference = ( + # 8.150.410.8, 8.150.410.9, 8.150.410.11, 8.150.410.13 and + # 8.150.410.14 NMAC + "https://www.srca.nm.gov/parts/title08/08.150.0410.html", + # 8.150.100.9 C-E NMAC + "https://www.srca.nm.gov/parts/title08/08.150.0100.html", + # 8.150.510.8 NMAC + "https://www.srca.nm.gov/parts/title08/08.150.0510.html", + # 8.150.520.14 NMAC + "https://www.srca.nm.gov/parts/title08/08.150.0520.html", + "https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=9", + ) + + def formula(spm_unit, period, parameters): + size = spm_unit("nm_liheap_household_size", period) + income = spm_unit("nm_liheap_countable_income", period) + limit = spm_unit("nm_liheap_income_limit", period) + # Annual income against 12 times the monthly limit stands in for the + # 30-day gross income test. No household is categorically eligible + # and no asset test applies. The 150% limit exceeds the 110% floor of + # 42 U.S.C. 8624(b)(2)(B) for every size, so the floor never binds. + income_eligible = income <= limit + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + heating_type = spm_unit("heating_type", period) + types = heating_type.possible_values + # A home without heat has no heating cost. An unspecified fuel + # qualifies only when heat is included in the rent. + fuel_qualifies = (heating_type != types.NONE) & ( + heat_in_rent | (heating_type != types.UNSPECIFIED) + ) + # Heat included in unsubsidized rent qualifies even when no utility + # cost is designated. Subsidized tenants whose heat is included in the + # rent qualify only with an additional out-of-pocket heating cost. + subsidized = spm_unit("receives_housing_assistance", period) | ( + spm_unit.household("is_in_public_housing", period) + ) + out_of_pocket = spm_unit("heating_expense", period) > 0 + rent_responsible = ~subsidized | out_of_pocket + # Households that heat with wood they gather themselves have a heating + # responsibility without a bill. + wood = heating_type == types.WOOD + direct_responsible = spm_unit("has_heating_expense", period) | wood + responsible = where(heat_in_rent, rent_responsible, direct_responsible) + return (size > 0) & income_eligible & fuel_qualifies & responsible diff --git a/policyengine_us/variables/gov/states/nm/hca/liheap/eligibility/nm_liheap_income_limit.py b/policyengine_us/variables/gov/states/nm/hca/liheap/eligibility/nm_liheap_income_limit.py new file mode 100644 index 00000000000..5c3d42e33ce --- /dev/null +++ b/policyengine_us/variables/gov/states/nm/hca/liheap/eligibility/nm_liheap_income_limit.py @@ -0,0 +1,38 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class nm_liheap_income_limit(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "New Mexico LIHEAP income limit" + defined_for = StateCode.NM + reference = ( + # 8.150.100.9 D NMAC + "https://www.srca.nm.gov/parts/title08/08.150.0100.html", + # 8.150.520.14 and 8.150.520.15 NMAC + "https://www.srca.nm.gov/parts/title08/08.150.0520.html", + "https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=9", + "https://www.hca.nm.gov/wp-content/uploads/DETAILED-MODEL-PLAN-LIHEAP_10_01_2026-4.pdf#page=8", + "https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf", + "https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nm.hca.liheap.eligibility + size = spm_unit("nm_liheap_household_size", period) + state_group = spm_unit.household("state_group_str", period) + guideline = fpg(size, state_group, period, parameters, year_lag=p.fpg_year_lag) + # The rate is applied to the guideline for each household size and + # the monthly amount is rounded to the nearest dollar, which + # reproduces the guides' printed rows for sizes 1-8. For sizes 9 and + # over, the guides' "each additional" increments can differ by $1 or + # more a month, growing with size (FFY2026 size 9: $7,457 by + # increment, $7,456 by rate). + # Monthly income is tested, so the annual limit is 12 times the + # monthly amount ($23,472 for one person in FFY2026); the FFY2026 + # guide's annual column prints 150% of the annual guideline ($23,475). + monthly_limit = np.floor(guideline * p.fpg_rate / MONTHS_IN_YEAR + 0.5) + return where(size > 0, monthly_limit * MONTHS_IN_YEAR, 0) diff --git a/policyengine_us/variables/gov/states/nm/hca/liheap/income/nm_liheap_countable_income.py b/policyengine_us/variables/gov/states/nm/hca/liheap/income/nm_liheap_countable_income.py new file mode 100644 index 00000000000..d1ea2b6fe39 --- /dev/null +++ b/policyengine_us/variables/gov/states/nm/hca/liheap/income/nm_liheap_countable_income.py @@ -0,0 +1,49 @@ +from policyengine_us.model_api import * + + +class nm_liheap_countable_income(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "New Mexico LIHEAP countable income" + defined_for = StateCode.NM + reference = ( + # 8.150.520.8 through 8.150.520.14 NMAC + "https://www.srca.nm.gov/parts/title08/08.150.0520.html", + # PDF pages 5-6 + "https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=5", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nm.hca.liheap.income + person = spm_unit.members + # Annual inputs stand in for the gross income of the 30 days before + # eligibility is determined, including its weekly x4 and biweekly x2 + # conversion. Gross earned income counts for members over the age of + # 18 and gross unearned income for every member. + earned_counted = person("age", period) > p.earned_income_age + # 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, options=[ADD]), 0) + member_income = earned * earned_counted + unearned + # The gross income of an ineligible non-citizen is divided by all + # members, eligible and ineligible, and counted for the eligible + # members only (8.150.520.10 B NMAC). + eligible_member = person("is_citizen_or_legal_immigrant", period) + eligible_share = spm_unit("nm_liheap_household_size", period) / spm_unit( + "spm_unit_size", period + ) + counted_income = where( + eligible_member, + member_income, + member_income * spm_unit.project(eligible_share), + ) + # TANF (New Mexico Works) is counted once at the SPM unit level + # through the take-up-gated tanf total. + return spm_unit.sum(counted_income) + spm_unit("tanf", period) diff --git a/policyengine_us/variables/gov/states/nm/hca/liheap/nm_liheap.py b/policyengine_us/variables/gov/states/nm/hca/liheap/nm_liheap.py new file mode 100644 index 00000000000..3e84cb35831 --- /dev/null +++ b/policyengine_us/variables/gov/states/nm/hca/liheap/nm_liheap.py @@ -0,0 +1,114 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class nm_liheap(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "New Mexico LIHEAP regular heating assistance" + documentation = ( + "New Mexico's one regular benefit per federal fiscal year, which the " + "household applies to heating or cooling: energy burden, propane, " + "income and vulnerable population points times the point value. " + "Heating and cooling share this one annual benefit. Verified for " + "FY2026 and FY2027; earlier years are unverified estimates through " + "parameter backfilling. Crisis assistance and weatherization are out " + "of scope." + ) + defined_for = "nm_liheap_eligible" + reference = ( + # 8.150.620.8 through 8.150.620.10 NMAC + "https://www.srca.nm.gov/parts/title08/08.150.0620.html", + # 8.150.110.8 D NMAC + "https://www.srca.nm.gov/parts/title08/08.150.0110.html", + "https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf", + "https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf", + # PDF pages 9-10 + "https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=9", + "https://www.hca.nm.gov/wp-content/uploads/DETAILED-MODEL-PLAN-LIHEAP_10_01_2026-4.pdf#page=9", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nm.hca.liheap + income = spm_unit("nm_liheap_countable_income", period) + heating_expense = spm_unit("heating_expense", period) + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + heating_type = spm_unit("heating_type", period) + types = heating_type.possible_values + # Households whose utilities are included in the rent and households + # that heat with wood they do not purchase receive no energy burden + # points. + unpurchased_wood = (heating_type == types.WOOD) & (heating_expense == 0) + receives_energy_points = ~heat_in_rent & ~unpurchased_wood + # The annual heating bill over 12 months, floored at the energy + # standard allowance, stands in for the highest monthly bill of the + # last 12 months, and annual income over 12 months for 30-day income. + # The ratio of the annual amounts equals the monthly bill over monthly + # income. It is computed in float64 with a single division, so a + # burden exactly at a cutoff lands on it even when the monthly + # amounts have no exact binary value. + annual_bill = max_( + heating_expense.astype(np.float64), + p.payment.energy_standard_allowance * MONTHS_IN_YEAR, + ) + income_64 = income.astype(np.float64) + has_income = income > 0 + burden = np.divide( + annual_bill, + income_64, + out=np.zeros_like(income_64), + where=has_income, + ) + burden_points = p.payment.energy_burden_points + # Households with zero income receive the top energy burden points. + energy_points = where( + has_income, burden_points.calc(burden), burden_points.amounts[-1] + ) + propane = heating_type == types.PROPANE + energy_cost_points = receives_energy_points * ( + energy_points + propane * p.payment.propane_points + ) + p_income = p.payment.income_points + size = spm_unit("nm_liheap_household_size", period) + state_group = spm_unit.household("state_group_str", period) + guideline = fpg( + size, + state_group, + period, + parameters, + year_lag=p.eligibility.fpg_year_lag, + ) + # Three income points at or below the low-income cutoff, 110% of the + # guideline (see low_income_fpg_rate), and two points above it. The + # rate is applied to the guideline for each household size and the + # monthly cutoff is rounded to the nearest dollar, which reproduces + # the guides' printed rows for sizes 1-8. For sizes 9 and over, the + # guides' "each additional" increments can differ by $1 or more a + # month, growing with size (FFY2027 size 9: $5,629 by increment, + # $5,628 by rate). + monthly_cutoff = np.floor( + guideline * p_income.low_income_fpg_rate / MONTHS_IN_YEAR + 0.5 + ) + monthly_income = income / MONTHS_IN_YEAR + income_points = where( + monthly_income <= monthly_cutoff, p_income.low, p_income.high + ) + # Each vulnerable group with at least one counted member adds points + # once. is_disabled stands in for disability-based income or a + # doctor's statement. + person = spm_unit.members + age = person("age", period) + counted = person("is_citizen_or_legal_immigrant", period) + young_child = spm_unit.any(counted & (age < p.payment.young_child_max_age + 1)) + elderly = spm_unit.any(counted & (age >= p.payment.elderly_age)) + disabled = spm_unit.any(counted & person("is_disabled", period)) + vulnerable_groups = ( + young_child.astype(int) + elderly.astype(int) + disabled.astype(int) + ) + vulnerable_points = vulnerable_groups * p.payment.vulnerable_points + points = energy_cost_points + income_points + vulnerable_points + # One benefit per federal fiscal year, with no bill cap. Supplemental + # benefits at the secretary's direction are not modeled. + return points * p.payment.point_value diff --git a/policyengine_us/variables/gov/states/nm/hca/liheap/nm_liheap_household_size.py b/policyengine_us/variables/gov/states/nm/hca/liheap/nm_liheap_household_size.py new file mode 100644 index 00000000000..1068d93c9b7 --- /dev/null +++ b/policyengine_us/variables/gov/states/nm/hca/liheap/nm_liheap_household_size.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * + + +class nm_liheap_household_size(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "New Mexico LIHEAP household size" + defined_for = StateCode.NM + reference = ( + # 8.150.410.8, 8.150.410.13 and 8.150.410.15 NMAC + "https://www.srca.nm.gov/parts/title08/08.150.0410.html", + # 8.150.520.10 NMAC + "https://www.srca.nm.gov/parts/title08/08.150.0520.html", + ) + + # Citizens and qualified noncitizens form the benefit group and the + # household size for the income limit and income points. The income of + # other members is prorated in nm_liheap_countable_income. SPM membership + # approximates the LIHEAP household, is_citizen_or_legal_immigrant + # approximates TANF-qualified noncitizen status, and Social Security + # number enumeration has no input. + adds = ["is_citizen_or_legal_immigrant"] diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 7d8c41658c6..c212cf347b3 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -3864,6 +3864,37 @@ candidates: prs: [] state: unreviewed note: PE-US follows agency practice; establish authority for departure from the codified regulation. +- id: us-nm:liheap-three-income-points + program: liheap + years: + from: 2026 + to: 2027 + type: federal-fiscal + question: Does New Mexico LIHEAP give three income points for income up to 100% of the poverty guideline, as the + regulation provides, or up to 110%, as the agency's point and income guides apply? + sources: + - kind: regulation + citation: 8.150.620.9 B NMAC + url: https://www.srca.nm.gov/parts/title08/08.150.0620.html + - kind: guidance + citation: HCA LIHEAP FFY2026 Point and Income Guide + url: https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf + - kind: guidance + citation: HCA LIHEAP FFY2027 Point and Income Guidelines + url: https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf + readings: + statute: No statute supplied; the regulation gives three points for income from zero to 100% of the poverty guideline + and two points from 100% to 150%. + other: The FFY2025-FFY2027 guides and the FFY2026 ISD 017 card give three points at or below 110% of the poverty guideline. + example: In FFY2026, a one-person household with monthly income of $1,400 (107% of the guideline) scores two income + points under the regulation and three under the guides. + pe_us: + follows: unknown + code: [] + prs: [] + state: ruled + note: 'PE-US follows the guides, which HCA applies; the regulation lets the secretary adjust point values but does + not mention this cutoff. Code not yet on main: nm_liheap, gov.states.nm.hca.liheap.payment.income_points.low_income_fpg_rate.' - id: us-nm:medical-care-credit-mixed-age-spouses program: income-tax years: From ab69ee973a24f4be511284ddaf1a01a042c8c285 Mon Sep 17 00:00:00 2001 From: Ziming Date: Wed, 28 Oct 2026 20:10:29 -0400 Subject: [PATCH 2/2] Address review round 1 for New Mexico 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 --- .../hca/liheap/income/earned_income_age.yaml | 3 +- .../hca/liheap/income/sources/unearned.yaml | 5 +- .../income_points/low_income_fpg_rate.yaml | 18 +- policyengine_us/programs.yaml | 2 +- .../eligibility/nm_liheap_eligible.yaml | 60 +++- .../income/nm_liheap_countable_income.yaml | 98 +++++++ .../gov/states/nm/hca/liheap/nm_liheap.yaml | 264 ++++++++++++++++-- .../liheap/eligibility/nm_liheap_eligible.py | 33 ++- .../gov/states/nm/hca/liheap/nm_liheap.py | 120 ++++---- source_conflicts/candidates.yaml | 41 ++- 10 files changed, 519 insertions(+), 125 deletions(-) diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/income/earned_income_age.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/income/earned_income_age.yaml index acbc449e6d5..a1e668d9703 100644 --- a/policyengine_us/parameters/gov/states/nm/hca/liheap/income/earned_income_age.yaml +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/income/earned_income_age.yaml @@ -2,7 +2,8 @@ description: New Mexico counts the earned income of household members older than # 8.150.520.12 B NMAC counts "the gross earned income of all household members # over the age of 18", so the earnings of a member aged 18 or younger are not # counted. The FY2026 plan leaves "Earned income of a child under the age of -# 18" unchecked (Section 1.9). +# 18" unchecked (Section 1.9), recorded as us-nm:liheap-plan-income-checklist +# in candidates.yaml. values: 2025-10-01: 18 diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/income/sources/unearned.yaml index 500f015d1ff..69cce3d92ce 100644 --- a/policyengine_us/parameters/gov/states/nm/hca/liheap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/income/sources/unearned.yaml @@ -4,6 +4,8 @@ values: # Gross OASDI benefits, including any Medicare premium deducted. - social_security - ssi + # New Mexico SSI state supplement (8.106.500 NMAC). + - nm_ssi_state_supplement # Retirement, disability and survivor payments. - pension_income # IRA, 401(k) and 403(b) withdrawals; the annual input cannot separate @@ -38,7 +40,8 @@ values: # Security "Excluding MediCare deduction" and leaves cash gifts unchecked; # 8.150.520.13 NMAC counts gross OASDI including the Medicare premium and # contributions from family or friends and excludes lump sums, and the rule -# is followed for these items. +# is followed for these items, recorded as us-nm:liheap-plan-income-checklist +# in candidates.yaml. metadata: unit: list period: year diff --git a/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/low_income_fpg_rate.yaml b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/low_income_fpg_rate.yaml index 804bfc647be..b8f2cdf6859 100644 --- a/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/low_income_fpg_rate.yaml +++ b/policyengine_us/parameters/gov/states/nm/hca/liheap/payment/income_points/low_income_fpg_rate.yaml @@ -1,13 +1,9 @@ description: New Mexico sets the low-income cutoff for income points at this multiple of the federal poverty guideline under the Low Income Home Energy Assistance Program. -# 8.150.620.9 B NMAC gives three points up to 100% of the guideline; HCA's -# FFY2025-FFY2027 point and income guides and the FFY2026 ISD 017 card use -# 110%, which is followed; the choice is listed in the conflict register -# backlog (candidates.yaml) as us-nm:liheap-three-income-points. The FFY2026 -# guide prints monthly cutoffs of $1,435 to $4,964 for household sizes 1-8 -# and the FFY2027 guide $1,463 to $5,108, each 110% of the guideline over 12 -# months rounded to the nearest dollar. +# 8.150.620.9 B NMAC gives three points for income from zero to 100% of the +# guideline. HCA's FFY2025-FFY2027 guides and the FFY2026 ISD 017 card print a +# 110% column, recorded as us-nm:liheap-three-income-points in candidates.yaml. values: - 2025-10-01: 1.1 + 2025-10-01: 1 metadata: unit: /1 @@ -16,9 +12,3 @@ metadata: reference: - title: 8.150.620.9 B NMAC href: https://www.srca.nm.gov/parts/title08/08.150.0620.html - - title: New Mexico LIHEAP FFY2026 Heating and Cooling Point and Income Guide, income points - href: https://www.hca.nm.gov/wp-content/uploads/Heating-and-Cooling-Point-and-Income-Guide-2026.pdf - - title: New Mexico LIHEAP FFY2027 Point and Income Guidelines, income guidelines and point thresholds - href: https://www.hca.nm.gov/wp-content/uploads/NM-LIHEAP-FFY-2027-Point-and-Income-Guidelines.pdf - - title: New Mexico ISD 017 Income Eligibility Guidelines, October 1, 2025 to September 30, 2026, LIHEAP 110% FPG or below - href: https://www.hca.nm.gov/wp-content/uploads/ISD-017-FPG-Cards-FY-2026-10.01.25-to-09.30.2026-final-1.pdf#page=1 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 2b23ab3d64f..eaa6eb786a3 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -1131,7 +1131,7 @@ programs: variable: nm_liheap parameter_prefix: gov.states.nm.hca.liheap verified_years: "2026-2027" - notes: FY2026-FY2027 annual benefit from the point system, with three income points at 110% of the poverty guideline as HCA's guides apply. Crisis assistance and weatherization are out of scope. + notes: FY2026-FY2027 annual benefit from the point system on the household's heating fuel or electric bill, with three income points up to 100% of the poverty guideline. Crisis assistance and weatherization are out of scope. - state: NY status: partial name: New York HEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.yaml index d74f4056be2..41ad7d8edf0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.yaml @@ -1,12 +1,15 @@ # Countable income must not exceed 150% of the poverty guideline (8.150.100.9 D and # 8.150.520.14 NMAC; FFY2026 size-one limit 1,956 a month = 23,472 a year). The -# household must incur a heating cost (8.150.410.9 A): wood-gathering households have a -# heating responsibility without a bill (8.150.410.9 C); heat included in non-subsidized -# rent qualifies, while subsidized households with heat in rent qualify only with an -# additional out-of-pocket cost (8.150.410.11 A-B). At least one member must be a citizen -# or qualified non-citizen (8.150.410.13). Wages of 12,000 are about 19 hours a week at -# the 2026 New Mexico minimum wage of 12.00; gas of 900 is 75 a month, within common -# New Mexico winter gas bills of 60 to 120 a month. +# household must incur an energy cost (8.150.410.9 A): a heating fuel or electric bill +# qualifies; wood-gathering households have a heating responsibility without a bill +# (8.150.410.9 C); heat included in non-subsidized rent qualifies, while subsidized +# households with heat in rent qualify only with an additional out-of-pocket heating fuel +# or electric bill (8.150.410.11 A-B). An unspecified heating fuel qualifies only with heat +# in rent. At least one member must be a citizen or qualified non-citizen (8.150.410.13). +# Wages of 12,000 are about 19 hours a week at the 2026 New Mexico minimum wage of 12.00; +# gas of 900 is 75 a month, within common New Mexico winter gas bills of 60 to 120 a +# month; electricity of 1,080 is about the EIA 2024 New Mexico average residential bill +# of 90 a month. - name: Case 1, income exactly at the size-one limit is eligible. period: 2026 @@ -47,7 +50,7 @@ output: nm_liheap_eligible: false -- name: Case 3, a home with no heating is ineligible. +- name: Case 3, a home with no heating and no energy bill is ineligible. period: 2026 input: people: @@ -249,3 +252,44 @@ output: # Size 0; zero income would otherwise pass the income test nm_liheap_eligible: false + +- name: Case 13, a home with no heating that pays an electric bill is eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NONE + pre_subsidy_electricity_expense: 1_080 + households: + household: + members: [person1] + state_code: NM + output: + # The 1,080 electric bill is an energy cost; 12,000 <= 23,472 + nm_liheap_eligible: true + +- name: Case 14, an unspecified heating fuel with an electric bill and heat outside rent is ineligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: UNSPECIFIED + heat_expense_included_in_rent: false + pre_subsidy_electricity_expense: 1_080 + households: + household: + members: [person1] + state_code: NM + output: + # The electric bill does not replace the required heating type + nm_liheap_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/income/nm_liheap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/income/nm_liheap_countable_income.yaml index a407c7b3726..a4a59340d2f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/income/nm_liheap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/income/nm_liheap_countable_income.yaml @@ -187,3 +187,101 @@ output: # 24,960 + max(-5,000, 0) + 300 = 25,260 nm_liheap_countable_income: 25_260 + +- name: Case 8, every earned income source is counted. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 12_000 + self_employment_income: 1_000 + sstb_self_employment_income: 2_000 + farm_operations_income: 4_000 + partnership_s_corp_income: 8_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 12,000 + 1,000 + 2,000 + 4,000 + 8,000 = 27,000; each source has a distinct + # amount, so dropping any one changes the total + nm_liheap_countable_income: 27_000 + +- name: Case 9, every unearned income source and the state SSI supplement are counted. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + # Lives in a licensed adult residential care home, so an SSI recipient + # receives the 100 a month state supplement. + is_in_adult_residential_care: true + # Each unearned source in list order receives 100 x its position. + social_security: 100 + ssi: 200 + pension_income: 300 + retirement_distributions: 400 + military_retirement_pay_survivors: 500 + railroad_benefits: 600 + survivor_benefits: 700 + veterans_benefits: 800 + disability_benefits: 900 + workers_compensation: 1_000 + unemployment_compensation: 1_100 + general_assistance: 1_200 + child_support_received: 1_300 + alimony_income: 1_400 + interest_income: 1_500 + ordinary_dividend_income: 1_600 + rental_income: 1_700 + farm_rent_income: 1_800 + estate_income: 1_900 + financial_assistance: 2_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # The reported 1,100 passes through to total_unemployment_compensation. + total_unemployment_compensation: 1_100 + military_retirement_income: 500 + # SSI of 200 / 12 = 16.67 > 0 each month, age 40 >= 18, in residential care: + # 100 x 12 = 1,200 + nm_ssi_state_supplement: 1_200 + # 100 x (1 + 2 + ... + 20) = 100 x 210 = 21,000; + 1,200 supplement = 22,200 + nm_liheap_countable_income: 22_200 + +- name: Case 10, a rental loss is floored at zero and does not offset wages. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 24_960 + rental_income: -6_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 24,960 + max(-6,000, 0) = 24,960; without the floor 24,960 - 6,000 = 18,960 + nm_liheap_countable_income: 24_960 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/nm_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/nm_liheap.yaml index ad943c84e9b..93848ea7777 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/nm_liheap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/liheap/nm_liheap.yaml @@ -1,12 +1,14 @@ # Benefit = total points x point value (8.150.620.10 A NMAC): 35 a point in FFY2026 and -# 40 in FFY2027 (Point and Income Guides). Energy burden = monthly heating bill (annual -# bill / 12, raised to the energy standard allowance of 291 in FFY2026 and 298 in FFY2027 -# when lower) / monthly countable income: 0 points below 6%, 1 from 6%, 2 from 11%, 3 -# from 16%; zero income receives 3; no energy points with heat in rent or gathered wood -# (8.150.620.9 A). Propane adds 2 (8.150.620.9 A(2)). Income points: 3 at or below 110% -# of the poverty guideline, otherwise 2 (Point and Income Guides; FFY2026 size-one cutoff -# 1,435 a month). Two points per category for a counted member 5 or younger, 60 or older, -# or disabled (8.150.620.9 C). Wages are at the 2026 New Mexico minimum wage of 12.00 an +# 40 in FFY2027 (Point and Income Guides). Energy burden = monthly bill (annual heating +# fuel or electric bill / 12, raised to the energy standard allowance of 291 in FFY2026 +# and 298 in FFY2027 when lower) / monthly countable income: 0 points below 6%, 1 from +# 6%, 2 from 11%, 3 from 16%; zero income receives 3; the bill giving more points is used +# (LHP 602); no energy points without a direct bill or with gathered wood (8.150.620.9 A). +# Propane adds 2 on the propane bill (8.150.620.9 A(2)). Income points: 3 at or below +# 100% of the guideline (8.150.620.9 B; size-one 15,650 a year), otherwise 2. Two points +# per category for a counted member 5 or younger, 60 or older, or disabled (is_disabled, +# SSDI, employment disability benefits or SSI as blind or disabled; 8.150.620.9 C). +# Wages are at the 2026 New Mexico minimum wage of 12.00 an # hour (24,960 full time); gas of 900 is 75 a month, within common winter gas bills of 60 # to 120; electricity of 1,080 is about the EIA 2024 New Mexico average residential bill # of 90 a month. @@ -31,7 +33,7 @@ state_code: NM output: # max(1,080 / 12, 291) / (21,825 / 12) = 291 / 1,818.75 = 0.16 -> 3 energy - # 1,818.75 > 1,435 -> 2 income; 5 points x 35 + # 1,818.75 > 1,304.17 -> 2 income; 5 points x 35 nm_liheap: 175 - name: Case 2, a burden just below 16 percent receives 2 energy points. @@ -80,7 +82,7 @@ state_code: NM output: # max(3,960 / 12, 291) / (36,000 / 12) = 330 / 3,000 = 0.11 -> 2 energy; 2 propane - # 3,000 > 2,443 (size three) -> 2 income; 6 points x 35 + # 3,000 > 2,220.83 (size three) -> 2 income; 6 points x 35 nm_liheap: 210 - name: Case 4, a burden just below 11 percent receives 1 energy point. @@ -137,7 +139,7 @@ state_code: NM output: # 291 / (58,200 / 12) = 291 / 4,850 = 0.06 -> 1 energy - # 4,850 > 3,955 (size six) -> 2 income; 3 points x 35 + # 4,850 > 3,595.83 (size six) -> 2 income; 3 points x 35 nm_liheap: 105 - name: Case 6, a burden just below 6 percent receives no energy points and the 2-point minimum. @@ -186,7 +188,7 @@ members: [person1] state_code: NM output: - # Zero income -> 3 energy; 0 <= 1,435 -> 3 income; 6 points x 35 + # Zero income -> 3 energy; 0 <= 1,304.17 -> 3 income; 6 points x 35 nm_liheap: 210 - name: Case 8, a propane-heated household with heat in rent receives no energy or propane points. @@ -210,7 +212,8 @@ members: [person1] state_code: NM output: - # 0 energy and 0 propane (8.150.620.9 A(3)(a)); 1,000 <= 1,435 -> 3 income; 3 points x 35 + # 0 energy and 0 propane (8.150.620.9 A(3)(a)); 1,000 <= 1,304.17 -> 3 income + # 3 points x 35 nm_liheap: 105 - name: Case 9, a household heating with gathered wood receives no energy points. @@ -256,14 +259,15 @@ # max(600 / 12, 291) / 1,000 = 0.291 -> 3 energy; 3 income; 6 points x 35 nm_liheap: 210 -- name: Case 11, income exactly at the 110 percent cutoff receives 3 income points. +- name: Case 11, income exactly at 100 percent of the guideline receives 3 income points. period: 2026 absolute_error_margin: 0.01 input: people: person1: age: 40 - employment_income: 17_220 + # About 25 hours a week at 12.00. + employment_income: 15_650 spm_units: spm_unit: members: [person1] @@ -274,18 +278,18 @@ members: [person1] state_code: NM output: - # 17,220 / 12 = 1,435 <= 1,435 -> 3 income; 291 / 1,435 = 0.203 -> 3 energy - # 6 points x 35 + # 15,650 <= 15,650 x 1 (1,304.17 <= 1,304.17 a month) -> 3 income + # 291 / 1,304.17 = 0.223 -> 3 energy; 6 points x 35 nm_liheap: 210 -- name: Case 12, income one dollar a year above the 110 percent cutoff receives 2 income points. +- name: Case 12, income one dollar a year above 100 percent of the guideline receives 2 income points. period: 2026 absolute_error_margin: 0.01 input: people: person1: age: 40 - employment_income: 17_221 + employment_income: 15_651 spm_units: spm_unit: members: [person1] @@ -296,7 +300,8 @@ members: [person1] state_code: NM output: - # 17,221 / 12 = 1,435.08 > 1,435 -> 2 income; 3 energy; 5 points x 35 + # 15,651 > 15,650 (1,304.25 > 1,304.17 a month) -> 2 income + # 291 / 1,304.25 = 0.223 -> 3 energy; 5 points x 35 nm_liheap: 175 - name: Case 13, a five-year-old adds vulnerable points. @@ -320,7 +325,7 @@ members: [person1, person2] state_code: NM output: - # 291 / 2,080 = 0.140 -> 2 energy; 2,080 > 1,939 (size two) -> 2 income + # 291 / 2,080 = 0.140 -> 2 energy; 2,080 > 1,762.50 (size two) -> 2 income # 2 vulnerable; 6 points x 35 nm_liheap: 210 @@ -366,11 +371,11 @@ members: [person1] state_code: NM output: - # 291 / 1,666.67 = 0.175 -> 3 energy; 1,666.67 > 1,435 -> 2 income + # 291 / 1,666.67 = 0.175 -> 3 energy; 1,666.67 > 1,304.17 -> 2 income # 2 vulnerable; 7 points x 35 nm_liheap: 245 -- name: Case 16, a disabled member adds vulnerable points. +- name: Case 16, a member flagged as disabled without disability-based income adds vulnerable points. period: 2026 absolute_error_margin: 0.01 input: @@ -379,8 +384,8 @@ age: 45 is_disabled: true ssi: 0 - # Disability insurance benefit of 1,500 a month. - social_security_disability: 18_000 + # About 29 hours a week at 12.00. + employment_income: 18_000 spm_units: spm_unit: members: [person1] @@ -391,8 +396,8 @@ members: [person1] state_code: NM output: - # 291 / 1,500 = 0.194 -> 3 energy; 1,500 > 1,435 -> 2 income - # 2 vulnerable; 7 points x 35 + # 291 / 1,500 = 0.194 -> 3 energy; 18,000 > 15,650 (1,500 > 1,304.17 a month) + # -> 2 income; 2 vulnerable for the disability flag; 7 points x 35 nm_liheap: 245 - name: Case 17, an undocumented 65-year-old adds no vulnerable points. @@ -417,7 +422,7 @@ members: [person1, person2] state_code: NM output: - # Size one: 291 / 1,500 = 0.194 -> 3 energy; 1,500 > 1,435 -> 2 income + # Size one: 291 / 1,500 = 0.194 -> 3 energy; 1,500 > 1,304.17 -> 2 income # 0 vulnerable; 5 points x 35 nm_liheap: 175 @@ -451,7 +456,7 @@ state_code: NM output: # max(150, 291) / 1,200 = 0.243 -> 3 energy; 2 propane - # 1,200 <= 2,947 (size four) -> 3 income + # 1,200 <= 2,679.17 (size four) -> 3 income # 2 for the children (once) + 2 for age 62 + 2 for disability = 6; 14 points x 35 nm_liheap: 490 @@ -496,7 +501,7 @@ state_code: NM output: # max(75, 298) / (22,350 / 12) = 298 / 1,862.50 = 0.16 -> 3 energy - # 1,862.50 > 1,463 -> 2 income; 5 points x 40 + # 1,862.50 > 1,330 -> 2 income; 5 points x 40 nm_liheap: 200 - name: Case 21, IRA withdrawals count as unearned income and put a retiree over the FFY2026 limit. @@ -526,3 +531,202 @@ # 24,400 > 1,956 x 12 = 23,472; without the withdrawals 14,400 would be # eligible for 3 energy + 3 income + 2 elderly = 8 points x 35 = 280 nm_liheap: 0 + +- name: Case 22, a subsidized tenant with heat in rent who pays an electric bill receives energy points from that bill. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + pre_subsidy_electricity_expense: 1_080 + households: + household: + members: [person1] + state_code: NM + output: + # No gas bill; electric bill max(1,080 / 12, 291) / (12,000 / 12) = 291 / 1,000 + # = 0.291 -> 3 energy; 12,000 <= 15,650 (1,000 <= 1,304.17 a month) -> 3 income + # 6 points x 35 + nm_liheap: 210 + +- name: Case 23, the propane bill with its propane points gives more points than a larger electric bill. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + # About 35 hours a week at 12.00. + employment_income: 21_600 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + # Propane of 75 a month averaged over the year. + bottled_gas_expense: 900 + pre_subsidy_electricity_expense: 1_080 + households: + household: + members: [person1] + state_code: NM + output: + # Propane bill: max(900 / 12, 291) / (21,600 / 12) = 291 / 1,800 = 0.162 -> 3 energy + # + 2 propane = 5; electric bill: max(1,080 / 12, 291) / 1,800 = 0.162 -> 3 + # max(5, 3) = 5 (the larger electric bill alone gives 3) + # 21,600 > 15,650 (1,800 > 1,304.17 a month) -> 2 income; 7 points x 35 + nm_liheap: 245 + +- name: Case 24, Social Security disability insurance adds vulnerable points without the disability flag. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + ssi: 0 + # Disability insurance benefit of 1,500 a month. + social_security_disability: 18_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 291 / 1,500 = 0.194 -> 3 energy; 18,000 > 15,650 (1,500 > 1,304.17 a month) + # -> 2 income; SSDI is disability-based income -> 2 vulnerable; 7 points x 35 + nm_liheap: 245 + +- name: Case 25, SSI paid on the basis of disability adds vulnerable points without the disability flag. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + meets_ssi_disability_criteria: true + # 2026 federal SSI benefit rate of 994 a month. + ssi: 11_928 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 291 / 994 = 0.293 -> 3 energy; 11,928 <= 15,650 (994 <= 1,304.17 a month) + # -> 3 income; SSI as disabled (no earnings, so no SGA) -> 2 vulnerable + # 8 points x 35 + nm_liheap: 280 + +- name: Case 26, SSI paid on the basis of age adds elderly points but no disability points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 66 + ssi: 11_928 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 291 / 994 = 0.293 -> 3 energy; 994 <= 1,304.17 -> 3 income + # 2 for age 66; aged SSI is not disability-based income -> 0; 8 points x 35 + nm_liheap: 280 + +- name: Case 27, a household heating with gathered wood receives no energy points from its electric bill. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: WOOD + wood_expense: 0 + pre_subsidy_electricity_expense: 1_080 + households: + household: + members: [person1] + state_code: NM + output: + # The electric bill alone would give max(1,080 / 12, 291) / 1,000 = 0.291 -> 3 + # energy, but gathered wood receives 0 energy (8.150.620.9 A(3)(b)) + # 12,000 <= 15,650 (1,000 <= 1,304.17 a month) -> 3 income; 3 points x 35 + nm_liheap: 105 + +- name: Case 28, employment disability benefits add vulnerable points without the disability flag. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + ssi: 0 + # Employer long-term disability benefit of 1,500 a month. + disability_benefits: 18_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 291 / 1,500 = 0.194 -> 3 energy; 18,000 > 15,650 (1,500 > 1,304.17 a month) + # -> 2 income; employment disability benefits are disability-based income + # -> 2 vulnerable; 7 points x 35 + nm_liheap: 245 + +- name: Case 29, a blind member reported as receiving SSI adds vulnerable points. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + is_blind: true + # Reported SSI receipt with no computed SSI payment. + receives_ssi: true + ssi: 0 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: NM + output: + # 291 / 1,000 = 0.291 -> 3 energy; 12,000 <= 15,650 (1,000 <= 1,304.17 a month) + # -> 3 income; SSI on the basis of blindness -> 2 vulnerable; 8 points x 35 + nm_liheap: 280 diff --git a/policyengine_us/variables/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.py b/policyengine_us/variables/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.py index f3b8b45428f..712a553cfcc 100644 --- a/policyengine_us/variables/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.py +++ b/policyengine_us/variables/gov/states/nm/hca/liheap/eligibility/nm_liheap_eligible.py @@ -32,22 +32,25 @@ def formula(spm_unit, period, parameters): heat_in_rent = spm_unit("heat_expense_included_in_rent", period) heating_type = spm_unit("heating_type", period) types = heating_type.possible_values - # A home without heat has no heating cost. An unspecified fuel - # qualifies only when heat is included in the rent. - fuel_qualifies = (heating_type != types.NONE) & ( - heat_in_rent | (heating_type != types.UNSPECIFIED) + # heating_type is a required input and an unspecified fuel qualifies + # only when heat is included in the rent. A home without heating + # stays eligible: the energy cost may be a cooling cost, and the one + # regular benefit pays a heating or cooling bill the household picks. + known_fuel = heat_in_rent | (heating_type != types.UNSPECIFIED) + # A heating fuel or electric bill is an energy cost, including the + # additional out-of-pocket cost of a subsidized tenant whose heat is + # included in the rent. Electricity used only for lighting has no + # separate input. + has_energy_bill = (spm_unit("heating_expense", period) > 0) | ( + spm_unit("pre_subsidy_electricity_expense", period) > 0 ) - # Heat included in unsubsidized rent qualifies even when no utility - # cost is designated. Subsidized tenants whose heat is included in the - # rent qualify only with an additional out-of-pocket heating cost. + # Without a bill, heat included in unsubsidized rent qualifies, and + # households that heat with wood they gather themselves have a heating + # responsibility. subsidized = spm_unit("receives_housing_assistance", period) | ( spm_unit.household("is_in_public_housing", period) ) - out_of_pocket = spm_unit("heating_expense", period) > 0 - rent_responsible = ~subsidized | out_of_pocket - # Households that heat with wood they gather themselves have a heating - # responsibility without a bill. - wood = heating_type == types.WOOD - direct_responsible = spm_unit("has_heating_expense", period) | wood - responsible = where(heat_in_rent, rent_responsible, direct_responsible) - return (size > 0) & income_eligible & fuel_qualifies & responsible + responsible = has_energy_bill | where( + heat_in_rent, ~subsidized, heating_type == types.WOOD + ) + return (size > 0) & income_eligible & known_fuel & responsible diff --git a/policyengine_us/variables/gov/states/nm/hca/liheap/nm_liheap.py b/policyengine_us/variables/gov/states/nm/hca/liheap/nm_liheap.py index 3e84cb35831..d70cccf6de9 100644 --- a/policyengine_us/variables/gov/states/nm/hca/liheap/nm_liheap.py +++ b/policyengine_us/variables/gov/states/nm/hca/liheap/nm_liheap.py @@ -10,12 +10,12 @@ class nm_liheap(Variable): label = "New Mexico LIHEAP regular heating assistance" documentation = ( "New Mexico's one regular benefit per federal fiscal year, which the " - "household applies to heating or cooling: energy burden, propane, " - "income and vulnerable population points times the point value. " - "Heating and cooling share this one annual benefit. Verified for " - "FY2026 and FY2027; earlier years are unverified estimates through " - "parameter backfilling. Crisis assistance and weatherization are out " - "of scope." + "household applies to the heating fuel or electric bill it picks for " + "heating or cooling: energy burden, propane, income and vulnerable " + "population points times the point value. Heating and cooling share " + "this one annual benefit. Verified for FY2026 and FY2027; earlier " + "years are unverified estimates through parameter backfilling. Crisis " + "assistance and weatherization are out of scope." ) defined_for = "nm_liheap_eligible" reference = ( @@ -28,47 +28,57 @@ class nm_liheap(Variable): # PDF pages 9-10 "https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=9", "https://www.hca.nm.gov/wp-content/uploads/DETAILED-MODEL-PLAN-LIHEAP_10_01_2026-4.pdf#page=9", + # PDF pages 2-3 + "https://www.hca.nm.gov/wp-content/uploads/LHP-602-English-LIHEAP-Application-05.01.2026.pdf#page=2", ) def formula(spm_unit, period, parameters): p = parameters(period).gov.states.nm.hca.liheap income = spm_unit("nm_liheap_countable_income", period) - heating_expense = spm_unit("heating_expense", period) - heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + heating_bill = spm_unit("heating_expense", period) + electric_bill = spm_unit("pre_subsidy_electricity_expense", period) heating_type = spm_unit("heating_type", period) types = heating_type.possible_values - # Households whose utilities are included in the rent and households - # that heat with wood they do not purchase receive no energy burden - # points. - unpurchased_wood = (heating_type == types.WOOD) & (heating_expense == 0) - receives_energy_points = ~heat_in_rent & ~unpurchased_wood - # The annual heating bill over 12 months, floored at the energy - # standard allowance, stands in for the highest monthly bill of the - # last 12 months, and annual income over 12 months for 30-day income. - # The ratio of the annual amounts equals the monthly bill over monthly - # income. It is computed in float64 with a single division, so a - # burden exactly at a cutoff lands on it even when the monthly + # The annual bill over 12 months, floored at the energy standard + # allowance, stands in for the highest monthly bill of the last 12 + # months, and annual income over 12 months for 30-day income. The + # ratio of the annual amounts equals the monthly bill over monthly + # income. It is computed in float64 with a single division per bill, + # so a burden exactly at a cutoff lands on it even when the monthly # amounts have no exact binary value. - annual_bill = max_( - heating_expense.astype(np.float64), - p.payment.energy_standard_allowance * MONTHS_IN_YEAR, - ) + allowance = p.payment.energy_standard_allowance * MONTHS_IN_YEAR income_64 = income.astype(np.float64) has_income = income > 0 - burden = np.divide( - annual_bill, - income_64, - out=np.zeros_like(income_64), - where=has_income, - ) burden_points = p.payment.energy_burden_points - # Households with zero income receive the top energy burden points. - energy_points = where( - has_income, burden_points.calc(burden), burden_points.amounts[-1] - ) + + def bill_points(bill): + burden = np.divide( + max_(bill.astype(np.float64), allowance), + income_64, + out=np.zeros_like(income_64), + where=has_income, + ) + # Households with zero income receive the top energy burden + # points. Without a direct bill there are no energy points. + points = where( + has_income, burden_points.calc(burden), burden_points.amounts[-1] + ) + return where(bill > 0, points, 0) + + # The household picks one heating or cooling bill, modeled as the one + # giving more points: the heating fuel bill, with the propane points + # when that fuel is propane, or the electric bill. Households whose + # utilities are all included in the rent have no direct bill, and + # households that heat with wood they do not purchase receive no + # energy points. propane = heating_type == types.PROPANE - energy_cost_points = receives_energy_points * ( - energy_points + propane * p.payment.propane_points + heating_points = bill_points(heating_bill) + where( + propane & (heating_bill > 0), p.payment.propane_points, 0 + ) + electric_points = bill_points(electric_bill) + unpurchased_wood = (heating_type == types.WOOD) & (heating_bill == 0) + energy_cost_points = where( + unpurchased_wood, 0, max_(heating_points, electric_points) ) p_income = p.payment.income_points size = spm_unit("nm_liheap_household_size", period) @@ -80,30 +90,38 @@ def formula(spm_unit, period, parameters): parameters, year_lag=p.eligibility.fpg_year_lag, ) - # Three income points at or below the low-income cutoff, 110% of the - # guideline (see low_income_fpg_rate), and two points above it. The - # rate is applied to the guideline for each household size and the - # monthly cutoff is rounded to the nearest dollar, which reproduces - # the guides' printed rows for sizes 1-8. For sizes 9 and over, the - # guides' "each additional" increments can differ by $1 or more a - # month, growing with size (FFY2027 size 9: $5,629 by increment, - # $5,628 by rate). - monthly_cutoff = np.floor( - guideline * p_income.low_income_fpg_rate / MONTHS_IN_YEAR + 0.5 - ) - monthly_income = income / MONTHS_IN_YEAR + # Three income points at or below the low-income cutoff, a share of + # the guideline (see low_income_fpg_rate), and two points above it. + # Annual income against the annual cutoff equals the monthly test. income_points = where( - monthly_income <= monthly_cutoff, p_income.low, p_income.high + income <= guideline * p_income.low_income_fpg_rate, + p_income.low, + p_income.high, ) # Each vulnerable group with at least one counted member adds points - # once. is_disabled stands in for disability-based income or a - # doctor's statement. + # once. A member receiving disability-based income (Social Security + # disability benefits, employment disability benefits, or SSI paid on + # the basis of blindness or disability, not age) qualifies, and + # is_disabled stands in for a doctor's statement. SSI is + # MONTH-defined, so add() sums it over the year. person = spm_unit.members age = person("age", period) counted = person("is_citizen_or_legal_immigrant", period) young_child = spm_unit.any(counted & (age < p.payment.young_child_max_age + 1)) elderly = spm_unit.any(counted & (age >= p.payment.elderly_age)) - disabled = spm_unit.any(counted & person("is_disabled", period)) + ssi_recipient = (add(person, period, ["ssi"]) > 0) | ( + add(person, period, ["receives_ssi"]) > 0 + ) + ssi_disability = ssi_recipient & ( + person("is_blind", period) | person("is_ssi_disabled", period) + ) + disability_income = ( + add(person, period, ["social_security_disability", "disability_benefits"]) + > 0 + ) | ssi_disability + disabled = spm_unit.any( + counted & (person("is_disabled", period) | disability_income) + ) vulnerable_groups = ( young_child.astype(int) + elderly.astype(int) + disabled.astype(int) ) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 617f350e69f..7b816212b60 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -4104,6 +4104,37 @@ candidates: prs: [] state: unreviewed note: PE-US follows agency practice; establish authority for departure from the codified regulation. +- id: us-nm:liheap-plan-income-checklist + program: liheap + years: + from: 2026 + to: 2027 + type: federal-fiscal + question: Does New Mexico LIHEAP count gross Social Security including the Medicare premium, exclude lump sums, count + cash contributions and count earnings only of members over age 18, as the regulation provides, or follow the state + plan's income checklist? + sources: + - kind: regulation + citation: 8.150.520.12 B and 8.150.520.13 NMAC + url: https://www.srca.nm.gov/parts/title08/08.150.0520.html + - kind: plan + citation: FY2026 and FY2027 LIHEAP State Plans, section 1.9 + url: https://liheapch.acf.gov/docs/2026/state-plans/NM_Plan_2026.pdf#page=6 + readings: + statute: No statute supplied; the regulation counts gross OASDI including the Medicare premium and contributions + from family or friends, excludes lump sums, and counts earnings of members over the age of 18. + other: The checklist marks Social Security excluding the Medicare deduction and one-time lump sums, and leaves cash + gifts and earned income of a child under 18 unchecked. + example: A one-person FFY2026 household with $2,000 a month of OASDI and a $202.90 Part B premium counts $2,000 under + the rule and $1,797.10 under the checklist. + pe_us: + follows: statute + code: [] + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10431 + state: ruled + note: 'Statute governs (2026-10-19 rule): PE-US follows the regulation for all four items. Code not yet on main: + nm_liheap_countable_income, gov.states.nm.hca.liheap.income.sources.unearned, gov.states.nm.hca.liheap.income.earned_income_age.' - id: us-nm:liheap-three-income-points program: liheap years: @@ -4129,12 +4160,14 @@ candidates: example: In FFY2026, a one-person household with monthly income of $1,400 (107% of the guideline) scores two income points under the regulation and three under the guides. pe_us: - follows: unknown + follows: statute code: [] - prs: [] + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10431 state: ruled - note: 'PE-US follows the guides, which HCA applies; the regulation lets the secretary adjust point values but does - not mention this cutoff. Code not yet on main: nm_liheap, gov.states.nm.hca.liheap.payment.income_points.low_income_fpg_rate.' + note: 'Statute governs (2026-10-19 rule): PE-US follows the regulation. The guides and the FFY2026 ISD 017 card apply + 110%, and 8.150.620.10 C lets the secretary adjust point values, not the cutoff. Code not yet on main: nm_liheap, + gov.states.nm.hca.liheap.payment.income_points.low_income_fpg_rate.' - id: us-nm:medical-care-credit-mixed-age-spouses program: income-tax years: