From bf3b670cc5d151e3625183b406719b4b97c6edc9 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sat, 3 Oct 2026 19:49:21 -0400 Subject: [PATCH 1/9] Add RI regular heating LIHEAP components --- changelog.d/ri-liheap.added.md | 1 + .../ri/dhs/liheap/eligibility/smi_rate.yaml | 16 + .../income/dependent_expense_sources.yaml | 15 + .../dhs/liheap/income/interest_exclusion.yaml | 13 + .../dhs/liheap/income/minimum_income_age.yaml | 11 + .../dhs/liheap/income/rental_income_rate.yaml | 15 + .../ri/dhs/liheap/income/sources/earned.yaml | 23 + .../dhs/liheap/income/sources/unearned.yaml | 41 ++ .../dhs/liheap/income/student_age_limit.yaml | 16 + .../liheap/payment/amount/deliverables.yaml | 23 + .../liheap/payment/amount/electricity.yaml | 23 + .../liheap/payment/amount/natural_gas.yaml | 23 + .../ri/dhs/liheap/payment/fpg_rates.yaml | 18 + .../ri/dhs/liheap/payment/fpg_year_lag.yaml | 13 + .../liheap/payment/utility_enhancement.yaml | 17 + policyengine_us/programs.yaml | 6 + .../eligibility/ri_liheap_income_limit.yaml | 44 ++ .../dhs/liheap/income/ri_liheap_income.yaml | 574 ++++++++++++++++++ .../payment/ri_liheap_base_payment.yaml | 122 ++++ .../liheap/payment/ri_liheap_income_band.yaml | 51 ++ .../dhs/liheap/ri_liheap_household_size.yaml | 77 +++ .../eligibility/ri_liheap_income_limit.py | 29 + .../ri/dhs/liheap/income/ri_liheap_income.py | 61 ++ .../liheap/payment/ri_liheap_base_payment.py | 69 +++ .../liheap/payment/ri_liheap_income_band.py | 27 + .../ri/dhs/liheap/ri_liheap_household_size.py | 15 + 26 files changed, 1343 insertions(+) create mode 100644 changelog.d/ri-liheap.added.md create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/eligibility/smi_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/income/dependent_expense_sources.yaml create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/income/interest_exclusion.yaml create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/income/minimum_income_age.yaml create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/income/rental_income_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/earned.yaml create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/unearned.yaml create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/income/student_age_limit.yaml create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/amount/deliverables.yaml create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/amount/electricity.yaml create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/amount/natural_gas.yaml create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/fpg_rates.yaml create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/fpg_year_lag.yaml create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/utility_enhancement.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/eligibility/ri_liheap_income_limit.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/income/ri_liheap_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/payment/ri_liheap_base_payment.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/payment/ri_liheap_income_band.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/ri_liheap_household_size.yaml create mode 100644 policyengine_us/variables/gov/states/ri/dhs/liheap/eligibility/ri_liheap_income_limit.py create mode 100644 policyengine_us/variables/gov/states/ri/dhs/liheap/income/ri_liheap_income.py create mode 100644 policyengine_us/variables/gov/states/ri/dhs/liheap/payment/ri_liheap_base_payment.py create mode 100644 policyengine_us/variables/gov/states/ri/dhs/liheap/payment/ri_liheap_income_band.py create mode 100644 policyengine_us/variables/gov/states/ri/dhs/liheap/ri_liheap_household_size.py diff --git a/changelog.d/ri-liheap.added.md b/changelog.d/ri-liheap.added.md new file mode 100644 index 00000000000..11c710c1d13 --- /dev/null +++ b/changelog.d/ri-liheap.added.md @@ -0,0 +1 @@ +Add Rhode Island LIHEAP household, income, and primary heating payment components, including the qualifying utility enhancement and FY2027 income-limit references. 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/dependent_expense_sources.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/dependent_expense_sources.yaml new file mode 100644 index 00000000000..a77fb6c29e3 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/dependent_expense_sources.yaml @@ -0,0 +1,15 @@ +description: Rhode Island deducts these reported dependent expenses from income under the Low Income Home Energy Assistance Program. +# Amounts approximate verified court orders. Existing inputs cannot establish +# the allowable court-ordered maximum or whether every payment was made. +values: + 2025-10-01: + - child_support_expense + - alimony_expense + +metadata: + unit: list + period: year + label: Rhode Island LIHEAP dependent expense 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 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..a99c552b814 --- /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 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..abf963d6b45 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/minimum_income_age.yaml @@ -0,0 +1,11 @@ +description: Rhode Island counts individual income beginning at 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..d3cf693f205 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/rental_income_rate.yaml @@ -0,0 +1,15 @@ +description: Rhode Island counts this share of rental receipts under the Low Income Home Energy Assistance Program. +# The manual gives the same rental rule on pages 17 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 + href: https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=17 + - title: Rhode Island LIHEAP Rental Income Reported Form, May 2025 Appendix M + href: https://dhs.ri.gov/media/9701/download?language=en 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..2fd8e19a36e --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/earned.yaml @@ -0,0 +1,23 @@ +description: Rhode Island counts these earned income sources under the Low Income Home Energy Assistance Program. +# Existing net business income is used without an additional 40% deduction. +# 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 + 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 + 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 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..97ead88ed22 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/unearned.yaml @@ -0,0 +1,41 @@ +description: Rhode Island counts these unearned income sources under the Low Income Home Energy Assistance Program. +# Interest and rental receipts have separate allowances; RI Works is added +# once at its SPMUnit entity, so none of those sources is duplicated here. +# pension_income includes public/military pensions; military_retirement_pay +# is a tax-classification input and must not be added independently. +# SSI is the federal benefit; a separate RI state supplement is not modeled. +# disability_benefits approximates employment-related TDI/TCI receipts. +# The manual expressly counts workers compensation in Section III.E. +# 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: + - unemployment_compensation + - strike_benefits + - social_security + - ssi + - pension_income + - survivor_benefits + - railroad_benefits + - alimony_income + - child_support_received + - workers_compensation + - disability_benefits + - financial_assistance + - ordinary_dividend_income + - veterans_benefits + # Manual Section III.E, Estate or Trust Income, page 17. + - estate_income + +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 + 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 + 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 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..b59a41f3be4 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/student_age_limit.yaml @@ -0,0 +1,16 @@ +description: Rhode Island excludes full-time student income below this age when the student is not the household head under the Low Income Home Energy Assistance Program. +# Age 18 through 23 means below the twenty-fourth birthday. +# The newer application omits the manual household-head exception without +# expressly repealing it. Current student-head treatment remains unverified. +values: + 2025-10-01: 24 + +metadata: + unit: year + period: year + label: Rhode Island LIHEAP student income exclusion age limit + 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, Full-time Students + href: https://dhs.ri.gov/media/9671/download?language=en 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/utility_enhancement.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/utility_enhancement.yaml new file mode 100644 index 00000000000..afbf591eaaf --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/utility_enhancement.yaml @@ -0,0 +1,17 @@ +description: Rhode Island adds this enhancement to the primary federal Low Income Home Energy Assistance Program grant credited to a qualifying gas or electric distribution-company account. +# This amount is sourced for the FY2026 heating season. Historical +# backfilling and later carryforward do not extend verified coverage. +# It is not part of the federal fuel matrix or an additional 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, pages 1 and 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=3 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 756dc2c4988..68addc506bc 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -804,6 +804,12 @@ programs: status: partial coverage: OR, DC, Riverside County, MA, IL, KS, MS, TX state_implementations: + - state: RI + status: in_progress + name: Rhode Island LIHEAP + full_name: Rhode Island Low Income Home Energy Assistance Program + parameter_prefix: gov.states.ri.dhs.liheap + notes: Income and ordinary primary-payment components are implemented, including the FY2026 enhancement for assumed qualifying gas/electric accounts. Heat-in-rent payment routing and the blank direct-electric cell remain unresolved; provider/account identity is an input limitation. Regular heating only; historical backfills do not establish verified coverage. - state: OR status: in_progress name: LIHEAP 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..45386fc0453 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/eligibility/ri_liheap_income_limit.yaml @@ -0,0 +1,44 @@ +# Rhode Island independent income and primary-bill matrix components; no final award is asserted. + +# FY2026 matrix p1 literal annual60%SMI column. Expected dollars are independent of the inferred nested-floor numerical pattern. +- 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] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, + NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200, 1_200] + 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] + heating_type: [NATURAL_GAS] + gas_expense: [1_200] + ri_liheap_household_size: [0] + output: + ri_liheap_income_limit: [0] + +# DHS FFY2027 income eligibility sheet p1, mandatory October 1, 2026. +# These are all14 literal annual values, independently 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] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, + NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, + NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + 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..5262c98d341 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/income/ri_liheap_income.yaml @@ -0,0 +1,574 @@ +# Rhode Island independent income and primary-bill matrix components; no final award is asserted. + +# Manual pp14-15 income categories, approved existing-net-input approximation:12000+2400+600+1200+1200=17400; no additional40% 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 + is_household_head: true + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1] + state_code: RI + output: + ri_liheap_income: 17_400 + +# Approved nonnegative net-source convention:12000 wages+600 positive business=12600; negative sources contribute0. +- 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 + is_household_head: true + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1] + state_code: RI + output: + ri_liheap_income: 12_600 + +# Manual p20 exempts all under18 income and all nonhead full-time student income through23. Included young-adult receipts are1200 wages+600 SSA; parent wages12000. Head-rule conflict is not tested. +- name: Case 3, nonhead age 17 nonstudent follows the whole income exclusion. + 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 + is_household_head: true + person2: + age: 17 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 1_200 + social_security: 600 + is_household_head: false + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 12_000 + +# Manual p20 exempts all under18 income and all nonhead full-time student income through23. Included young-adult receipts are1200 wages+600 SSA; parent wages12000. Head-rule conflict is not tested. +- name: Case 4, nonhead age 18 full time student follows the whole income exclusion. + 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 + is_household_head: true + person2: + age: 18 + immigration_status: CITIZEN + is_full_time_student: true + ssi: 0 + employment_income: 1_200 + social_security: 600 + is_household_head: false + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 12_000 + +# Manual p20 exempts all under18 income and all nonhead full-time student income through23. Included young-adult receipts are1200 wages+600 SSA; parent wages12000. Head-rule conflict is not tested. +- name: Case 5, nonhead age 23 full time student follows the whole income exclusion. + 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 + is_household_head: true + person2: + age: 23 + immigration_status: CITIZEN + is_full_time_student: true + ssi: 0 + employment_income: 1_200 + social_security: 600 + is_household_head: false + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 12_000 + +# Manual p20 exempts all under18 income and all nonhead full-time student income through23. Included young-adult receipts are1200 wages+600 SSA; parent wages12000. Head-rule conflict is not tested. +- name: Case 6, nonhead age 24 full time student follows the whole income exclusion. + 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 + is_household_head: true + person2: + age: 24 + immigration_status: CITIZEN + is_full_time_student: true + ssi: 0 + employment_income: 1_200 + social_security: 600 + is_household_head: false + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 13_800 + +# Manual p20 exempts all under18 income and all nonhead full-time student income through23. Included young-adult receipts are1200 wages+600 SSA; parent wages12000. Head-rule conflict is not tested. +- name: Case 7, nonhead age 18 nonstudent follows the whole income exclusion. + 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 + is_household_head: true + person2: + age: 18 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 1_200 + social_security: 600 + is_household_head: false + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 13_800 + +# Manual pp16,20:600 combined interest-500 once=100;600 dividends count fully; total700. +- 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 + is_household_head: true + person2: + age: 39 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + interest_income: 300 + ordinary_dividend_income: 360 + is_household_head: false + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 700 + +# Manual pp16,20 excludes the first500 annually; excess above500 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] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_200, 1_200, 1_200] + age: [40, 40, 40] + is_full_time_student: [false, false, false] + is_household_head: [true, true, true] + ssi: [0, 0, 0] + ri_works: [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 pp16-17,20 and current rental form:12000+(6000+2400)*50%=16200; 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 + is_household_head: true + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1] + state_code: RI + output: + ri_liheap_income: 16_200 + +# Manual pp15,20:6000 wages+1200 annual adult federal SSI+3600 annual family RI Works=10800. Child SSI2400 excluded; no invented or duplicated RI state supplement. +- name: Case 11, adult federal SSI and RI Works are each annualized 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 + is_household_head: true + person2: + age: 8 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 2_400 + is_household_head: false + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 3_600 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 10_800 + +# Manual p21:12000-1200 child support-600 alimony-2400 unsubsidized childcare=7800. Reported support represents legally mandated 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 + is_household_head: true + person2: + age: 8 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + is_household_head: false + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 0 + childcare_expenses: 2_400 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 7_800 + +# Manual p21 conditions child daycare deduction on no federal/state subsidy:12000-1200-600=10200;2400 reported childcare is not deducted when subsidies arepositive. +- 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 + is_household_head: true + person2: + age: 8 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + is_household_head: false + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 0 + childcare_expenses: 2_400 + child_care_subsidies: 1_200 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 10_200 + +# Legally mandated support2400 exceeds income1200; final countable income is floored0. +- 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 + is_household_head: true + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1] + state_code: RI + output: + ri_liheap_income: 0 + +# Manual p12: full parent income20000 despite size1; minor SSA excluded underp20. +# Matrix size1 threshold19563 < 20000 <= 23475 yields band4 and federal gas175. +# DHS October 2025 DIV 1-1 adds the primary utility enhancement350: total525. +# 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 + is_household_head: true + person2: + age: 8 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + social_security: 1_200 + is_household_head: false + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 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 pp16-18:12000 wages+1200 child support+600 alimony+600 family help=14400; SNAP6000 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 + is_household_head: true + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 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/trust income andp20 excludes all nonhead full-time student income through23:12000 adult wages+1200 adult periodic estate=13200;2400 student estate 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 + is_household_head: true + person2: + age: 20 + immigration_status: CITIZEN + is_full_time_student: true + ssi: 0 + estate_income: 2_400 + is_household_head: false + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 13_200 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..502903f1c90 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/payment/ri_liheap_base_payment.yaml @@ -0,0 +1,122 @@ +# Rhode Island independent income and primary-bill matrix components; no final award is asserted. + +# 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. Positive primary bills10 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 p1 bandB deliverables805; current landlord form lists oil,kerosene,propane,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 +# deliverable657, gas159 + utility enhancement350 = 509, electric268 + 350 = 618, +# independent of band. This is the ordinary primary route, not heat-in-rent. +- name: Case 3, subsidized primary bill rows use the lowest fuel amount 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] + +# ExplicitNONE and zero qualified size are unsupported heating households; 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 federal232 + state350 = 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. Oil861 remains861 despite an electric bill; gas232 + 350 = 582; +# electric393 + 350 = 743. Having both gas and electric bills does not double350. +- 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 adding350. 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] 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..cc47446bb8a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/payment/ri_liheap_income_band.yaml @@ -0,0 +1,51 @@ +# Rhode Island independent income and primary-bill matrix components; no final award is asserted. + +# FY2026 matrix p1 size1 thresholds11738,15650,19563,23475. Inclusive boundaries and next-cent cases do not create gaps from the76/101/126/151% labels. +- 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] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, + NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200] + 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] + +# Matrix p1 size2 FPG21150*125%=26437.50 rounds26438 halfup. Printed31725 repeats150% and is a documented anomaly;26438.01 entersband4. +- 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] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200] + 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 historical2025 estimate uses2024 FPG15060:75%=11295 and125%=18825. Does not freeze2025 FPG into earlier requested years. +- name: Case 3, backfilled poverty bands use the requested year prior guideline. + period: 2025 + input: + state_code: [RI, RI, RI, RI] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_200, 1_200, 1_200, 1_200] + 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 band0, not the lowest positive payment band. +- name: Case 4, zero qualified size has no payment band. + period: 2026 + input: + state_code: [RI] + heating_type: [NATURAL_GAS] + gas_expense: [1_200] + 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_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..c0e9b067b96 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/ri_liheap_household_size.yaml @@ -0,0 +1,77 @@ +# Rhode Island independent income and primary-bill matrix components; no final award is asserted. + +# Manual pp12-13 counts 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 + is_household_head: true + person2: + age: 8 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + is_household_head: false + person3: + age: 35 + immigration_status: ASYLEE + is_full_time_student: false + ssi: 0 + is_household_head: false + person4: + age: 22 + immigration_status: DACA + is_full_time_student: false + ssi: 0 + is_household_head: false + person5: + age: 39 + immigration_status: UNDOCUMENTED + is_full_time_student: false + ssi: 0 + is_household_head: false + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 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 + is_household_head: true + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ri_works: 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_income_limit.py b/policyengine_us/variables/gov/states/ri/dhs/liheap/eligibility/ri_liheap_income_limit.py new file mode 100644 index 00000000000..496ca750e6f --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/eligibility/ri_liheap_income_limit.py @@ -0,0 +1,29 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.hhs_smi import smi + + +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 FY2027 sheet, linked by the administering provider EBCAP. + "https://9e947cb6.delivery.rocketcdn.me/wp-content/uploads/2026/09/LIHEAP-Income-Eligibilty-FFY-2027.pdf", + ) + + 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) + four_person_smi = parameters(period).gov.hhs.smi.amount[state] + size_share = smi(size, state, period, parameters) / four_person_smi + # The FY2026/FY2027 tables floor 60% of the four-person amount before + # adjusting for size, then floor again. This matches all 14 rows; + # the ordering is a numerical reconciliation, not explicit legal text. + four_person_limit = np.floor(four_person_smi * p.smi_rate) + return where(size > 0, np.floor(four_person_limit * size_share), 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..4ebb7b37052 --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/income/ri_liheap_income.py @@ -0,0 +1,61 @@ +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, pages 14-21; plan sections 1.8-1.9, pages 5-7. + "https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=14", + "https://liheapch.acf.gov/docs/2026/state-plans/RI_Plan_2026.pdf#page=5", + "https://dhs.ri.gov/media/9671/download?language=en", + "https://dhs.ri.gov/media/9701/download?language=en", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ri.dhs.liheap.income + person = spm_unit.members + age = person("age", period) + # Retain the manual's head-of-household exception. The abbreviated + # May 2025 instructions omit it without expressly repealing it; + # the current treatment of student heads remains unverified. + exempt_student = ( + (age < p.student_age_limit) + & person("is_full_time_student", period) + & ~person("is_household_head", 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. + earnings = 0 + for source in p.sources.earned: + earnings = earnings + max_(person(source, period), 0) + # ADD annualizes monthly SSI before applying person-level exclusions. + other_income = add(person, period, p.sources.unearned, options=[ADD]) + 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(person("interest_income", period) * counted_person) + countable_interest = max_(interest - p.interest_exclusion, 0) + # RI Works is a household grant, counted once, not once per member. + # There is no RI state SSI supplement variable in the current model. + works = spm_unit("ri_works", period, options=[ADD]) + income = household_income + countable_interest + works + # These reported amounts approximate verified court-ordered payments. + dependent_expenses = add(spm_unit, period, p.dependent_expense_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. Medicare deductions are user-deferred. + return max_(income - dependent_expenses - 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..fc76094aa4e --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/payment/ri_liheap_base_payment.py @@ -0,0 +1,69 @@ +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 for a qualifying primary gas or electric " + "utility account. This component assumes the modeled primary grant " + "reaches the qualifying utility account; it does not determine final " + "eligibility or select a heat-in-rent payment route." + ) + 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", + "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, physical page 2. + "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 + ) + primary_bill = spm_unit("heating_expense", period) > 0 + # Matrix row F uses the lowest payment for subsidized households + # responsible for their primary heating bill, regardless of income band. + 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. No supported schedule mapping was found + # for COAL, OTHER, SOLAR or UNSPECIFIED; their zero is an unverified + # estimate, not a legal denial of assistance. + default=0, + ) + utility_fuel = (fuel == fuels.NATURAL_GAS) | (fuel == fuels.ELECTRICITY) + # 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. + # 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_fuel & (amount > 0), p.utility_enhancement, 0) + # This ungated primary payment has no actual-expense cap. It does not + # calculate the unresolved HIR routes or establish final eligibility. + size = spm_unit("ri_liheap_household_size", period) + return where(size > 0, 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_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"]) From 70b98eac888e79a2fa1a658eb4b056bc0ff95ece Mon Sep 17 00:00:00 2001 From: Ziming Date: Sat, 3 Oct 2026 22:10:39 -0400 Subject: [PATCH 2/9] Add Rhode Island LIHEAP heating eligibility --- changelog.d/ri-liheap.added.md | 2 +- policyengine_us/programs.yaml | 2 +- .../eligibility/ri_liheap_eligible.yaml | 41 +++++++++++++++++++ .../liheap/eligibility/ri_liheap_eligible.py | 40 ++++++++++++++++++ 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/eligibility/ri_liheap_eligible.yaml create mode 100644 policyengine_us/variables/gov/states/ri/dhs/liheap/eligibility/ri_liheap_eligible.py diff --git a/changelog.d/ri-liheap.added.md b/changelog.d/ri-liheap.added.md index 11c710c1d13..2bb2a3b0b89 100644 --- a/changelog.d/ri-liheap.added.md +++ b/changelog.d/ri-liheap.added.md @@ -1 +1 @@ -Add Rhode Island LIHEAP household, income, and primary heating payment components, including the qualifying utility enhancement and FY2027 income-limit references. +Add Rhode Island LIHEAP household, income, heating eligibility and primary payment components, including the qualifying utility enhancement and FY2027 income-limit references. diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 68addc506bc..1332aa7829c 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -809,7 +809,7 @@ programs: name: Rhode Island LIHEAP full_name: Rhode Island Low Income Home Energy Assistance Program parameter_prefix: gov.states.ri.dhs.liheap - notes: Income and ordinary primary-payment components are implemented, including the FY2026 enhancement for assumed qualifying gas/electric accounts. Heat-in-rent payment routing and the blank direct-electric cell remain unresolved; provider/account identity is an input limitation. Regular heating only; historical backfills do not establish verified coverage. + notes: Income, regular heating eligibility and ordinary primary-payment components are implemented, including the FY2026 enhancement for assumed qualifying gas/electric accounts. Heat-in-rent households may qualify; the direct versus secondary-electric payment route and blank direct-electric cell remain unresolved. Provider/account identity is an input limitation. Regular heating only; historical backfills do not establish verified coverage. - state: OR status: in_progress name: 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..f20cb3d9caf --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/eligibility/ri_liheap_eligible.yaml @@ -0,0 +1,41 @@ +# 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] + ri_works: [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] + ri_works: [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] 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..102113731be --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/eligibility/ri_liheap_eligible.py @@ -0,0 +1,40 @@ +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 + # DHS manual physical pages 5, 6 and 12. + reference = ( + "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. A utility + # burden permits subsidized heat-in-rent eligibility, but does not select + # the direct versus secondary-electric payment route. + energy_burden = primary_obligation | ( + heat_in_rent & (~subsidized | separate_electric_bill) + ) + heating_type = spm_unit("heating_type", period) + return ( + (size > 0) + & (income <= limit) + & energy_burden + & (heating_type != heating_type.possible_values.NONE) + ) From 8cfa4ffb12b40ce010ebea9cafbd76ae5b074927 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sun, 4 Oct 2026 20:16:01 -0400 Subject: [PATCH 3/9] Add Rhode Island LIHEAP direct-heating award --- changelog.d/ri-liheap.added.md | 2 +- policyengine_us/programs.yaml | 6 +- .../gov/states/ri/dhs/liheap/ri_liheap.yaml | 93 +++++++++++++++++++ .../gov/states/ri/dhs/liheap/ri_liheap.py | 22 +++++ 4 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/ri_liheap.yaml create mode 100644 policyengine_us/variables/gov/states/ri/dhs/liheap/ri_liheap.py diff --git a/changelog.d/ri-liheap.added.md b/changelog.d/ri-liheap.added.md index 2bb2a3b0b89..4c68a2454f8 100644 --- a/changelog.d/ri-liheap.added.md +++ b/changelog.d/ri-liheap.added.md @@ -1 +1 @@ -Add Rhode Island LIHEAP household, income, heating eligibility and primary payment components, including the qualifying utility enhancement and FY2027 income-limit references. +Add Rhode Island LIHEAP direct-heating assistance, income and heating eligibility, including the qualifying utility enhancement and FY2027 income-limit references; heat-in-rent payments remain unmodeled. diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 1332aa7829c..eff7ca959f8 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -805,11 +805,13 @@ programs: coverage: OR, DC, Riverside County, MA, IL, KS, MS, TX state_implementations: - state: RI - status: in_progress + 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 - notes: Income, regular heating eligibility and ordinary primary-payment components are implemented, including the FY2026 enhancement for assumed qualifying gas/electric accounts. Heat-in-rent households may qualify; the direct versus secondary-electric payment route and blank direct-electric cell remain unresolved. Provider/account identity is an input limitation. Regular heating only; historical backfills do not establish verified coverage. + verified_years: "2026" + notes: FY2026 ordinary direct-heating award combines eligibility and the primary schedule, including the enhancement for assumed qualifying gas/electric accounts. Heat-in-rent eligibility is modeled but its payment is not; zero is not a legal denial. Unlisted fuel mappings, current student-head treatment and provider/account identity remain documented limitations. FY2027 income limits are verified separately; later payment carry-forward and historical backfills are unverified. Regular heating only. - state: OR status: in_progress name: LIHEAP 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..bbdf778caa4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/ri_liheap.yaml @@ -0,0 +1,93 @@ +# 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] + ri_works: [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 uses the lowest fuel payment when subsidized households pay +# their own primary heating bill: 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] + ri_works: [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] + ri_works: [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] + +# Accepted modeling limit: heat-in-rent payment selection is not implemented. +# Zero here means no modeled direct-heating award, not legal ineligibility. +# Existing eligibility remains true for the supported rental arrangements. +- name: Case 4, eligible heat-in-rent households do not receive an invented direct-heating award. + 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] + ri_works: [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: [0, 0, 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..6b18084e97c --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/ri_liheap.py @@ -0,0 +1,22 @@ +from policyengine_us.model_api import * + + +class ri_liheap(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Rhode Island LIHEAP direct-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): + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + payment = spm_unit("ri_liheap_base_payment", period) + # Heat-in-rent eligibility is modeled, but the rule selecting its + # direct or secondary-electric payment is unresolved. Its zero here + # denotes an unmodeled award, not legal ineligibility. + return where(heat_in_rent, 0, payment) From b786095a5b575b619270400bf1345451d9c47d67 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 6 Oct 2026 18:20:06 -0400 Subject: [PATCH 4/9] Fix Rhode Island LIHEAP issues from review - Start regular heating payments in FY2026 with a dated in_effect switch: no FY2025 benefit matrix is published, so 2025 and earlier pay $0. - Use the eligibility primary-bill test for subsidized row F. - Count military retirement pay, retirement distributions and gambling winnings; floor each unearned source at zero. - Count TANF through the take-up-gated tanf variable instead of ri_works. - Deduct the Medicare Part B premium per manual III.H (p21); rename the deduction list to deduction_sources. - Put RI on the LIHEAP coverage line and shorten its note. - Add tests for each change. Co-Authored-By: Claude Opus 5.5 --- .../dhs/liheap/income/deduction_sources.yaml | 22 ++ .../income/dependent_expense_sources.yaml | 15 -- .../dhs/liheap/income/sources/unearned.yaml | 16 +- .../ri/dhs/liheap/payment/in_effect.yaml | 19 ++ .../liheap/payment/utility_enhancement.yaml | 4 +- policyengine_us/programs.yaml | 4 +- .../eligibility/ri_liheap_eligible.yaml | 4 +- .../dhs/liheap/income/ri_liheap_income.yaml | 211 ++++++++++++++++-- .../payment/ri_liheap_base_payment.yaml | 37 +++ .../gov/states/ri/dhs/liheap/ri_liheap.yaml | 144 +++++++++++- .../dhs/liheap/ri_liheap_household_size.yaml | 4 +- .../ri/dhs/liheap/income/ri_liheap_income.py | 28 ++- .../liheap/payment/ri_liheap_base_payment.py | 10 +- 13 files changed, 455 insertions(+), 63 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/income/deduction_sources.yaml delete mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/income/dependent_expense_sources.yaml create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/in_effect.yaml 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..efca07e3c68 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/deduction_sources.yaml @@ -0,0 +1,22 @@ +description: Rhode Island deducts these reported expenses from income 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". This follows the manual. The Part B + # premium is the only Medicare expense with an input, so 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 diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/dependent_expense_sources.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/dependent_expense_sources.yaml deleted file mode 100644 index a77fb6c29e3..00000000000 --- a/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/dependent_expense_sources.yaml +++ /dev/null @@ -1,15 +0,0 @@ -description: Rhode Island deducts these reported dependent expenses from income under the Low Income Home Energy Assistance Program. -# Amounts approximate verified court orders. Existing inputs cannot establish -# the allowable court-ordered maximum or whether every payment was made. -values: - 2025-10-01: - - child_support_expense - - alimony_expense - -metadata: - unit: list - period: year - label: Rhode Island LIHEAP dependent expense 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 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 index 97ead88ed22..bfaefa5d02f 100644 --- 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 @@ -1,8 +1,7 @@ description: Rhode Island counts these unearned income sources under the Low Income Home Energy Assistance Program. -# Interest and rental receipts have separate allowances; RI Works is added -# once at its SPMUnit entity, so none of those sources is duplicated here. -# pension_income includes public/military pensions; military_retirement_pay -# is a tax-classification input and must not be added independently. +# 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. # The manual expressly counts workers compensation in Section III.E. @@ -15,7 +14,12 @@ values: - 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 @@ -27,6 +31,10 @@ values: - 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. This follows the manual. + - gambling_winnings metadata: unit: list diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/in_effect.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/in_effect.yaml new file mode 100644 index 00000000000..0a15155bcfb --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/in_effect.yaml @@ -0,0 +1,19 @@ +description: Rhode Island provides the regular heating payment under the Low Income Home Energy Assistance Program when this is true. +# Payment amounts are sourced from the FY2026 grant matrix onward. The FY2025 +# grant matrix and enhancement amount were not located, and FY2025 used a +# different $64 minimum and $1,148 maximum (FY2025 plan Section 2.6), so the +# modeled payment starts October 1, 2025. Zero before then means no modeled +# award, not legal ineligibility. +values: + 2000-01-01: false + 2025-10-01: true + +metadata: + unit: bool + period: year + label: Rhode Island LIHEAP payment in effect + 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 + - title: Rhode Island FY2025 State Plan, Section 2.6 Benefit Levels + href: https://liheapch.acf.gov/docs/2025/state-plans/RI_Plan_2025.pdf#page=9 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 index afbf591eaaf..e03e820f336 100644 --- 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 @@ -1,6 +1,6 @@ description: Rhode Island adds this enhancement to the primary federal Low Income Home Energy Assistance Program grant credited to a qualifying gas or electric distribution-company account. -# This amount is sourced for the FY2026 heating season. Historical -# backfilling and later carryforward do not extend verified coverage. +# This amount is sourced for the FY2026 heating season. Payments before +# FY2026 are not modeled (in_effect); later carryforward is unverified. # It is not part of the federal fuel matrix or an additional crisis/cooling # grant. Provider and actual federal-grant account routing are input limits. values: diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index eff7ca959f8..b9f2b193544 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -802,7 +802,7 @@ programs: category: Benefits agency: HHS status: partial - coverage: OR, DC, Riverside County, MA, IL, KS, MS, TX + coverage: OR, DC, Riverside County, MA, IL, KS, MS, RI, TX state_implementations: - state: RI status: partial @@ -811,7 +811,7 @@ programs: variable: ri_liheap parameter_prefix: gov.states.ri.dhs.liheap verified_years: "2026" - notes: FY2026 ordinary direct-heating award combines eligibility and the primary schedule, including the enhancement for assumed qualifying gas/electric accounts. Heat-in-rent eligibility is modeled but its payment is not; zero is not a legal denial. Unlisted fuel mappings, current student-head treatment and provider/account identity remain documented limitations. FY2027 income limits are verified separately; later payment carry-forward and historical backfills are unverified. Regular heating only. + notes: FY2026 regular heating only, with payments starting October 2025 and no modeled award for heat-in-rent households. Crisis, cooling and weatherization assistance are out of scope. - state: OR status: in_progress name: 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 index f20cb3d9caf..aaa8f20898b 100644 --- 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 @@ -9,7 +9,7 @@ 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] - ri_works: [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] @@ -32,7 +32,7 @@ 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] - ri_works: [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] 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 index 5262c98d341..10bb0e612a3 100644 --- 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 @@ -22,7 +22,7 @@ members: [person1] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 households: @@ -53,7 +53,7 @@ members: [person1] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 households: @@ -89,7 +89,7 @@ members: [person1, person2] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 households: @@ -125,7 +125,7 @@ members: [person1, person2] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 households: @@ -161,7 +161,7 @@ members: [person1, person2] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 households: @@ -197,7 +197,7 @@ members: [person1, person2] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 households: @@ -233,7 +233,7 @@ members: [person1, person2] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 households: @@ -270,7 +270,7 @@ members: [person1, person2] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 households: @@ -292,7 +292,7 @@ is_full_time_student: [false, false, false] is_household_head: [true, true, true] ssi: [0, 0, 0] - ri_works: [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] @@ -319,7 +319,7 @@ members: [person1] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 households: @@ -329,8 +329,10 @@ output: ri_liheap_income: 16_200 -# Manual pp15,20:6000 wages+1200 annual adult federal SSI+3600 annual family RI Works=10800. Child SSI2400 excluded; no invented or duplicated RI state supplement. -- name: Case 11, adult federal SSI and RI Works are each annualized once while child SSI is excluded. +# Manual pp15,20:6000 wages+1200 annual adult federal SSI+3600 annual family TANF=10800. +# TANF is the take-up-gated SPM-unit tanf aggregate (which includes RI Works), counted once. +# Child SSI2400 excluded; no invented or duplicated RI state supplement. +- 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: @@ -353,7 +355,7 @@ members: [person1, person2] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 3_600 + tanf: 3_600 childcare_expenses: 0 child_care_subsidies: 0 households: @@ -389,7 +391,7 @@ members: [person1, person2] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 2_400 child_care_subsidies: 0 households: @@ -425,7 +427,7 @@ members: [person1, person2] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 2_400 child_care_subsidies: 1_200 households: @@ -454,7 +456,7 @@ members: [person1] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 households: @@ -492,7 +494,7 @@ members: [person1, person2] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 households: @@ -526,7 +528,7 @@ members: [person1] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 snap: 6_000 @@ -563,7 +565,7 @@ members: [person1, person2] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 households: @@ -572,3 +574,174 @@ 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] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_200, 1_200, 1_200] + age: [45, 60, 62] + is_full_time_student: [false, false, false] + is_household_head: [true, true, true] + 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] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_200, 1_200, 1_200] + age: [50, 50, 50] + is_full_time_student: [false, false, false] + is_household_head: [true, true, true] + 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 + is_household_head: true + person2: + age: 5 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + is_household_head: false + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + 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 + is_household_head: true + person2: + age: 5 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + is_household_head: false + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + 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 + is_household_head: true + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + 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] + heating_type: [NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_200, 1_200] + age: [67, 67] + is_full_time_student: [false, false] + is_household_head: [true, true] + 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] 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 index 502903f1c90..4b8b8e4f83e 100644 --- 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 @@ -120,3 +120,40 @@ 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] + +# Payment in_effect is false before October 1, 2025 because the FY2025 grant matrix was +# not located, so the FY2026 band-1 amounts (oil 861, gas 582, electric 743) are not +# backfilled into 2025. +- name: Case 9, no primary payment is modeled before the FY2026 schedule. + 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: [0, 0, 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 index bbdf778caa4..ecc679d99f2 100644 --- 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 @@ -10,7 +10,7 @@ immigration_status: [CITIZEN, CITIZEN, CITIZEN] employment_income: [10_000, 10_000, 10_000] ssi: [0, 0, 0] - ri_works: [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] @@ -35,7 +35,7 @@ immigration_status: [CITIZEN, CITIZEN, CITIZEN] employment_income: [10_000, 10_000, 10_000] ssi: [0, 0, 0] - ri_works: [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] @@ -59,7 +59,7 @@ 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] - ri_works: [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] @@ -79,7 +79,7 @@ immigration_status: [CITIZEN, CITIZEN, CITIZEN] employment_income: [10_000, 10_000, 10_000] ssi: [0, 0, 0] - ri_works: [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] @@ -91,3 +91,139 @@ output: ri_liheap_eligible: [true, true, true] ri_liheap: [0, 0, 0] + +# The FY2025 grant matrix was not located and FY2025 paid a different $64 to $1,148 +# range (FY2025 plan 2.6), so payment in_effect is false before October 1, 2025. +# Eligibility still holds in 2025: 10000 <= 11295 (75% of the 2024 FPG 15060) is +# band 1, but the modeled base payment and award are 0 rather than FY2026's 861. +- name: Case 5, an eligible household has no modeled award before the FY2026 payment schedule. + 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: [0] + ri_liheap: [0] + +# 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] 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 index c0e9b067b96..ad018947364 100644 --- 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 @@ -40,7 +40,7 @@ members: [person1, person2, person3, person4, person5] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 households: @@ -66,7 +66,7 @@ members: [person1] heating_type: NATURAL_GAS gas_expense: 1_200 - ri_works: 0 + tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 households: 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 index 4ebb7b37052..2ed08655bca 100644 --- 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 @@ -31,11 +31,14 @@ def formula(spm_unit, period, parameters): 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) - # ADD annualizes monthly SSI before applying person-level exclusions. - other_income = add(person, period, p.sources.unearned, options=[ADD]) + 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) @@ -45,17 +48,20 @@ def formula(spm_unit, period, parameters): household_income = spm_unit.sum( (earnings + other_income + rental_income) * counted_person ) - interest = spm_unit.sum(person("interest_income", period) * counted_person) + interest = spm_unit.sum( + max_(person("interest_income", period), 0) * counted_person + ) countable_interest = max_(interest - p.interest_exclusion, 0) - # RI Works is a household grant, counted once, not once per member. - # There is no RI state SSI supplement variable in the current model. - works = spm_unit("ri_works", period, options=[ADD]) - income = household_income + countable_interest + works - # These reported amounts approximate verified court-ordered payments. - dependent_expenses = add(spm_unit, period, p.dependent_expense_sources) + # 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. Medicare deductions are user-deferred. - return max_(income - dependent_expenses - childcare_deduction, 0) + # 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 index fc76094aa4e..647846d56c8 100644 --- 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 @@ -30,7 +30,10 @@ def formula(spm_unit, period, parameters): subsidized = spm_unit.household("is_in_public_housing", period) | spm_unit( "receives_housing_assistance", period ) - primary_bill = spm_unit("heating_expense", period) > 0 + # Same primary heating obligation test as ri_liheap_eligible. + primary_bill = spm_unit("has_heating_expense", period) | ( + spm_unit("heating_expense", period) > 0 + ) # Matrix row F uses the lowest payment for subsidized households # responsible for their primary heating bill, regardless of income band. band = where(subsidized & primary_bill, len(p.fpg_rates) + 1, band) @@ -66,4 +69,7 @@ def formula(spm_unit, period, parameters): # This ungated primary payment has no actual-expense cap. It does not # calculate the unresolved HIR routes or establish final eligibility. size = spm_unit("ri_liheap_household_size", period) - return where(size > 0, amount + enhancement, 0) + # Payment amounts are sourced from FY2026 onward. Before October 1, + # 2025, in_effect is false and the modeled payment is zero rather + # than the different, unlocated FY2025 schedule. + return where((size > 0) & p.in_effect, amount + enhancement, 0) From 0facfe2d02ea13ca3b5ffe5e718d9dbbd3e9e6a4 Mon Sep 17 00:00:00 2001 From: Ziming Date: Wed, 7 Oct 2026 22:56:54 -0400 Subject: [PATCH 5/9] Fix issues from review: exclude every full-time student aged 18-23 Follow the FY2026/FY2027 application instructions over the 2020 manual's household-head exception, drop the unused is_household_head test inputs, and note the plan-versus-manual conflict for workers' compensation and TDI/TCI. Co-Authored-By: Claude Opus 5.5 --- .../dhs/liheap/income/sources/unearned.yaml | 6 +- .../dhs/liheap/income/student_age_limit.yaml | 13 +-- .../dhs/liheap/income/ri_liheap_income.yaml | 99 ++++++++++--------- .../dhs/liheap/ri_liheap_household_size.yaml | 6 -- .../ri/dhs/liheap/income/ri_liheap_income.py | 14 +-- 5 files changed, 71 insertions(+), 67 deletions(-) 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 index bfaefa5d02f..80347615706 100644 --- 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 @@ -4,7 +4,6 @@ description: Rhode Island counts these unearned income sources under the Low Inc # 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. -# The manual expressly counts workers compensation in Section III.E. # 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. @@ -24,6 +23,11 @@ values: - 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 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 index b59a41f3be4..0f955e2ba06 100644 --- 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 @@ -1,7 +1,8 @@ -description: Rhode Island excludes full-time student income below this age when the student is not the household head under the Low Income Home Energy Assistance Program. -# Age 18 through 23 means below the twenty-fourth birthday. -# The newer application omits the manual household-head exception without -# expressly repealing it. Current student-head treatment remains unverified. +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 2020 manual excluded only a child who was not the household head; the +# current instructions govern. values: 2025-10-01: 24 @@ -10,7 +11,7 @@ metadata: period: year label: Rhode Island LIHEAP student income exclusion age limit reference: + - title: Rhode Island FY2026 LIHEAP Application Instructions, Appendix H, Full-time Students + href: https://westbaycap.org/wp-content/uploads/2025/08/Appendix-H-Application-Instructions-FY-26.pdf#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 - - title: Rhode Island FY2026 Application Instructions, Full-time Students - href: https://dhs.ri.gov/media/9671/download?language=en 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 index 10bb0e612a3..9804ccd0cec 100644 --- 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 @@ -16,7 +16,6 @@ sstb_self_employment_income: 600 farm_operations_income: 1_200 partnership_s_corp_income: 1_200 - is_household_head: true spm_units: spm_unit: members: [person1] @@ -47,7 +46,6 @@ self_employment_income: -6_000 farm_operations_income: -1_200 partnership_s_corp_income: 600 - is_household_head: true spm_units: spm_unit: members: [person1] @@ -63,8 +61,10 @@ output: ri_liheap_income: 12_600 -# Manual p20 exempts all under18 income and all nonhead full-time student income through23. Included young-adult receipts are1200 wages+600 SSA; parent wages12000. Head-rule conflict is not tested. -- name: Case 3, nonhead age 17 nonstudent follows the whole income exclusion. +# 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: @@ -75,7 +75,6 @@ is_full_time_student: false ssi: 0 employment_income: 12_000 - is_household_head: true person2: age: 17 immigration_status: CITIZEN @@ -83,7 +82,6 @@ ssi: 0 employment_income: 1_200 social_security: 600 - is_household_head: false spm_units: spm_unit: members: [person1, person2] @@ -99,8 +97,10 @@ output: ri_liheap_income: 12_000 -# Manual p20 exempts all under18 income and all nonhead full-time student income through23. Included young-adult receipts are1200 wages+600 SSA; parent wages12000. Head-rule conflict is not tested. -- name: Case 4, nonhead age 18 full time student follows the whole income exclusion. +# 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: @@ -111,7 +111,6 @@ is_full_time_student: false ssi: 0 employment_income: 12_000 - is_household_head: true person2: age: 18 immigration_status: CITIZEN @@ -119,7 +118,6 @@ ssi: 0 employment_income: 1_200 social_security: 600 - is_household_head: false spm_units: spm_unit: members: [person1, person2] @@ -135,8 +133,10 @@ output: ri_liheap_income: 12_000 -# Manual p20 exempts all under18 income and all nonhead full-time student income through23. Included young-adult receipts are1200 wages+600 SSA; parent wages12000. Head-rule conflict is not tested. -- name: Case 5, nonhead age 23 full time student follows the whole income exclusion. +# 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: @@ -147,7 +147,6 @@ is_full_time_student: false ssi: 0 employment_income: 12_000 - is_household_head: true person2: age: 23 immigration_status: CITIZEN @@ -155,7 +154,6 @@ ssi: 0 employment_income: 1_200 social_security: 600 - is_household_head: false spm_units: spm_unit: members: [person1, person2] @@ -171,8 +169,10 @@ output: ri_liheap_income: 12_000 -# Manual p20 exempts all under18 income and all nonhead full-time student income through23. Included young-adult receipts are1200 wages+600 SSA; parent wages12000. Head-rule conflict is not tested. -- name: Case 6, nonhead age 24 full time student follows the whole income exclusion. +# 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: @@ -183,7 +183,6 @@ is_full_time_student: false ssi: 0 employment_income: 12_000 - is_household_head: true person2: age: 24 immigration_status: CITIZEN @@ -191,7 +190,6 @@ ssi: 0 employment_income: 1_200 social_security: 600 - is_household_head: false spm_units: spm_unit: members: [person1, person2] @@ -207,8 +205,10 @@ output: ri_liheap_income: 13_800 -# Manual p20 exempts all under18 income and all nonhead full-time student income through23. Included young-adult receipts are1200 wages+600 SSA; parent wages12000. Head-rule conflict is not tested. -- name: Case 7, nonhead age 18 nonstudent follows the whole income exclusion. +# 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: @@ -219,7 +219,6 @@ is_full_time_student: false ssi: 0 employment_income: 12_000 - is_household_head: true person2: age: 18 immigration_status: CITIZEN @@ -227,7 +226,6 @@ ssi: 0 employment_income: 1_200 social_security: 600 - is_household_head: false spm_units: spm_unit: members: [person1, person2] @@ -256,7 +254,6 @@ ssi: 0 interest_income: 300 ordinary_dividend_income: 240 - is_household_head: true person2: age: 39 immigration_status: CITIZEN @@ -264,7 +261,6 @@ ssi: 0 interest_income: 300 ordinary_dividend_income: 360 - is_household_head: false spm_units: spm_unit: members: [person1, person2] @@ -290,7 +286,6 @@ gas_expense: [1_200, 1_200, 1_200] age: [40, 40, 40] is_full_time_student: [false, false, false] - is_household_head: [true, true, true] ssi: [0, 0, 0] tanf: [0, 0, 0] childcare_expenses: [0, 0, 0] @@ -313,7 +308,6 @@ employment_income: 12_000 rental_income: 6_000 farm_rent_income: 2_400 - is_household_head: true spm_units: spm_unit: members: [person1] @@ -343,13 +337,11 @@ is_full_time_student: false ssi: 1_200 employment_income: 6_000 - is_household_head: true person2: age: 8 immigration_status: CITIZEN is_full_time_student: false ssi: 2_400 - is_household_head: false spm_units: spm_unit: members: [person1, person2] @@ -379,13 +371,11 @@ employment_income: 12_000 child_support_expense: 1_200 alimony_expense: 600 - is_household_head: true person2: age: 8 immigration_status: CITIZEN is_full_time_student: false ssi: 0 - is_household_head: false spm_units: spm_unit: members: [person1, person2] @@ -415,13 +405,11 @@ employment_income: 12_000 child_support_expense: 1_200 alimony_expense: 600 - is_household_head: true person2: age: 8 immigration_status: CITIZEN is_full_time_student: false ssi: 0 - is_household_head: false spm_units: spm_unit: members: [person1, person2] @@ -450,7 +438,6 @@ ssi: 0 employment_income: 1_200 child_support_expense: 2_400 - is_household_head: true spm_units: spm_unit: members: [person1] @@ -481,14 +468,12 @@ is_full_time_student: false ssi: 0 employment_income: 20_000 - is_household_head: true person2: age: 8 immigration_status: CITIZEN is_full_time_student: false ssi: 0 social_security: 1_200 - is_household_head: false spm_units: spm_unit: members: [person1, person2] @@ -522,7 +507,6 @@ child_support_received: 1_200 alimony_income: 600 financial_assistance: 600 - is_household_head: true spm_units: spm_unit: members: [person1] @@ -539,7 +523,9 @@ output: ri_liheap_income: 14_400 -# Manual p17 counts estate/trust income andp20 excludes all nonhead full-time student income through23:12000 adult wages+1200 adult periodic estate=13200;2400 student estate excluded. +# 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 @@ -552,14 +538,12 @@ ssi: 0 employment_income: 12_000 estate_income: 1_200 - is_household_head: true person2: age: 20 immigration_status: CITIZEN is_full_time_student: true ssi: 0 estate_income: 2_400 - is_household_head: false spm_units: spm_unit: members: [person1, person2] @@ -587,7 +571,6 @@ gas_expense: [1_200, 1_200, 1_200] age: [45, 60, 62] is_full_time_student: [false, false, false] - is_household_head: [true, true, true] ssi: [0, 0, 0] tanf: [0, 0, 0] childcare_expenses: [0, 0, 0] @@ -610,7 +593,6 @@ gas_expense: [1_200, 1_200, 1_200] age: [50, 50, 50] is_full_time_student: [false, false, false] - is_household_head: [true, true, true] ssi: [0, 0, 0] tanf: [0, 0, 0] childcare_expenses: [0, 0, 0] @@ -634,13 +616,11 @@ immigration_status: CITIZEN is_full_time_student: false ssi: 0 - is_household_head: true person2: age: 5 immigration_status: CITIZEN is_full_time_student: false ssi: 0 - is_household_head: false spm_units: spm_unit: members: [person1, person2] @@ -670,13 +650,11 @@ immigration_status: CITIZEN is_full_time_student: false ssi: 0 - is_household_head: true person2: age: 5 immigration_status: CITIZEN is_full_time_student: false ssi: 0 - is_household_head: false spm_units: spm_unit: members: [person1, person2] @@ -708,7 +686,6 @@ ssi: 0 employment_income: 20_000 gambling_winnings: 1_500 - is_household_head: true spm_units: spm_unit: members: [person1] @@ -736,7 +713,6 @@ gas_expense: [1_200, 1_200] age: [67, 67] is_full_time_student: [false, false] - is_household_head: [true, true] ssi: [0, 0] tanf: [0, 0] childcare_expenses: [0, 0] @@ -745,3 +721,32 @@ 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] + heating_type: NATURAL_GAS + gas_expense: 1_200 + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1] + state_code: RI + output: + ri_liheap_income: 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 index ad018947364..11fadac8904 100644 --- 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 @@ -10,31 +10,26 @@ immigration_status: LEGAL_PERMANENT_RESIDENT is_full_time_student: false ssi: 0 - is_household_head: true person2: age: 8 immigration_status: CITIZEN is_full_time_student: false ssi: 0 - is_household_head: false person3: age: 35 immigration_status: ASYLEE is_full_time_student: false ssi: 0 - is_household_head: false person4: age: 22 immigration_status: DACA is_full_time_student: false ssi: 0 - is_household_head: false person5: age: 39 immigration_status: UNDOCUMENTED is_full_time_student: false ssi: 0 - is_household_head: false spm_units: spm_unit: members: [person1, person2, person3, person4, person5] @@ -60,7 +55,6 @@ immigration_status: UNDOCUMENTED is_full_time_student: false ssi: 0 - is_household_head: true spm_units: spm_unit: members: [person1] 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 index 2ed08655bca..83abb5bc382 100644 --- 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 @@ -14,19 +14,19 @@ class ri_liheap_income(Variable): "https://liheapch.acf.gov/docs/2026/state-plans/RI_Plan_2026.pdf#page=5", "https://dhs.ri.gov/media/9671/download?language=en", "https://dhs.ri.gov/media/9701/download?language=en", + "https://westbaycap.org/wp-content/uploads/2025/08/Appendix-H-Application-Instructions-FY-26.pdf#page=2", ) def formula(spm_unit, period, parameters): p = parameters(period).gov.states.ri.dhs.liheap.income person = spm_unit.members age = person("age", period) - # Retain the manual's head-of-household exception. The abbreviated - # May 2025 instructions omit it without expressly repealing it; - # the current treatment of student heads remains unverified. - exempt_student = ( - (age < p.student_age_limit) - & person("is_full_time_student", period) - & ~person("is_household_head", period) + # The FY2026 (Appendix H) and FY2027 application instructions (p2) + # exclude the income of every full-time student aged 18-23. The 2020 + # 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. From af2757578a4602dd99350af5a82ceb104882791b Mon Sep 17 00:00:00 2001 From: Ziming Date: Wed, 7 Oct 2026 23:24:38 -0400 Subject: [PATCH 6/9] Apply the reference page-citation convention One reference entry per source; single-page citations keep the page only in the href; multi-page titles end with a quoted #page list and the href opens the first cited page; variable references use '# PDF pages ...' comments above multi-page hrefs (CLAUDE.md, PolicyEngine/policyengine-us PR 10009). Reference lines only. Co-Authored-By: Claude Opus 5.5 --- .../states/ri/dhs/liheap/payment/utility_enhancement.yaml | 4 ++-- .../states/ri/dhs/liheap/eligibility/ri_liheap_eligible.py | 2 +- .../gov/states/ri/dhs/liheap/income/ri_liheap_income.py | 5 ++++- .../states/ri/dhs/liheap/payment/ri_liheap_base_payment.py | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) 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 index e03e820f336..e97dc7ba928 100644 --- 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 @@ -13,5 +13,5 @@ metadata: 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, pages 1 and 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=3 + - 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 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 index 102113731be..677460fd4a3 100644 --- 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 @@ -7,8 +7,8 @@ class ri_liheap_eligible(Variable): definition_period = YEAR label = "Rhode Island LIHEAP regular heating eligibility" defined_for = StateCode.RI - # DHS manual physical pages 5, 6 and 12. 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", ) 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 index 83abb5bc382..6655d0ed2ea 100644 --- 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 @@ -9,8 +9,11 @@ class ri_liheap_income(Variable): label = "Rhode Island LIHEAP countable income" defined_for = StateCode.RI reference = ( - # Manual sections III.E-H, pages 14-21; plan sections 1.8-1.9, pages 5-7. + # 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", "https://dhs.ri.gov/media/9671/download?language=en", "https://dhs.ri.gov/media/9701/download?language=en", 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 index 647846d56c8..4971c83d049 100644 --- 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 @@ -20,7 +20,7 @@ class ri_liheap_base_payment(Variable): "https://dhs.ri.gov/media/9696/download?language=en", "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, physical page 2. + # 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", ) From aa4ccc33b9f3780c39bb4dc6a2c47083918d6b39 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sun, 11 Oct 2026 23:36:51 -0400 Subject: [PATCH 7/9] Pay Rhode Island LIHEAP heat-in-rent grants Heat-in-rent households receive the FY2026 matrix rows G-I: a $400 direct grant, or an $80 secondary-electric grant ($50 when subsidized) when a separate electric bill is reported, with the $350 utility enhancement on the secondary-electric grant. Subsidized heat-in-rent tenants without a utility burden are ineligible. The research-date payment gate is removed, so earlier years backfill the FY2026 schedule as unverified estimates. Utility provider and grant receipt stay an assumption; no new input. Co-Authored-By: Claude Opus 5.5 --- changelog.d/ri-liheap.added.md | 2 +- .../ri/dhs/liheap/payment/heat_in_rent.yaml | 17 +++++ .../ri/dhs/liheap/payment/in_effect.yaml | 19 ------ .../liheap/payment/utility_enhancement.yaml | 4 +- policyengine_us/programs.yaml | 2 +- .../payment/ri_liheap_base_payment.yaml | 8 +-- .../gov/states/ri/dhs/liheap/ri_liheap.yaml | 65 +++++++++++++++---- .../liheap/eligibility/ri_liheap_eligible.py | 8 +-- .../liheap/payment/ri_liheap_base_payment.py | 41 ++++++++---- .../gov/states/ri/dhs/liheap/ri_liheap.py | 9 +-- 10 files changed, 111 insertions(+), 64 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/heat_in_rent.yaml delete mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/in_effect.yaml diff --git a/changelog.d/ri-liheap.added.md b/changelog.d/ri-liheap.added.md index 4c68a2454f8..28f27503963 100644 --- a/changelog.d/ri-liheap.added.md +++ b/changelog.d/ri-liheap.added.md @@ -1 +1 @@ -Add Rhode Island LIHEAP direct-heating assistance, income and heating eligibility, including the qualifying utility enhancement and FY2027 income-limit references; heat-in-rent payments remain unmodeled. +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/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..bf6e235476c --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/heat_in_rent.yaml @@ -0,0 +1,17 @@ +description: Rhode Island provides these regular heating grants for heat-in-rent households under the Low Income Home Energy Assistance Program. + +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/in_effect.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/in_effect.yaml deleted file mode 100644 index 0a15155bcfb..00000000000 --- a/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/in_effect.yaml +++ /dev/null @@ -1,19 +0,0 @@ -description: Rhode Island provides the regular heating payment under the Low Income Home Energy Assistance Program when this is true. -# Payment amounts are sourced from the FY2026 grant matrix onward. The FY2025 -# grant matrix and enhancement amount were not located, and FY2025 used a -# different $64 minimum and $1,148 maximum (FY2025 plan Section 2.6), so the -# modeled payment starts October 1, 2025. Zero before then means no modeled -# award, not legal ineligibility. -values: - 2000-01-01: false - 2025-10-01: true - -metadata: - unit: bool - period: year - label: Rhode Island LIHEAP payment in effect - 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 - - title: Rhode Island FY2025 State Plan, Section 2.6 Benefit Levels - href: https://liheapch.acf.gov/docs/2025/state-plans/RI_Plan_2025.pdf#page=9 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 index e97dc7ba928..4fb92902b68 100644 --- 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 @@ -1,6 +1,6 @@ description: Rhode Island adds this enhancement to the primary federal Low Income Home Energy Assistance Program grant credited to a qualifying gas or electric distribution-company account. -# This amount is sourced for the FY2026 heating season. Payments before -# FY2026 are not modeled (in_effect); later carryforward is unverified. +# This amount is sourced for the FY2026 heating season. Earlier backfilled +# amounts and later carryforward are unverified historical/future estimates. # It is not part of the federal fuel matrix or an additional crisis/cooling # grant. Provider and actual federal-grant account routing are input limits. values: diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index db09a800285..98b1dccd906 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -817,7 +817,7 @@ programs: variable: ri_liheap parameter_prefix: gov.states.ri.dhs.liheap verified_years: "2026" - notes: FY2026 regular heating only, with payments starting October 2025 and no modeled award for heat-in-rent households. Crisis, cooling and weatherization assistance are out of scope. + notes: FY2026 regular heating including heat-in-rent grants. Separate electric bills select secondary-electric grants; qualifying utility provider and grant receipt are assumed. Unpriced primary fuels and earlier backfilled years remain unverified. Crisis, cooling and weatherization assistance are out of scope. - state: DC status: complete name: DC LIHEAP 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 index 4b8b8e4f83e..97ffd0acaf4 100644 --- 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 @@ -141,10 +141,8 @@ output: ri_liheap_base_payment: [509, 618, 657, 582] -# Payment in_effect is false before October 1, 2025 because the FY2025 grant matrix was -# not located, so the FY2026 band-1 amounts (oil 861, gas 582, electric 743) are not -# backfilled into 2025. -- name: Case 9, no primary payment is modeled before the FY2026 schedule. +# Earlier backfilled amounts are unverified historical estimates. +- name: Case 9, earlier years backfill the researched payment schedule as an estimate. period: 2025 absolute_error_margin: 0.01 input: @@ -156,4 +154,4 @@ ri_liheap_household_size: [1, 1, 1] ri_liheap_income_band: [1, 1, 1] output: - ri_liheap_base_payment: [0, 0, 0] + ri_liheap_base_payment: [861, 582, 743] 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 index ecc679d99f2..53cf5c6ac44 100644 --- 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 @@ -68,10 +68,9 @@ output: ri_liheap: [509, 0, 0, 0, 0, 0] -# Accepted modeling limit: heat-in-rent payment selection is not implemented. -# Zero here means no modeled direct-heating award, not legal ineligibility. -# Existing eligibility remains true for the supported rental arrangements. -- name: Case 4, eligible heat-in-rent households do not receive an invented direct-heating award. +# FY2026 rows G and I: direct grant400; subsidized secondary electric50 +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] @@ -90,13 +89,11 @@ is_in_public_housing: [false, false, true] output: ri_liheap_eligible: [true, true, true] - ri_liheap: [0, 0, 0] + ri_liheap: [400, 400, 400] -# The FY2025 grant matrix was not located and FY2025 paid a different $64 to $1,148 -# range (FY2025 plan 2.6), so payment in_effect is false before October 1, 2025. -# Eligibility still holds in 2025: 10000 <= 11295 (75% of the 2024 FPG 15060) is -# band 1, but the modeled base payment and award are 0 rather than FY2026's 861. -- name: Case 5, an eligible household has no modeled award before the FY2026 payment schedule. +# Earlier years backfill the researched FY2026 schedule under the all-state +# convention. This is an unverified historical estimate, not a FY2025 policy test. +- name: Case 5, earlier years backfill an unverified heating award estimate. period: 2025 absolute_error_margin: 0.01 input: @@ -117,8 +114,8 @@ ri_liheap_income: [10_000] ri_liheap_eligible: [true] ri_liheap_income_band: [1] - ri_liheap_base_payment: [0] - ri_liheap: [0] + 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. @@ -227,3 +224,47 @@ ri_liheap_eligible: [true] ri_liheap_income_band: [4] ri_liheap: [704] + +# FY2026 matrix G-I, manual II.B, and DHS October2025 DIV1-1: HIR direct400, +# secondary electric80 +350, subsidized secondary electric50 +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] + +# Backfilling is an estimate; it must not imply that LIHEAP began in FY2026. +- 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 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 index 677460fd4a3..aef6f9a003d 100644 --- 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 @@ -25,11 +25,9 @@ def formula(spm_unit, period, parameters): 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. A utility - # burden permits subsidized heat-in-rent eligibility, but does not select - # the direct versus secondary-electric payment route. - energy_burden = primary_obligation | ( - heat_in_rent & (~subsidized | separate_electric_bill) + # Reported obligations and rental terms are assumed verified. + energy_burden = where( + heat_in_rent, ~subsidized | separate_electric_bill, primary_obligation ) heating_type = spm_unit("heating_type", period) return ( 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 index 4971c83d049..34ddb5096a2 100644 --- 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 @@ -9,15 +9,18 @@ class ri_liheap_base_payment(Variable): label = "Rhode Island LIHEAP primary heating payment with utility enhancement" documentation = ( "Ordinary primary heating payment, including the federal matrix grant " - "and the state enhancement for a qualifying primary gas or electric " - "utility account. This component assumes the modeled primary grant " - "reaches the qualifying utility account; it does not determine final " - "eligibility or select a heat-in-rent payment route." + "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. 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", + # 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. @@ -57,19 +60,33 @@ def formula(spm_unit, period, parameters): # estimate, not a legal denial of assistance. 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. + # Subsidized tenants without a utility burden are ineligible. + heat_in_rent_payment = where( + separate_electric_bill, + secondary_electric_payment, + where(subsidized, 0, hir.direct), + ) + 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. + # 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_fuel & (amount > 0), p.utility_enhancement, 0) - # This ungated primary payment has no actual-expense cap. It does not - # calculate the unresolved HIR routes or establish final eligibility. + 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) - # Payment amounts are sourced from FY2026 onward. Before October 1, - # 2025, in_effect is false and the modeled payment is zero rather - # than the different, unlocated FY2025 schedule. - return where((size > 0) & p.in_effect, amount + enhancement, 0) + return where((size > 0) & (fuel != fuels.NONE), amount + enhancement, 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 index 6b18084e97c..493b3d858d7 100644 --- a/policyengine_us/variables/gov/states/ri/dhs/liheap/ri_liheap.py +++ b/policyengine_us/variables/gov/states/ri/dhs/liheap/ri_liheap.py @@ -6,7 +6,7 @@ class ri_liheap(Variable): entity = SPMUnit definition_period = YEAR unit = USD - label = "Rhode Island LIHEAP direct-heating assistance" + 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", @@ -14,9 +14,4 @@ class ri_liheap(Variable): ) def formula(spm_unit, period, parameters): - heat_in_rent = spm_unit("heat_expense_included_in_rent", period) - payment = spm_unit("ri_liheap_base_payment", period) - # Heat-in-rent eligibility is modeled, but the rule selecting its - # direct or secondary-electric payment is unresolved. Its zero here - # denotes an unmodeled award, not legal ineligibility. - return where(heat_in_rent, 0, payment) + return spm_unit("ri_liheap_base_payment", period) From c8b6cafa254ee7f4c2b4ecfd6fab26f4bdda2d51 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sun, 11 Oct 2026 23:45:52 -0400 Subject: [PATCH 8/9] Use the shared SMI helper for the Rhode Island LIHEAP income limit Calls liheap_smi_limit from PR 10019 instead of repeating the HHS floor-then-floor calculation. Limits are unchanged. Co-Authored-By: Claude Opus 5.5 --- .../dhs/liheap/eligibility/ri_liheap_income_limit.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) 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 index 496ca750e6f..1d3ac224343 100644 --- 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 @@ -1,5 +1,5 @@ from policyengine_us.model_api import * -from policyengine_us.variables.gov.hhs.hhs_smi import smi +from policyengine_us.variables.gov.hhs.hhs_smi import liheap_smi_limit class ri_liheap_income_limit(Variable): @@ -20,10 +20,6 @@ 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) - four_person_smi = parameters(period).gov.hhs.smi.amount[state] - size_share = smi(size, state, period, parameters) / four_person_smi - # The FY2026/FY2027 tables floor 60% of the four-person amount before - # adjusting for size, then floor again. This matches all 14 rows; - # the ordering is a numerical reconciliation, not explicit legal text. - four_person_limit = np.floor(four_person_smi * p.smi_rate) - return where(size > 0, np.floor(four_person_limit * size_share), 0) + # 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) From 09fddd623b6690207b7bc260d05c898dcf15bd8b Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 15 Oct 2026 16:11:37 -0400 Subject: [PATCH 9/9] Address review round 1 on Rhode Island LIHEAP - S1: The unearned list reads the raw unemployment_compensation, so main's #9558 guard fails - S2: Row G prints no Electric amount, but the PR pays $400 for electric heat in unsubsidized rent with no electric - S3: Three pieces of logic have no test that fails when they break: excluded members' interest, 8 unearned sources - S4: Six parameter descriptions fall outside the house template - S5: Wrong rental anchor, Appendix H without page 2 and on two hosts, multi-page titles without page lists Also applies the in-scope suggestions. Tests not run locally; CI validates. Co-Authored-By: Claude Opus 5.5 --- .../dhs/liheap/income/deduction_sources.yaml | 15 +- .../dhs/liheap/income/interest_exclusion.yaml | 2 +- .../dhs/liheap/income/minimum_income_age.yaml | 2 +- .../dhs/liheap/income/rental_income_rate.yaml | 10 +- .../ri/dhs/liheap/income/sources/earned.yaml | 14 +- .../dhs/liheap/income/sources/unearned.yaml | 17 +- .../dhs/liheap/income/student_age_limit.yaml | 8 +- .../ri/dhs/liheap/payment/heat_in_rent.yaml | 3 + .../liheap/payment/utility_enhancement.yaml | 14 +- policyengine_us/programs.yaml | 2 +- .../eligibility/ri_liheap_eligible.yaml | 23 +++ .../eligibility/ri_liheap_income_limit.yaml | 15 +- .../dhs/liheap/income/ri_liheap_income.yaml | 194 ++++++++++++------ .../payment/ri_liheap_base_payment.yaml | 23 +-- .../liheap/payment/ri_liheap_income_band.yaml | 19 +- .../gov/states/ri/dhs/liheap/ri_liheap.yaml | 46 ++++- .../dhs/liheap/ri_liheap_household_size.yaml | 8 +- .../liheap/eligibility/ri_liheap_eligible.py | 7 + .../eligibility/ri_liheap_income_limit.py | 4 +- .../ri/dhs/liheap/income/ri_liheap_income.py | 11 +- .../liheap/payment/ri_liheap_base_payment.py | 31 +-- 21 files changed, 298 insertions(+), 170 deletions(-) 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 index efca07e3c68..05d14911b2a 100644 --- 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 @@ -1,4 +1,4 @@ -description: Rhode Island deducts these reported expenses from income under the Low Income Home Energy Assistance Program. +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 @@ -7,10 +7,11 @@ values: - 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". This follows the manual. The Part B - # premium is the only Medicare expense with an input, so prescription and - # other Medicare costs are not deducted. + # 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: @@ -20,3 +21,7 @@ metadata: 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 index a99c552b814..839140a3a23 100644 --- 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 @@ -10,4 +10,4 @@ metadata: - 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 + 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 index abf963d6b45..abb8718b611 100644 --- 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 @@ -1,4 +1,4 @@ -description: Rhode Island counts individual income beginning at this age under the Low Income Home Energy Assistance Program. +description: Rhode Island excludes the income of members below this age under the Low Income Home Energy Assistance Program. values: 2025-10-01: 18 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 index d3cf693f205..e9d3aaa7060 100644 --- 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 @@ -1,5 +1,5 @@ -description: Rhode Island counts this share of rental receipts under the Low Income Home Energy Assistance Program. -# The manual gives the same rental rule on pages 17 and 20. +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 @@ -9,7 +9,7 @@ metadata: period: year label: Rhode Island LIHEAP rental income inclusion rate reference: - - title: Rhode Island LIHEAP Manual, Sections III.E and III.G Rental Income - href: https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=17 + - 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 + 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 index 2fd8e19a36e..cd26671b908 100644 --- 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 @@ -1,6 +1,8 @@ -description: Rhode Island counts these earned income sources under the Low Income Home Energy Assistance Program. +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. -# Gross business receipts and business-expense inputs remain deferred. +# 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: @@ -15,9 +17,11 @@ metadata: period: year label: Rhode Island LIHEAP earned income sources reference: - - title: Rhode Island LIHEAP Manual, Sections III.E-G Income Verification and Exclusions + - 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 + - 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 + 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 index 80347615706..3780cf078f5 100644 --- 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 @@ -1,4 +1,4 @@ -description: Rhode Island counts these unearned income sources under the Low Income Home Energy Assistance Program. +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. @@ -9,7 +9,7 @@ description: Rhode Island counts these unearned income sources under the Low Inc # settlements also lack sufficiently specific existing inputs. values: 2025-10-01: - - unemployment_compensation + - total_unemployment_compensation - strike_benefits - social_security - ssi @@ -35,9 +35,10 @@ values: - 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. This follows the manual. + # 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: @@ -45,9 +46,9 @@ metadata: period: year label: Rhode Island LIHEAP unearned income sources reference: - - title: Rhode Island LIHEAP Manual, Sections III.E-G Income Verification and Exclusions + - 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 + - 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 + 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 index 0f955e2ba06..38c07b6b0f7 100644 --- 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 @@ -1,7 +1,7 @@ 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 2020 manual excluded only a child who was not the household head; the +# The 2019 manual excluded only a child who was not the household head; the # current instructions govern. values: 2025-10-01: 24 @@ -11,7 +11,9 @@ metadata: period: year label: Rhode Island LIHEAP student income exclusion age limit reference: - - title: Rhode Island FY2026 LIHEAP Application Instructions, Appendix H, Full-time Students - href: https://westbaycap.org/wp-content/uploads/2025/08/Appendix-H-Application-Instructions-FY-26.pdf#page=2 + - 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/heat_in_rent.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/heat_in_rent.yaml index bf6e235476c..0a72a487137 100644 --- 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 @@ -1,4 +1,7 @@ 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 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 index 4fb92902b68..0b44b5599c7 100644 --- 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 @@ -1,8 +1,10 @@ -description: Rhode Island adds this enhancement to the primary federal Low Income Home Energy Assistance Program grant credited to a qualifying gas or electric distribution-company account. -# This amount is sourced for the FY2026 heating season. Earlier backfilled -# amounts and later carryforward are unverified historical/future estimates. -# It is not part of the federal fuel matrix or an additional crisis/cooling -# grant. Provider and actual federal-grant account routing are input limits. +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 @@ -15,3 +17,5 @@ metadata: 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 d743bb821bc..85225bd9d49 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -817,7 +817,7 @@ programs: variable: ri_liheap parameter_prefix: gov.states.ri.dhs.liheap verified_years: "2026" - notes: FY2026 regular heating including heat-in-rent grants. Separate electric bills select secondary-electric grants; qualifying utility provider and grant receipt are assumed. Unpriced primary fuels and earlier backfilled years remain unverified. Crisis, cooling and weatherization assistance are out of scope. + 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 index aaa8f20898b..9f7e219ba92 100644 --- 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 @@ -39,3 +39,26 @@ 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 index 45386fc0453..52c98528ecf 100644 --- 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 @@ -1,15 +1,9 @@ -# Rhode Island independent income and primary-bill matrix components; no final award is asserted. - -# FY2026 matrix p1 literal annual60%SMI column. Expected dollars are independent of the inferred nested-floor numerical pattern. +# 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] - heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, - NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] - gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200, 1_200] 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, @@ -21,23 +15,18 @@ absolute_error_margin: 0.01 input: state_code: [RI] - heating_type: [NATURAL_GAS] - gas_expense: [1_200] ri_liheap_household_size: [0] output: ri_liheap_income_limit: [0] # DHS FFY2027 income eligibility sheet p1, mandatory October 1, 2026. -# These are all14 literal annual values, independently read from the DHS table. +# 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] - heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, - NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, - NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] 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, 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 index 9804ccd0cec..5736e6aee28 100644 --- 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 @@ -1,6 +1,4 @@ -# Rhode Island independent income and primary-bill matrix components; no final award is asserted. - -# Manual pp14-15 income categories, approved existing-net-input approximation:12000+2400+600+1200+1200=17400; no additional40% deduction. +# 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 @@ -19,8 +17,6 @@ spm_units: spm_unit: members: [person1] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 @@ -31,7 +27,7 @@ output: ri_liheap_income: 17_400 -# Approved nonnegative net-source convention:12000 wages+600 positive business=12600; negative sources contribute0. +# 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 @@ -49,8 +45,6 @@ spm_units: spm_unit: members: [person1] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 @@ -85,8 +79,6 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 @@ -121,8 +113,6 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 @@ -157,8 +147,6 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 @@ -193,8 +181,6 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 @@ -229,8 +215,6 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 @@ -241,7 +225,7 @@ output: ri_liheap_income: 13_800 -# Manual pp16,20:600 combined interest-500 once=100;600 dividends count fully; total700. +# 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 @@ -264,8 +248,6 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 @@ -276,14 +258,12 @@ output: ri_liheap_income: 700 -# Manual pp16,20 excludes the first500 annually; excess above500 counts. +# 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] - heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] - gas_expense: [1_200, 1_200, 1_200] age: [40, 40, 40] is_full_time_student: [false, false, false] ssi: [0, 0, 0] @@ -294,7 +274,7 @@ output: ri_liheap_income: [0, 0, 0.5] -# Manual pp16-17,20 and current rental form:12000+(6000+2400)*50%=16200; existing rent inputs approximate reported rents. +# 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 @@ -311,8 +291,6 @@ spm_units: spm_unit: members: [person1] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 @@ -323,9 +301,9 @@ output: ri_liheap_income: 16_200 -# Manual pp15,20:6000 wages+1200 annual adult federal SSI+3600 annual family TANF=10800. +# 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 SSI2400 excluded; no invented or duplicated RI state supplement. +# 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 @@ -345,8 +323,6 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 3_600 childcare_expenses: 0 child_care_subsidies: 0 @@ -357,7 +333,7 @@ output: ri_liheap_income: 10_800 -# Manual p21:12000-1200 child support-600 alimony-2400 unsubsidized childcare=7800. Reported support represents legally mandated payments. +# 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 @@ -379,8 +355,6 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 2_400 child_care_subsidies: 0 @@ -391,7 +365,7 @@ output: ri_liheap_income: 7_800 -# Manual p21 conditions child daycare deduction on no federal/state subsidy:12000-1200-600=10200;2400 reported childcare is not deducted when subsidies arepositive. +# 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 @@ -413,8 +387,6 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 2_400 child_care_subsidies: 1_200 @@ -425,7 +397,7 @@ output: ri_liheap_income: 10_200 -# Legally mandated support2400 exceeds income1200; final countable income is floored0. +# 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 @@ -441,8 +413,6 @@ spm_units: spm_unit: members: [person1] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 @@ -453,9 +423,9 @@ output: ri_liheap_income: 0 -# Manual p12: full parent income20000 despite size1; minor SSA excluded underp20. -# Matrix size1 threshold19563 < 20000 <= 23475 yields band4 and federal gas175. -# DHS October 2025 DIV 1-1 adds the primary utility enhancement350: total525. +# 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 @@ -492,7 +462,7 @@ ri_liheap_income_band: 4 ri_liheap_base_payment: 525 -# Manual pp16-18:12000 wages+1200 child support+600 alimony+600 family help=14400; SNAP6000 excluded. +# 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 @@ -510,8 +480,6 @@ spm_units: spm_unit: members: [person1] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 @@ -547,8 +515,6 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 @@ -567,8 +533,6 @@ absolute_error_margin: 0.01 input: state_code: [RI, RI, RI] - heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] - gas_expense: [1_200, 1_200, 1_200] age: [45, 60, 62] is_full_time_student: [false, false, false] ssi: [0, 0, 0] @@ -589,8 +553,6 @@ absolute_error_margin: 0.01 input: state_code: [RI, RI, RI] - heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] - gas_expense: [1_200, 1_200, 1_200] age: [50, 50, 50] is_full_time_student: [false, false, false] ssi: [0, 0, 0] @@ -624,8 +586,6 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf_if_takes_up: 8_412 takes_up_tanf_if_eligible: true childcare_expenses: 0 @@ -658,8 +618,6 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf_if_takes_up: 8_412 takes_up_tanf_if_eligible: false childcare_expenses: 0 @@ -689,8 +647,6 @@ spm_units: spm_unit: members: [person1] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 @@ -709,8 +665,6 @@ absolute_error_margin: 0.01 input: state_code: [RI, RI] - heating_type: [NATURAL_GAS, NATURAL_GAS] - gas_expense: [1_200, 1_200] age: [67, 67] is_full_time_student: [false, false] ssi: [0, 0] @@ -739,8 +693,6 @@ spm_units: spm_unit: members: [person1] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 @@ -750,3 +702,121 @@ 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 index 97ffd0acaf4..6f131c5ed72 100644 --- 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 @@ -1,9 +1,7 @@ -# Rhode Island independent income and primary-bill matrix components; no final award is asserted. - # 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. Positive primary bills10 do not cap these amounts. +# 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 @@ -20,7 +18,7 @@ output: ri_liheap_base_payment: [861, 805, 752, 704, 657, 582, 561, 542, 525, 509, 743, 707, 674, 645, 618] -# FY2026 matrix p1 bandB deliverables805; current landlord form lists oil,kerosene,propane,wood/pellets. Coal is not asserted. +# 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 @@ -37,9 +35,9 @@ ri_liheap_base_payment: [805, 805, 805, 805] # FY2026 matrix p1 row F: subsidized households with their own primary bill get -# deliverable657, gas159 + utility enhancement350 = 509, electric268 + 350 = 618, +# 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 lowest fuel amount for either subsidy flag. +- name: Case 3, subsidized primary bill rows use the row F amounts for either subsidy flag. period: 2026 absolute_error_margin: 0.01 input: @@ -55,7 +53,7 @@ output: ri_liheap_base_payment: [657, 509, 618, 657, 509, 618] -# ExplicitNONE and zero qualified size are unsupported heating households; this does not classify unpriced fuels as legally ineligible. +# 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 @@ -68,7 +66,7 @@ output: ri_liheap_base_payment: [0, 0] -# Ordinary RI natural gas band A is federal232 + state350 = 582; +# 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 @@ -83,8 +81,8 @@ ri_liheap_base_payment: [582, 0] # The enhancement follows the qualifying primary utility grant, not every utility -# bill. Oil861 remains861 despite an electric bill; gas232 + 350 = 582; -# electric393 + 350 = 743. Having both gas and electric bills does not double350. +# 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 @@ -103,7 +101,7 @@ ri_liheap_base_payment: [861, 582, 743] # No ordinary primary price is established for these fuels. A utility bill alone -# cannot justify adding350. Zeros represent unsupported components, not legal +# 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 @@ -141,7 +139,8 @@ output: ri_liheap_base_payment: [509, 618, 657, 582] -# Earlier backfilled amounts are unverified historical estimates. +# 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 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 index cc47446bb8a..5a8635d0af3 100644 --- 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 @@ -1,50 +1,39 @@ -# Rhode Island independent income and primary-bill matrix components; no final award is asserted. - -# FY2026 matrix p1 size1 thresholds11738,15650,19563,23475. Inclusive boundaries and next-cent cases do not create gaps from the76/101/126/151% labels. +# 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] - heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, - NATURAL_GAS, NATURAL_GAS] - gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200] 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] -# Matrix p1 size2 FPG21150*125%=26437.50 rounds26438 halfup. Printed31725 repeats150% and is a documented anomaly;26438.01 entersband4. +# 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] - heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] - gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200] 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 historical2025 estimate uses2024 FPG15060:75%=11295 and125%=18825. Does not freeze2025 FPG into earlier requested years. +# 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] - heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] - gas_expense: [1_200, 1_200, 1_200, 1_200] 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 band0, not the lowest positive payment band. +# 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] - heating_type: [NATURAL_GAS] - gas_expense: [1_200] ri_liheap_household_size: [0] ri_liheap_income: [0] output: 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 index 53cf5c6ac44..80b2c79d9dc 100644 --- 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 @@ -24,8 +24,8 @@ ri_liheap_income: [10_000, 10_000, 10_000] ri_liheap: [582, 743, 861] -# FY2026 row F uses the lowest fuel payment when subsidized households pay -# their own primary heating bill: gas 159 + 350, electric 268 + 350, oil 657. +# 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 @@ -68,7 +68,7 @@ output: ri_liheap: [509, 0, 0, 0, 0, 0] -# FY2026 rows G and I: direct grant400; subsidized secondary electric50 +350. +# 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 @@ -91,8 +91,9 @@ ri_liheap_eligible: [true, true, true] ri_liheap: [400, 400, 400] -# Earlier years backfill the researched FY2026 schedule under the all-state -# convention. This is an unverified historical estimate, not a FY2025 policy test. +# 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 @@ -225,8 +226,8 @@ ri_liheap_income_band: [4] ri_liheap: [704] -# FY2026 matrix G-I, manual II.B, and DHS October2025 DIV1-1: HIR direct400, -# secondary electric80 +350, subsidized secondary electric50 +350. The primary +# 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 @@ -252,7 +253,8 @@ ri_liheap_eligible: [true, true, true, true, true, true, false, false] ri_liheap: [400, 400, 400, 430, 400, 400, 0, 0] -# Backfilling is an estimate; it must not imply that LIHEAP began in FY2026. +# 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: @@ -268,3 +270,31 @@ 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 index 11fadac8904..3580fe6bfff 100644 --- 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 @@ -1,6 +1,4 @@ -# Rhode Island independent income and primary-bill matrix components; no final award is asserted. - -# Manual pp12-13 counts citizens and qualified noncitizens; all three qualifying members count regardless of age. +# 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: @@ -33,8 +31,6 @@ spm_units: spm_unit: members: [person1, person2, person3, person4, person5] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 0 @@ -58,8 +54,6 @@ spm_units: spm_unit: members: [person1] - heating_type: NATURAL_GAS - gas_expense: 1_200 tanf: 0 childcare_expenses: 0 child_care_subsidies: 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 index aef6f9a003d..413c6ad693a 100644 --- 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 @@ -26,9 +26,16 @@ def formula(spm_unit, period, parameters): ) 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) 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 index 1d3ac224343..b29ff8f1a48 100644 --- 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 @@ -12,8 +12,8 @@ class ri_liheap_income_limit(Variable): 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 FY2027 sheet, linked by the administering provider EBCAP. - "https://9e947cb6.delivery.rocketcdn.me/wp-content/uploads/2026/09/LIHEAP-Income-Eligibilty-FFY-2027.pdf", + # DHS FFY2027 income eligibility sheet. + "https://dhs.ri.gov/media/12266/download?language=en", ) def formula(spm_unit, period, parameters): 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 index 6655d0ed2ea..e0b10f08d5e 100644 --- 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 @@ -15,9 +15,12 @@ class ri_liheap_income(Variable): # Plan sections 1.8-1.9 # PDF pages 5-7 "https://liheapch.acf.gov/docs/2026/state-plans/RI_Plan_2026.pdf#page=5", - "https://dhs.ri.gov/media/9671/download?language=en", - "https://dhs.ri.gov/media/9701/download?language=en", - "https://westbaycap.org/wp-content/uploads/2025/08/Appendix-H-Application-Instructions-FY-26.pdf#page=2", + # 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): @@ -25,7 +28,7 @@ def formula(spm_unit, period, parameters): 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 2020 + # 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( 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 index 34ddb5096a2..f860ad9af1b 100644 --- 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 @@ -12,13 +12,14 @@ class ri_liheap_base_payment(Variable): "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. Utility-provider and account ownership " + "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", + "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", @@ -37,8 +38,10 @@ def formula(spm_unit, period, parameters): primary_bill = spm_unit("has_heating_expense", period) | ( spm_unit("heating_expense", period) > 0 ) - # Matrix row F uses the lowest payment for subsidized households - # responsible for their primary heating bill, regardless of income band. + # 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 @@ -55,9 +58,10 @@ def formula(spm_unit, period, parameters): p.amount.natural_gas[band], p.amount.electricity[band], ], - # NONE means no heating. No supported schedule mapping was found - # for COAL, OTHER, SOLAR or UNSPECIFIED; their zero is an unverified - # estimate, not a legal denial of assistance. + # 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) @@ -66,13 +70,14 @@ def formula(spm_unit, period, parameters): 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. - # Subsidized tenants without a utility burden are ineligible. + # 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, - where(subsidized, 0, hir.direct), + 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)