From bf3b670cc5d151e3625183b406719b4b97c6edc9 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sat, 3 Oct 2026 19:49:21 -0400 Subject: [PATCH 01/10] 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 02/10] 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 03/10] 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 04/10] 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 05/10] 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 06/10] 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 07/10] 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 08/10] 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 09/10] 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) From 2376cb4b4e13624e903d2413349c4f9a7492655a Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 19 Oct 2026 17:01:57 -0400 Subject: [PATCH 10/10] Address Pavel's review on Rhode Island LIHEAP Co-Authored-By: Claude Opus 5.5 --- changelog.d/ri-liheap-wave2a.added.md | 1 + changelog.d/ri-liheap.added.md | 1 - .../parameters/gov/hhs/liheap/fpg_floor.yaml | 11 + .../states/ri/dhs/liheap/fpg_year_lag.yaml | 19 ++ .../dhs/liheap/income/deduction_sources.yaml | 6 +- .../dhs/liheap/income/interest_exclusion.yaml | 2 +- .../dhs/liheap/income/rental_income_rate.yaml | 2 +- .../ri/dhs/liheap/income/sources/earned.yaml | 14 +- .../dhs/liheap/income/sources/unearned.yaml | 12 +- .../ri/dhs/liheap/payment/fpg_year_lag.yaml | 13 - .../ri/dhs/liheap/payment/heat_in_rent.yaml | 14 +- .../liheap/payment/utility_enhancement.yaml | 2 +- .../eligibility/ri_liheap_eligible.yaml | 25 ++ .../dhs/liheap/income/ri_liheap_income.yaml | 103 ++++++- .../gov/states/ri/dhs/liheap/integration.yaml | 263 ++++++++++++++++++ .../payment/ri_liheap_base_payment.yaml | 22 ++ .../gov/states/ri/dhs/liheap/ri_liheap.yaml | 172 ++++++++++++ .../liheap/eligibility/ri_liheap_eligible.py | 15 +- .../ri/dhs/liheap/income/ri_liheap_income.py | 11 +- .../liheap/payment/ri_liheap_base_payment.py | 36 ++- .../liheap/payment/ri_liheap_income_band.py | 4 +- .../gov/states/ri/dhs/liheap/ri_liheap.py | 4 +- .../ri/dhs/liheap/ri_liheap_household_size.py | 7 +- 23 files changed, 694 insertions(+), 65 deletions(-) create mode 100644 changelog.d/ri-liheap-wave2a.added.md delete mode 100644 changelog.d/ri-liheap.added.md create mode 100644 policyengine_us/parameters/gov/hhs/liheap/fpg_floor.yaml create mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/fpg_year_lag.yaml delete mode 100644 policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/fpg_year_lag.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/integration.yaml diff --git a/changelog.d/ri-liheap-wave2a.added.md b/changelog.d/ri-liheap-wave2a.added.md new file mode 100644 index 00000000000..321a6ccad89 --- /dev/null +++ b/changelog.d/ri-liheap-wave2a.added.md @@ -0,0 +1 @@ +Add Rhode Island LIHEAP regular heating assistance, income and heating eligibility with the federal 110% poverty floor, including heat-in-rent grants and the qualifying utility enhancement. Earlier years backfill the researched FY2026 schedule as unverified estimates. diff --git a/changelog.d/ri-liheap.added.md b/changelog.d/ri-liheap.added.md deleted file mode 100644 index 28f27503963..00000000000 --- a/changelog.d/ri-liheap.added.md +++ /dev/null @@ -1 +0,0 @@ -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/hhs/liheap/fpg_floor.yaml b/policyengine_us/parameters/gov/hhs/liheap/fpg_floor.yaml new file mode 100644 index 00000000000..aeb7f8c5b07 --- /dev/null +++ b/policyengine_us/parameters/gov/hhs/liheap/fpg_floor.yaml @@ -0,0 +1,11 @@ +description: The US Department of Health and Human Services limits income-based exclusion to households with income of at least this percentage of the poverty guideline under the Low Income Home Energy Assistance Program. +# Pub. L. 98-558, section 605(a)(2), added the floor for fiscal year 1986 and later. +values: + 1985-10-01: 1.1 +metadata: + period: year + unit: /1 + label: LIHEAP poverty guideline income floor + reference: + - title: 42 U.S.C. 8624(b)(2)(B) + href: https://www.law.cornell.edu/uscode/text/42/8624#b_2_B diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/fpg_year_lag.yaml new file mode 100644 index 00000000000..2524164c74e --- /dev/null +++ b/policyengine_us/parameters/gov/states/ri/dhs/liheap/fpg_year_lag.yaml @@ -0,0 +1,19 @@ +description: Rhode Island uses the federal poverty guidelines from this many years before the model year for payment bands and the federal income floor under the Low Income Home Energy Assistance Program. +# The FY2026 matrix uses the 2025 guidelines, which HHS made mandatory for LIHEAP +# from October 1, 2025, so model period 2026 (FY2026) uses the 2025 guidelines +# and 2027 (FY2027) the 2026 guidelines, for both the bands and the floor. +# Backfilled years use their corresponding lagged guidelines and are unverified. +values: + 2025-10-01: 1 + +metadata: + unit: year + period: year + label: Rhode Island LIHEAP poverty guideline year lag + reference: + - title: Rhode Island FY2026 Grant Amounts and Income Eligibility Matrix, note 2 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf + - title: LIHEAP IM 2025-02, Attachment 2, poverty guidelines for mandatory use in FY26 + href: https://acf.gov/sites/default/files/documents/ocs/COMM_LIHEAP_IM2025-02_FPGSte-Table_Att2.pdf#page=1 + - title: LIHEAP IM 2026-01, poverty guidelines for optional use in FY26 and mandatory use in FY27 + href: https://acf.gov/ocs/policy-guidance/liheap-im2026-01-federal-poverty-guidelines-fpg-and-state-median-income-smi diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/deduction_sources.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/deduction_sources.yaml index 05d14911b2a..23e90873225 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 @@ -8,8 +8,10 @@ values: - alimony_expense # Manual Section III.H, page 21, deducts "All Medicare expenses, including # prescriptions." FY2026 plan Section 1.9, page 6, counts Social Security - # "Including MediCare deduction", which DHS's draft FFY2027 plan summary glosses - # as "meaning the Medicare amount deducted is not counted", so the sources agree. + # "Including MediCare deduction". Only DHS's draft FFY2027 plan summary + # glosses this, as "meaning the Medicare amount deducted is not counted"; + # read that way, the plan agrees with the manual. The draft is cited until + # DHS posts the final FFY2027 summary. # The Part B premium is the only Medicare expense with an input; prescription # and other Medicare costs are not deducted. - medicare_part_b_premium 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 839140a3a23..df5d993729a 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 @@ -9,5 +9,5 @@ metadata: 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 + - title: Rhode Island FY2026 LIHEAP Application Instructions, Appendix H (May 2025), Dividend or Interest Income href: https://dhs.ri.gov/media/9671/download?language=en#page=2 diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/rental_income_rate.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/rental_income_rate.yaml index e9d3aaa7060..3d0835b6ed1 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 @@ -11,5 +11,5 @@ metadata: reference: - title: "Rhode Island LIHEAP Manual, Sections III.E and III.G Rental Income #page 16,20" href: https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=16 - - title: Rhode Island LIHEAP Rental Income Reported Form, May 2025 Appendix M + - title: Rhode Island LIHEAP Rental Income Reported Form, Appendix M (May 2025) href: https://dhs.ri.gov/media/9701/download?language=en#page=1 diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/earned.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/earned.yaml index cd26671b908..dbd9c8316d0 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,8 +1,10 @@ description: Rhode Island uses these sources as earned income under the Low Income Home Energy Assistance Program. -# Existing net business income is used without an additional 40% deduction. -# The manual (Section III.H, page 20) deducts 40% of gross self-employment -# receipts, and DHS's FY2026 plan summary (page 1) lists "Self-employment -# income (60%)". Gross business receipts and business-expense inputs remain deferred. +# Manual Section III.H (page 20) deducts 40% of gross self-employment receipts, +# and DHS's FY2026 plan summary (page 1) lists "Self-employment income (60%)". +# The model has only net business income and counts it in full, as Kansas LIHEAP +# does; this equals 60% of receipts only when expenses are 40% of receipts, and +# otherwise over- or understates countable income by receipts x (0.4 - expense +# ratio). No gross-receipts or business-expense input is added. # Active versus passive partnership receipts are not separately identifiable. values: 2025-10-01: @@ -21,7 +23,7 @@ metadata: href: https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=14 - title: "Rhode Island FY2026 State Plan, Sections 1.8 and 1.9 #page 5-7" href: https://liheapch.acf.gov/docs/2026/state-plans/RI_Plan_2026.pdf#page=5 - - title: Rhode Island FY2026 Application Instructions, Proof of Income + - title: Rhode Island FY2026 LIHEAP Application Instructions, Appendix H (May 2025), Proof of Income href: https://dhs.ri.gov/media/9671/download?language=en#page=2 - - title: Rhode Island DHS Summary of the FFY2026 LIHEAP State Plan, Countable Income + - title: Rhode Island DHS Summary of the FFY2026 LIHEAP State Plan, Section 1, Program Components href: https://dhs.ri.gov/media/9371/download?language=en#page=1 diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/income/sources/unearned.yaml index 3780cf078f5..392a708392a 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 @@ -5,8 +5,12 @@ description: Rhode Island uses these sources as unearned income under the Low In # SSI is the federal benefit; a separate RI state supplement is not modeled. # disability_benefits approximates employment-related TDI/TCI receipts. # VA education and Aid and Attendance exclusions cannot be separated from -# the broad veterans input. Jury duty, royalties, foster receipts and legal -# settlements also lack sufficiently specific existing inputs. +# the broad veterans input. Strike pay and VA benefits are counted under FY2026 +# plan Section 1.9 (page 6) and the DHS FY2026 plan summary (page 1). Payments +# from mortgages or sales contracts, AmeriCorp living allowances, jury duty pay, +# royalties, foster care receipts and legal settlements lack specific existing +# inputs and are not counted; contract income counts only when reported as +# wages or self-employment income. values: 2025-10-01: - total_unemployment_compensation @@ -50,5 +54,7 @@ metadata: href: https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=14 - title: "Rhode Island FY2026 State Plan, Sections 1.8 and 1.9 #page 5-7" href: https://liheapch.acf.gov/docs/2026/state-plans/RI_Plan_2026.pdf#page=5 - - title: Rhode Island FY2026 Application Instructions, Proof of Income + - title: Rhode Island FY2026 LIHEAP Application Instructions, Appendix H (May 2025), Proof of Income href: https://dhs.ri.gov/media/9671/download?language=en#page=2 + - title: Rhode Island DHS Summary of the FFY2026 LIHEAP State Plan, Section 1, Program Components + href: https://dhs.ri.gov/media/9371/download?language=en#page=1 diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/fpg_year_lag.yaml deleted file mode 100644 index e27edccd7d2..00000000000 --- a/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/fpg_year_lag.yaml +++ /dev/null @@ -1,13 +0,0 @@ -description: Rhode Island uses the federal poverty guideline from this many years before the benefit year to set payment bands under the Low Income Home Energy Assistance Program. -# The FY2026 table uses the 2025 federal poverty guidelines. -# Backfilled years use their corresponding lagged guidelines and are unverified. -values: - 2025-10-01: 1 - -metadata: - unit: year - period: year - label: Rhode Island LIHEAP payment federal poverty guideline year lag - reference: - - title: Rhode Island FY2026 Benefit Matrix, FPG year note - href: https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf diff --git a/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/heat_in_rent.yaml b/policyengine_us/parameters/gov/states/ri/dhs/liheap/payment/heat_in_rent.yaml index 0a72a487137..5b873ff838c 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,10 +1,18 @@ 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. +# the Electric cell blank, so its electricity value is 0. Unlisted fuels (coal, +# solar) and an unknown fuel read the Other cell. Rows H and I print one amount +# each and go to the household's separate electric account whatever the fuel. direct: - 2025-10-01: 400 + deliverables: + 2025-10-01: 400 + natural_gas: + 2025-10-01: 400 + electricity: + 2025-10-01: 0 + other: + 2025-10-01: 400 secondary_electric: 2025-10-01: 80 subsidized_secondary_electric: 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 0b44b5599c7..d94224949f7 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 @@ -17,5 +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 + - title: DHS FFY2027 LIHEAP Enhancement Charge filing, PUC Docket 26-34-GE, October 2, 2026 (2027 credit amount not yet set) href: https://ripuc.ri.gov/sites/g/files/xkgbur841/files/2026-10/26-34-GE%20LIHEAP%20Enhancement%20Charge%20-%20FFY%202027%20Filing%20%2810-2-2026%29.pdf#page=2 diff --git a/policyengine_us/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 9f7e219ba92..f215fbaa388 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 @@ -62,3 +62,28 @@ ri_liheap_income: 0 ri_liheap_income_limit: 0 ri_liheap_eligible: false + +# Threshold edge for the federal floor, 42 U.S.C. 8624(b)(2)(B): income "less than +# 110 percent of the poverty level" cannot be excluded. Size and income are forced +# because the floor binds only from 23 qualified members in 2026. The 60% SMI limit +# is floor(floor(0.6 x 135,424) x 1.83) = floor(81,254 x 1.83) = floor(148,694.82) +# = 148,694, where 1.83 = 0.52 + 5 x 0.16 + 17 x 0.03. The floor uses the 2025 +# guideline (one-year lag): 15,650 + 22 x 5,500 = 136,650, and 1.1 x 136,650 = +# 150,315.00. All three incomes exceed the limit; 150,314.99 is below the floor +# (eligible) and 150,315 and 150,315.01 are not. A zero-year lag (2026 guideline, +# 1.1 x 140,920 = 155,012) would make 150,315 eligible, and a two-year lag (2024 +# guideline, 1.1 x 133,420 = 146,762) would make 150,314.99 ineligible. +- name: Case 4, threshold edge at the federal 110 percent poverty floor for twenty three members. + period: 2026 + input: + state_code: [RI, RI, RI] + ri_liheap_household_size: [23, 23, 23] + ri_liheap_income: [150_314.99, 150_315, 150_315.01] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_200, 1_200, 1_200] + heat_expense_included_in_rent: [false, false, false] + receives_housing_assistance: [false, false, false] + is_in_public_housing: [false, false, false] + output: + ri_liheap_income_limit: [148_694, 148_694, 148_694] + ri_liheap_eligible: [true, false, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/income/ri_liheap_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/income/ri_liheap_income.yaml index 5736e6aee28..549caeff381 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 @@ -703,9 +703,11 @@ 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) +# R.I. DLT Unemployment Insurance FAQ +# (https://dlt.ri.gov/individuals/unemployment-insurance/unemployment-insurance-faq): +# the weekly rate is 3.85% of the average of the two highest base-period quarters +# (2026 maximum $745), and the claim lasts 33% of base-period wages divided by the +# weekly rate, capped at 26 weeks. A worker earning 10,000 a quarter (40,000 a year) # and laid off at the end of 2025 gets 10,000 x 0.0385 = 385 a week. The duration is # 0.33 x 40,000 / 385 = 34.3 weeks, capped at 26, so the claim pays 26 x 385 = 10,010. # Rhode Island has no modeled UI, so the total equals the reported amount; manual page 15 @@ -735,11 +737,12 @@ 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. +# Manual pages 14-17 count every listed unearned source except strike pay and VA +# benefits, which FY2026 plan Section 1.9 (page 6) counts. Each is set to a distinct +# amount, so dropping any one changes the total: 1,000 + 500 + 1,200 + 600 + 1,500 + +# 2,000 + 700 + 800 + 900 + 1,100 + 1,300 + 1,400 + 1,600 + 300 + 200 + 1,700 + 400 + 100 +# = 17,300. Interest, rent and TANF are 0. At age 60 with no Social Security disability +# the person is not Medicare-enrolled, so no Part B premium is deducted. - name: Case 26, every listed unearned income source counts. period: 2026 absolute_error_margin: 0.01 @@ -820,3 +823,87 @@ state_code: RI output: ri_liheap_income: 12_000 + +# Each member's interest is floored at zero before the household's $500 exclusion +# (manual page 20), so a negative interest entry cannot cancel another member's +# interest: 12,000 wages + max(0 + 700 - 500, 0) = 12,200. Summing before the floor +# would give max(-1,000 + 700 - 500, 0) = 0 and a total of 12,000. +- name: Case 28, a negative interest entry does not offset another member's interest. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 12_000 + interest_income: -1_000 + person2: + age: 44 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + interest_income: 700 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2] + state_code: RI + output: + ri_liheap_income: 12_200 + +# Manual III.H (page 21) deducts court-documented support without limiting the +# deduction to members whose income counts, so the model deducts every member's +# support and alimony paid, the same household-level reading as the Part B premium. +# Wages use the 2026 Rhode Island minimum wage of $16 an hour: the parent works full +# time (16 x 40 x 52 = 33,280), the 20-year-old full-time student 10 hours a week +# (8,320, excluded by Appendix H page 2) and the undocumented member 20 hours a week +# (16,640, counted although not in the size). Counted income 33,280 + 16,640 = +# 49,920, less the student's 1,200 of child support and the undocumented member's +# 1,800 of alimony: 49,920 - 3,000 = 46,920. Deducting only counted members' expenses +# would give 48,120; only qualified members' expenses, 48,720. +- name: Case 29, support paid by excluded members is deducted from household income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 33_280 + person2: + age: 20 + immigration_status: CITIZEN + is_full_time_student: true + ssi: 0 + employment_income: 8_320 + child_support_expense: 1_200 + person3: + age: 50 + immigration_status: UNDOCUMENTED + is_full_time_student: false + ssi: 0 + employment_income: 16_640 + alimony_expense: 1_800 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + households: + household: + members: [person1, person2, person3] + state_code: RI + output: + ri_liheap_household_size: 2 + ri_liheap_income: 46_920 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/integration.yaml new file mode 100644 index 00000000000..e1803885d7d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/integration.yaml @@ -0,0 +1,263 @@ +# Sources for the expected values in this file: +# - FY2026 Grant Amounts and Income Eligibility (one page; rows A-I and the income +# table): https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf +# - DHS LIHEAP Manual (2019), sections II.B, III and III.E-III.H (pages 5-6, 12, 14-21): +# https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=5 +# - FY2026 application instructions, Appendix H, page 2 (full-time students): +# https://dhs.ri.gov/media/9671/download?language=en#page=2 +# - DHS response to Division data request 1-1 (the $350 enhancement): +# https://ripuc.ri.gov/sites/g/files/xkgbur841/files/2025-10/25-38-GE%20-%20LIHEAP%20Enhancement%20-%20DPUC%20Data%20Requests%20Set%201%20%2810-9-25%29%20DHS%20Response.pdf#page=1 +# Wages use the 2026 Rhode Island minimum wage, $16 an hour (R.I. DLT). Band tops are +# the 2025 guideline (15,650 + 5,500 per additional person) times 75%, 100%, 125% and +# 150%, rounded half up to the dollar. These match the matrix income table except the +# size-two 125% cell, which repeats 31,725 (payment/ri_liheap_income_band.yaml Case 2). + +# A retiree aged 72 who lives alone and receives only federal SSI, the 2026 individual +# federal benefit rate of $994 a month (SSA, SSI Federal Payment Amounts): 994 x 12 = +# 11,928. Her income is below the poverty guideline, so the Medicare Savings Program +# pays her Part B premium. She heats with electricity billed in her own name. +- name: Case 1, an elderly SSI recipient with her own electric heating bill receives the row B electric grant. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 72 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 11_928 + medicare_part_b_premium: 0 + spm_units: + spm_unit: + members: [person1] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_500 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [person1] + state_code: RI + is_in_public_housing: false + output: + # She pays her own primary heating bill (manual III.A, page 12). + has_heating_expense: true + # A citizen counts in the size (manual page 12). + ri_liheap_household_size: 1 + # SSI counts as fixed income (manual page 15): 11,928. + ri_liheap_income: 11_928 + # Matrix, 60% SMI annual, one person. + ri_liheap_income_limit: 42_252 + # 11,738 (75%) < 11,928 <= 15,650 (100%): row B. + ri_liheap_income_band: 2 + ri_liheap_eligible: true + # Row B Electric 357 + 350 enhancement = 707. + ri_liheap_base_payment: 707 + ri_liheap: 707 + +# A single parent aged 34 working full time at the minimum wage (16 x 40 x 52 = 33,280) +# with two school-age children, no child care costs, no RI Works and no SSI. Natural gas +# heat is billed in the parent's name. +- name: Case 2, a working single parent with two children receives the row C natural gas grant. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 34 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 33_280 + person2: + age: 9 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 6 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [person1, person2, person3] + state_code: RI + is_in_public_housing: false + output: + has_heating_expense: true + ri_liheap_household_size: 3 + # Wages count gross (manual page 14); the children have no income. + ri_liheap_income: 33_280 + # Matrix, 60% SMI annual, three people. + ri_liheap_income_limit: 68_253 + # Size 3 guideline 26,650: 26,650 (100%) < 33,280 <= 33,313 (125%, 33,312.50 + # rounded half up): row C. + ri_liheap_income_band: 3 + ri_liheap_eligible: true + # Row C Natural Gas 192 + 350 enhancement = 542. + ri_liheap_base_payment: 542 + ri_liheap: 542 + +# Two parents (one full time at the minimum wage, 33,280; one 20 hours a week, 16 x 20 x +# 52 = 16,640), a 20-year-old full-time college student working 10 hours a week (16 x 10 +# x 52 = 8,320) and a 15-year-old. They buy their own fuel oil. +- name: Case 3, a family of four with a full-time college student receives the row E delivered fuel grant. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 46 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 33_280 + person2: + age: 44 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 16_640 + person3: + age: 20 + immigration_status: CITIZEN + is_full_time_student: true + ssi: 0 + employment_income: 8_320 + person4: + age: 15 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + heating_type: FUEL_OIL + fuel_oil_expense: 2_400 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [person1, person2, person3, person4] + state_code: RI + is_in_public_housing: false + output: + has_heating_expense: true + ri_liheap_household_size: 4 + # The student's 8,320 is excluded (Appendix H, page 2): 33,280 + 16,640 = 49,920. + ri_liheap_income: 49_920 + # Matrix, 60% SMI annual, four people. + ri_liheap_income_limit: 81_254 + # Size 4 guideline 32,150: 49,920 > 48,225 (150%): row E. + ri_liheap_income_band: 5 + ri_liheap_eligible: true + # Row E Deliverables 657; delivered fuel gets no utility enhancement. + ri_liheap_base_payment: 657 + ri_liheap: 657 + +# A public housing tenant aged 58 working 20 hours a week at the minimum wage (16,640) +# whose rent includes heat. He does not know the building's fuel (UNSPECIFIED) and pays +# his own electric bill. +- name: Case 4, a public housing tenant with heat in rent and his own electric bill receives the row I grant. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 58 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 16_640 + spm_units: + spm_unit: + members: [person1] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + heating_type: UNSPECIFIED + pre_subsidy_electricity_expense: 900 + heat_expense_included_in_rent: true + receives_housing_assistance: false + households: + household: + members: [person1] + state_code: RI + is_in_public_housing: true + output: + # Heat is paid through rent, not a bill of his own. + has_heating_expense: false + ri_liheap_household_size: 1 + ri_liheap_income: 16_640 + ri_liheap_income_limit: 42_252 + # 15,650 (100%) < 16,640 <= 19,563 (125%): band 3, which the heat-in-rent rows do + # not use. + ri_liheap_income_band: 3 + # Subsidized renters need a utility burden (manual II.B, page 6); his electric bill + # is one. + ri_liheap_eligible: true + # Row I (subsidized housing, secondary electric) 50 + 350 enhancement = 400. + ri_liheap_base_payment: 400 + ri_liheap: 400 + +# Two adults aged 30 and 29, each working full time at the minimum wage (33,280 each), +# with natural gas heat billed in their names. +- name: Case 5, a working couple above the two-person income limit receives nothing. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 33_280 + person2: + age: 29 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + employment_income: 33_280 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [person1, person2] + state_code: RI + is_in_public_housing: false + output: + has_heating_expense: true + ri_liheap_household_size: 2 + # 33,280 + 33,280 = 66,560. + ri_liheap_income: 66_560 + ri_liheap_income_limit: 55_252 + # Size 2 guideline 21,150: 66,560 > 31,725 (150%): row E. + ri_liheap_income_band: 5 + # 66,560 > 55,252, and above the federal floor 1.1 x 21,150 = 23,265. + ri_liheap_eligible: false + # The base payment does not apply eligibility: row E Natural Gas 159 + 350 = 509. + ri_liheap_base_payment: 509 + ri_liheap: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/payment/ri_liheap_base_payment.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/payment/ri_liheap_base_payment.yaml index 6f131c5ed72..c3c58a8125a 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 @@ -154,3 +154,25 @@ ri_liheap_income_band: [1, 1, 1] output: ri_liheap_base_payment: [861, 582, 743] + +# Row G is the direct grant to an unsubsidized heat-in-rent household with no separate +# electric bill. Subsidized renters with no utility burden are ineligible (manual II.B, +# page 6), so a subsidized heat-in-rent household with no electric bill gets no row G +# grant under either subsidy flag: 0 and 0. The same unsubsidized household reads the +# row G Natural Gas cell, 400, with no enhancement because no utility account receives +# the grant. +- name: Case 10, subsidized heat-in-rent households without an electric bill get no direct grant. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + ri_liheap_household_size: [1, 1, 1] + ri_liheap_income_band: [1, 1, 1] + heat_expense_included_in_rent: [true, true, true] + gas_expense: [0, 0, 0] + pre_subsidy_electricity_expense: [0, 0, 0] + receives_housing_assistance: [true, false, false] + is_in_public_housing: [false, true, false] + output: + ri_liheap_base_payment: [0, 0, 400] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/ri_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/dhs/liheap/ri_liheap.yaml index 80b2c79d9dc..46f7cef637f 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 @@ -298,3 +298,175 @@ ri_liheap_eligible: [true, true] ri_liheap_base_payment: [0, 430] ri_liheap: [0, 430] + +# Threshold edge for the federal floor, 42 U.S.C. 8624(b)(2)(B), with the 2026 example +# from review: 23 qualified members (forced size) and 150,000 of countable income +# (forced). The 60% SMI limit is 148,694 (eligibility Case 4), but 150,000 is below +# the floor 1.1 x 136,650 = 150,315 on the 2025 guideline, so the household stays +# eligible. Bands use the same guideline: 136,650 (100%) < 150,000 <= 170,813 (125%, +# 170,812.50 rounded half up) is band 3 (row C): natural gas 192 + 350 = 542. +- name: Case 13, threshold edge, a twenty three member household below the federal poverty floor is paid above the state median income limit. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI] + ri_liheap_household_size: [23] + ri_liheap_income: [150_000] + heating_type: [NATURAL_GAS] + gas_expense: [1_200] + heat_expense_included_in_rent: [false] + receives_housing_assistance: [false] + is_in_public_housing: [false] + output: + ri_liheap_income_limit: [148_694] + ri_liheap_eligible: [true] + ri_liheap_income_band: [3] + ri_liheap_base_payment: [542] + ri_liheap: [542] + +# FY2026 row G pays $400 under Deliverables, Natural Gas and Other. HeatingType documents +# UNSPECIFIED as heat in rent with an unknown fuel, and Appendix B has no coal or solar +# box, so UNSPECIFIED, SOLAR and COAL read the Other cell: 400. With no electric bill no +# utility account receives the grant, so there is no enhancement. Heat in unsubsidized +# rent meets the energy burden; wages are the 2026 Rhode Island minimum wage, 16 x 40 x +# 52 = 33,280 <= 42,252 (size 1). +- name: Case 14, heat in unsubsidized rent with an unspecified, solar or coal fuel receives the row G Other amount. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI] + age: [40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN] + employment_income: [33_280, 33_280, 33_280] + ssi: [0, 0, 0] + tanf: [0, 0, 0] + childcare_expenses: [0, 0, 0] + child_care_subsidies: [0, 0, 0] + heating_type: [UNSPECIFIED, SOLAR, COAL] + pre_subsidy_electricity_expense: [0, 0, 0] + heat_expense_included_in_rent: [true, true, true] + receives_housing_assistance: [false, false, false] + is_in_public_housing: [false, false, false] + output: + ri_liheap_eligible: [true, true, true] + ri_liheap_base_payment: [400, 400, 400] + ri_liheap: [400, 400, 400] + +# Outside heat in rent the Other column is blank in rows A-F, and Appendix B has no coal +# or solar box. Coal, solar and other households with their own bill meet the energy +# burden and the income test (33,280 <= 42,252), so they are eligible, but no matrix +# amount applies and no utility grant carries the enhancement: 0. +- name: Case 15, coal, solar and other heat with a direct bill stay eligible but receive no matrix amount. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [RI, RI, RI] + age: [40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN] + employment_income: [33_280, 33_280, 33_280] + ssi: [0, 0, 0] + tanf: [0, 0, 0] + childcare_expenses: [0, 0, 0] + child_care_subsidies: [0, 0, 0] + heating_type: [COAL, SOLAR, OTHER] + coal_expense: [1_200, 0, 0] + pre_subsidy_electricity_expense: [0, 1_200, 0] + other_heating_fuel_expense: [0, 0, 1_200] + heat_expense_included_in_rent: [false, false, false] + receives_housing_assistance: [false, false, false] + is_in_public_housing: [false, false, false] + output: + ri_liheap_eligible: [true, true, true] + ri_liheap: [0, 0, 0] + +# Threshold edge for the two-person limit, reached end to end. The undocumented parent is +# not counted in the size (manual page 12) but her wages count, so the qualified size is +# 2 (the citizen spouse and child) and the FY2026 limit is 55,252, not the three-person +# 68,253. Income 55,252 equals the limit, which is inclusive. Size 2 bands use the 2025 +# guideline 21,150: 55,252 > 31,725 (150%) is band 5 (row E): natural gas 159 + 350 = 509. +- name: Case 16, threshold edge, a mixed-status family at the two-person income limit is eligible. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + is_full_time_student: false + ssi: 0 + employment_income: 55_252 + person2: + age: 38 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + person3: + age: 9 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [person1, person2, person3] + state_code: RI + is_in_public_housing: false + output: + ri_liheap_household_size: 2 + ri_liheap_income: 55_252 + ri_liheap_income_limit: 55_252 + ri_liheap_eligible: true + ri_liheap_income_band: 5 + ri_liheap: 509 + +# Threshold edge: the same family with one more cent of wages exceeds the two-person +# limit of 55,252 and is far above the federal floor (1.1 x 21,150 = 23,265), so it is +# ineligible and receives 0. Using all three members would give the 68,253 limit. +- name: Case 17, threshold edge, the same mixed-status family one cent above the two-person limit is ineligible. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + is_full_time_student: false + ssi: 0 + employment_income: 55_252.01 + person2: + age: 38 + immigration_status: CITIZEN + is_full_time_student: false + ssi: 0 + person3: + age: 9 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + childcare_expenses: 0 + child_care_subsidies: 0 + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household: + members: [person1, person2, person3] + state_code: RI + is_in_public_housing: false + output: + ri_liheap_household_size: 2 + ri_liheap_income_limit: 55_252 + ri_liheap_eligible: false + ri_liheap: 0 diff --git a/policyengine_us/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 413c6ad693a..051a76ec40e 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 @@ -1,4 +1,5 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg class ri_liheap_eligible(Variable): @@ -10,13 +11,25 @@ class ri_liheap_eligible(Variable): reference = ( # PDF pages 5-6, 12 "https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=5", + # PDF pages 8, 45 "https://liheapch.acf.gov/docs/2026/state-plans/RI_Plan_2026.pdf#page=8", + "https://www.law.cornell.edu/uscode/text/42/8624#b_2_B", ) def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ri.dhs.liheap size = spm_unit("ri_liheap_household_size", period) income = spm_unit("ri_liheap_income", period) limit = spm_unit("ri_liheap_income_limit", period) + # 42 U.S.C. 8624(b)(2)(B) bars excluding a household on income below 110% + # of the poverty guideline. The 60% SMI limit falls below that floor from + # household size 23 in 2026 and 2027 (21 and 19 in the 2025 and 2024 + # backfills). + p_hhs = parameters(period).gov.hhs.liheap + state_group = spm_unit.household("state_group_str", period) + guideline = fpg(size, state_group, period, parameters, year_lag=p.fpg_year_lag) + floor = np.round(guideline * p_hhs.fpg_floor, 2) + income_eligible = (income <= limit) | (income < floor) heat_in_rent = spm_unit("heat_expense_included_in_rent", period) subsidized = spm_unit("receives_housing_assistance", period) | ( spm_unit.household("is_in_public_housing", period) @@ -39,7 +52,7 @@ def formula(spm_unit, period, parameters): heating_type = spm_unit("heating_type", period) return ( (size > 0) - & (income <= limit) + & income_eligible & energy_burden & (heating_type != heating_type.possible_values.NONE) ) 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 e0b10f08d5e..07958f6297e 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 @@ -35,10 +35,13 @@ def formula(spm_unit, period, parameters): "is_full_time_student", period ) counted_person = (age >= p.minimum_income_age) & ~exempt_student - # Existing net business receipts approximate the gross-receipts rule. - # No additional 40% business deduction or new expense input is used. - # Each source is floored at zero, so a loss in one source cannot - # offset another. + # Manual III.H (page 20) counts self-employment at 60% of gross receipts + # (a 40% deduction). The model has only net business income and counts it + # in full, as ks_liheap_countable_earned_income does; this matches the + # manual only when expenses are 40% of receipts, overstates countable + # income when expenses are lower and understates it when they are higher. + # No gross-receipts or business-expense input is added. Each source is + # floored at zero, so a loss in one source cannot offset another. earnings = 0 for source in p.sources.earned: earnings = earnings + max_(person(source, period), 0) 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 f860ad9af1b..68a73a8f38d 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,9 +12,11 @@ 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, 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." + "row G grant for the household's fuel column is paid directly. Row G's " + "Electric cell is blank, so electric heat gets none, and coal, solar " + "and unspecified fuels read the Other cell. Utility-provider and " + "account ownership requirements are assumed satisfied. Final " + "eligibility is separate." ) defined_for = StateCode.RI reference = ( @@ -51,8 +53,13 @@ def formula(spm_unit, period, parameters): | (fuel == fuels.PROPANE) | (fuel == fuels.WOOD) ) + fuel_columns = [ + deliverable, + fuel == fuels.NATURAL_GAS, + fuel == fuels.ELECTRICITY, + ] amount = select( - [deliverable, fuel == fuels.NATURAL_GAS, fuel == fuels.ELECTRICITY], + fuel_columns, [ p.amount.deliverables[band], p.amount.natural_gas[band], @@ -60,8 +67,8 @@ def formula(spm_unit, period, parameters): ], # 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. + # primary amount, and UNSPECIFIED receives none because heating_type is a + # required input. Heat in rent replaces this amount with rows G-I below. default=0, ) heat_in_rent = spm_unit("heat_expense_included_in_rent", period) @@ -70,12 +77,17 @@ 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; 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) + # Rows H-I go to the separate electric account whatever the fuel. Row G is + # the direct grant when there is no separate electric bill to credit, read + # from the household's fuel column; OTHER, COAL, SOLAR and UNSPECIFIED (heat + # in rent with an unknown fuel) read the Other cell, and the blank Electric + # cell is 0. Subsidized tenants without a utility burden are ineligible. + direct_amount = select( + fuel_columns, + [hir.direct.deliverables, hir.direct.natural_gas, hir.direct.electricity], + default=hir.direct.other, + ) + direct_payment = where(subsidized, 0, direct_amount) heat_in_rent_payment = where( separate_electric_bill, secondary_electric_payment, direct_payment ) 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 index 42ba45dc2a9..27480867836 100644 --- 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 @@ -11,7 +11,7 @@ class ri_liheap_income_band(Variable): 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 + p = parameters(period).gov.states.ri.dhs.liheap size = spm_unit("ri_liheap_household_size", period) state_group = spm_unit.household("state_group_str", period) guideline = fpg( @@ -19,7 +19,7 @@ def formula(spm_unit, period, parameters): ) income = spm_unit("ri_liheap_income", period) band = np.ones_like(size, dtype=int) - for rate in p.fpg_rates: + for rate in p.payment.fpg_rates: threshold = np.floor(guideline * rate + 0.5) band = band + (income > threshold) # Use the published percentage labels. The size-two 125% table cell 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 493b3d858d7..5ed835095bf 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 @@ -12,6 +12,4 @@ class ri_liheap(Variable): "https://liheapch.acf.gov/docs/2026/benefits-matricies/RI_BenefitMatrix_2026.pdf", "https://ripuc.ri.gov/eventsactions/docket/4290-DHS-DR-PUC%203-6%20attachment%20LIHEAP%20Manual%202020%20-%20Final.pdf#page=5", ) - - def formula(spm_unit, period, parameters): - return spm_unit("ri_liheap_base_payment", period) + adds = ["ri_liheap_base_payment"] diff --git a/policyengine_us/variables/gov/states/ri/dhs/liheap/ri_liheap_household_size.py b/policyengine_us/variables/gov/states/ri/dhs/liheap/ri_liheap_household_size.py index 2bd106fca09..3a9df4078ac 100644 --- 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 @@ -9,7 +9,6 @@ class ri_liheap_household_size(Variable): 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"]) + # Section III counts only citizens and qualified noncitizens in size, + # while income from all members is considered. No SNAP-specific bars. + adds = ["is_citizen_or_legal_immigrant"]