diff --git a/changelog.d/liheap-federal-fpg-floor.fixed.md b/changelog.d/liheap-federal-fpg-floor.fixed.md new file mode 100644 index 00000000000..18c4f573594 --- /dev/null +++ b/changelog.d/liheap-federal-fpg-floor.fixed.md @@ -0,0 +1 @@ +Apply the federal 110% poverty guideline floor to District of Columbia, Massachusetts, Mississippi, New Jersey and Oregon LIHEAP income eligibility. diff --git a/policyengine_us/parameters/gov/hhs/liheap/fpg_floor.yaml b/policyengine_us/parameters/gov/hhs/liheap/fpg_floor.yaml new file mode 100644 index 00000000000..aeb7f8c5b07 --- /dev/null +++ b/policyengine_us/parameters/gov/hhs/liheap/fpg_floor.yaml @@ -0,0 +1,11 @@ +description: The US Department of Health and Human Services limits income-based exclusion to households with income of at least this percentage of the poverty guideline under the Low Income Home Energy Assistance Program. +# Pub. L. 98-558, section 605(a)(2), added the floor for fiscal year 1986 and later. +values: + 1985-10-01: 1.1 +metadata: + period: year + unit: /1 + label: LIHEAP poverty guideline income floor + reference: + - title: 42 U.S.C. 8624(b)(2)(B) + href: https://www.law.cornell.edu/uscode/text/42/8624#b_2_B diff --git a/policyengine_us/parameters/gov/states/dc/doee/liheap/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/dc/doee/liheap/fpg_year_lag.yaml new file mode 100644 index 00000000000..a59be34ef39 --- /dev/null +++ b/policyengine_us/parameters/gov/states/dc/doee/liheap/fpg_year_lag.yaml @@ -0,0 +1,18 @@ +description: The District of Columbia uses the poverty guidelines from this many years before the model year for the federal income floor under the Low Income Home Energy Assistance Program. +# HHS makes each January's guidelines mandatory for LIHEAP from the next +# October 1, so model period 2024 (FY2024) uses the 2023 guidelines and 2026 +# (FY2026) the 2025 guidelines. The District's own limit uses state median +# income. +values: + 2021-10-01: 1 +metadata: + unit: year + period: year + label: DC LIHEAP poverty guideline year lag + reference: + - title: LIHEAP Information Memoranda, annual poverty guideline memoranda + href: https://acf.gov/ocs/policy-guidance/liheap-information-memoranda + - title: LIHEAP IM 2023-01, poverty guidelines for optional use in FFY 2023 and mandatory use in FFY 2024 + href: https://acf.gov/archive/ocs/policy-guidance/liheap-im-2023-01-federal-poverty-guidelines-optional-use-ffy-2023and + - 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 diff --git a/policyengine_us/parameters/gov/states/ms/mdhs/liheap/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/ms/mdhs/liheap/fpg_year_lag.yaml new file mode 100644 index 00000000000..6bc3c65113b --- /dev/null +++ b/policyengine_us/parameters/gov/states/ms/mdhs/liheap/fpg_year_lag.yaml @@ -0,0 +1,15 @@ +description: Mississippi uses the poverty guidelines from this many years before the model year for the federal income floor under the Low Income Home Energy Assistance Program. +# HHS makes each January's guidelines mandatory for LIHEAP from the next +# October 1, so model period 2026 (FY2026) uses the 2025 guidelines and 2027 +# (FY2027) the 2026 guidelines. Mississippi's own limit uses state median income. +values: + 2025-10-01: 1 +metadata: + unit: year + period: year + label: Mississippi 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: LIHEAP IM 2026-01, poverty guidelines for optional use in FY26 and mandatory use in FY27 + href: https://acf.gov/ocs/policy-guidance/liheap-im2026-01-federal-poverty-guidelines-fpg-and-state-median-income-smi diff --git a/policyengine_us/parameters/gov/states/nj/dca/liheap/eligibility/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/nj/dca/liheap/eligibility/fpg_year_lag.yaml new file mode 100644 index 00000000000..12808097bbe --- /dev/null +++ b/policyengine_us/parameters/gov/states/nj/dca/liheap/eligibility/fpg_year_lag.yaml @@ -0,0 +1,20 @@ +description: New Jersey uses the poverty guidelines from this many years before the model year for the federal income floor under the Low Income Home Energy Assistance Program. +# HHS makes each January's guidelines mandatory for LIHEAP from the next +# October 1, so model period 2024 (FY2024) uses the 2023 guidelines and 2026 +# (FY2026) the 2025 guidelines. New Jersey's own limit uses state median +# income. +values: + 2021-10-01: 1 +metadata: + unit: year + period: year + label: New Jersey LIHEAP poverty guideline year lag + reference: + - title: LIHEAP Information Memoranda, annual poverty guideline memoranda + href: https://acf.gov/ocs/policy-guidance/liheap-information-memoranda + - title: LIHEAP IM 2023-01, poverty guidelines for optional use in FFY 2023 and mandatory use in FFY 2024 + href: https://acf.gov/archive/ocs/policy-guidance/liheap-im-2023-01-federal-poverty-guidelines-optional-use-ffy-2023and + - title: LIHEAP IM 2025-02, Attachment 2, poverty guidelines for mandatory use in FY26 + href: https://acf.gov/sites/default/files/documents/ocs/COMM_LIHEAP_IM2025-02_FPGSte-Table_Att2.pdf#page=1 + - title: LIHEAP IM 2026-01, poverty guidelines for optional use in FY26 and mandatory use in FY27 + href: https://acf.gov/ocs/policy-guidance/liheap-im2026-01-federal-poverty-guidelines-fpg-and-state-median-income-smi diff --git a/policyengine_us/parameters/gov/states/or/ohcs/liheap/eligibility/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/or/ohcs/liheap/eligibility/fpg_year_lag.yaml new file mode 100644 index 00000000000..d5bc8654377 --- /dev/null +++ b/policyengine_us/parameters/gov/states/or/ohcs/liheap/eligibility/fpg_year_lag.yaml @@ -0,0 +1,18 @@ +description: Oregon uses the poverty guidelines from this many years before the model year for the federal income floor under the Low Income Home Energy Assistance Program. +# HHS makes each January's guidelines mandatory for LIHEAP from the next +# October 1, so model period 2025 (PY2025) uses the 2024 guidelines, 2026 +# (PY2026) the 2025 guidelines and 2027 (PY2027) the 2026 guidelines. Oregon's +# own limit uses state median income. +values: + 2024-10-01: 1 +metadata: + unit: year + period: year + label: Oregon LIHEAP poverty guideline year lag + reference: + - title: LIHEAP IM 2024-02, Attachment 2, poverty guidelines for mandatory use in FY25 + href: https://acf.gov/sites/default/files/documents/ocs/COMM_LIHEAP_IM%202024-02_Att2FPGTables_States.pdf#page=1 + - title: LIHEAP IM 2025-02, Attachment 2, poverty guidelines for mandatory use in FY26 + href: https://acf.gov/sites/default/files/documents/ocs/COMM_LIHEAP_IM2025-02_FPGSte-Table_Att2.pdf#page=1 + - title: LIHEAP IM 2026-01, poverty guidelines for optional use in FY26 and mandatory use in FY27 + href: https://acf.gov/ocs/policy-guidance/liheap-im2026-01-federal-poverty-guidelines-fpg-and-state-median-income-smi diff --git a/policyengine_us/tests/policy/baseline/gov/states/dc/doee/liheap/dc_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/dc/doee/liheap/dc_liheap_eligible.yaml index 08b7843f8b1..a4b65bf8b07 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/dc/doee/liheap/dc_liheap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/dc/doee/liheap/dc_liheap_eligible.yaml @@ -17,3 +17,44 @@ state_code: DC output: dc_liheap_eligible: false + +- name: Case 3, federal 110% poverty floor above the 60% state median income limit at size 36. + period: 2024 + input: + state_code: [DC, DC, DC, DC, DC] + spm_unit_size: [36, 36, 36, 36, 35] + irs_gross_income: [212_000, 213_927.99, 213_928, 213_929, 210_000] + output: + # 42 U.S.C. 8624(b)(2)(B) bars excluding a household on income below 110% + # of the poverty guideline. FY2024 uses the 2023 guidelines (LIHEAP IM + # 2023-01): 14,580 + 5,140 x 35 = 194,480 for 36 people, and 110% is + # 213,928. The 60% SMI limit for 36 people is 212,669.78. + # Household 1 is below the 60% SMI limit: eligible. + # Household 2 is one cent below the floor: eligible. + # Household 3 is exactly at the floor, which is not below 110%: not eligible. + # Household 4 is above the floor: not eligible. + # Household 5 has 35 people, whose 209,795.87 limit exceeds the 208,274 + # floor (110% of 189,340), so the limit still applies: not eligible. + dc_liheap_eligible: [true, true, false, false, false] + +- name: Case 4, the floor uses the poverty guidelines from the year before the model year. + period: 2024 + input: + state_code: DC + spm_unit_size: 36 + irs_gross_income: 215_000 + output: + # FY2024 uses the 2023 guidelines, so the floor is 213,928. The 2024 + # guidelines would give 110% x (15,060 + 5,380 x 35) = 223,696. + dc_liheap_eligible: false + +- name: Case 5, FY2023 uses the 2022 poverty guidelines for the floor. + period: 2023 + input: + state_code: [DC, DC] + spm_unit_size: [39, 39] + irs_gross_income: [212_244.99, 212_245] + output: + # 110% x (13,590 + 4,720 x 38) = 212,245 for 39 people, above the + # 210,731.60 FY2023 60% SMI limit. + dc_liheap_eligible: [true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ma/doer/liheap/eligibility/ma_liheap_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ma/doer/liheap/eligibility/ma_liheap_income_eligible.yaml index 80e9d026c33..1c39c912b43 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ma/doer/liheap/eligibility/ma_liheap_income_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ma/doer/liheap/eligibility/ma_liheap_income_eligible.yaml @@ -27,3 +27,45 @@ output: ma_liheap_income_eligible: true # hhs_smi: 119_895 + +- name: Case 4, federal 110% poverty floor above the 60% state median income limit at size 34. + period: 2026 + input: + state_code: [MA, MA, MA, MA, MA] + spm_unit_size: [34, 34, 34, 34, 33] + ma_liheap_income: [215_000, 216_864.99, 216_865, 216_866, 213_000] + output: + # 42 U.S.C. 8624(b)(2)(B) bars excluding a household on income below 110% + # of the poverty guideline. FY2026 uses the 2025 guidelines (LIHEAP IM + # 2025-02): 15,650 + 5,500 x 33 = 197,150 for 34 people, and 110% is + # 216,865. The 60% SMI limit for 34 people is 215,077.68. + # Household 1 is below the 60% SMI limit: eligible. + # Household 2 is one cent below the floor: eligible. + # Household 3 is exactly at the floor, which is not below 110%: not eligible. + # Household 4 is above the floor: not eligible. + # Household 5 has 33 people, whose 212,090.49 limit exceeds the 210,815 + # floor (110% of 191,650), so the limit still applies: not eligible. + ma_liheap_income_eligible: [true, true, false, false, false] + +- name: Case 5, the floor uses the poverty guidelines from the year before the model year. + period: 2026 + input: + state_code: MA + spm_unit_size: 34 + ma_liheap_income: 220_000 + output: + # FY2026 uses the 2025 guidelines, so the floor is 216,865. The 2026 + # guidelines would give 110% x (15,960 + 5,680 x 33) = 223,740. + ma_liheap_income_eligible: false + +- name: Case 6, FY2025 uses the 2024 poverty guidelines for the floor. + period: 2025 + input: + state_code: [MA, MA] + spm_unit_size: [32, 32] + ma_liheap_income: [200_023.99, 200_024] + output: + # LIHEAP IM 2024-02 makes the 2024 guidelines mandatory in FY2025: + # 110% x (15,060 + 5,380 x 31) = 200,024 for 32 people, above the + # 198,676.80 FY2025 60% SMI limit. + ma_liheap_income_eligible: [true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ms/mdhs/liheap/ms_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ms/mdhs/liheap/ms_liheap.yaml index e940bc034f8..54a044a2b9a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ms/mdhs/liheap/ms_liheap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ms/mdhs/liheap/ms_liheap.yaml @@ -379,3 +379,22 @@ # Household 2 pays its own electric bill of 45 a month. Matrix page 1, # household size 1, first band, Electric 700: min(700, 540) = 540. ms_liheap: [0, 540] +- name: Case 20, a household that qualifies only through the federal 110% poverty floor is paid from the last income band. + period: 2026 + input: + state_code: [MS, MS] + age: [40, 40] + ms_liheap_household_size: [11, 11] + ms_liheap_income: [77_000, 77_715] + heating_type: [PROPANE, PROPANE] + heating_expense: [2_000, 2_000] + output: + # Both incomes exceed the 75,593 size-11 limit (manual PDF page 58). + # Household 1 is below the 77,715 floor (110% of the 2025 guideline of + # 70,650) and qualifies under 42 U.S.C. 8624(b)(2)(B). Household 2 is at + # the floor and does not qualify. + ms_liheap_eligible: [true, false] + # The size-11 matrix (manual PDF pages 59-64) has an open fourth band + # above 52,988. Propane column: 800 - 3 x 50 = 650. A 2,000 bill exceeds + # the cap, and no electric bill is reported. + ms_liheap: [650, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ms/mdhs/liheap/ms_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ms/mdhs/liheap/ms_liheap_eligible.yaml index f2a24f109ae..6f75d720b6e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ms/mdhs/liheap/ms_liheap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ms/mdhs/liheap/ms_liheap_eligible.yaml @@ -64,3 +64,51 @@ # Household 2 lives in public housing with heat included in the rent and no # energy bill of its own: not eligible. ms_liheap_eligible: [true, false] +- name: Case 5, federal 110% poverty floor above the 60% state median income limit at size 11. + period: 2026 + input: + state_code: [MS, MS, MS, MS, MS] + age: [40, 40, 40, 40, 40] + ms_liheap_household_size: [11, 11, 11, 11, 10] + ms_liheap_income: [75_593, 77_714.99, 77_715, 77_716, 74_051] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY] + heating_expense: [1_000, 1_000, 1_000, 1_000, 1_000] + output: + # 42 U.S.C. 8624(b)(2)(B) bars excluding a household on income below 110% + # of the poverty guideline. FY2026 uses the 2025 guidelines (LIHEAP IM + # 2025-02): 15,650 + 5,500 x 10 = 70,650 for 11 people, and 110% is 77,715. + # The 60% SMI limit for 11 people is 75,593 (manual PDF page 58). + # Household 1 is at the 60% SMI limit: eligible. + # Household 2 is one cent below the floor: eligible. + # Household 3 is exactly at the floor, which is not below 110%: not eligible. + # Household 4 is above the floor: not eligible. + # Household 5 has 10 people, whose 74,050 limit exceeds the 71,665 floor + # (110% of 65,150), so the limit still applies: not eligible. + ms_liheap_eligible: [true, true, false, false, false] +- name: Case 6, the floor uses the poverty guidelines from the year before the model year. + period: 2026 + input: + state_code: MS + age: 40 + ms_liheap_household_size: 11 + ms_liheap_income: 78_000 + heating_type: ELECTRICITY + heating_expense: 1_000 + output: + # FY2026 uses the 2025 guidelines, so the floor is 77,715. The 2026 + # guidelines would give 110% x (15,960 + 5,680 x 10) = 80,036. + ms_liheap_eligible: false +- name: Case 7, FY2027 uses the 2026 poverty guidelines for the floor. + period: 2027 + input: + state_code: [MS, MS] + age: [40, 40] + ms_liheap_household_size: [11, 11] + ms_liheap_income: [80_035.99, 80_036] + heating_type: [ELECTRICITY, ELECTRICITY] + heating_expense: [1_000, 1_000] + output: + # LIHEAP IM 2026-01 makes the 2026 guidelines mandatory in FY2027: + # 110% x (15,960 + 5,680 x 10) = 80,036 for 11 people, above the 79,402 + # FY2027 60% SMI limit. + ms_liheap_eligible: [true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/sa/nc_sa.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/sa/nc_sa.yaml index d7a1f2a8e82..bb91214fa85 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/sa/nc_sa.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/sa/nc_sa.yaml @@ -485,7 +485,8 @@ nc_sa: 0 # G.S. 108A-42.1 adjusts the rates each January 1 by the Social Security -# cost-of-living adjustment. The 2027 rate and FBR are projections. +# cost-of-living adjustment. The 2027 rate is uprated until North Carolina +# publishes it; the 2027 FBR is the SSA amount announced with the 3.6% COLA. # https://www.ncleg.gov/EnactedLegislation/Statutes/PDF/BySection/Chapter_108A/GS_108A-42.1.pdf - name: Case 37, basic facility rate follows the cost-of-living adjustment in 2027. period: 2027-01 @@ -495,13 +496,13 @@ age: 65 nc_sa_care_setting: ADULT_CARE_HOME output: - # 1,397 uprated by the projected Social Security index and rounded to the - # dollar. - nc_sa_rate: 1_436 - # Projected FBR. - ssi: 1_021.83 - # 1,436 + 70 - 1,021.83 = 484.17 -> 484. - nc_sa: 484 + # 1,397 uprated by the Social Security index (2027 CPI-W 328.746 / 2026 + # 317.265 = 1.036187) and rounded to the dollar: 1,447.55 -> 1,448. + nc_sa_rate: 1_448 + # 2027 SSI federal benefit rate. + ssi: 1_029 + # 1,448 + 70 - 1,029 = 489. + nc_sa: 489 # An applicant "ineligible for SSI solely due to being over income for SSI" # qualifies without applying for SSI (SA-3100 I, II.A.6): "If the a/b diff --git a/policyengine_us/tests/policy/baseline/gov/states/nj/dca/liheap/eligibility/nj_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nj/dca/liheap/eligibility/nj_liheap_eligible.yaml index 6b15e9404d2..d99aa38c1ff 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nj/dca/liheap/eligibility/nj_liheap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nj/dca/liheap/eligibility/nj_liheap_eligible.yaml @@ -120,3 +120,48 @@ is_tax_unit_dependent: [false, true] output: nj_liheap_eligible: [true, true] + +- name: Case 7, federal 110% poverty floor above the 60% state median income limit at size 32. + period: 2026 + input: + state_code: [NJ, NJ, NJ, NJ, NJ] + nj_liheap_household_size: [32, 32, 32, 32, 31] + nj_liheap_countable_income: [202_092, 204_764.99, 204_765, 204_766, 199_201] + heat_expense_included_in_rent: [true, true, true, true, true] + output: + # 42 U.S.C. 8624(b)(2)(B) bars excluding a household on income below 110% + # of the poverty guideline. FY2026 uses the 2025 guidelines (LIHEAP IM + # 2025-02): 15,650 + 5,500 x 31 = 186,150 for 32 people, and 110% is + # 204,765. The annualized 60% SMI limit for 32 people is 202,092. + # Household 1 is at the 60% SMI limit: eligible. + # Household 2 is one cent below the floor: eligible. + # Household 3 is exactly at the floor, which is not below 110%: not eligible. + # Household 4 is above the floor: not eligible. + # Household 5 has 31 people, whose 199,200 limit exceeds the 198,715 floor + # (110% of 180,650), so the limit still applies: not eligible. + nj_liheap_eligible: [true, true, false, false, false] + +- name: Case 8, the floor uses the poverty guidelines from the year before the model year. + period: 2026 + input: + state_code: NJ + nj_liheap_household_size: 32 + nj_liheap_countable_income: 208_000 + heat_expense_included_in_rent: true + output: + # FY2026 uses the 2025 guidelines, so the floor is 204,765. The 2026 + # guidelines would give 110% x (15,960 + 5,680 x 31) = 211,244. + nj_liheap_eligible: false + +- name: Case 9, FY2024 uses the 2023 poverty guidelines for the floor. + period: 2024 + input: + state_code: [NJ, NJ] + nj_liheap_household_size: [28, 28] + nj_liheap_countable_income: [168_695.99, 168_696] + heat_expense_included_in_rent: [true, true] + output: + # LIHEAP IM 2023-01 makes the 2023 guidelines mandatory in FY2024: + # 110% x (14,580 + 5,140 x 27) = 168,696 for 28 people, above the 167,952 + # FY2024 annualized 60% SMI limit. + nj_liheap_eligible: [true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/or/ohcs/liheap/eligibility/or_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/or/ohcs/liheap/eligibility/or_liheap_eligible.yaml index d1b3ed8f290..8abd2edcc64 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/or/ohcs/liheap/eligibility/or_liheap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/or/ohcs/liheap/eligibility/or_liheap_eligible.yaml @@ -43,3 +43,62 @@ # 7. public housing is subsidized housing, so heat included in rent is # again "No", ineligible. or_liheap_eligible: [false, true, false, true, true, false, false] + +- name: Case 2, federal 110% poverty floor above the 60% state median income limit at size 20. + period: 2026 + input: + state_code: [OR, OR, OR, OR, OR] + spm_unit_size: [20, 20, 20, 20, 19] + or_liheap_countable_income: [128_441, 132_164.99, 132_165, 132_166, 126_227] + heat_expense_included_in_rent: [true, true, true, true, true] + output: + # 42 U.S.C. 8624(b)(2)(B) bars excluding a household on income below 110% + # of the poverty guideline. PY2026 uses the 2025 guidelines (LIHEAP IM + # 2025-02): 15,650 + 5,500 x 19 = 120,150 for 20 people, and 110% is + # 132,165. The 60% SMI limit for 20 people is 128,441 (federal size + # factors; the manual's table stops at 12 on page 77). + # 1. at the 60% SMI limit: eligible; + # 2. one cent below the floor: eligible; + # 3. exactly at the floor, which is not below 110%: ineligible; + # 4. above the floor: ineligible; + # 5. 19 people, whose 126,226 limit exceeds the 126,115 floor (110% of + # 114,650), so the limit still applies: ineligible. + or_liheap_eligible: [true, true, false, false, false] + +- name: Case 3, the floor uses the poverty guidelines from the year before the model year. + period: 2026 + input: + state_code: OR + spm_unit_size: 20 + or_liheap_countable_income: 134_000 + heat_expense_included_in_rent: true + output: + # PY2026 uses the 2025 guidelines, so the floor is 132,165. The 2026 + # guidelines would give 110% x (15,960 + 5,680 x 19) = 136,268. + or_liheap_eligible: false + +- name: Case 4, PY2025 uses the 2024 poverty guidelines for the floor. + period: 2025 + input: + state_code: [OR, OR] + spm_unit_size: [19, 19] + or_liheap_countable_income: [123_089.99, 123_090] + heat_expense_included_in_rent: [true, true] + output: + # LIHEAP IM 2024-02 makes the 2024 guidelines mandatory in FY2025: + # 110% x (15,060 + 5,380 x 18) = 123,090 for 19 people, above the 121,051 + # PY2025 60% SMI limit. + or_liheap_eligible: [true, false] + +- name: Case 5, PY2027 uses the 2026 poverty guidelines for the floor. + period: 2027 + input: + state_code: [OR, OR] + spm_unit_size: [20, 20] + or_liheap_countable_income: [136_267.99, 136_268] + heat_expense_included_in_rent: [true, true] + output: + # LIHEAP IM 2026-01 makes the 2026 guidelines mandatory in FY2027: + # 110% x (15,960 + 5,680 x 19) = 136,268 for 20 people, above the 134,937 + # PY2027 60% SMI limit. + or_liheap_eligible: [true, false] diff --git a/policyengine_us/variables/gov/states/dc/doee/liheap/dc_liheap_eligible.py b/policyengine_us/variables/gov/states/dc/doee/liheap/dc_liheap_eligible.py index cff8a5bd276..0a0bca7c37e 100644 --- a/policyengine_us/variables/gov/states/dc/doee/liheap/dc_liheap_eligible.py +++ b/policyengine_us/variables/gov/states/dc/doee/liheap/dc_liheap_eligible.py @@ -1,4 +1,5 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg class dc_liheap_eligible(Variable): @@ -7,12 +8,25 @@ class dc_liheap_eligible(Variable): label = "Eligible for the DC LIHEAP" definition_period = YEAR defined_for = StateCode.DC - reference = "https://doee.dc.gov/liheap" + reference = ( + "https://doee.dc.gov/liheap", + "https://www.law.cornell.edu/uscode/text/42/8624", + ) def formula(spm_unit, period, parameters): p = parameters(period).gov.hhs.liheap + p_dc = parameters(period).gov.states.dc.doee.liheap state_median_income = spm_unit("hhs_smi", period) # The income concept is not clearly defined, assuming IRS gross income income = add(spm_unit, period, ["irs_gross_income"]) smi_limit = state_median_income * p.smi_limit - return income <= smi_limit + # 42 U.S.C. 8624(b)(2)(B) bars excluding a household on income below 110% + # of the poverty guideline. The 60% SMI limit falls below that floor from + # household size 34 to 39 in 2022 to 2024. + size = spm_unit("spm_unit_size", period) + state_group = spm_unit.household("state_group_str", period) + guideline = fpg( + size, state_group, period, parameters, year_lag=p_dc.fpg_year_lag + ) + floor = np.round(guideline * p.fpg_floor, 2) + return (income <= smi_limit) | (income < floor) diff --git a/policyengine_us/variables/gov/states/ma/doer/liheap/eligibility/ma_liheap_income_eligible.py b/policyengine_us/variables/gov/states/ma/doer/liheap/eligibility/ma_liheap_income_eligible.py index 4cad79d38bb..275933fc1eb 100644 --- a/policyengine_us/variables/gov/states/ma/doer/liheap/eligibility/ma_liheap_income_eligible.py +++ b/policyengine_us/variables/gov/states/ma/doer/liheap/eligibility/ma_liheap_income_eligible.py @@ -8,10 +8,17 @@ class ma_liheap_income_eligible(Variable): definition_period = YEAR defined_for = StateCode.MA reference = ( - "https://www.mass.gov/info-details/learn-about-home-energy-assistance-heap" + "https://www.mass.gov/info-details/learn-about-home-energy-assistance-heap", + "https://www.law.cornell.edu/uscode/text/42/8624", ) def formula(spm_unit, period, parameters): + p = parameters(period).gov.hhs.liheap income = spm_unit("ma_liheap_income", period) income_threshold = spm_unit("ma_liheap_state_median_income_threshold", period) - return income <= income_threshold + # 42 U.S.C. 8624(b)(2)(B) bars excluding a household on income below 110% + # of the poverty guideline. The 60% SMI limit falls below that floor from + # household size 30 to 34 in 2022 to 2027. ma_liheap_fpg already uses the + # guidelines in force on October 1 of the preceding year. + floor = np.round(spm_unit("ma_liheap_fpg", period) * p.fpg_floor, 2) + return (income <= income_threshold) | (income < floor) diff --git a/policyengine_us/variables/gov/states/ms/mdhs/liheap/ms_liheap_eligible.py b/policyengine_us/variables/gov/states/ms/mdhs/liheap/ms_liheap_eligible.py index 968a281af7c..d049215ee51 100644 --- a/policyengine_us/variables/gov/states/ms/mdhs/liheap/ms_liheap_eligible.py +++ b/policyengine_us/variables/gov/states/ms/mdhs/liheap/ms_liheap_eligible.py @@ -1,4 +1,5 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg class ms_liheap_eligible(Variable): @@ -12,6 +13,9 @@ class ms_liheap_eligible(Variable): "https://www.sos.ms.gov/adminsearch/ACCode/00000693c.pdf#page=22", # Section 2.3, explanations of policies (page 9). "https://liheapch.acf.gov/docs/2026/state-plans/MS_Plan_2026.pdf#page=9", + # Assurance (2), the 110% poverty floor (page 44). + "https://liheapch.acf.gov/docs/2026/state-plans/MS_Plan_2026.pdf#page=44", + "https://www.law.cornell.edu/uscode/text/42/8624", ) def formula(spm_unit, period, parameters): @@ -19,6 +23,17 @@ def formula(spm_unit, period, parameters): size = spm_unit("ms_liheap_household_size", period) income = spm_unit("ms_liheap_income", period) limit = spm_unit("ms_liheap_income_limit", period) + # 42 U.S.C. 8624(b)(2)(B), repeated in FY2026 plan assurance (2) (page 44), + # bars excluding a household on income below 110% of the poverty + # guideline. The Rule 6.1 C limit and its printed 60% SMI table (manual + # PDF pages 22 and 58) fall below that floor at sizes 11 to 20 in FY2026 + # (75,593 against 77,715 at size 11), and the modeled limit does so from + # size 11 in FY2026 and FY2027. + p_hhs = parameters(period).gov.hhs.liheap + state_group = spm_unit.household("state_group_str", period) + guideline = fpg(size, state_group, period, parameters, year_lag=p.fpg_year_lag) + floor = np.round(guideline * p_hhs.fpg_floor, 2) + income_eligible = (income <= limit) | (income < floor) has_adult = spm_unit.any(spm_unit.members("age", period) >= p.adult_age) # Existing inputs do not establish emancipated-minor headship, institutional # residence, or utility account ownership. A household adult is the modeled @@ -43,6 +58,4 @@ def formula(spm_unit, period, parameters): # subsidized housing qualifies only with separately billed energy, and # the landlord-evidence route is for unsubsidized renters. housing_eligible = ~subsidized | ~heat_in_rent - return ( - (size > 0) & (income <= limit) & has_adult & responsible & housing_eligible - ) + return (size > 0) & income_eligible & has_adult & responsible & housing_eligible diff --git a/policyengine_us/variables/gov/states/nj/dca/liheap/eligibility/nj_liheap_eligible.py b/policyengine_us/variables/gov/states/nj/dca/liheap/eligibility/nj_liheap_eligible.py index 9d2bf976a34..5f0d70e3ea3 100644 --- a/policyengine_us/variables/gov/states/nj/dca/liheap/eligibility/nj_liheap_eligible.py +++ b/policyengine_us/variables/gov/states/nj/dca/liheap/eligibility/nj_liheap_eligible.py @@ -1,4 +1,5 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg class nj_liheap_eligible(Variable): @@ -15,6 +16,7 @@ class nj_liheap_eligible(Variable): "https://www.law.cornell.edu/regulations/new-jersey/N-J-A-C-5-49-2-2", "https://www.law.cornell.edu/regulations/new-jersey/N-J-A-C-5-49-2-3", "https://www.nj.gov/bpu/newsroom/2021/approved/20211019.html", + "https://www.law.cornell.edu/uscode/text/42/8624", ) documentation = ( "Automatic enrollment does not waive the income test, and regular heating has " @@ -37,6 +39,17 @@ def formula(spm_unit, period, parameters): person("is_full_time_student", period) & person("is_tax_unit_dependent", period) ) + size = spm_unit("nj_liheap_household_size", period) + income = spm_unit("nj_liheap_countable_income", period) + limit = spm_unit("nj_liheap_income_limit", period) + # 42 U.S.C. 8624(b)(2)(B) bars excluding a household on income below 110% + # of the poverty guideline. The 60% SMI limit falls below that floor from + # household size 28 to 32 in FY2022 to FY2027. + p_hhs = parameters(period).gov.hhs.liheap + state_group = spm_unit.household("state_group_str", period) + guideline = fpg(size, state_group, period, parameters, year_lag=p.fpg_year_lag) + floor = np.round(guideline * p_hhs.fpg_floor, 2) + income_eligible = (income <= limit) | (income < floor) # The modeled 60% SMI rules begin with FY2022 (BPU, October 19, 2021); # the earlier poverty-level standard is not encoded. return ( @@ -44,9 +57,6 @@ def formula(spm_unit, period, parameters): & (spm_unit("has_heating_expense", period) | heat_in_rent) & ~(heat_in_rent & subsidized) & ~dependent_students_only - & (spm_unit("nj_liheap_household_size", period) > 0) - & ( - spm_unit("nj_liheap_countable_income", period) - <= spm_unit("nj_liheap_income_limit", period) - ) + & (size > 0) + & income_eligible ) diff --git a/policyengine_us/variables/gov/states/or/ohcs/liheap/eligibility/or_liheap_eligible.py b/policyengine_us/variables/gov/states/or/ohcs/liheap/eligibility/or_liheap_eligible.py index 0273768c7a3..19814dfb755 100644 --- a/policyengine_us/variables/gov/states/or/ohcs/liheap/eligibility/or_liheap_eligible.py +++ b/policyengine_us/variables/gov/states/or/ohcs/liheap/eligibility/or_liheap_eligible.py @@ -1,4 +1,5 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg class or_liheap_eligible(Variable): @@ -11,6 +12,7 @@ class or_liheap_eligible(Variable): reference = ( "https://www.oregon.gov/ohcs/energy-weatherization/Documents/2026%20Final%20Energy%20Assistance%20Intake%20Operations%20%26%20Policy%20Manual.pdf#page=61", "https://www.law.cornell.edu/regulations/oregon/Or-Admin-Code-SS-813-200-0020", + "https://www.law.cornell.edu/uscode/text/42/8624", ) documentation = ( "SPM members approximate the economic household, with identity and SSN " @@ -25,14 +27,21 @@ class or_liheap_eligible(Variable): ) def formula(spm_unit, period, parameters): + p = parameters(period).gov.states["or"].ohcs.liheap.eligibility + p_hhs = parameters(period).gov.hhs.liheap heat_in_rent = spm_unit("heat_expense_included_in_rent", period) subsidized = spm_unit( "receives_housing_assistance", period ) | spm_unit.household("is_in_public_housing", period) heating_cost = spm_unit("has_heating_expense", period) | heat_in_rent income = spm_unit("or_liheap_countable_income", period) - return ( - heating_cost - & ~(heat_in_rent & subsidized) - & (income <= spm_unit("or_liheap_income_limit", period)) - ) + limit = spm_unit("or_liheap_income_limit", period) + # 42 U.S.C. 8624(b)(2)(B) bars excluding a household on income below 110% + # of the poverty guideline. The 60% SMI limit falls below that floor from + # household size 19 in PY2025 and size 20 in PY2026 and PY2027. + size = spm_unit("spm_unit_size", period) + state_group = spm_unit.household("state_group_str", period) + guideline = fpg(size, state_group, period, parameters, year_lag=p.fpg_year_lag) + floor = np.round(guideline * p_hhs.fpg_floor, 2) + income_eligible = (income <= limit) | (income < floor) + return heating_cost & ~(heat_in_rent & subsidized) & income_eligible