diff --git a/changelog.d/ri-liheap.added.md b/changelog.d/ri-liheap.added.md new file mode 100644 index 00000000000..28f27503963 --- /dev/null +++ b/changelog.d/ri-liheap.added.md @@ -0,0 +1 @@ +Add Rhode Island LIHEAP regular heating assistance, income and heating eligibility, 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/income/deduction_sources.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/deduction_sources.yaml new file mode 100644 index 00000000000..05d14911b2a --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/deduction_sources.yaml @@ -0,0 +1,27 @@ +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", which DHS's draft FFY2027 plan summary glosses + # as "meaning the Medicare amount deducted is not counted", so the sources agree. + # 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..839140a3a23 --- /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 Application Instructions, 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..e9d3aaa7060 --- /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, May 2025 Appendix M + 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..cd26671b908 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/earned.yaml @@ -0,0 +1,27 @@ +description: Rhode Island uses these sources as earned income under the Low Income Home Energy Assistance Program. +# Existing net business income is used without an additional 40% deduction. +# The 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%)". Gross business receipts and business-expense inputs remain deferred. +# 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 Application Instructions, 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, Countable Income + 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..3780cf078f5 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/unearned.yaml @@ -0,0 +1,54 @@ +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. Jury duty, royalties, foster receipts and legal +# settlements also lack sufficiently specific existing inputs. +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 Application Instructions, Proof of 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/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/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/fpg_year_lag.yaml new file mode 100644 index 00000000000..e27edccd7d2 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/fpg_year_lag.yaml @@ -0,0 +1,13 @@ +description: Rhode Island uses the federal poverty guideline from this many years before the benefit year to set payment bands under the Low Income Home Energy Assistance Program. +# The FY2026 table uses the 2025 federal poverty guidelines. +# Backfilled years use their corresponding lagged guidelines and are unverified. +values: + 2025-10-01: 1 + +metadata: + unit: year + period: year + label: Rhode Island LIHEAP payment federal poverty guideline year lag + reference: + - title: Rhode Island FY2026 Benefit Matrix, FPG year note + 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..0a72a487137 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/heat_in_rent.yaml @@ -0,0 +1,20 @@ +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 electric heat receives no direct grant. Rows H and I +# print one amount each and go to the household's separate electric account. + +direct: + 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..0b44b5599c7 --- /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 + 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 5250ab3aa89..85225bd9d49 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..9f7e219ba92 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/eligibility/ri_liheap_eligible.yaml @@ -0,0 +1,64 @@ +# 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 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..5736e6aee28 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/income/ri_liheap_income.yaml @@ -0,0 +1,822 @@ +# 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 UI 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; 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 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..6f131c5ed72 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/payment/ri_liheap_base_payment.yaml @@ -0,0 +1,156 @@ +# 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] 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..80b2c79d9dc --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/ri_liheap.yaml @@ -0,0 +1,300 @@ +# 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] 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..413c6ad693a --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/eligibility/ri_liheap_eligible.py @@ -0,0 +1,45 @@ +from policyengine_us.model_api import * + + +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", + "https://liheapch.acf.gov/docs/2026/state-plans/RI_Plan_2026.pdf#page=8", + ) + + def formula(spm_unit, period, parameters): + size = spm_unit("ri_liheap_household_size", period) + income = spm_unit("ri_liheap_income", period) + limit = spm_unit("ri_liheap_income_limit", period) + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + subsidized = spm_unit("receives_housing_assistance", period) | ( + spm_unit.household("is_in_public_housing", period) + ) + 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 <= limit) + & 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..e0b10f08d5e --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/income/ri_liheap_income.py @@ -0,0 +1,73 @@ +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 + # Existing net business receipts approximate the gross-receipts rule. + # No additional 40% business deduction or new expense input is used. + # 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..f860ad9af1b --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/payment/ri_liheap_base_payment.py @@ -0,0 +1,97 @@ +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 " + "grant is paid directly, except that electric heat gets none because " + "row G's Electric cell is blank. 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) + ) + amount = select( + [deliverable, fuel == fuels.NATURAL_GAS, fuel == fuels.ELECTRICITY], + [ + 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; UNSPECIFIED receives none because heating_type is a + # required input. + 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 apply to the secondary electric account. Row G is the direct grant + # when there is no separate electric bill to credit; it prints $400 under + # Deliverables, Natural Gas and Other but leaves the Electric cell blank, so + # electric heat receives no row G grant. Subsidized tenants without a utility + # burden are ineligible. + direct_payment = where(subsidized | (fuel == fuels.ELECTRICITY), 0, hir.direct) + 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..42ba45dc2a9 --- /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.payment + 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.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..493b3d858d7 --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/ri_liheap.py @@ -0,0 +1,17 @@ +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", + ) + + def formula(spm_unit, period, parameters): + return spm_unit("ri_liheap_base_payment", period) 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..2bd106fca09 --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/ri_liheap_household_size.py @@ -0,0 +1,15 @@ +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" + + def formula(spm_unit, period, parameters): + # Section III counts only citizens and qualified noncitizens in size, + # while income from all members is considered. No SNAP-specific bars. + return add(spm_unit, period, ["is_citizen_or_legal_immigrant"])