diff --git a/changelog.d/ri-liheap-wave2a.added.md b/changelog.d/ri-liheap-wave2a.added.md new file mode 100644 index 00000000000..321a6ccad89 --- /dev/null +++ b/changelog.d/ri-liheap-wave2a.added.md @@ -0,0 +1 @@ +Add Rhode Island LIHEAP regular heating assistance, income and heating eligibility with the federal 110% poverty floor, including heat-in-rent grants and the qualifying utility enhancement. Earlier years backfill the researched FY2026 schedule as unverified estimates. diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/eligibility/smi_rate.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/eligibility/smi_rate.yaml new file mode 100644 index 00000000000..c0aaad74144 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/eligibility/smi_rate.yaml @@ -0,0 +1,16 @@ +description: Rhode Island limits annual income to this share of state median income under the Low Income Home Energy Assistance Program. +# Floor the four-person share before size adjustment, then floor the result. +# This ordering reconciles all 14 published annual limits; the source does +# not expressly prescribe the rounding order. +values: + 2025-10-01: 0.6 + +metadata: + unit: /1 + period: year + label: Rhode Island LIHEAP state median income rate + reference: + - title: Rhode Island FY2026 State Plan, Section 2.1 + href: https://liheapch.acf.gov/docs/2026/state-plans/RI_Plan_2026.pdf#page=8 + - title: Rhode Island FY2026 Income Eligibility Matrix + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/fpg_year_lag.yaml new file mode 100644 index 00000000000..2524164c74e --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/fpg_year_lag.yaml @@ -0,0 +1,19 @@ +description: Rhode Island uses the federal poverty guidelines from this many years before the model year for payment bands and the federal income floor under the Low Income Home Energy Assistance Program. +# The FY2026 matrix uses the 2025 guidelines, which HHS made mandatory for LIHEAP +# from October 1, 2025, so model period 2026 (FY2026) uses the 2025 guidelines +# and 2027 (FY2027) the 2026 guidelines, for both the bands and the floor. +# Backfilled years use their corresponding lagged guidelines and are unverified. +values: + 2025-10-01: 1 + +metadata: + unit: year + period: year + label: Rhode Island LIHEAP poverty guideline year lag + reference: + - title: Rhode Island FY2026 Grant Amounts and Income Eligibility Matrix, note 2 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf + - 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/ri/dhs/liheap/income/deduction_sources.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/deduction_sources.yaml new file mode 100644 index 00000000000..23e90873225 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/deduction_sources.yaml @@ -0,0 +1,29 @@ +description: Rhode Island uses these expenses as income deductions under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: + # Amounts approximate verified court orders. Existing inputs cannot + # establish the allowable court-ordered maximum or whether every payment + # was made. + - child_support_expense + - alimony_expense + # Manual Section III.H, page 21, deducts "All Medicare expenses, including + # prescriptions." FY2026 plan Section 1.9, page 6, counts Social Security + # "Including MediCare deduction". Only DHS's draft FFY2027 plan summary + # glosses this, as "meaning the Medicare amount deducted is not counted"; + # read that way, the plan agrees with the manual. The draft is cited until + # DHS posts the final FFY2027 summary. + # The Part B premium is the only Medicare expense with an input; prescription + # and other Medicare costs are not deducted. + - medicare_part_b_premium + +metadata: + unit: list + period: year + label: Rhode Island LIHEAP income deduction sources + reference: + - title: Rhode Island LIHEAP Manual, Section III.H Deductions from Income + href: https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=21 + - title: Rhode Island FY2026 State Plan, Section 1.9 + href: https://liheapch.acf.gov/docs/2026/state-plans/RI_Plan_2026.pdf#page=6 + - title: Rhode Island DHS Draft Summary of the FFY2027 LIHEAP State Plan, Countable Income + href: https://dhs.ri.gov/media/11711/download?language=en#page=3 diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/interest_exclusion.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/interest_exclusion.yaml new file mode 100644 index 00000000000..df5d993729a --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/interest_exclusion.yaml @@ -0,0 +1,13 @@ +description: Rhode Island excludes this annual amount of household interest income under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 500 + +metadata: + unit: currency-USD + period: year + label: Rhode Island LIHEAP annual interest exclusion + reference: + - title: Rhode Island LIHEAP Manual, Section III.G Income Exclusions Allowed by DHS + href: https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=20 + - title: Rhode Island FY2026 LIHEAP Application Instructions, Appendix H (May 2025), Dividend or Interest Income + href: https://dhs.ri.gov/media/9671/download?language=en#page=2 diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/minimum_income_age.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/minimum_income_age.yaml new file mode 100644 index 00000000000..abb8718b611 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/minimum_income_age.yaml @@ -0,0 +1,11 @@ +description: Rhode Island excludes the income of members below this age under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 18 + +metadata: + unit: year + period: year + label: Rhode Island LIHEAP minimum income age + reference: + - title: Rhode Island LIHEAP Manual, Section III.G Income Exclusions Allowed by DHS + href: https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=20 diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/rental_income_rate.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/rental_income_rate.yaml new file mode 100644 index 00000000000..3d0835b6ed1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/rental_income_rate.yaml @@ -0,0 +1,15 @@ +description: Rhode Island uses this share of rental income as countable income under the Low Income Home Energy Assistance Program. +# The manual gives the same rental rule on pages 16 and 20. +# Existing rental fields may contain net data; they approximate gross rents. +values: + 2025-10-01: 0.5 + +metadata: + unit: /1 + period: year + label: Rhode Island LIHEAP rental income inclusion rate + reference: + - title: "Rhode Island LIHEAP Manual, Sections III.E and III.G Rental Income #page 16,20" + href: https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=16 + - title: Rhode Island LIHEAP Rental Income Reported Form, Appendix M (May 2025) + href: https://dhs.ri.gov/media/9701/download?language=en#page=1 diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/earned.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/earned.yaml new file mode 100644 index 00000000000..dbd9c8316d0 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/earned.yaml @@ -0,0 +1,29 @@ +description: Rhode Island uses these sources as earned income under the Low Income Home Energy Assistance Program. +# Manual Section III.H (page 20) deducts 40% of gross self-employment receipts, +# and DHS's FY2026 plan summary (page 1) lists "Self-employment income (60%)". +# The model has only net business income and counts it in full, as Kansas LIHEAP +# does; this equals 60% of receipts only when expenses are 40% of receipts, and +# otherwise over- or understates countable income by receipts x (0.4 - expense +# ratio). No gross-receipts or business-expense input is added. +# Active versus passive partnership receipts are not separately identifiable. +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: Rhode Island LIHEAP earned income sources + reference: + - title: "Rhode Island LIHEAP Manual, Sections III.E-G Income Verification and Exclusions #page 14-20" + href: https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=14 + - title: "Rhode Island FY2026 State Plan, Sections 1.8 and 1.9 #page 5-7" + href: https://liheapch.acf.gov/docs/2026/state-plans/RI_Plan_2026.pdf#page=5 + - title: Rhode Island FY2026 LIHEAP Application Instructions, Appendix H (May 2025), Proof of Income + href: https://dhs.ri.gov/media/9671/download?language=en#page=2 + - title: Rhode Island DHS Summary of the FFY2026 LIHEAP State Plan, Section 1, Program Components + href: https://dhs.ri.gov/media/9371/download?language=en#page=1 diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/unearned.yaml new file mode 100644 index 00000000000..392a708392a --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/unearned.yaml @@ -0,0 +1,60 @@ +description: Rhode Island uses these sources as unearned income under the Low Income Home Energy Assistance Program. +# Interest and rental receipts have separate allowances, and TANF (RI Works) +# is added once through the SPM-unit tanf aggregate, so none of those +# sources is duplicated here. Each listed source is floored at zero. +# SSI is the federal benefit; a separate RI state supplement is not modeled. +# disability_benefits approximates employment-related TDI/TCI receipts. +# VA education and Aid and Attendance exclusions cannot be separated from +# the broad veterans input. Strike pay and VA benefits are counted under FY2026 +# plan Section 1.9 (page 6) and the DHS FY2026 plan summary (page 1). Payments +# from mortgages or sales contracts, AmeriCorp living allowances, jury duty pay, +# royalties, foster care receipts and legal settlements lack specific existing +# inputs and are not counted; contract income counts only when reported as +# wages or self-employment income. +values: + 2025-10-01: + - total_unemployment_compensation + - strike_benefits + - social_security + - ssi + # Military retirement pay is a separate input from the pension inputs that + # pension_income adds, so both are listed; a household reports the amount + # in one or the other. + - pension_income + - military_retirement_pay + - retirement_distributions + - survivor_benefits + - railroad_benefits + - alimony_income + - child_support_received + # Manual Section III.E counts "Temporary Disability Insurance (TDI), + # Temporary Caregivers Insurance (TCI)" (page 15) and "Workers' + # Compensation" (page 16); FY2026 plan Section 1.9, page 6, leaves + # "Insurance payments made directly to the insured" unchecked. This + # follows the manual. + - workers_compensation + - disability_benefits + - financial_assistance + - ordinary_dividend_income + - veterans_benefits + # Manual Section III.E, Estate or Trust Income, page 17. + - estate_income + # Manual Section III.E, page 17, lists "Cash Prizes and Lottery Winnings" as + # income. FY2026 plan Section 1.9, page 6, leaves "winnings from lotteries" + # unchecked, and DHS's FY2026 and draft FFY2027 plan summaries and the + # application instructions do not list them. This follows the manual. + - gambling_winnings + +metadata: + unit: list + period: year + label: Rhode Island LIHEAP unearned income sources + reference: + - title: "Rhode Island LIHEAP Manual, Sections III.E-G Income Verification and Exclusions #page 14-20" + href: https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=14 + - title: "Rhode Island FY2026 State Plan, Sections 1.8 and 1.9 #page 5-7" + href: https://liheapch.acf.gov/docs/2026/state-plans/RI_Plan_2026.pdf#page=5 + - title: Rhode Island FY2026 LIHEAP Application Instructions, Appendix H (May 2025), Proof of Income + href: https://dhs.ri.gov/media/9671/download?language=en#page=2 + - title: Rhode Island DHS Summary of the FFY2026 LIHEAP State Plan, Section 1, Program Components + href: https://dhs.ri.gov/media/9371/download?language=en#page=1 diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/student_age_limit.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/student_age_limit.yaml new file mode 100644 index 00000000000..38c07b6b0f7 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/student_age_limit.yaml @@ -0,0 +1,19 @@ +description: Rhode Island excludes the income of full-time students below this age under the Low Income Home Energy Assistance Program. +# Ages 18 through 23 means below the twenty-fourth birthday. The FY2026 and +# FY2027 application instructions exclude every full-time student aged 18-23. +# The 2019 manual excluded only a child who was not the household head; the +# current instructions govern. +values: + 2025-10-01: 24 + +metadata: + unit: year + period: year + label: Rhode Island LIHEAP student income exclusion age limit + reference: + - title: Rhode Island FY2026 LIHEAP Application Instructions, Appendix H (May 2025), Full-time Students + href: https://dhs.ri.gov/media/9671/download?language=en#page=2 + - title: Rhode Island FY2027 LIHEAP Application Instructions, Appendix H (July 2026), Full-time Students + href: https://dhs.ri.gov/media/12306/download?language=en#page=2 + - title: Rhode Island LIHEAP Manual, Section III.G Income Exclusions Allowed by DHS + href: https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=20 diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/amount/deliverables.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/amount/deliverables.yaml new file mode 100644 index 00000000000..e64d796d40a --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/amount/deliverables.yaml @@ -0,0 +1,23 @@ +description: Rhode Island provides this regular primary heating payment by income band for households using delivered fuels under the Low Income Home Energy Assistance Program. + +1: + 2025-10-01: 861 +2: + 2025-10-01: 805 +3: + 2025-10-01: 752 +4: + 2025-10-01: 704 +5: + 2025-10-01: 657 + +metadata: + unit: currency-USD + period: year + label: Rhode Island LIHEAP delivered fuels primary heating payment + breakdown: + - range(1, 6) + propagate_metadata_to_children: true + reference: + - title: Rhode Island FY2026 Grant Amounts, primary heating rows A-F + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/amount/electricity.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/amount/electricity.yaml new file mode 100644 index 00000000000..3d5aa2a63fe --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/amount/electricity.yaml @@ -0,0 +1,23 @@ +description: Rhode Island provides this regular primary heating payment by income band for households using electricity under the Low Income Home Energy Assistance Program. + +1: + 2025-10-01: 393 +2: + 2025-10-01: 357 +3: + 2025-10-01: 324 +4: + 2025-10-01: 295 +5: + 2025-10-01: 268 + +metadata: + unit: currency-USD + period: year + label: Rhode Island LIHEAP electricity primary heating payment + breakdown: + - range(1, 6) + propagate_metadata_to_children: true + reference: + - title: Rhode Island FY2026 Grant Amounts, primary heating rows A-F + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/amount/natural_gas.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/amount/natural_gas.yaml new file mode 100644 index 00000000000..94566fc902f --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/amount/natural_gas.yaml @@ -0,0 +1,23 @@ +description: Rhode Island provides this regular primary heating payment by income band for households using natural gas under the Low Income Home Energy Assistance Program. + +1: + 2025-10-01: 232 +2: + 2025-10-01: 211 +3: + 2025-10-01: 192 +4: + 2025-10-01: 175 +5: + 2025-10-01: 159 + +metadata: + unit: currency-USD + period: year + label: Rhode Island LIHEAP natural gas primary heating payment + breakdown: + - range(1, 6) + propagate_metadata_to_children: true + reference: + - title: Rhode Island FY2026 Grant Amounts, primary heating rows A-F + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/fpg_rates.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/fpg_rates.yaml new file mode 100644 index 00000000000..3cd448e8f19 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/fpg_rates.yaml @@ -0,0 +1,18 @@ +description: Rhode Island sets payment band boundaries at these shares of the federal poverty guideline under the Low Income Home Energy Assistance Program. +# Thresholds are rounded half up to annual dollars and are upper-inclusive. +# The size-two 125% cell prints 31,725, repeating its 150% value. The labeled +# 125% rule instead gives 26,438; use the rule rather than that table anomaly. +values: + 2025-10-01: + - 0.75 + - 1 + - 1.25 + - 1.5 + +metadata: + unit: list + period: year + label: Rhode Island LIHEAP payment federal poverty guideline rates + reference: + - title: Rhode Island FY2026 Grant Amounts and Income Eligibility Matrix + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/heat_in_rent.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/heat_in_rent.yaml new file mode 100644 index 00000000000..5b873ff838c --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/heat_in_rent.yaml @@ -0,0 +1,28 @@ +description: Rhode Island provides these regular heating grants for heat-in-rent households under the Low Income Home Energy Assistance Program. +# Row G (direct) prints $400 under Deliverables, Natural Gas and Other and leaves +# the Electric cell blank, so its electricity value is 0. Unlisted fuels (coal, +# solar) and an unknown fuel read the Other cell. Rows H and I print one amount +# each and go to the household's separate electric account whatever the fuel. + +direct: + deliverables: + 2025-10-01: 400 + natural_gas: + 2025-10-01: 400 + electricity: + 2025-10-01: 0 + other: + 2025-10-01: 400 +secondary_electric: + 2025-10-01: 80 +subsidized_secondary_electric: + 2025-10-01: 50 + +metadata: + unit: currency-USD + period: year + label: Rhode Island LIHEAP heat-in-rent grant + propagate_metadata_to_children: true + reference: + - title: Rhode Island FY2026 Grant Amounts, heat-in-rent rows G-I + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/utility_enhancement.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/utility_enhancement.yaml new file mode 100644 index 00000000000..d94224949f7 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/utility_enhancement.yaml @@ -0,0 +1,21 @@ +description: Rhode Island provides this enhancement with a primary heating grant credited to a gas or electric distribution-company account under the Low Income Home Energy Assistance Program. +# This amount is sourced for the FY2026 heating season; earlier periods backfill it +# as an unverified estimate. DHS's FFY2027 filing (Docket 26-34-GE, October 2, +# 2026, page 2) gives one additional credit to accounts receiving a 2027 primary +# benefit but leaves the amount to DHS, so $350 in FY2027 is an unverified estimate. +# It is not part of the federal fuel matrix or a crisis/cooling grant. Provider and +# actual federal-grant account routing are input limits. +values: + 2025-10-01: 350 + +metadata: + unit: currency-USD + period: year + label: Rhode Island LIHEAP primary utility enhancement + reference: + - title: DHS response to Division data request 1-1, issued October 9, 2025, FY2026 enhancement + href: https://ripuc.ri.gov/sites/g/files/xkgbur841/files/2025-10/25-38-GE%20-%20LIHEAP%20Enhancement%20-%20DPUC%20Data%20Requests%20Set%201%20%2810-9-25%29%20DHS%20Response.pdf#page=1 + - title: "PUC Order 25579, January 8, 2026 #page 1,3" + href: https://ripuc.ri.gov/sites/g/files/xkgbur841/files/2026-01/RI%20DHS%2025-38-GE_Ord25579%20-%201-8-26.pdf#page=1 + - title: DHS FFY2027 LIHEAP Enhancement Charge filing, PUC Docket 26-34-GE, October 2, 2026 (2027 credit amount not yet set) + href: https://ripuc.ri.gov/sites/g/files/xkgbur841/files/2026-10/26-34-GE%20LIHEAP%20Enhancement%20Charge%20-%20FFY%202027%20Filing%20%2810-2-2026%29.pdf#page=2 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 8ea5f36e8e8..fb9dd2eb309 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -808,8 +808,16 @@ programs: category: Benefits agency: HHS status: partial - coverage: DC, Riverside County, MA, IL, IN, KS, KY, MD, MS, NC, NE, NJ, NY, OR, TX + coverage: DC, Riverside County, MA, IL, IN, KS, KY, MD, MS, NC, NE, NJ, NY, OR, RI, TX state_implementations: + - state: RI + status: partial + name: Rhode Island LIHEAP + full_name: Rhode Island Low Income Home Energy Assistance Program + variable: ri_liheap + parameter_prefix: gov.states.ri.dhs.liheap + verified_years: "2026" + notes: FY2026 regular heating, including heat-in-rent grants and the $350 utility enhancement, assuming a qualifying utility account. Earlier years and FY2027 payments reuse FY2026 amounts as unverified estimates; crisis, cooling and weatherization are out of scope. - state: DC status: complete name: DC LIHEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/eligibility/ri_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/eligibility/ri_liheap_eligible.yaml new file mode 100644 index 00000000000..f215fbaa388 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/eligibility/ri_liheap_eligible.yaml @@ -0,0 +1,89 @@ +# DHS manual physical pages 5-6 and 12 allows heat included in rent and excludes +# subsidized renters without any utility burden. These are eligibility checks; +# they do not choose among the separate direct and secondary-electric awards. +- name: Case 1, heating and rental arrangements establish the required energy burden. + period: 2026 + input: + state_code: [RI, RI, RI, RI, RI, RI, RI, RI] + age: [40, 40, 40, 40, 40, 40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN, CITIZEN, CITIZEN, CITIZEN, CITIZEN, CITIZEN] + employment_income: [12_000, 12_000, 12_000, 12_000, 12_000, 12_000, 12_000, 12_000] + ssi: [0, 0, 0, 0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0, 0, 0, 0] + childcare_expenses: [0, 0, 0, 0, 0, 0, 0, 0] + child_care_subsidies: [0, 0, 0, 0, 0, 0, 0, 0] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_200, 0, 0, 0, 0, 1_200, 0, 0] + pre_subsidy_electricity_expense: [0, 0, 0, 120, 0, 0, 120, 0] + heat_expense_included_in_rent: [false, false, true, true, true, false, true, true] + receives_housing_assistance: [false, false, false, true, true, true, false, false] + is_in_public_housing: [false, false, false, false, false, false, true, true] + output: + ri_liheap_eligible: [true, false, true, true, false, true, true, false] + +# FY2026 matrix's size-1 annual ceiling is 42,252, inclusive. The ordinary +# income/member requirements still apply when a positive heating bill exists; +# explicit NONE and a household outside Rhode Island cannot qualify. +- name: Case 2, income and qualified membership remain necessary for heating assistance. + period: 2026 + input: + state_code: [RI, RI, RI, RI, MA] + age: [40, 40, 40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, UNDOCUMENTED, CITIZEN, CITIZEN] + employment_income: [42_252, 42252.01, 12_000, 12_000, 12_000] + ssi: [0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0] + childcare_expenses: [0, 0, 0, 0, 0] + child_care_subsidies: [0, 0, 0, 0, 0] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NONE, NATURAL_GAS] + gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200] + output: + ri_liheap_eligible: [true, false, false, false, false] + +# Manual page 12 counts only citizens, permanent residents and qualified aliens, so the +# size is 0 and the limit is 0. Income 0 <= 0 passes, the gas bill meets the energy +# burden and the fuel is not NONE, so only the size gate (size > 0) makes the household +# ineligible. +- name: Case 3, a household with no qualified member and no income is ineligible. + period: 2026 + input: + state_code: RI + age: 40 + immigration_status: UNDOCUMENTED + employment_income: 0 + ssi: 0 + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + heating_type: NATURAL_GAS + gas_expense: 1_200 + output: + ri_liheap_household_size: 0 + ri_liheap_income: 0 + ri_liheap_income_limit: 0 + ri_liheap_eligible: false + +# Threshold edge for the federal floor, 42 U.S.C. 8624(b)(2)(B): income "less than +# 110 percent of the poverty level" cannot be excluded. Size and income are forced +# because the floor binds only from 23 qualified members in 2026. The 60% SMI limit +# is floor(floor(0.6 x 135,424) x 1.83) = floor(81,254 x 1.83) = floor(148,694.82) +# = 148,694, where 1.83 = 0.52 + 5 x 0.16 + 17 x 0.03. The floor uses the 2025 +# guideline (one-year lag): 15,650 + 22 x 5,500 = 136,650, and 1.1 x 136,650 = +# 150,315.00. All three incomes exceed the limit; 150,314.99 is below the floor +# (eligible) and 150,315 and 150,315.01 are not. A zero-year lag (2026 guideline, +# 1.1 x 140,920 = 155,012) would make 150,315 eligible, and a two-year lag (2024 +# guideline, 1.1 x 133,420 = 146,762) would make 150,314.99 ineligible. +- name: Case 4, threshold edge at the federal 110 percent poverty floor for twenty three members. + period: 2026 + input: + state_code: [RI, RI, RI] + ri_liheap_household_size: [23, 23, 23] + ri_liheap_income: [150_314.99, 150_315, 150_315.01] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_200, 1_200, 1_200] + heat_expense_included_in_rent: [false, false, false] + receives_housing_assistance: [false, false, false] + is_in_public_housing: [false, false, false] + output: + ri_liheap_income_limit: [148_694, 148_694, 148_694] + ri_liheap_eligible: [true, false, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/eligibility/ri_liheap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/eligibility/ri_liheap_income_limit.yaml new file mode 100644 index 00000000000..52c98528ecf --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/eligibility/ri_liheap_income_limit.yaml @@ -0,0 +1,33 @@ +# FY2026 matrix, "60% SMI Annual" column for sizes 1-14, read from the printed table rather than computed. +- name: Case 1, all fourteen published annual SMI limits match exactly. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI, RI, RI, RI, RI, RI, RI, RI, RI, RI, RI, RI] + ri_liheap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] + output: + ri_liheap_income_limit: [42_252, 55_252, 68_253, 81_254, 94_254, 107_255, 109_692, 112_130, 114_568, + 117_005, 119_443, 121_881, 124_318, 126_756] + +# A household with no qualified member receives no one-person income limit. +- name: Case 2, no qualified household size has zero income limit. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI] + ri_liheap_household_size: [0] + output: + ri_liheap_income_limit: [0] + +# DHS FFY2027 income eligibility sheet p1, mandatory October 1, 2026. +# These are all 14 annual values, read from the DHS table. +# This verifies FY2027 income limits only, not an unresearched FY2027 payment. +- name: Case 3, all fourteen published FY2027 annual income limits match exactly. + period: 2027 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI, RI, RI, RI, RI, RI, RI, RI, RI, RI, RI, RI] + ri_liheap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] + output: + ri_liheap_income_limit: [43_410, 56_767, 70_124, 83_481, 96_837, 110_194, + 112_699, 115_203, 117_708, 120_212, 122_717, 125_221, 127_725, 130_230] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/income/ri_liheap_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/income/ri_liheap_income.yaml new file mode 100644 index 00000000000..549caeff381 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/income/ri_liheap_income.yaml @@ -0,0 +1,909 @@ +# Manual pages 14-15 income categories; existing net business inputs approximate gross receipts: 12,000 + 2,400 + 600 + 1,200 + 1,200 = 17,400, with no additional 40% deduction. +- name: Case 1, adult business income uses existing net receipts without another business deduction. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 12_000 + self_employment_income: 2_400 + sstb_self_employment_income: 600 + farm_operations_income: 1_200 + partnership_s_corp_income: 1_200 + spm_units: + spm_unit: + members: [person1] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1] + state_code: RI + output: + ri_liheap_income: 17_400 + +# Each source is floored at zero: 12,000 wages + 600 positive business = 12,600; negative sources contribute 0. +- name: Case 2, business source losses do not offset wages or other positive receipts. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 12_000 + self_employment_income: -6_000 + farm_operations_income: -1_200 + partnership_s_corp_income: 600 + spm_units: + spm_unit: + members: [person1] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1] + state_code: RI + output: + ri_liheap_income: 12_600 + +# Manual III.G p20 excludes all income under age 18; the FY2026 instructions (Appendix H p2) +# exclude every full-time student aged 18-23. The young adult has 1,200 wages and 600 Social +# Security; the parent has 12,000 wages. +- name: Case 3, a 17-year-old's income is excluded as a minor's. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 12_000 + person2: + age: 17 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 1_200 + social_security: 600 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 12_000 + +# Manual III.G p20 excludes all income under age 18; the FY2026 instructions (Appendix H p2) +# exclude every full-time student aged 18-23. The young adult has 1,200 wages and 600 Social +# Security; the parent has 12,000 wages. +- name: Case 4, an 18-year-old full-time student's income is excluded. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 12_000 + person2: + age: 18 + immigration_status: CITIZEN + is_full_time_student: true + ssi: 0 + employment_income: 1_200 + social_security: 600 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 12_000 + +# Manual III.G p20 excludes all income under age 18; the FY2026 instructions (Appendix H p2) +# exclude every full-time student aged 18-23. The young adult has 1,200 wages and 600 Social +# Security; the parent has 12,000 wages. +- name: Case 5, a 23-year-old full-time student's income is excluded. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 12_000 + person2: + age: 23 + immigration_status: CITIZEN + is_full_time_student: true + ssi: 0 + employment_income: 1_200 + social_security: 600 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 12_000 + +# Manual III.G p20 excludes all income under age 18; the FY2026 instructions (Appendix H p2) +# exclude every full-time student aged 18-23. The young adult has 1,200 wages and 600 Social +# Security; the parent has 12,000 wages. +- name: Case 6, a 24-year-old full-time student's income counts. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 12_000 + person2: + age: 24 + immigration_status: CITIZEN + is_full_time_student: true + ssi: 0 + employment_income: 1_200 + social_security: 600 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 13_800 + +# Manual III.G p20 excludes all income under age 18; the FY2026 instructions (Appendix H p2) +# exclude every full-time student aged 18-23. The young adult has 1,200 wages and 600 Social +# Security; the parent has 12,000 wages. +- name: Case 7, an 18-year-old who is not a student has income counted. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 12_000 + person2: + age: 18 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 1_200 + social_security: 600 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 13_800 + +# Manual pages 16 and 20: 600 combined interest - 500 once = 100; 600 dividends count fully; total 700. +- name: Case 8, one household interest exclusion applies across adults while dividends count fully. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + interest_income: 300 + ordinary_dividend_income: 240 + person2: + age: 39 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + interest_income: 300 + ordinary_dividend_income: 360 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 700 + +# Manual pages 16 and 20 exclude the first 500 a year; the excess above 500 counts. +- name: Case 9, interest at the annual allowance is excluded and positive excess counts. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI] + age: [40, 40, 40] + is_full_time_student: [false, false, false] + ssi: [0, 0, 0] + tanf: [0, 0, 0] + childcare_expenses: [0, 0, 0] + child_care_subsidies: [0, 0, 0] + interest_income: [499.99, 500, 500.5] + output: + ri_liheap_income: [0, 0, 0.5] + +# Manual pages 16 and 20 and the rental income form: 12,000 + (6,000 + 2,400) x 50% = 16,200; existing rent inputs approximate reported rents. +- name: Case 10, both rental streams receive the fifty percent allowance. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 12_000 + rental_income: 6_000 + farm_rent_income: 2_400 + spm_units: + spm_unit: + members: [person1] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1] + state_code: RI + output: + ri_liheap_income: 16_200 + +# Manual pages 15 and 20: 6,000 wages + 1,200 adult federal SSI + 3,600 family TANF = 10,800. +# TANF is the take-up-gated SPM-unit tanf aggregate (which includes RI Works), counted once. +# Child SSI of 2,400 is excluded; no Rhode Island state supplement is added. +- name: Case 11, adult federal SSI is annualized and family TANF counts once while child SSI is excluded. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 1_200 + employment_income: 6_000 + person2: + age: 8 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 2_400 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 3_600 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 10_800 + +# Manual page 21: 12,000 - 1,200 child support - 600 alimony - 2,400 unsubsidized child care = 7,800. Reported support stands for court-ordered payments. +- name: Case 12, court ordered support and unsubsidized childcare expenses reduce income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 12_000 + child_support_expense: 1_200 + alimony_expense: 600 + person2: + age: 8 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + childcare_expenses: 2_400 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 7_800 + +# Manual page 21 allows the child day care deduction only without a federal or state subsidy: 12,000 - 1,200 - 600 = 10,200; the 2,400 of child care is not deducted when subsidies are positive. +- name: Case 13, subsidized childcare expenses do not receive the unsubsidized care deduction. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 12_000 + child_support_expense: 1_200 + alimony_expense: 600 + person2: + age: 8 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + childcare_expenses: 2_400 + child_care_subsidies: 1_200 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 10_200 + +# Court-ordered support of 2,400 exceeds income of 1,200; countable income is floored at 0. +- name: Case 14, deductions above income floor the result at zero. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 1_200 + child_support_expense: 2_400 + spm_units: + spm_unit: + members: [person1] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1] + state_code: RI + output: + ri_liheap_income: 0 + +# Manual page 12: the nonqualified parent's 20,000 counts although the size is 1; the minor's Social Security is excluded (page 20). +# Size 1: 19,563 < 20,000 <= 23,475 is band 4, federal gas 175. +# DHS DIV 1-1 (October 2025) adds the 350 utility enhancement: total 525. +# This assumes qualifying provider and primary-grant receipt, not final eligibility. +- name: Case 15, nonqualified parent income counts in full for the qualified child size and band. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + is_full_time_student: false + ssi: 0 + employment_income: 20_000 + person2: + age: 8 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + social_security: 1_200 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_household_size: 1 + ri_liheap_income: 20_000 + ri_liheap_income_band: 4 + ri_liheap_base_payment: 525 + +# Manual pages 16-18: 12,000 wages + 1,200 child support + 600 alimony + 600 family help = 14,400; SNAP of 6,000 is excluded. +- name: Case 16, SNAP is excluded while ordinary support receipts count. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 12_000 + child_support_received: 1_200 + alimony_income: 600 + financial_assistance: 600 + spm_units: + spm_unit: + members: [person1] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + snap: 6_000 + households: + household: + members: [person1] + state_code: RI + output: + ri_liheap_income: 14_400 + +# Manual p17 counts estate and trust income; the FY2026 instructions (Appendix H p2) exclude a +# full-time student aged 18-23: 12,000 adult wages + 1,200 adult periodic estate = 13,200; +# the student's 2,400 estate income is excluded. +- name: Case 17, periodic estate income follows the same ordinary age and student exclusions. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 12_000 + estate_income: 1_200 + person2: + age: 20 + immigration_status: CITIZEN + is_full_time_student: true + ssi: 0 + estate_income: 2_400 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 13_200 + +# Plan 1.9 p6 counts retirement/pension benefits; manual p15 accepts 1099-R income. +# Military retired pay and retirement account distributions are inputs separate from +# pension_income: 30000 military retired pay, 18000 IRA and 12000 401(k) each count in full. +- name: Case 18, military retirement pay and retirement account distributions count as unearned income. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI] + age: [45, 60, 62] + is_full_time_student: [false, false, false] + ssi: [0, 0, 0] + tanf: [0, 0, 0] + childcare_expenses: [0, 0, 0] + child_care_subsidies: [0, 0, 0] + military_retirement_pay: [30_000, 0, 0] + taxable_ira_distributions: [0, 18_000, 0] + taxable_401k_distributions: [0, 0, 12_000] + output: + ri_liheap_income: [30_000, 18_000, 12_000] + +# Each source is floored at zero before summing, so no loss offsets another source: +# 30000 wages + max(-2500 estate, 0) = 30000; 30000 + max(-4000 rent, 0) x 50% = 30000; +# both losses together also leave 30000. Netting the estate loss would give 27500. +- name: Case 19, estate and rental losses do not offset wages. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI] + age: [50, 50, 50] + is_full_time_student: [false, false, false] + ssi: [0, 0, 0] + tanf: [0, 0, 0] + childcare_expenses: [0, 0, 0] + child_care_subsidies: [0, 0, 0] + employment_income: [30_000, 30_000, 30_000] + estate_income: [-2_500, 0, -2_500] + rental_income: [0, -4_000, -4_000] + output: + ri_liheap_income: [30_000, 30_000, 30_000] + +# The RI Works grant counts only through the take-up-gated SPM-unit tanf aggregate. +# Parent and child with no other income: tanf_if_takes_up 8412 = size-2 RI Works +# payment standard 701 x 12. Taking up TANF gives tanf 8412 and countable income 8412. +- name: Case 20, a family that takes up TANF counts its grant once. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + person2: + age: 5 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + tanf_if_takes_up: 8_412 + takes_up_tanf_if_eligible: true + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + tanf: 8_412 + ri_liheap_income: 8_412 + +# Same family with the same 8412 RI Works entitlement, but it does not take up TANF: +# tanf is 0, so no TANF income counts and countable income is 0. +- name: Case 21, a family that does not take up TANF counts no TANF grant. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + person2: + age: 5 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + tanf_if_takes_up: 8_412 + takes_up_tanf_if_eligible: false + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + tanf: 0 + ri_liheap_income: 0 + +# Manual III.E p17 lists "Cash Prizes and Lottery Winnings" as income; this follows the +# manual over plan 1.9 p6: 20000 wages + 1500 lottery winnings = 21500. +- name: Case 22, lottery winnings count as unearned income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 20_000 + gambling_winnings: 1_500 + spm_units: + spm_unit: + members: [person1] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1] + state_code: RI + output: + ri_liheap_income: 21_500 + +# Manual III.H p21 deducts "All Medicare expenses"; the Part B premium is the only one +# with an input, so it is pinned. 24000 Social Security - 2434.80 Part B premium +# (2026 standard 202.90 x 12) = 21565.20. A retiree paying no premium keeps 24000. +- name: Case 23, the Medicare Part B premium paid is deducted from income. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI] + age: [67, 67] + is_full_time_student: [false, false] + ssi: [0, 0] + tanf: [0, 0] + childcare_expenses: [0, 0] + child_care_subsidies: [0, 0] + social_security: [24_000, 24_000] + medicare_part_b_premium: [2_434.80, 0] + output: + ri_liheap_income: [21_565.20, 24_000] + +# The FY2026 instructions (Appendix H p2) exclude every full-time student aged 18-23, +# including one who heads the household: 16,640 of part-time wages (20 hours a week at +# Rhode Island's 2026 minimum wage of $16 an hour) are not counted. +- name: Case 24, a full-time student aged 22 living alone has income excluded. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 22 + immigration_status: CITIZEN + is_full_time_student: true + ssi: 0 + employment_income: 16_640 + spm_units: + spm_unit: + members: [person1] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1] + state_code: RI + output: + ri_liheap_income: 0 + +# R.I. DLT Unemployment Insurance FAQ +# (https://dlt.ri.gov/individuals/unemployment-insurance/unemployment-insurance-faq): +# the weekly rate is 3.85% of the average of the two highest base-period quarters +# (2026 maximum $745), and the claim lasts 33% of base-period wages divided by the +# weekly rate, capped at 26 weeks. A worker earning 10,000 a quarter (40,000 a year) +# and laid off at the end of 2025 gets 10,000 x 0.0385 = 385 a week. The duration is +# 0.33 x 40,000 / 385 = 34.3 weeks, capped at 26, so the claim pays 26 x 385 = 10,010. +# Rhode Island has no modeled UI, so the total equals the reported amount; manual page 15 +# counts unemployment benefits. +- name: Case 25, reported unemployment compensation counts as unearned income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + unemployment_compensation: 10_010 + spm_units: + spm_unit: + members: [person1] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1] + state_code: RI + output: + total_unemployment_compensation: 10_010 + ri_liheap_income: 10_010 + +# Manual pages 14-17 count every listed unearned source except strike pay and VA +# benefits, which FY2026 plan Section 1.9 (page 6) counts. Each is set to a distinct +# amount, so dropping any one changes the total: 1,000 + 500 + 1,200 + 600 + 1,500 + +# 2,000 + 700 + 800 + 900 + 1,100 + 1,300 + 1,400 + 1,600 + 300 + 200 + 1,700 + 400 + 100 +# = 17,300. Interest, rent and TANF are 0. At age 60 with no Social Security disability +# the person is not Medicare-enrolled, so no Part B premium is deducted. +- name: Case 26, every listed unearned income source counts. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 60 + immigration_status: CITIZEN + is_full_time_student: false + unemployment_compensation: 1_000 + strike_benefits: 500 + social_security: 1_200 + ssi: 600 + pension_income: 1_500 + military_retirement_pay: 2_000 + retirement_distributions: 700 + survivor_benefits: 800 + railroad_benefits: 900 + alimony_income: 1_100 + child_support_received: 1_300 + workers_compensation: 1_400 + disability_benefits: 1_600 + financial_assistance: 300 + ordinary_dividend_income: 200 + veterans_benefits: 1_700 + estate_income: 400 + gambling_winnings: 100 + spm_units: + spm_unit: + members: [person1] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1] + state_code: RI + output: + ri_liheap_income: 17_300 + +# Manual page 20 excludes "Child Income (under 18)" and Appendix H page 2 excludes +# full-time students aged 18-23, so only the parent's income counts: 12,000 wages, and +# counted interest 0 gives max(0 - 500, 0) = 0, for a total of 12,000. The minor's 800 of +# interest is about 20,000 of savings at 4%. Counting the excluded members' interest +# would give 12,000 + (800 + 600 - 500) = 12,900. +- name: Case 27, interest of a minor and of a full-time student is excluded. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 12_000 + person2: + age: 17 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + interest_income: 800 + person3: + age: 20 + immigration_status: CITIZEN + is_full_time_student: true + ssi: 0 + interest_income: 600 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2, person3] + state_code: RI + output: + ri_liheap_income: 12_000 + +# Each member's interest is floored at zero before the household's $500 exclusion +# (manual page 20), so a negative interest entry cannot cancel another member's +# interest: 12,000 wages + max(0 + 700 - 500, 0) = 12,200. Summing before the floor +# would give max(-1,000 + 700 - 500, 0) = 0 and a total of 12,000. +- name: Case 28, a negative interest entry does not offset another member's interest. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 12_000 + interest_income: -1_000 + person2: + age: 44 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + interest_income: 700 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 12_200 + +# Manual III.H (page 21) deducts court-documented support without limiting the +# deduction to members whose income counts, so the model deducts every member's +# support and alimony paid, the same household-level reading as the Part B premium. +# Wages use the 2026 Rhode Island minimum wage of $16 an hour: the parent works full +# time (16 x 40 x 52 = 33,280), the 20-year-old full-time student 10 hours a week +# (8,320, excluded by Appendix H page 2) and the undocumented member 20 hours a week +# (16,640, counted although not in the size). Counted income 33,280 + 16,640 = +# 49,920, less the student's 1,200 of child support and the undocumented member's +# 1,800 of alimony: 49,920 - 3,000 = 46,920. Deducting only counted members' expenses +# would give 48,120; only qualified members' expenses, 48,720. +- name: Case 29, support paid by excluded members is deducted from household income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 33_280 + person2: + age: 20 + immigration_status: CITIZEN + is_full_time_student: true + ssi: 0 + employment_income: 8_320 + child_support_expense: 1_200 + person3: + age: 50 + immigration_status: UNDOCUMENTED + is_full_time_student: false + ssi: 0 + employment_income: 16_640 + alimony_expense: 1_800 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2, person3] + state_code: RI + output: + ri_liheap_household_size: 2 + ri_liheap_income: 46_920 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/integration.yaml new file mode 100644 index 00000000000..e1803885d7d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/integration.yaml @@ -0,0 +1,263 @@ +# Sources for the expected values in this file: +# - FY2026 Grant Amounts and Income Eligibility (one page; rows A-I and the income +# table): https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf +# - DHS LIHEAP Manual (2019), sections II.B, III and III.E-III.H (pages 5-6, 12, 14-21): +# https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=5 +# - FY2026 application instructions, Appendix H, page 2 (full-time students): +# https://dhs.ri.gov/media/9671/download?language=en#page=2 +# - DHS response to Division data request 1-1 (the $350 enhancement): +# https://ripuc.ri.gov/sites/g/files/xkgbur841/files/2025-10/25-38-GE%20-%20LIHEAP%20Enhancement%20-%20DPUC%20Data%20Requests%20Set%201%20%2810-9-25%29%20DHS%20Response.pdf#page=1 +# Wages use the 2026 Rhode Island minimum wage, $16 an hour (R.I. DLT). Band tops are +# the 2025 guideline (15,650 + 5,500 per additional person) times 75%, 100%, 125% and +# 150%, rounded half up to the dollar. These match the matrix income table except the +# size-two 125% cell, which repeats 31,725 (payment/ri_liheap_income_band.yaml Case 2). + +# A retiree aged 72 who lives alone and receives only federal SSI, the 2026 individual +# federal benefit rate of $994 a month (SSA, SSI Federal Payment Amounts): 994 x 12 = +# 11,928. Her income is below the poverty guideline, so the Medicare Savings Program +# pays her Part B premium. She heats with electricity billed in her own name. +- name: Case 1, an elderly SSI recipient with her own electric heating bill receives the row B electric grant. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 72 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 11_928 + medicare_part_b_premium: 0 + spm_units: + spm_unit: + members: [person1] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_500 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [person1] + state_code: RI + is_in_public_housing: false + output: + # She pays her own primary heating bill (manual III.A, page 12). + has_heating_expense: true + # A citizen counts in the size (manual page 12). + ri_liheap_household_size: 1 + # SSI counts as fixed income (manual page 15): 11,928. + ri_liheap_income: 11_928 + # Matrix, 60% SMI annual, one person. + ri_liheap_income_limit: 42_252 + # 11,738 (75%) < 11,928 <= 15,650 (100%): row B. + ri_liheap_income_band: 2 + ri_liheap_eligible: true + # Row B Electric 357 + 350 enhancement = 707. + ri_liheap_base_payment: 707 + ri_liheap: 707 + +# A single parent aged 34 working full time at the minimum wage (16 x 40 x 52 = 33,280) +# with two school-age children, no child care costs, no RI Works and no SSI. Natural gas +# heat is billed in the parent's name. +- name: Case 2, a working single parent with two children receives the row C natural gas grant. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 34 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 33_280 + person2: + age: 9 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 6 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [person1, person2, person3] + state_code: RI + is_in_public_housing: false + output: + has_heating_expense: true + ri_liheap_household_size: 3 + # Wages count gross (manual page 14); the children have no income. + ri_liheap_income: 33_280 + # Matrix, 60% SMI annual, three people. + ri_liheap_income_limit: 68_253 + # Size 3 guideline 26,650: 26,650 (100%) < 33,280 <= 33,313 (125%, 33,312.50 + # rounded half up): row C. + ri_liheap_income_band: 3 + ri_liheap_eligible: true + # Row C Natural Gas 192 + 350 enhancement = 542. + ri_liheap_base_payment: 542 + ri_liheap: 542 + +# Two parents (one full time at the minimum wage, 33,280; one 20 hours a week, 16 x 20 x +# 52 = 16,640), a 20-year-old full-time college student working 10 hours a week (16 x 10 +# x 52 = 8,320) and a 15-year-old. They buy their own fuel oil. +- name: Case 3, a family of four with a full-time college student receives the row E delivered fuel grant. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 46 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 33_280 + person2: + age: 44 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 16_640 + person3: + age: 20 + immigration_status: CITIZEN + is_full_time_student: true + ssi: 0 + employment_income: 8_320 + person4: + age: 15 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + heating_type: FUEL_OIL + fuel_oil_expense: 2_400 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [person1, person2, person3, person4] + state_code: RI + is_in_public_housing: false + output: + has_heating_expense: true + ri_liheap_household_size: 4 + # The student's 8,320 is excluded (Appendix H, page 2): 33,280 + 16,640 = 49,920. + ri_liheap_income: 49_920 + # Matrix, 60% SMI annual, four people. + ri_liheap_income_limit: 81_254 + # Size 4 guideline 32,150: 49,920 > 48,225 (150%): row E. + ri_liheap_income_band: 5 + ri_liheap_eligible: true + # Row E Deliverables 657; delivered fuel gets no utility enhancement. + ri_liheap_base_payment: 657 + ri_liheap: 657 + +# A public housing tenant aged 58 working 20 hours a week at the minimum wage (16,640) +# whose rent includes heat. He does not know the building's fuel (UNSPECIFIED) and pays +# his own electric bill. +- name: Case 4, a public housing tenant with heat in rent and his own electric bill receives the row I grant. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 58 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 16_640 + spm_units: + spm_unit: + members: [person1] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + heating_type: UNSPECIFIED + pre_subsidy_electricity_expense: 900 + heat_expense_included_in_rent: true + receives_housing_assistance: false + households: + household: + members: [person1] + state_code: RI + is_in_public_housing: true + output: + # Heat is paid through rent, not a bill of his own. + has_heating_expense: false + ri_liheap_household_size: 1 + ri_liheap_income: 16_640 + ri_liheap_income_limit: 42_252 + # 15,650 (100%) < 16,640 <= 19,563 (125%): band 3, which the heat-in-rent rows do + # not use. + ri_liheap_income_band: 3 + # Subsidized renters need a utility burden (manual II.B, page 6); his electric bill + # is one. + ri_liheap_eligible: true + # Row I (subsidized housing, secondary electric) 50 + 350 enhancement = 400. + ri_liheap_base_payment: 400 + ri_liheap: 400 + +# Two adults aged 30 and 29, each working full time at the minimum wage (33,280 each), +# with natural gas heat billed in their names. +- name: Case 5, a working couple above the two-person income limit receives nothing. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 33_280 + person2: + age: 29 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 33_280 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [person1, person2] + state_code: RI + is_in_public_housing: false + output: + has_heating_expense: true + ri_liheap_household_size: 2 + # 33,280 + 33,280 = 66,560. + ri_liheap_income: 66_560 + ri_liheap_income_limit: 55_252 + # Size 2 guideline 21,150: 66,560 > 31,725 (150%): row E. + ri_liheap_income_band: 5 + # 66,560 > 55,252, and above the federal floor 1.1 x 21,150 = 23,265. + ri_liheap_eligible: false + # The base payment does not apply eligibility: row E Natural Gas 159 + 350 = 509. + ri_liheap_base_payment: 509 + ri_liheap: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/payment/ri_liheap_base_payment.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/payment/ri_liheap_base_payment.yaml new file mode 100644 index 00000000000..c3c58a8125a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/payment/ri_liheap_base_payment.yaml @@ -0,0 +1,178 @@ +# FY2026 matrix p1 rows A-E supplies the federal base. DHS October 2025 DIV 1-1 +# and PUC Order 25579 p3 separately establish the $350 primary utility enhancement. +# Gas: [232, 211, 192, 175, 159] + 350; electric: [393, 357, 324, 295, 268] + 350. +# Deliverables remain unchanged. Primary bills of 10 do not cap these amounts. +# Qualifying utility-provider accounts and actual primary-grant receipt are assumed. +- name: Case 1, all fifteen ordinary primary amounts include the utility enhancement where applicable. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI, RI, RI, RI, RI, RI, RI, RI, RI, RI, RI, RI, RI] + heating_type: [FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, + NATURAL_GAS, NATURAL_GAS, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY] + gas_expense: [0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0] + ri_liheap_household_size: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ri_liheap_income_band: [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5] + fuel_oil_expense: [10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + pre_subsidy_electricity_expense: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10] + output: + ri_liheap_base_payment: [861, 805, 752, 704, 657, 582, 561, 542, 525, 509, 743, 707, 674, 645, 618] + +# FY2026 matrix row B Deliverables pays 805; the landlord form lists oil, kerosene, propane and wood/pellets. Coal is not asserted. +- name: Case 2, supported delivered fuels share the same published deliverable amount. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI, RI] + heating_type: [FUEL_OIL, KEROSENE, PROPANE, WOOD] + gas_expense: [0, 0, 0, 0] + ri_liheap_household_size: [1, 1, 1, 1] + ri_liheap_income_band: [2, 2, 2, 2] + fuel_oil_expense: [10, 10, 0, 0] + bottled_gas_expense: [0, 0, 10, 0] + wood_expense: [0, 0, 0, 10] + output: + ri_liheap_base_payment: [805, 805, 805, 805] + +# FY2026 matrix p1 row F: subsidized households with their own primary bill get +# deliverables 657, gas 159 + utility enhancement 350 = 509, electric 268 + 350 = 618, +# independent of band. This is the ordinary primary route, not heat-in-rent. +- name: Case 3, subsidized primary bill rows use the row F amounts for either subsidy flag. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI, RI, RI, RI] + heating_type: [FUEL_OIL, NATURAL_GAS, ELECTRICITY, FUEL_OIL, NATURAL_GAS, ELECTRICITY] + gas_expense: [0, 10, 0, 0, 10, 0] + ri_liheap_household_size: [1, 1, 1, 1, 1, 1] + ri_liheap_income_band: [1, 2, 3, 5, 4, 1] + receives_housing_assistance: [true, true, true, false, false, false] + is_in_public_housing: [false, false, false, true, true, true] + fuel_oil_expense: [10, 0, 0, 10, 0, 0] + pre_subsidy_electricity_expense: [0, 0, 10, 0, 0, 10] + output: + ri_liheap_base_payment: [657, 509, 618, 657, 509, 618] + +# Explicit NONE and zero qualified size receive no base payment; this does not classify unpriced fuels as legally ineligible. +- name: Case 4, explicit no heating and zero qualified size return no base payment. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI] + heating_type: [NONE, NATURAL_GAS] + gas_expense: [1_200, 1_200] + ri_liheap_household_size: [1, 0] + ri_liheap_income_band: [1, 0] + output: + ri_liheap_base_payment: [0, 0] + +# Ordinary RI natural gas band A is federal 232 + state 350 = 582; +# a Massachusetts unit has no RI primary payment component. +- name: Case 5, state gating preserves Rhode Island and excludes an out of state household. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, MA] + heating_type: [NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_200, 1_200] + ri_liheap_household_size: [1, 1] + ri_liheap_income_band: [1, 1] + output: + ri_liheap_base_payment: [582, 0] + +# The enhancement follows the qualifying primary utility grant, not every utility +# bill. Oil 861 remains 861 despite an electric bill; gas 232 + 350 = 582; +# electric 393 + 350 = 743. Having both gas and electric bills does not double the 350. +- name: Case 6, primary fuel determines enhancement despite other utility bills. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI] + heating_type: [FUEL_OIL, NATURAL_GAS, ELECTRICITY] + ri_liheap_household_size: [1, 1, 1] + ri_liheap_income_band: [1, 1, 1] + heat_expense_included_in_rent: [false, false, false] + receives_housing_assistance: [false, false, false] + is_in_public_housing: [false, false, false] + fuel_oil_expense: [1_200, 0, 0] + gas_expense: [1_200, 1_200, 1_200] + pre_subsidy_electricity_expense: [1_200, 1_200, 1_200] + output: + ri_liheap_base_payment: [861, 582, 743] + +# No ordinary primary price is established for these fuels. A utility bill alone +# cannot justify adding 350. Zeros represent unsupported components, not legal +# ineligibility findings for unknown or unpriced fuels; NONE means no heating. +- name: Case 7, unpriced and absent heating receive no utility enhancement. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI, RI, RI] + heating_type: [COAL, SOLAR, OTHER, UNSPECIFIED, NONE] + ri_liheap_household_size: [1, 1, 1, 1, 1] + ri_liheap_income_band: [1, 1, 1, 1, 1] + heat_expense_included_in_rent: [false, false, false, false, false] + receives_housing_assistance: [false, false, false, false, false] + is_in_public_housing: [false, false, false, false, false] + gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200] + pre_subsidy_electricity_expense: [1_200, 1_200, 1_200, 1_200, 1_200] + output: + ri_liheap_base_payment: [0, 0, 0, 0, 0] + +# Row F uses the same primary heating obligation as eligibility, so the has_heating_expense +# flag without a bill amount selects it: gas 159 + 350 = 509, electric 268 + 350 = 618, +# wood 657. An unsubsidized flagged gas household stays in band 1: 232 + 350 = 582. +- name: Case 8, a heating obligation flag without a bill amount selects the subsidized row. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI, RI] + heating_type: [NATURAL_GAS, ELECTRICITY, WOOD, NATURAL_GAS] + has_heating_expense: [true, true, true, true] + gas_expense: [0, 0, 0, 0] + pre_subsidy_electricity_expense: [0, 0, 0, 0] + wood_expense: [0, 0, 0, 0] + ri_liheap_household_size: [1, 1, 1, 1] + ri_liheap_income_band: [1, 1, 1, 1] + receives_housing_assistance: [true, true, true, false] + is_in_public_housing: [false, false, false, false] + output: + ri_liheap_base_payment: [509, 618, 657, 582] + +# Period 2025 backfills the FY2026 cells; FY2025 plan item 2.6 gives a $64 minimum and +# $1,148 maximum benefit, so these check the backfill only, not FY2025 policy. +- name: Case 9, earlier years backfill the researched payment schedule as an estimate. + period: 2025 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI] + heating_type: [FUEL_OIL, NATURAL_GAS, ELECTRICITY] + fuel_oil_expense: [1_200, 0, 0] + gas_expense: [0, 1_200, 0] + pre_subsidy_electricity_expense: [0, 0, 1_200] + ri_liheap_household_size: [1, 1, 1] + ri_liheap_income_band: [1, 1, 1] + output: + ri_liheap_base_payment: [861, 582, 743] + +# Row G is the direct grant to an unsubsidized heat-in-rent household with no separate +# electric bill. Subsidized renters with no utility burden are ineligible (manual II.B, +# page 6), so a subsidized heat-in-rent household with no electric bill gets no row G +# grant under either subsidy flag: 0 and 0. The same unsubsidized household reads the +# row G Natural Gas cell, 400, with no enhancement because no utility account receives +# the grant. +- name: Case 10, subsidized heat-in-rent households without an electric bill get no direct grant. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + ri_liheap_household_size: [1, 1, 1] + ri_liheap_income_band: [1, 1, 1] + heat_expense_included_in_rent: [true, true, true] + gas_expense: [0, 0, 0] + pre_subsidy_electricity_expense: [0, 0, 0] + receives_housing_assistance: [true, false, false] + is_in_public_housing: [false, true, false] + output: + ri_liheap_base_payment: [0, 0, 400] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/payment/ri_liheap_income_band.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/payment/ri_liheap_income_band.yaml new file mode 100644 index 00000000000..5a8635d0af3 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/payment/ri_liheap_income_band.yaml @@ -0,0 +1,40 @@ +# FY2026 matrix, size 1 tops: 11,738, 15,650, 19,563 and 23,475. Each top is inclusive and the next cent moves up a band, so the 76%, 101%, 126% and 151% labels leave no gaps. +- name: Case 1, one person poverty bands include the exact upper dollar and exclude the next cent. + period: 2026 + input: + state_code: [RI, RI, RI, RI, RI, RI, RI, RI, RI] + ri_liheap_household_size: [1, 1, 1, 1, 1, 1, 1, 1, 1] + ri_liheap_income: [0, 11_738, 11738.01, 15_650, 15650.01, 19_563, 19563.01, 23_475, 23475.01] + output: + ri_liheap_income_band: [1, 1, 2, 2, 3, 3, 4, 4, 5] + +# Size 2: 2025 guideline 21,150 x 125% = 26,437.50, rounded half up to 26,438. The printed 125% cell, 31,725, repeats the 150% value (documented anomaly), so 26,438.01 enters band 4. +- name: Case 2, two person one hundred twenty five percent band follows the rate rather than the anomalous + printed cell. + period: 2026 + input: + state_code: [RI, RI, RI, RI, RI] + ri_liheap_household_size: [2, 2, 2, 2, 2] + ri_liheap_income: [26437.5, 26_438, 26438.01, 31_725, 31725.01] + output: + ri_liheap_income_band: [3, 3, 4, 4, 5] + +# Unverified 2025 estimate on the 2024 guideline 15,060: 75% = 11,295 and 125% = 18,825. Earlier years use their own lagged guideline. +- name: Case 3, backfilled poverty bands use the requested year prior guideline. + period: 2025 + input: + state_code: [RI, RI, RI, RI] + ri_liheap_household_size: [1, 1, 1, 1] + ri_liheap_income: [11_295, 11295.01, 18_825, 18825.01] + output: + ri_liheap_income_band: [1, 2, 3, 4] + +# No qualified member means band 0, not the lowest positive payment band. +- name: Case 4, zero qualified size has no payment band. + period: 2026 + input: + state_code: [RI] + ri_liheap_household_size: [0] + ri_liheap_income: [0] + output: + ri_liheap_income_band: [0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/ri_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/ri_liheap.yaml new file mode 100644 index 00000000000..46f7cef637f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/ri_liheap.yaml @@ -0,0 +1,472 @@ +# FY2026 DHS matrix row A: gas 232, electricity 393, delivered fuel 861. +# Qualifying primary gas/electric grants receive the separate 350 enhancement. +# These raw-input cases assume a qualifying utility account and grant receipt. +- name: Case 1, ordinary direct heating receives the published award and applicable enhancement. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI] + age: [40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN] + employment_income: [10_000, 10_000, 10_000] + ssi: [0, 0, 0] + tanf: [0, 0, 0] + childcare_expenses: [0, 0, 0] + child_care_subsidies: [0, 0, 0] + heating_type: [NATURAL_GAS, ELECTRICITY, FUEL_OIL] + gas_expense: [1_200, 0, 0] + pre_subsidy_electricity_expense: [0, 1_200, 1_200] + fuel_oil_expense: [0, 0, 1_200] + heat_expense_included_in_rent: [false, false, false] + receives_housing_assistance: [false, false, false] + is_in_public_housing: [false, false, false] + output: + ri_liheap_income: [10_000, 10_000, 10_000] + ri_liheap: [582, 743, 861] + +# FY2026 row F (subsidized housing with a primary heating bill) prints the row E cells at +# any income: gas 159 + 350, electric 268 + 350, oil 657. +- name: Case 2, subsidized households with direct primary bills use the subsidized schedule. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI] + age: [40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN] + employment_income: [10_000, 10_000, 10_000] + ssi: [0, 0, 0] + tanf: [0, 0, 0] + childcare_expenses: [0, 0, 0] + child_care_subsidies: [0, 0, 0] + heating_type: [NATURAL_GAS, ELECTRICITY, FUEL_OIL] + gas_expense: [1_200, 0, 0] + pre_subsidy_electricity_expense: [0, 1_200, 0] + fuel_oil_expense: [0, 0, 1_200] + heat_expense_included_in_rent: [false, false, false] + receives_housing_assistance: [true, false, true] + is_in_public_housing: [false, true, false] + output: + ri_liheap: [509, 618, 657] + +# FY2026 size-1 income ceiling 42,252 is inclusive. The final award must +# also enforce state, qualified membership and a heating obligation. +- name: Case 3, final payment enforces income and other eligibility requirements. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI, MA, RI, RI] + age: [40, 40, 40, 40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN, CITIZEN, UNDOCUMENTED, CITIZEN] + employment_income: [42_252, 42252.01, 10_000, 10_000, 10_000, 10_000] + ssi: [0, 0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0, 0] + childcare_expenses: [0, 0, 0, 0, 0, 0] + child_care_subsidies: [0, 0, 0, 0, 0, 0] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NONE] + gas_expense: [1_200, 1_200, 0, 1_200, 1_200, 1_200] + heat_expense_included_in_rent: [false, false, false, false, false, false] + output: + ri_liheap: [509, 0, 0, 0, 0, 0] + +# FY2026 rows G and I: direct grant 400; subsidized secondary electric 50 + 350. +# A qualifying secondary utility account receiving the grant is assumed. +- name: Case 4, eligible heat-in-rent households receive their regular grants. + period: 2026 + input: + state_code: [RI, RI, RI] + age: [40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN] + employment_income: [10_000, 10_000, 10_000] + ssi: [0, 0, 0] + tanf: [0, 0, 0] + childcare_expenses: [0, 0, 0] + child_care_subsidies: [0, 0, 0] + heating_type: [NATURAL_GAS, NATURAL_GAS, ELECTRICITY] + gas_expense: [0, 0, 0] + pre_subsidy_electricity_expense: [0, 120, 120] + heat_expense_included_in_rent: [true, true, true] + receives_housing_assistance: [false, true, false] + is_in_public_housing: [false, false, true] + output: + ri_liheap_eligible: [true, true, true] + ri_liheap: [400, 400, 400] + +# Period 2025 backfills the FY2026 schedule (undated formulas, parameter backfilling). +# FY2025 plan item 2.6 gives a $64 minimum and $1,148 maximum benefit, so 861 checks +# the backfill only; it is not FY2025 policy. +- name: Case 5, earlier years backfill an unverified heating award estimate. + period: 2025 + absolute_error_margin: 0.01 + input: + state_code: [RI] + age: [40] + immigration_status: [CITIZEN] + employment_income: [10_000] + ssi: [0] + tanf: [0] + childcare_expenses: [0] + child_care_subsidies: [0] + heating_type: [FUEL_OIL] + fuel_oil_expense: [1_200] + heat_expense_included_in_rent: [false] + receives_housing_assistance: [false] + is_in_public_housing: [false] + output: + ri_liheap_income: [10_000] + ri_liheap_eligible: [true] + ri_liheap_income_band: [1] + ri_liheap_base_payment: [861] + ri_liheap: [861] + +# Same household in 2026: 10000 <= 42252 size-1 limit; 10000 <= 11738 (75% of the +# 2025 FPG 15650) is band 1 (row A); delivered fuel 861 with no utility enhancement. +- name: Case 6, the same household receives the FY2026 delivered fuel award in 2026. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI] + age: [40] + immigration_status: [CITIZEN] + employment_income: [10_000] + ssi: [0] + tanf: [0] + childcare_expenses: [0] + child_care_subsidies: [0] + heating_type: [FUEL_OIL] + fuel_oil_expense: [1_200] + heat_expense_included_in_rent: [false] + receives_housing_assistance: [false] + is_in_public_housing: [false] + output: + ri_liheap_income: [10_000] + ri_liheap_income_limit: [42_252] + ri_liheap_eligible: [true] + ri_liheap_income_band: [1] + ri_liheap_base_payment: [861] + ri_liheap: [861] + +# Row F uses the same primary heating obligation test as eligibility (has_heating_expense +# or a positive fuel bill), so a subsidized household with the flag and no bill amount gets +# gas 159 + 350 = 509, electric 268 + 350 = 618 and wood 657, not band 1 (582 / 743 / 861). +- name: Case 7, subsidized households with a heating obligation but no bill amount use the subsidized row. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI] + age: [40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN] + employment_income: [10_000, 10_000, 10_000] + ssi: [0, 0, 0] + tanf: [0, 0, 0] + childcare_expenses: [0, 0, 0] + child_care_subsidies: [0, 0, 0] + heating_type: [NATURAL_GAS, ELECTRICITY, WOOD] + has_heating_expense: [true, true, true] + gas_expense: [0, 0, 0] + pre_subsidy_electricity_expense: [0, 0, 0] + wood_expense: [0, 0, 0] + heat_expense_included_in_rent: [false, false, false] + receives_housing_assistance: [true, false, true] + is_in_public_housing: [false, true, false] + output: + ri_liheap_eligible: [true, true, true] + ri_liheap_income_band: [1, 1, 1] + ri_liheap_base_payment: [509, 618, 657] + ri_liheap: [509, 618, 657] + +# 18000 IRA distributions count in full. Size-1 FY2026 bands use the 2025 FPG 15650: +# 15650 < 18000 <= 19563 (125%) is band 3 (row C); delivered fuel pays 752. +- name: Case 8, retirement account distributions set the payment band. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI] + age: [60] + immigration_status: [CITIZEN] + taxable_ira_distributions: [18_000] + ssi: [0] + tanf: [0] + childcare_expenses: [0] + child_care_subsidies: [0] + heating_type: [FUEL_OIL] + fuel_oil_expense: [1_200] + heat_expense_included_in_rent: [false] + receives_housing_assistance: [false] + is_in_public_housing: [false] + output: + ri_liheap_income: [18_000] + ri_liheap_eligible: [true] + ri_liheap_income_band: [3] + ri_liheap: [752] + +# 24000 Social Security - 2434.80 pinned Part B premium (202.90 x 12) = 21565.20. +# 19563 < 21565.20 <= 23475 is band 4 (row D): delivered fuel 704. Without the +# Medicare deduction, 24000 > 23475 would be band 5 and pay 657. +- name: Case 9, the Medicare Part B premium deduction sets a lower payment band. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI] + age: [67] + immigration_status: [CITIZEN] + social_security: [24_000] + medicare_part_b_premium: [2_434.80] + ssi: [0] + tanf: [0] + childcare_expenses: [0] + child_care_subsidies: [0] + heating_type: [FUEL_OIL] + fuel_oil_expense: [1_200] + heat_expense_included_in_rent: [false] + receives_housing_assistance: [false] + is_in_public_housing: [false] + output: + ri_liheap_income: [21_565.20] + ri_liheap_eligible: [true] + ri_liheap_income_band: [4] + ri_liheap: [704] + +# FY2026 matrix rows G-I, manual II.B and DHS DIV 1-1 (October 2025): heat-in-rent direct 400, +# secondary electric 80 + 350, subsidized secondary electric 50 + 350. The primary +# heating fuel does not change a grant routed to the separate electric account. +- name: Case 10, heat-in-rent routes use existing electric bills and housing flags. + period: 2026 + input: + state_code: [RI, RI, RI, RI, RI, RI, RI, RI] + age: [40, 40, 40, 40, 40, 40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN, CITIZEN, CITIZEN, CITIZEN, CITIZEN, CITIZEN] + employment_income: [0, 0, 0, 0, 0, 0, 0, 0] + ssi: [0, 0, 0, 0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0, 0, 0, 0] + childcare_expenses: [0, 0, 0, 0, 0, 0, 0, 0] + child_care_subsidies: [0, 0, 0, 0, 0, 0, 0, 0] + heating_type: [FUEL_OIL, NATURAL_GAS, OTHER, FUEL_OIL, NATURAL_GAS, ELECTRICITY, NATURAL_GAS, NONE] + rent: [12_000, 12_000, 12_000, 12_000, 12_000, 12_000, 12_000, 12_000] + gas_expense: [0, 0, 0, 0, 0, 0, 0, 0] + pre_subsidy_electricity_expense: [0, 0, 0, 1_200, 1_200, 1_200, 0, 1_200] + heat_expense_included_in_rent: [true, true, true, true, true, true, true, true] + receives_housing_assistance: [false, false, false, false, true, false, true, false] + is_in_public_housing: [false, false, false, false, false, true, false, false] + has_heating_expense: [false, false, false, false, false, false, true, false] + output: + ri_liheap_income: [0, 0, 0, 0, 0, 0, 0, 0] + ri_liheap_eligible: [true, true, true, true, true, true, false, false] + ri_liheap: [400, 400, 400, 430, 400, 400, 0, 0] + +# Period 2025 backfills the FY2026 heat-in-rent grants and $350 enhancement. No FY2025 +# matrix or enhancement amount was located, so 430 checks the backfill only. +- name: Case 11, earlier heat-in-rent awards backfill without claiming verified history. + period: 2025 + input: + state_code: RI + age: 40 + immigration_status: CITIZEN + employment_income: 10_000 + heating_type: NATURAL_GAS + rent: 12_000 + heat_expense_included_in_rent: true + pre_subsidy_electricity_expense: 1_200 + receives_housing_assistance: false + is_in_public_housing: false + output: + ri_liheap: 430 + +# FY2026 row G prints $400 under Deliverables, Natural Gas and Other and leaves the +# Electric cell blank. Wages are the 2026 Rhode Island minimum wage: 16 x 40 x 52 = +# 33,280 <= 42,252 (size 1), and heat in unsubsidized rent meets the energy burden. +# Unit 1 has no separate electric bill, so row G applies: blank Electric cell, 0, and +# no enhancement because no utility account receives a grant. Unit 2 has an electric +# bill, so row H applies: 80 + 350 enhancement (DIV 1-1) = 430. +- name: Case 12, electric heat included in unsubsidized rent follows the blank row G cell. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI] + age: [40, 40] + immigration_status: [CITIZEN, CITIZEN] + employment_income: [33_280, 33_280] + ssi: [0, 0] + tanf: [0, 0] + childcare_expenses: [0, 0] + child_care_subsidies: [0, 0] + heating_type: [ELECTRICITY, ELECTRICITY] + pre_subsidy_electricity_expense: [0, 1_200] + heat_expense_included_in_rent: [true, true] + receives_housing_assistance: [false, false] + is_in_public_housing: [false, false] + output: + ri_liheap_eligible: [true, true] + ri_liheap_base_payment: [0, 430] + ri_liheap: [0, 430] + +# Threshold edge for the federal floor, 42 U.S.C. 8624(b)(2)(B), with the 2026 example +# from review: 23 qualified members (forced size) and 150,000 of countable income +# (forced). The 60% SMI limit is 148,694 (eligibility Case 4), but 150,000 is below +# the floor 1.1 x 136,650 = 150,315 on the 2025 guideline, so the household stays +# eligible. Bands use the same guideline: 136,650 (100%) < 150,000 <= 170,813 (125%, +# 170,812.50 rounded half up) is band 3 (row C): natural gas 192 + 350 = 542. +- name: Case 13, threshold edge, a twenty three member household below the federal poverty floor is paid above the state median income limit. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI] + ri_liheap_household_size: [23] + ri_liheap_income: [150_000] + heating_type: [NATURAL_GAS] + gas_expense: [1_200] + heat_expense_included_in_rent: [false] + receives_housing_assistance: [false] + is_in_public_housing: [false] + output: + ri_liheap_income_limit: [148_694] + ri_liheap_eligible: [true] + ri_liheap_income_band: [3] + ri_liheap_base_payment: [542] + ri_liheap: [542] + +# FY2026 row G pays $400 under Deliverables, Natural Gas and Other. HeatingType documents +# UNSPECIFIED as heat in rent with an unknown fuel, and Appendix B has no coal or solar +# box, so UNSPECIFIED, SOLAR and COAL read the Other cell: 400. With no electric bill no +# utility account receives the grant, so there is no enhancement. Heat in unsubsidized +# rent meets the energy burden; wages are the 2026 Rhode Island minimum wage, 16 x 40 x +# 52 = 33,280 <= 42,252 (size 1). +- name: Case 14, heat in unsubsidized rent with an unspecified, solar or coal fuel receives the row G Other amount. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI] + age: [40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN] + employment_income: [33_280, 33_280, 33_280] + ssi: [0, 0, 0] + tanf: [0, 0, 0] + childcare_expenses: [0, 0, 0] + child_care_subsidies: [0, 0, 0] + heating_type: [UNSPECIFIED, SOLAR, COAL] + pre_subsidy_electricity_expense: [0, 0, 0] + heat_expense_included_in_rent: [true, true, true] + receives_housing_assistance: [false, false, false] + is_in_public_housing: [false, false, false] + output: + ri_liheap_eligible: [true, true, true] + ri_liheap_base_payment: [400, 400, 400] + ri_liheap: [400, 400, 400] + +# Outside heat in rent the Other column is blank in rows A-F, and Appendix B has no coal +# or solar box. Coal, solar and other households with their own bill meet the energy +# burden and the income test (33,280 <= 42,252), so they are eligible, but no matrix +# amount applies and no utility grant carries the enhancement: 0. +- name: Case 15, coal, solar and other heat with a direct bill stay eligible but receive no matrix amount. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI] + age: [40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN] + employment_income: [33_280, 33_280, 33_280] + ssi: [0, 0, 0] + tanf: [0, 0, 0] + childcare_expenses: [0, 0, 0] + child_care_subsidies: [0, 0, 0] + heating_type: [COAL, SOLAR, OTHER] + coal_expense: [1_200, 0, 0] + pre_subsidy_electricity_expense: [0, 1_200, 0] + other_heating_fuel_expense: [0, 0, 1_200] + heat_expense_included_in_rent: [false, false, false] + receives_housing_assistance: [false, false, false] + is_in_public_housing: [false, false, false] + output: + ri_liheap_eligible: [true, true, true] + ri_liheap: [0, 0, 0] + +# Threshold edge for the two-person limit, reached end to end. The undocumented parent is +# not counted in the size (manual page 12) but her wages count, so the qualified size is +# 2 (the citizen spouse and child) and the FY2026 limit is 55,252, not the three-person +# 68,253. Income 55,252 equals the limit, which is inclusive. Size 2 bands use the 2025 +# guideline 21,150: 55,252 > 31,725 (150%) is band 5 (row E): natural gas 159 + 350 = 509. +- name: Case 16, threshold edge, a mixed-status family at the two-person income limit is eligible. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + is_full_time_student: false + ssi: 0 + employment_income: 55_252 + person2: + age: 38 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + person3: + age: 9 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [person1, person2, person3] + state_code: RI + is_in_public_housing: false + output: + ri_liheap_household_size: 2 + ri_liheap_income: 55_252 + ri_liheap_income_limit: 55_252 + ri_liheap_eligible: true + ri_liheap_income_band: 5 + ri_liheap: 509 + +# Threshold edge: the same family with one more cent of wages exceeds the two-person +# limit of 55,252 and is far above the federal floor (1.1 x 21,150 = 23,265), so it is +# ineligible and receives 0. Using all three members would give the 68,253 limit. +- name: Case 17, threshold edge, the same mixed-status family one cent above the two-person limit is ineligible. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + is_full_time_student: false + ssi: 0 + employment_income: 55_252.01 + person2: + age: 38 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + person3: + age: 9 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [person1, person2, person3] + state_code: RI + is_in_public_housing: false + output: + ri_liheap_household_size: 2 + ri_liheap_income_limit: 55_252 + ri_liheap_eligible: false + ri_liheap: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/ri_liheap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/ri_liheap_household_size.yaml new file mode 100644 index 00000000000..3580fe6bfff --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/ri_liheap_household_size.yaml @@ -0,0 +1,65 @@ +# Manual pages 12-13 count citizens and qualified noncitizens; all three qualifying members count regardless of age. +- name: Case 1, qualified adults and children count while nonqualified residents do not. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: LEGAL_PERMANENT_RESIDENT + is_full_time_student: false + ssi: 0 + person2: + age: 8 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + person3: + age: 35 + immigration_status: ASYLEE + is_full_time_student: false + ssi: 0 + person4: + age: 22 + immigration_status: DACA + is_full_time_student: false + ssi: 0 + person5: + age: 39 + immigration_status: UNDOCUMENTED + is_full_time_student: false + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2, person3, person4, person5] + state_code: RI + output: + ri_liheap_household_size: 3 + +# Manual p12 excludes nonqualified members from the benefit size. +- name: Case 2, a household of only nonqualified members has size zero. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + is_full_time_student: false + ssi: 0 + spm_units: + spm_unit: + members: [person1] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1] + state_code: RI + output: + ri_liheap_household_size: 0 diff --git a/policyengine_us/variables/gov/states/ri/dhs/liheap/eligibility/ri_liheap_eligible.py b/policyengine_us/variables/gov/states/ri/dhs/liheap/eligibility/ri_liheap_eligible.py new file mode 100644 index 00000000000..051a76ec40e --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/eligibility/ri_liheap_eligible.py @@ -0,0 +1,58 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class ri_liheap_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Rhode Island LIHEAP regular heating eligibility" + defined_for = StateCode.RI + reference = ( + # PDF pages 5-6, 12 + "https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=5", + # PDF pages 8, 45 + "https://liheapch.acf.gov/docs/2026/state-plans/RI_Plan_2026.pdf#page=8", + "https://www.law.cornell.edu/uscode/text/42/8624#b_2_B", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ri.dhs.liheap + size = spm_unit("ri_liheap_household_size", period) + income = spm_unit("ri_liheap_income", period) + limit = spm_unit("ri_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 23 in 2026 and 2027 (21 and 19 in the 2025 and 2024 + # backfills). + 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) + 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) + ) + primary_obligation = spm_unit("has_heating_expense", period) | ( + spm_unit("heating_expense", period) > 0 + ) + separate_electric_bill = spm_unit("pre_subsidy_electricity_expense", period) > 0 + # Reported obligations and rental terms are assumed verified. + # Manual II.B (page 6): "Subsidized renters with no utility burden are + # ineligible for this program." FY2026 plan item 2.3 (page 8) answers No to + # differing policies for "Renters Living in subsidized housing?". This + # follows the manual. + energy_burden = where( + heat_in_rent, ~subsidized | separate_electric_bill, primary_obligation + ) + # Electric heat in unsubsidized rent with no electric bill (blank row G + # cell), and COAL, OTHER and SOLAR outside heat in rent, stay eligible but + # have no matrix amount, so ri_liheap pays $0. + heating_type = spm_unit("heating_type", period) + return ( + (size > 0) + & income_eligible + & energy_burden + & (heating_type != heating_type.possible_values.NONE) + ) diff --git a/policyengine_us/variables/gov/states/ri/dhs/liheap/eligibility/ri_liheap_income_limit.py b/policyengine_us/variables/gov/states/ri/dhs/liheap/eligibility/ri_liheap_income_limit.py new file mode 100644 index 00000000000..b29ff8f1a48 --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/eligibility/ri_liheap_income_limit.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.hhs_smi import liheap_smi_limit + + +class ri_liheap_income_limit(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Rhode Island LIHEAP annual income limit" + defined_for = StateCode.RI + reference = ( + "https://liheapch.acf.gov/docs/2026/state-plans/RI_Plan_2026.pdf#page=8", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf", + # DHS FFY2027 income eligibility sheet. + "https://dhs.ri.gov/media/12266/download?language=en", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ri.dhs.liheap.eligibility + size = spm_unit("ri_liheap_household_size", period) + state = spm_unit.household("state_code_str", period) + # The HHS floor order reproduces all 14 FY2026 and FY2027 rows. + limit = liheap_smi_limit(size, state, p.smi_rate, period, parameters) + return where(size > 0, limit, 0) diff --git a/policyengine_us/variables/gov/states/ri/dhs/liheap/income/ri_liheap_income.py b/policyengine_us/variables/gov/states/ri/dhs/liheap/income/ri_liheap_income.py new file mode 100644 index 00000000000..07958f6297e --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/income/ri_liheap_income.py @@ -0,0 +1,76 @@ +from policyengine_us.model_api import * + + +class ri_liheap_income(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Rhode Island LIHEAP countable income" + defined_for = StateCode.RI + reference = ( + # Manual sections III.E-H + # PDF pages 14-21 + "https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=14", + # Plan sections 1.8-1.9 + # PDF pages 5-7 + "https://liheapch.acf.gov/docs/2026/state-plans/RI_Plan_2026.pdf#page=5", + # Application instructions (Appendix H), May 2025 (FY2026) and + # July 2026 (FY2027) + "https://dhs.ri.gov/media/9671/download?language=en#page=2", + "https://dhs.ri.gov/media/12306/download?language=en#page=2", + # Rental Income Reported Form (Appendix M), May 2025 + "https://dhs.ri.gov/media/9701/download?language=en#page=1", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ri.dhs.liheap.income + person = spm_unit.members + age = person("age", period) + # The FY2026 (Appendix H) and FY2027 application instructions (p2) + # exclude the income of every full-time student aged 18-23. The 2019 + # manual (III.G p20) excluded only a child who was not the household + # head; the current instructions govern. + exempt_student = (age < p.student_age_limit) & person( + "is_full_time_student", period + ) + counted_person = (age >= p.minimum_income_age) & ~exempt_student + # Manual III.H (page 20) counts self-employment at 60% of gross receipts + # (a 40% deduction). The model has only net business income and counts it + # in full, as ks_liheap_countable_earned_income does; this matches the + # manual only when expenses are 40% of receipts, overstates countable + # income when expenses are lower and understates it when they are higher. + # No gross-receipts or business-expense input is added. Each source is + # floored at zero, so a loss in one source cannot offset another. + earnings = 0 + for source in p.sources.earned: + earnings = earnings + max_(person(source, period), 0) + other_income = 0 + for source in p.sources.unearned: + other_income = other_income + max_(person(source, period), 0) + rental_income = ( + max_(person("rental_income", period), 0) + + max_(person("farm_rent_income", period), 0) + ) * p.rental_income_rate + # Rental inputs approximate reported gross rents; model data may + # instead contain net rents. No new gross-rental input is introduced. + household_income = spm_unit.sum( + (earnings + other_income + rental_income) * counted_person + ) + interest = spm_unit.sum( + max_(person("interest_income", period), 0) * counted_person + ) + countable_interest = max_(interest - p.interest_exclusion, 0) + # The annual tanf aggregate includes ri_works and applies take-up; + # ri_works alone is the entitlement a nonrecipient could get. The + # household grant is counted once, not once per member. + tanf = spm_unit("tanf", period) + income = household_income + countable_interest + tanf + deductions = add(spm_unit, period, p.deduction_sources) + childcare = spm_unit("childcare_expenses", period) + subsidies = spm_unit("child_care_subsidies", period) + childcare_deduction = where(subsidies == 0, childcare, 0) + # Adult daycare/nursing-home attribution cannot be isolated reliably + # from broader care inputs, and Medicare prescription costs have no + # input. + return max_(income - deductions - childcare_deduction, 0) diff --git a/policyengine_us/variables/gov/states/ri/dhs/liheap/payment/ri_liheap_base_payment.py b/policyengine_us/variables/gov/states/ri/dhs/liheap/payment/ri_liheap_base_payment.py new file mode 100644 index 00000000000..68a73a8f38d --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/payment/ri_liheap_base_payment.py @@ -0,0 +1,109 @@ +from policyengine_us.model_api import * + + +class ri_liheap_base_payment(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Rhode Island LIHEAP primary heating payment with utility enhancement" + documentation = ( + "Ordinary primary heating payment, including the federal matrix grant " + "and the state enhancement when the grant reaches a qualifying gas " + "or electric utility account. For heat in rent, a reported separate " + "electric bill selects the secondary-electric route; otherwise the " + "row G grant for the household's fuel column is paid directly. Row G's " + "Electric cell is blank, so electric heat gets none, and coal, solar " + "and unspecified fuels read the Other cell. Utility-provider and " + "account ownership requirements are assumed satisfied. Final " + "eligibility is separate." + ) + defined_for = StateCode.RI + reference = ( + "https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf", + "https://dhs.ri.gov/media/9696/download?language=en#page=1", + # PDF pages 5-6: heat-in-rent payment and subsidized utility burden. + "https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=5", + "https://webserver.rilegislature.gov/Statutes/TITLE39/39-1/39-1-27.12.htm", + # PUC 1-5: the utility credits the enhancement only after receiving + # the household's federal grant. + "https://ripuc.ri.gov/sites/g/files/xkgbur841/files/2025-11/LIHEAP%2025-38-GE_PUC%20Data%20Requests%20Set%201%20to%20DHS%20update.pdf#page=2", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ri.dhs.liheap.payment + band = max_(spm_unit("ri_liheap_income_band", period), 1) + subsidized = spm_unit.household("is_in_public_housing", period) | spm_unit( + "receives_housing_assistance", period + ) + # Same primary heating obligation test as ri_liheap_eligible. + primary_bill = spm_unit("has_heating_expense", period) | ( + spm_unit("heating_expense", period) > 0 + ) + # Row F (subsidized housing with a primary heating bill) applies at any + # income. The FY2026 matrix prints the same cells for rows E and F, so row F + # reads row E's band; a matrix with different row F cells would need its own + # amounts. + band = where(subsidized & primary_bill, len(p.fpg_rates) + 1, band) + fuel = spm_unit("heating_type", period) + fuels = fuel.possible_values + deliverable = ( + (fuel == fuels.FUEL_OIL) + | (fuel == fuels.KEROSENE) + | (fuel == fuels.PROPANE) + | (fuel == fuels.WOOD) + ) + fuel_columns = [ + deliverable, + fuel == fuels.NATURAL_GAS, + fuel == fuels.ELECTRICITY, + ] + amount = select( + fuel_columns, + [ + p.amount.deliverables[band], + p.amount.natural_gas[band], + p.amount.electricity[band], + ], + # NONE means no heating. The Other column is blank in rows A-F, so OTHER, + # COAL and SOLAR (the landlord form has no coal or solar box) receive no + # primary amount, and UNSPECIFIED receives none because heating_type is a + # required input. Heat in rent replaces this amount with rows G-I below. + default=0, + ) + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + separate_electric_bill = spm_unit("pre_subsidy_electricity_expense", period) > 0 + hir = p.heat_in_rent + secondary_electric_payment = where( + subsidized, hir.subsidized_secondary_electric, hir.secondary_electric + ) + # Rows H-I go to the separate electric account whatever the fuel. Row G is + # the direct grant when there is no separate electric bill to credit, read + # from the household's fuel column; OTHER, COAL, SOLAR and UNSPECIFIED (heat + # in rent with an unknown fuel) read the Other cell, and the blank Electric + # cell is 0. Subsidized tenants without a utility burden are ineligible. + direct_amount = select( + fuel_columns, + [hir.direct.deliverables, hir.direct.natural_gas, hir.direct.electricity], + default=hir.direct.other, + ) + direct_payment = where(subsidized, 0, direct_amount) + heat_in_rent_payment = where( + separate_electric_bill, secondary_electric_payment, direct_payment + ) + amount = where(heat_in_rent, heat_in_rent_payment, amount) + utility_fuel = (fuel == fuels.NATURAL_GAS) | (fuel == fuels.ELECTRICITY) + utility_grant = where(heat_in_rent, separate_electric_bill, utility_fuel) + # Existing inputs do not identify the utility provider or the account + # receiving the federal grant. For this ordinary primary component, + # assume a qualifying distribution-company account and grant receipt, + # including the first regular grant to a secondary electric account. + # Block Island Utility District and Clear River Electric and Water + # District (formerly Pascoag) are excluded by law, but state/county + # alone cannot identify every account. No enhancement goes to + # deliverable fuels, unpriced fuels, or a direct $400 HIR payment. + enhancement = where(utility_grant & (amount > 0), p.utility_enhancement, 0) + # The regular grant has no actual-expense cap. Earlier backfilled + # results are unverified estimates, not a policy start date. + size = spm_unit("ri_liheap_household_size", period) + return where((size > 0) & (fuel != fuels.NONE), amount + enhancement, 0) diff --git a/policyengine_us/variables/gov/states/ri/dhs/liheap/payment/ri_liheap_income_band.py b/policyengine_us/variables/gov/states/ri/dhs/liheap/payment/ri_liheap_income_band.py new file mode 100644 index 00000000000..27480867836 --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/payment/ri_liheap_income_band.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class ri_liheap_income_band(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Rhode Island LIHEAP benefit income band" + defined_for = StateCode.RI + reference = "https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf" + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ri.dhs.liheap + size = spm_unit("ri_liheap_household_size", period) + state_group = spm_unit.household("state_group_str", period) + guideline = fpg( + max_(size, 1), state_group, period, parameters, year_lag=p.fpg_year_lag + ) + income = spm_unit("ri_liheap_income", period) + band = np.ones_like(size, dtype=int) + for rate in p.payment.fpg_rates: + threshold = np.floor(guideline * rate + 0.5) + band = band + (income > threshold) + # Use the published percentage labels. The size-two 125% table cell + # repeats $31,725 (150%); the labeled 125% rule gives $26,438. + return where(size > 0, band, 0) diff --git a/policyengine_us/variables/gov/states/ri/dhs/liheap/ri_liheap.py b/policyengine_us/variables/gov/states/ri/dhs/liheap/ri_liheap.py new file mode 100644 index 00000000000..5ed835095bf --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/ri_liheap.py @@ -0,0 +1,15 @@ +from policyengine_us.model_api import * + + +class ri_liheap(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Rhode Island LIHEAP regular heating assistance" + defined_for = "ri_liheap_eligible" + reference = ( + "https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf", + "https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=5", + ) + adds = ["ri_liheap_base_payment"] diff --git a/policyengine_us/variables/gov/states/ri/dhs/liheap/ri_liheap_household_size.py b/policyengine_us/variables/gov/states/ri/dhs/liheap/ri_liheap_household_size.py new file mode 100644 index 00000000000..3a9df4078ac --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/ri_liheap_household_size.py @@ -0,0 +1,14 @@ +from policyengine_us.model_api import * + + +class ri_liheap_household_size(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Rhode Island LIHEAP qualified household size" + defined_for = StateCode.RI + reference = "https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=12" + + # Section III counts only citizens and qualified noncitizens in size, + # while income from all members is considered. No SNAP-specific bars. + adds = ["is_citizen_or_legal_immigrant"]