From bd4f34728e78fc22e8733848127da532e92bf257 Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 23 Oct 2026 18:11:10 -0400 Subject: [PATCH 1/2] Add Wisconsin WHEAP regular heating benefit Co-Authored-By: Claude Opus 5.5 --- changelog.d/wi-liheap-wave1d.added.md | 1 + .../doa/wheap/eligibility/fpg_year_lag.yaml | 13 + .../wi/doa/wheap/eligibility/smi_rate.yaml | 14 + .../states/wi/doa/wheap/income/adult_age.yaml | 10 + .../wi/doa/wheap/income/deductions.yaml | 14 + .../doa/wheap/income/investment_minimum.yaml | 10 + .../income/retirement_distribution_age.yaml | 10 + .../wi/doa/wheap/income/sources/earned.yaml | 11 + .../doa/wheap/income/sources/investment.yaml | 12 + .../wi/doa/wheap/income/sources/rental.yaml | 14 + .../wheap/income/sources/self_employment.yaml | 16 + .../wi/doa/wheap/income/sources/unearned.yaml | 26 ++ .../wi/doa/wheap/payment/benefit_ratio.yaml | 10 + .../wheap/payment/electric_baseload_rate.yaml | 12 + .../wi/doa/wheap/payment/funding_factor.yaml | 10 + .../states/wi/doa/wheap/payment/maximum.yaml | 12 + .../states/wi/doa/wheap/payment/minimum.yaml | 14 + .../wheap/payment/poverty_ratio/maximum.yaml | 10 + .../wheap/payment/poverty_ratio/minimum.yaml | 10 + .../wheap/payment/proxy/base/electricity.yaml | 23 + .../wheap/payment/proxy/base/fuel_oil.yaml | 23 + .../wheap/payment/proxy/base/natural_gas.yaml | 23 + .../doa/wheap/payment/proxy/base/propane.yaml | 23 + .../wheap/payment/proxy/base/wood_other.yaml | 23 + .../wi/doa/wheap/payment/proxy/cap_rate.yaml | 12 + .../doa/wheap/payment/proxy/floor_rate.yaml | 14 + .../wi/doa/wheap/payment/proxy/max_rooms.yaml | 12 + .../payment/proxy/per_room/electricity.yaml | 23 + .../payment/proxy/per_room/fuel_oil.yaml | 23 + .../payment/proxy/per_room/natural_gas.yaml | 23 + .../wheap/payment/proxy/per_room/propane.yaml | 23 + .../payment/proxy/per_room/wood_other.yaml | 23 + policyengine_us/programs.yaml | 10 +- .../wi_wheap_categorically_eligible.yaml | 188 ++++++++ .../wheap/eligibility/wi_wheap_eligible.yaml | 410 ++++++++++++++++++ .../eligibility/wi_wheap_income_limit.yaml | 151 +++++++ .../income/wi_wheap_countable_income.yaml | 291 +++++++++++++ .../wheap/payment/wi_wheap_heating_cost.yaml | 211 +++++++++ .../gov/states/wi/doa/wheap/wi_wheap.yaml | 331 ++++++++++++++ .../wi/doa/wheap/wi_wheap_household_size.yaml | 34 ++ .../wi_wheap_categorically_eligible.py | 44 ++ .../wheap/eligibility/wi_wheap_eligible.py | 52 +++ .../eligibility/wi_wheap_income_limit.py | 27 ++ .../wheap/income/wi_wheap_countable_income.py | 70 +++ .../wheap/payment/wi_wheap_dwelling_type.py | 22 + .../wheap/payment/wi_wheap_heating_cost.py | 85 ++++ .../gov/states/wi/doa/wheap/wi_wheap.py | 49 +++ .../wi/doa/wheap/wi_wheap_household_size.py | 26 ++ 48 files changed, 2497 insertions(+), 1 deletion(-) create mode 100644 changelog.d/wi-liheap-wave1d.added.md create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/eligibility/fpg_year_lag.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/eligibility/smi_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/income/adult_age.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/income/deductions.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/income/investment_minimum.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/income/retirement_distribution_age.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/earned.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/investment.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/rental.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/self_employment.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/unearned.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/benefit_ratio.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/electric_baseload_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/funding_factor.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/maximum.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/minimum.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/poverty_ratio/maximum.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/poverty_ratio/minimum.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/electricity.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/fuel_oil.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/natural_gas.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/propane.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/wood_other.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/cap_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/floor_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/max_rooms.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/electricity.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/fuel_oil.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/natural_gas.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/propane.yaml create mode 100644 policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/wood_other.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_categorically_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_income_limit.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/income/wi_wheap_countable_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/wi_wheap.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/wi_wheap_household_size.yaml create mode 100644 policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_categorically_eligible.py create mode 100644 policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_eligible.py create mode 100644 policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_income_limit.py create mode 100644 policyengine_us/variables/gov/states/wi/doa/wheap/income/wi_wheap_countable_income.py create mode 100644 policyengine_us/variables/gov/states/wi/doa/wheap/payment/wi_wheap_dwelling_type.py create mode 100644 policyengine_us/variables/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.py create mode 100644 policyengine_us/variables/gov/states/wi/doa/wheap/wi_wheap.py create mode 100644 policyengine_us/variables/gov/states/wi/doa/wheap/wi_wheap_household_size.py diff --git a/changelog.d/wi-liheap-wave1d.added.md b/changelog.d/wi-liheap-wave1d.added.md new file mode 100644 index 00000000000..d39b7180444 --- /dev/null +++ b/changelog.d/wi-liheap-wave1d.added.md @@ -0,0 +1 @@ +Add Wisconsin Home Energy Assistance Program regular heating benefit. diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/eligibility/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/eligibility/fpg_year_lag.yaml new file mode 100644 index 00000000000..617902a7616 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/eligibility/fpg_year_lag.yaml @@ -0,0 +1,13 @@ +description: Wisconsin uses federal poverty guidelines from this many years before the modeled benefit year under the Home Energy Assistance Program. +# Model year 2026 is federal fiscal year 2026, which uses the 2025 guidelines +# ($15,650 plus $5,500 per added person). The federal 110% income floor uses +# the same guideline year. +values: + 2025-10-01: 1 +metadata: + unit: year + period: year + label: Wisconsin WHEAP federal poverty guideline year lag + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, Computation!C6:C8 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/eligibility/smi_rate.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/eligibility/smi_rate.yaml new file mode 100644 index 00000000000..7454e3aa5a8 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/eligibility/smi_rate.yaml @@ -0,0 +1,14 @@ +description: Wisconsin limits income to this share of the state median income under the Home Energy Assistance Program. +values: + 2025-10-01: 0.6 +metadata: + unit: /1 + period: year + label: Wisconsin WHEAP state median income rate + reference: + - title: Wis. Stat. 16.27(5)(b) + href: https://docs.legis.wisconsin.gov/statutes/statutes/16/i/27 + - title: "Wisconsin PY26 WHEAP Manual, sections 2.3.1 and 3.3.1 and Appendix E #page 26,45,166" + href: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=26 + - title: Wisconsin HE+ Benefit Calculator FFY2026, SMI_Lookup!A4:D23 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/income/adult_age.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/adult_age.yaml new file mode 100644 index 00000000000..bbbf45aa9fc --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/adult_age.yaml @@ -0,0 +1,10 @@ +description: Wisconsin counts earned income only for household members at or above this age under the Home Energy Assistance Program. +values: + 2025-10-01: 18 +metadata: + unit: year + period: year + label: Wisconsin WHEAP adult earned income age + reference: + - title: "Wisconsin PY26 WHEAP Manual, section 3.3.2 and Tables 3.4 and 3.6 #page 46,58-59" + href: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=46 diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/income/deductions.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/deductions.yaml new file mode 100644 index 00000000000..65b22a493ea --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/deductions.yaml @@ -0,0 +1,14 @@ +description: Wisconsin deducts these expenses from countable income under the Home Energy Assistance Program. +# Only court-ordered child support paid is deductible; the input does not +# distinguish voluntary payments. The Medicare premium deduction applies only +# to Social Security and Railroad Retirement benefits, in the income formula. +values: + 2025-10-01: + - child_support_expense +metadata: + unit: list + period: year + label: Wisconsin WHEAP income deductions + reference: + - title: "Wisconsin PY26 WHEAP Manual, sections 3.3.2 item 5 and 3.3.3 and Table 3.5 #page 48-50,58" + href: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=48 diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/income/investment_minimum.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/investment_minimum.yaml new file mode 100644 index 00000000000..c20a6d0f69a --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/investment_minimum.yaml @@ -0,0 +1,10 @@ +description: Wisconsin counts a household member's combined interest and dividends only when they reach this annual amount under the Home Energy Assistance Program. +values: + 2025-10-01: 120 +metadata: + unit: currency-USD + period: year + label: Wisconsin WHEAP interest and dividend minimum + reference: + - title: "Wisconsin PY26 WHEAP Manual, section 3.3.2 item 3 and Tables 3.4 and 3.6 #page 47,55,59" + href: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=47 diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/income/retirement_distribution_age.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/retirement_distribution_age.yaml new file mode 100644 index 00000000000..c6b30d280f6 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/retirement_distribution_age.yaml @@ -0,0 +1,10 @@ +description: Wisconsin counts all retirement account withdrawals as income for account owners at or above this age under the Home Energy Assistance Program. +values: + 2025-10-01: 73 +metadata: + unit: year + period: year + label: Wisconsin WHEAP retirement withdrawal age + reference: + - title: "Wisconsin PY26 WHEAP Manual, section 3.3.2 item 7(g) and Table 3.6 #page 49,59" + href: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=49 diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/earned.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/earned.yaml new file mode 100644 index 00000000000..0d26bfbd114 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/earned.yaml @@ -0,0 +1,11 @@ +description: Wisconsin counts these sources as earned income for adults who are not high school students under the Home Energy Assistance Program. +values: + 2025-10-01: + - employment_income +metadata: + unit: list + period: year + label: Wisconsin WHEAP earned income sources + reference: + - title: "Wisconsin PY26 WHEAP Manual, section 3.3.2 and Tables 3.4 and 3.6 #page 46,58-60" + href: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=46 diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/investment.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/investment.yaml new file mode 100644 index 00000000000..b79193e50b6 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/investment.yaml @@ -0,0 +1,12 @@ +description: Wisconsin combines these interest and dividend sources for each household member under the Home Energy Assistance Program. +values: + 2025-10-01: + - interest_income + - ordinary_dividend_income +metadata: + unit: list + period: year + label: Wisconsin WHEAP interest and dividend sources + reference: + - title: "Wisconsin PY26 WHEAP Manual, section 3.3.2 item 3 and Table 3.4 #page 47,55" + href: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=47 diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/rental.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/rental.yaml new file mode 100644 index 00000000000..09396d01c35 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/rental.yaml @@ -0,0 +1,14 @@ +description: Wisconsin includes these rental and royalty sources in the self-employment offset under the Home Energy Assistance Program. +# The $120 land rent threshold and rent paid by other household members are +# not modeled. +values: + 2025-10-01: + - rental_income + - farm_rent_income +metadata: + unit: list + period: year + label: Wisconsin WHEAP rental income sources + reference: + - title: "Wisconsin PY26 WHEAP Manual, section 3.3.5 and Table 3.4 #page 50-51,56" + href: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=50 diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/self_employment.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/self_employment.yaml new file mode 100644 index 00000000000..57035461db3 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/self_employment.yaml @@ -0,0 +1,16 @@ +description: Wisconsin nets these self-employment sources across sources and household members under the Home Energy Assistance Program. +# Net business inputs are used without a further expense deduction. Income +# reported on a Self-Generated Income Report Form is not separable. +values: + 2025-10-01: + - self_employment_income + - sstb_self_employment_income + - farm_operations_income + - partnership_s_corp_income +metadata: + unit: list + period: year + label: Wisconsin WHEAP self-employment income sources + reference: + - title: "Wisconsin PY26 WHEAP Manual, section 3.3.5 and Table 3.4 #page 50-51,56" + href: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=50 diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/unearned.yaml new file mode 100644 index 00000000000..67a68361948 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/unearned.yaml @@ -0,0 +1,26 @@ +description: Wisconsin counts these sources as unearned income under the Home Energy Assistance Program. +# Gift income beyond financial_assistance, tribal per capita payments and land +# contract interest have no input. +values: + 2025-10-01: + - ssi + - pension_income + - military_retirement_income + - veterans_benefits + - workers_compensation + - disability_benefits + - alimony_income + - child_support_received + - strike_benefits + - general_assistance + - gambling_winnings + - estate_income + - financial_assistance + - survivor_benefits +metadata: + unit: list + period: year + label: Wisconsin WHEAP unearned income sources + reference: + - title: "Wisconsin PY26 WHEAP Manual, sections 3.3.2-3.3.3 and Table 3.4 #page 48-49,55-58" + href: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=48 diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/benefit_ratio.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/benefit_ratio.yaml new file mode 100644 index 00000000000..d466fe3cf58 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/benefit_ratio.yaml @@ -0,0 +1,10 @@ +description: Wisconsin subtracts the household poverty ratio from this constant in the regular heating benefit formula under the Home Energy Assistance Program. +values: + 2025-10-01: 2.66667 +metadata: + unit: /1 + period: year + label: Wisconsin WHEAP benefit ratio constant + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, Computation!C15 and C39 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/electric_baseload_rate.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/electric_baseload_rate.yaml new file mode 100644 index 00000000000..3d73b976c33 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/electric_baseload_rate.yaml @@ -0,0 +1,12 @@ +description: Wisconsin removes this share of the reported electric bill as non-heating use when electricity is the heating fuel under the Home Energy Assistance Program. +values: + 2025-10-01: 0.6 +metadata: + unit: /1 + period: year + label: Wisconsin WHEAP electric baseload rate + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, Benefits!D19 and Computation!C19, C22 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx + - title: Wisconsin PY26 WHEAP Manual, section 2.3.1 + href: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=26 diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/funding_factor.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/funding_factor.yaml new file mode 100644 index 00000000000..38adfce919b --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/funding_factor.yaml @@ -0,0 +1,10 @@ +description: Wisconsin divides the regular heating benefit formula by this funding factor under the Home Energy Assistance Program. +values: + 2025-10-01: 2.94269 +metadata: + unit: /1 + period: year + label: Wisconsin WHEAP funding factor + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, Computation!C16 and C39 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/maximum.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/maximum.yaml new file mode 100644 index 00000000000..44febcbfc5d --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/maximum.yaml @@ -0,0 +1,12 @@ +description: Wisconsin limits the regular heating benefit to this amount under the Home Energy Assistance Program. +# The FFY2026 proxy table and formula constants yield at most $2,574, so the +# cap does not bind. +values: + 2025-10-01: 2_580 +metadata: + unit: currency-USD + period: year + label: Wisconsin WHEAP maximum benefit + reference: + - title: Wisconsin LIHEAP State Plan FFY2026, agency-posted edition, section 2.6 + href: https://web.archive.org/web/20260316195114/https://energyandhousing.wi.gov/Documents/WHEAP/LiheapPlan_FFY2026.pdf#page=7 diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/minimum.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/minimum.yaml new file mode 100644 index 00000000000..246816b7921 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/minimum.yaml @@ -0,0 +1,14 @@ +description: Wisconsin pays no regular heating benefit below this amount under the Home Energy Assistance Program. +values: + 2025-10-01: 30 +metadata: + unit: currency-USD + period: year + label: Wisconsin WHEAP minimum benefit + reference: + - title: Wisconsin PY26 WHEAP Manual, section 2.3.1 + href: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=26 + - title: Wisconsin HE+ Benefit Calculator FFY2026, Computation!C18 and C42 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx + - title: Wisconsin LIHEAP State Plan FFY2026, section 2.6 + href: https://liheapch.acf.gov/docs/2026/state-plans/WI_Plan_2026.pdf#page=12 diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/poverty_ratio/maximum.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/poverty_ratio/maximum.yaml new file mode 100644 index 00000000000..9707a504a55 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/poverty_ratio/maximum.yaml @@ -0,0 +1,10 @@ +description: Wisconsin caps the household poverty ratio at this value in the regular heating benefit formula under the Home Energy Assistance Program. +values: + 2025-10-01: 2.15 +metadata: + unit: /1 + period: year + label: Wisconsin WHEAP maximum poverty ratio + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, Computation!C10 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/poverty_ratio/minimum.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/poverty_ratio/minimum.yaml new file mode 100644 index 00000000000..6abf3d90100 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/poverty_ratio/minimum.yaml @@ -0,0 +1,10 @@ +description: Wisconsin floors the household poverty ratio at this value in the regular heating benefit formula under the Home Energy Assistance Program. +values: + 2025-10-01: 0.6 +metadata: + unit: /1 + period: year + label: Wisconsin WHEAP minimum poverty ratio + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, Computation!C11 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/electricity.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/electricity.yaml new file mode 100644 index 00000000000..8e6dafb191f --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/electricity.yaml @@ -0,0 +1,23 @@ +description: Wisconsin sets this base heat proxy amount for electricity heat by dwelling type under the Home Energy Assistance Program. +# The motel or camper row is the rooming house, motel and hotel row of the +# calculator. Cells the calculator flags as estimated (IsFake) are used as +# published. +SINGLE_FAMILY: + 2025-10-01: 261 +TWO_TO_FOUR_UNITS: + 2025-10-01: 207 +APARTMENT: + 2025-10-01: 88 +MOBILE_HOME: + 2025-10-01: 227 +MOTEL_OR_CAMPER: + 2025-10-01: 88 +metadata: + unit: currency-USD + period: year + label: Wisconsin WHEAP electricity base heat proxy + breakdown: + - wi_wheap_dwelling_type + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, HT_Proxy!F7, F12, F17, F22, F27 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/fuel_oil.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/fuel_oil.yaml new file mode 100644 index 00000000000..b6d66e6d9e4 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/fuel_oil.yaml @@ -0,0 +1,23 @@ +description: Wisconsin sets this base heat proxy amount for fuel oil heat by dwelling type under the Home Energy Assistance Program. +# The motel or camper row is the rooming house, motel and hotel row of the +# calculator. Cells the calculator flags as estimated (IsFake) are used as +# published. +SINGLE_FAMILY: + 2025-10-01: 1_221 +TWO_TO_FOUR_UNITS: + 2025-10-01: 1_335 +APARTMENT: + 2025-10-01: 1_335 +MOBILE_HOME: + 2025-10-01: 1_521 +MOTEL_OR_CAMPER: + 2025-10-01: 1_335 +metadata: + unit: currency-USD + period: year + label: Wisconsin WHEAP fuel oil base heat proxy + breakdown: + - wi_wheap_dwelling_type + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, HT_Proxy!F4, F9, F14, F19, F24 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/natural_gas.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/natural_gas.yaml new file mode 100644 index 00000000000..1ebdaef1c8d --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/natural_gas.yaml @@ -0,0 +1,23 @@ +description: Wisconsin sets this base heat proxy amount for natural gas heat by dwelling type under the Home Energy Assistance Program. +# The motel or camper row is the rooming house, motel and hotel row of the +# calculator. Cells the calculator flags as estimated (IsFake) are used as +# published. +SINGLE_FAMILY: + 2025-10-01: 354 +TWO_TO_FOUR_UNITS: + 2025-10-01: 297 +APARTMENT: + 2025-10-01: 116 +MOBILE_HOME: + 2025-10-01: 471 +MOTEL_OR_CAMPER: + 2025-10-01: 116 +metadata: + unit: currency-USD + period: year + label: Wisconsin WHEAP natural gas base heat proxy + breakdown: + - wi_wheap_dwelling_type + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, HT_Proxy!F5, F10, F15, F20, F25 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/propane.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/propane.yaml new file mode 100644 index 00000000000..88a078f1e1a --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/propane.yaml @@ -0,0 +1,23 @@ +description: Wisconsin sets this base heat proxy amount for propane heat by dwelling type under the Home Energy Assistance Program. +# The motel or camper row is the rooming house, motel and hotel row of the +# calculator. Cells the calculator flags as estimated (IsFake) are used as +# published. +SINGLE_FAMILY: + 2025-10-01: 736 +TWO_TO_FOUR_UNITS: + 2025-10-01: 751 +APARTMENT: + 2025-10-01: 751 +MOBILE_HOME: + 2025-10-01: 723 +MOTEL_OR_CAMPER: + 2025-10-01: 751 +metadata: + unit: currency-USD + period: year + label: Wisconsin WHEAP propane base heat proxy + breakdown: + - wi_wheap_dwelling_type + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, HT_Proxy!F6, F11, F16, F21, F26 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/wood_other.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/wood_other.yaml new file mode 100644 index 00000000000..5f151337b51 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/base/wood_other.yaml @@ -0,0 +1,23 @@ +description: Wisconsin sets this base heat proxy amount for wood or other fuels heat by dwelling type under the Home Energy Assistance Program. +# The motel or camper row is the rooming house, motel and hotel row of the +# calculator. Cells the calculator flags as estimated (IsFake) are used as +# published. +SINGLE_FAMILY: + 2025-10-01: 524 +TWO_TO_FOUR_UNITS: + 2025-10-01: 440 +APARTMENT: + 2025-10-01: 172 +MOBILE_HOME: + 2025-10-01: 697 +MOTEL_OR_CAMPER: + 2025-10-01: 172 +metadata: + unit: currency-USD + period: year + label: Wisconsin WHEAP wood or other fuels base heat proxy + breakdown: + - wi_wheap_dwelling_type + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, HT_Proxy!F8, F13, F18, F23, F28 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/cap_rate.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/cap_rate.yaml new file mode 100644 index 00000000000..51e833b8c98 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/cap_rate.yaml @@ -0,0 +1,12 @@ +description: Wisconsin caps the heating cost used for the regular heating benefit at this multiple of the heat proxy amount under the Home Energy Assistance Program. +values: + 2025-10-01: 2 +metadata: + unit: /1 + period: year + label: Wisconsin WHEAP heat proxy cap rate + reference: + - title: Wisconsin PY26 WHEAP Manual, section 2.3.1 item 3 + href: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=27 + - title: Wisconsin HE+ Benefit Calculator FFY2026, Computation!C30 and C33 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/floor_rate.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/floor_rate.yaml new file mode 100644 index 00000000000..27bba6bf070 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/floor_rate.yaml @@ -0,0 +1,14 @@ +description: Wisconsin bases the regular heating benefit on at least this share of the heat proxy amount under the Home Energy Assistance Program. +# The same share applies when heat is included in rent or no heating cost is +# reported. +values: + 2025-10-01: 0.75 +metadata: + unit: /1 + period: year + label: Wisconsin WHEAP heat proxy floor rate + reference: + - title: "Wisconsin PY26 WHEAP Manual, section 2.3.1 items 2-5 #page 26-27" + href: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=26 + - title: Wisconsin HE+ Benefit Calculator FFY2026, Computation!C31, C34 and C35 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/max_rooms.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/max_rooms.yaml new file mode 100644 index 00000000000..74638734a17 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/max_rooms.yaml @@ -0,0 +1,12 @@ +description: Wisconsin limits the room count used in the heat proxy amount to this number under the Home Energy Assistance Program. +values: + 2025-10-01: 9 +metadata: + unit: int + period: year + label: Wisconsin WHEAP heat proxy maximum rooms + reference: + - title: "Wisconsin PY26 WHEAP Manual, sections 2.3.1 item 7 and 4.4.17 #page 27,79" + href: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=27 + - title: Wisconsin HE+ Benefit Calculator FFY2026, Benefits!B27 and D12 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/electricity.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/electricity.yaml new file mode 100644 index 00000000000..727552c9c7b --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/electricity.yaml @@ -0,0 +1,23 @@ +description: Wisconsin adds this heat proxy amount per room for electricity heat by dwelling type under the Home Energy Assistance Program. +# The motel or camper row is the rooming house, motel and hotel row of the +# calculator. Cells the calculator flags as estimated (IsFake) are used as +# published. +SINGLE_FAMILY: + 2025-10-01: 108 +TWO_TO_FOUR_UNITS: + 2025-10-01: 106 +APARTMENT: + 2025-10-01: 111 +MOBILE_HOME: + 2025-10-01: 120 +MOTEL_OR_CAMPER: + 2025-10-01: 111 +metadata: + unit: currency-USD + period: year + label: Wisconsin WHEAP electricity heat proxy per room + breakdown: + - wi_wheap_dwelling_type + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, HT_Proxy!E7, E12, E17, E22, E27 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/fuel_oil.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/fuel_oil.yaml new file mode 100644 index 00000000000..befbcc813f1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/fuel_oil.yaml @@ -0,0 +1,23 @@ +description: Wisconsin adds this heat proxy amount per room for fuel oil heat by dwelling type under the Home Energy Assistance Program. +# The motel or camper row is the rooming house, motel and hotel row of the +# calculator. Cells the calculator flags as estimated (IsFake) are used as +# published. +SINGLE_FAMILY: + 2025-10-01: 68 +TWO_TO_FOUR_UNITS: + 2025-10-01: 8 +APARTMENT: + 2025-10-01: 8 +MOBILE_HOME: + 2025-10-01: -13 +MOTEL_OR_CAMPER: + 2025-10-01: 8 +metadata: + unit: currency-USD + period: year + label: Wisconsin WHEAP fuel oil heat proxy per room + breakdown: + - wi_wheap_dwelling_type + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, HT_Proxy!E4, E9, E14, E19, E24 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/natural_gas.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/natural_gas.yaml new file mode 100644 index 00000000000..131bfd8ae55 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/natural_gas.yaml @@ -0,0 +1,23 @@ +description: Wisconsin adds this heat proxy amount per room for natural gas heat by dwelling type under the Home Energy Assistance Program. +# The motel or camper row is the rooming house, motel and hotel row of the +# calculator. Cells the calculator flags as estimated (IsFake) are used as +# published. +SINGLE_FAMILY: + 2025-10-01: 60 +TWO_TO_FOUR_UNITS: + 2025-10-01: 65 +APARTMENT: + 2025-10-01: 68 +MOBILE_HOME: + 2025-10-01: 28 +MOTEL_OR_CAMPER: + 2025-10-01: 68 +metadata: + unit: currency-USD + period: year + label: Wisconsin WHEAP natural gas heat proxy per room + breakdown: + - wi_wheap_dwelling_type + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, HT_Proxy!E5, E10, E15, E20, E25 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/propane.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/propane.yaml new file mode 100644 index 00000000000..b7e55bd0b81 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/propane.yaml @@ -0,0 +1,23 @@ +description: Wisconsin adds this heat proxy amount per room for propane heat by dwelling type under the Home Energy Assistance Program. +# The motel or camper row is the rooming house, motel and hotel row of the +# calculator. Cells the calculator flags as estimated (IsFake) are used as +# published. +SINGLE_FAMILY: + 2025-10-01: 82 +TWO_TO_FOUR_UNITS: + 2025-10-01: 51 +APARTMENT: + 2025-10-01: 51 +MOBILE_HOME: + 2025-10-01: 69 +MOTEL_OR_CAMPER: + 2025-10-01: 51 +metadata: + unit: currency-USD + period: year + label: Wisconsin WHEAP propane heat proxy per room + breakdown: + - wi_wheap_dwelling_type + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, HT_Proxy!E6, E11, E16, E21, E26 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/wood_other.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/wood_other.yaml new file mode 100644 index 00000000000..4f3c7e5ec57 --- /dev/null +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/proxy/per_room/wood_other.yaml @@ -0,0 +1,23 @@ +description: Wisconsin adds this heat proxy amount per room for wood or other fuels heat by dwelling type under the Home Energy Assistance Program. +# The motel or camper row is the rooming house, motel and hotel row of the +# calculator. Cells the calculator flags as estimated (IsFake) are used as +# published. +SINGLE_FAMILY: + 2025-10-01: 89 +TWO_TO_FOUR_UNITS: + 2025-10-01: 96 +APARTMENT: + 2025-10-01: 101 +MOBILE_HOME: + 2025-10-01: 41 +MOTEL_OR_CAMPER: + 2025-10-01: 101 +metadata: + unit: currency-USD + period: year + label: Wisconsin WHEAP wood or other fuels heat proxy per room + breakdown: + - wi_wheap_dwelling_type + reference: + - title: Wisconsin HE+ Benefit Calculator FFY2026, HT_Proxy!E8, E13, E18, E23, E28 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 43fdd714b80..dcd5775cb31 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -808,7 +808,7 @@ programs: category: Benefits agency: HHS status: partial - coverage: AL, DC, Riverside County, MA, FL, IL, IN, KS, KY, MD, MS, NC, NE, NJ, NY, OH, OK, OR, SD, RI, TX + coverage: AL, DC, Riverside County, MA, FL, IL, IN, KS, KY, MD, MS, NC, NE, NJ, NY, OH, OK, OR, SD, RI, TX, WI state_implementations: - state: AL status: partial @@ -960,6 +960,14 @@ programs: variable: tx_ceap parameter_prefix: gov.states.tx.tdhca.ceap notes: Models the 150% FPG income limit, the alternative 60% SMI limit from the FY2024 state plan (applied through 2024), benefit amounts, and categorical eligibility through TANF enrollment, SNAP eligibility, and SSI receipt. Not modeled - net rather than gross gambling winnings, the Medicare premium deduction from counted Social Security, and the exclusion of means-tested veterans' pensions from counted income (10 TAC 6.4(c)) + - state: WI + status: partial + name: Wisconsin WHEAP + full_name: Wisconsin Home Energy Assistance Program + variable: wi_wheap + parameter_prefix: gov.states.wi.doa.wheap + verified_years: "2026" + notes: FFY2026 regular heating benefit from the HE+ formula with the published heat proxy table. The non-heating electric benefit, crisis and weatherization are out of scope. # --- SSA programs --- - id: ssi diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_categorically_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_categorically_eligible.yaml new file mode 100644 index 00000000000..d6365f20c76 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_categorically_eligible.yaml @@ -0,0 +1,188 @@ +# Wisconsin WHEAP categorical eligibility: a household in which all members +# receive W-2 cash (TANF), FoodShare (SNAP) or SSI passes the income test +# (Wis. Stat. 16.27(5)(c); PY2026 WHEAP Manual 3.3.1). Annual receipt stands +# in for receipt during the entire prior month. + +- name: Case 1, FoodShare household is categorically eligible. + period: 2026 + input: + people: + person1: + age: 31 + employment_income: 22_000 + person2: + age: 7 + person3: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + snap: 3_600 + tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: WI + output: + # FoodShare 300 a month covers the whole household. + wi_wheap_categorically_eligible: true + +- name: Case 2, household in which every member receives SSI is categorically eligible. + period: 2026 + input: + people: + person1: + age: 72 + ssi: 8_946 + person2: + age: 70 + ssi: 8_946 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + snap: 0 + tanf: 0 + households: + household: + members: [person1, person2] + state_code: WI + output: + # 2026 SSI couple rate 1,491 a month, 745.50 each (8,946 a year each). + wi_wheap_categorically_eligible: true + +- name: Case 3, household with one member who does not receive SSI is not categorically eligible. + period: 2026 + input: + people: + person1: + age: 67 + ssi: 11_928 + person2: + age: 45 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + snap: 0 + tanf: 0 + households: + household: + members: [person1, person2] + state_code: WI + output: + # Manual 3.3.1: every member must receive a qualifying benefit; the + # working adult receives none. + wi_wheap_categorically_eligible: false + +- name: Case 4, W-2 cash household is categorically eligible. + period: 2026 + input: + people: + person1: + age: 26 + person2: + age: 2 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + tanf: 7_296 + snap: 0 + households: + household: + members: [person1, person2] + state_code: WI + output: + # W-2 Transitions grant 608 a month x 12 = 7,296. + wi_wheap_categorically_eligible: true + +- name: Case 5, FoodShare household with an undocumented member is not categorically eligible. + period: 2026 + input: + people: + person1: + age: 30 + immigration_status: UNDOCUMENTED + employment_income: 18_000 + person2: + age: 6 + immigration_status: CITIZEN + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + snap: 4_800 + tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: WI + output: + # Manual 3.3.1 item 2(d): if some but not all members (including + # ineligible non-citizens) receive a qualifying benefit, the household is + # not categorical. FoodShare covers only the two citizen children. + wi_wheap_categorically_eligible: false + +- name: Case 6, household in which every member reports SSI receipt is categorically eligible. + period: 2026 + input: + people: + person1: + age: 72 + receives_ssi: true + ssi: 0 + person2: + age: 70 + receives_ssi: true + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + snap: 0 + tanf: 0 + households: + household: + members: [person1, person2] + state_code: WI + output: + # Receiving SSI = computed SSI above 0 or reported receipt (receives_ssi). + wi_wheap_categorically_eligible: true + +- name: Case 7, FoodShare household with a refugee member outside the FoodShare group is not categorically eligible. + period: 2026 + input: + people: + person1: + age: 36 + immigration_status: REFUGEE + employment_income: 40_000 + person2: + age: 34 + immigration_status: CITIZEN + person3: + age: 6 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + snap: 900 + tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: WI + output: + # From July 2025, P.L. 119-21 limits SNAP to citizens, lawful permanent + # residents and Cuban/Haitian entrants (7 U.S.C. 2015(f)), so the refugee + # is excluded from the FoodShare group; FoodShare (75 a month) covers the + # citizen spouse and child only. The refugee receives no SSI or W-2, so not + # every member is a recipient (Manual 3.3.1 item 2(d)). + wi_wheap_categorically_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_eligible.yaml new file mode 100644 index 00000000000..3d679a839c1 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_eligible.yaml @@ -0,0 +1,410 @@ +# Wisconsin WHEAP regular heating eligibility: at least one citizen or +# eligible noncitizen; income at or below 60 percent of state median income, +# below 110 percent of the poverty guideline (42 U.S.C. 8624(b)(2)(B)), or +# categorical eligibility (Wis. Stat. 16.27(5)(b), (5)(c)); a reported heating +# fuel; and an energy burden: a heating bill paid by the household, or heat +# included in rent without government rental assistance (PY2026 WHEAP Manual +# 3.2.5 and Table 3.2). + +- name: Case 1, income exactly at the limit is eligible. + period: 2026 + input: + people: + person1: + age: 41 + employment_income: 50_243 + person2: + age: 39 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 900 + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2] + state_code: WI + output: + # Size 2 limit 50,243 (Appendix E); 50,243 <= 50,243. + wi_wheap_eligible: true + +- name: Case 2, income one dollar over the limit is ineligible. + period: 2026 + input: + people: + person1: + age: 41 + employment_income: 50_244 + person2: + age: 39 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 900 + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2] + state_code: WI + output: + # 50,244 > 50,243, above 110% of the 2025 guideline (23,265), not + # categorical. + wi_wheap_eligible: false + +- name: Case 3, FoodShare household above the limit is eligible through categorical eligibility. + period: 2026 + input: + people: + person1: + age: 44 + employment_income: 64_000 + person2: + age: 42 + employment_income: 40_000 + person3: + age: 15 + person4: + age: 13 + person5: + age: 11 + person6: + age: 9 + person7: + age: 6 + person8: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + heating_type: NATURAL_GAS + heating_expense: 1_150 + snap: 1_200 + tanf: 0 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + state_code: WI + output: + # 104,000 > size 8 limit 101,965, but FoodShare receipt (supplied as an + # input) passes the income test under Wis. Stat. 16.27(5)(c). + wi_wheap_eligible: true + +- name: Case 4, twenty-person household above the limit with income one dollar below 110 percent of the poverty guideline is eligible. + period: 2026 + input: + people: + person1: + age: 48 + employment_income: 42_164 + person2: + age: 46 + employment_income: 35_000 + person3: + age: 26 + employment_income: 30_000 + person4: + age: 24 + employment_income: 25_000 + person5: {age: 44} + person6: {age: 22} + person7: {age: 19} + person8: {age: 17} + person9: {age: 16} + person10: {age: 15} + person11: {age: 14} + person12: {age: 13} + person13: {age: 12} + person14: {age: 11} + person15: {age: 10} + person16: {age: 9} + person17: {age: 7} + person18: {age: 5} + person19: {age: 3} + person20: {age: 1} + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13, person14, person15, person16, person17, person18, person19, person20] + heating_type: NATURAL_GAS + heating_expense: 2_000 + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13, person14, person15, person16, person17, person18, person19, person20] + state_code: WI + output: + # Size 20 limit 128,565 < income 42,164 + 35,000 + 30,000 + 25,000 = + # 132,164. 2025 guideline for 20: 15,650 + 19 x 5,500 = 120,150; + # x 1.1 = 132,165 > 132,164, so the federal floor bars exclusion. + wi_wheap_income_limit: 128_565 + wi_wheap_eligible: true + +- name: Case 5, household without a citizen or qualified noncitizen is ineligible. + period: 2026 + input: + people: + person1: + age: 33 + immigration_status: UNDOCUMENTED + employment_income: 30_000 + person2: + age: 31 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 800 + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2] + state_code: WI + output: + # Manual 3.2.1: the case head must be a citizen or eligible noncitizen; + # no member qualifies, so the eligible-member size is 0. + wi_wheap_eligible: false + +- name: Case 6, mixed-status household with a citizen member is eligible. + period: 2026 + input: + people: + person1: + age: 41 + immigration_status: UNDOCUMENTED + employment_income: 30_000 + person2: + age: 39 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 800 + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2] + state_code: WI + output: + # Eligible-member size 1, limit 38,421; all income counts: 30,000 <= 38,421. + wi_wheap_eligible: true + +- name: Case 7, heat included in rent with rental assistance is ineligible. + period: 2026 + input: + people: + person1: + age: 58 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + tanf: 0 + snap: 0 + households: + household: + members: [person1] + state_code: WI + output: + # Manual 3.2.5.1 and Table 3.2: subsidized housing with heat in rent has no + # energy burden. + wi_wheap_eligible: false + +- name: Case 8, heat included in rent in public housing is ineligible. + period: 2026 + input: + people: + person1: + age: 58 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 0 + heat_expense_included_in_rent: true + tanf: 0 + snap: 0 + households: + household: + members: [person1] + state_code: WI + is_in_public_housing: true + output: + # Public housing is government-assisted housing: no energy burden. + wi_wheap_eligible: false + +- name: Case 9, heat included in rent without rental assistance is eligible. + period: 2026 + input: + people: + person1: + age: 58 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 0 + heat_expense_included_in_rent: true + tanf: 0 + snap: 0 + households: + household: + members: [person1] + state_code: WI + output: + # Table 3.2: heat in rent without rental assistance is an energy burden; + # 24,000 <= size 1 limit 38,421. + wi_wheap_eligible: true + +- name: Case 10, rental assistance tenant who pays the heating bill directly is eligible. + period: 2026 + input: + people: + person1: + age: 58 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 700 + receives_housing_assistance: true + tanf: 0 + snap: 0 + households: + household: + members: [person1] + state_code: WI + output: + # Manual 4.4.19: a subsidized tenant paying the vendor has an energy + # burden; only heat in rent is excluded. + wi_wheap_eligible: true + +- name: Case 11, unspecified heating fuel is ineligible. + period: 2026 + input: + people: + person1: + age: 58 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: UNSPECIFIED + heating_expense: 900 + tanf: 0 + snap: 0 + households: + household: + members: [person1] + state_code: WI + output: + # Expense supplied so only the fuel gate decides: UNSPECIFIED pays nothing. + wi_wheap_eligible: false + +- name: Case 12, a home with no heating is ineligible. + period: 2026 + input: + people: + person1: + age: 58 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NONE + heating_expense: 600 + tanf: 0 + snap: 0 + households: + household: + members: [person1] + state_code: WI + output: + # Expense supplied so only the fuel gate decides: NONE has no heating fuel. + wi_wheap_eligible: false + +- name: Case 13, household paying its own heating with no heating cost is ineligible. + period: 2026 + input: + people: + person1: + age: 58 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 0 + tanf: 0 + snap: 0 + households: + household: + members: [person1] + state_code: WI + output: + # Manual 3.2.5: no heating payment and heat not in rent, so no energy + # burden. + wi_wheap_eligible: false + +- name: Case 14, twenty-person household above the limit with income exactly at 110 percent of the poverty guideline is ineligible. + period: 2026 + input: + people: + person1: + age: 48 + employment_income: 42_165 + person2: + age: 46 + employment_income: 35_000 + person3: + age: 26 + employment_income: 30_000 + person4: + age: 24 + employment_income: 25_000 + person5: {age: 44} + person6: {age: 22} + person7: {age: 19} + person8: {age: 17} + person9: {age: 16} + person10: {age: 15} + person11: {age: 14} + person12: {age: 13} + person13: {age: 12} + person14: {age: 11} + person15: {age: 10} + person16: {age: 9} + person17: {age: 7} + person18: {age: 5} + person19: {age: 3} + person20: {age: 1} + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13, person14, person15, person16, person17, person18, person19, person20] + heating_type: NATURAL_GAS + heating_expense: 2_000 + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13, person14, person15, person16, person17, person18, person19, person20] + state_code: WI + output: + # Income 132,165 is not less than the 132,165 floor (42 U.S.C. + # 8624(b)(2)(B): "less than 110 percent"), exceeds the size 20 limit + # 128,565, and the household is not categorical. + wi_wheap_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_income_limit.yaml new file mode 100644 index 00000000000..1cec28b9e24 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_income_limit.yaml @@ -0,0 +1,151 @@ +# Wisconsin WHEAP annual income limit: 60 percent of state median income by +# household size (Wis. Stat. 16.27(5)(b)). PY2026 annual limits from WHEAP +# Manual Appendix E (sizes 1-8) and the FFY2026 HE+ Benefit Calculator +# SMI_Lookup sheet (sizes 9-20). Household size counts eligible members only. + +- name: Case 1, one-person household. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 35 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + households: + household: + members: [person1] + state_code: WI + output: + # Appendix E, size 1 annual income: 38,421. + wi_wheap_income_limit: 38_421 + +- name: Case 2, four-person household. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 38 + person2: + age: 36 + person3: + age: 10 + person4: + age: 6 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + households: + household: + members: [person1, person2, person3, person4] + state_code: WI + output: + # Appendix E, size 4: 73,888 (floor of 0.6 x 123,148 four-person SMI). + wi_wheap_income_limit: 73_888 + +- name: Case 3, eight-person household. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 42 + person2: + age: 40 + person3: + age: 17 + person4: + age: 15 + person5: + age: 12 + person6: + age: 10 + person7: + age: 7 + person8: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + heating_type: NATURAL_GAS + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + state_code: WI + output: + # Appendix E, size 8: 101,965. + wi_wheap_income_limit: 101_965 + +- name: Case 4, twenty-person household uses the last row of the calculator table. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + wi_wheap_household_size: 20 + households: + household: + members: [person1] + state_code: WI + output: + # Size supplied directly. SMI_Lookup row 20 annual income: 128,565. + wi_wheap_income_limit: 128_565 + +- name: Case 5, an undocumented member does not raise the household size for the limit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 34 + immigration_status: UNDOCUMENTED + person2: + age: 32 + immigration_status: CITIZEN + person3: + age: 5 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + households: + household: + members: [person1, person2, person3] + state_code: WI + output: + # Manual 4.4.26.2: two eligible members, so the size 2 limit 50,243 + # applies instead of the size 3 limit 62,065. + wi_wheap_income_limit: 50_243 + +- name: Case 6, household with no eligible member has no limit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + immigration_status: UNDOCUMENTED + person2: + age: 29 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + households: + household: + members: [person1, person2] + state_code: WI + output: + # Size 0; the table starts at size 1. + wi_wheap_income_limit: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/income/wi_wheap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/income/wi_wheap_countable_income.yaml new file mode 100644 index 00000000000..d31779af5e7 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/income/wi_wheap_countable_income.yaml @@ -0,0 +1,291 @@ +# Wisconsin WHEAP countable gross income, PY2026 WHEAP Manual 3.3 and +# Tables 3.4-3.6: wages of members 18 or older and not in high school, +# self-employment and rental income netted across the household and floored at +# zero, unearned income, Social Security and Railroad Retirement net of Medicare +# premiums, interest and dividends of $120 or more per member, retirement +# withdrawals at 73 or older, W-2 cash grants, less court-ordered child support +# paid. Unemployment compensation is ignored. + +- name: Case 1, earnings of a 16-year-old and an 18-year-old high school student are excluded. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + employment_income: 42_000 + person2: + age: 16 + employment_income: 3_000 + self_employment_income: 1_200 + person3: + age: 18 + is_in_k12_school: true + employment_income: 2_500 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2, person3] + state_code: WI + output: + # Manual 3.3.2 and Table 3.6: earnings of members under 18 and of high + # school students are ignored, including the 16-year-old's 1,200 of + # lawn-mowing self-employment income. Only the parent's 42,000 counts. + wi_wheap_countable_income: 42_000 + +- name: Case 2, wages of an 18-year-old who has left high school count. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + employment_income: 42_000 + person2: + age: 18 + is_in_k12_school: false + employment_income: 6_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2] + state_code: WI + output: + # Age 18 is not under 18, so the exclusion does not apply: + # 42,000 + 6,000 = 48,000. + wi_wheap_countable_income: 48_000 + +- name: Case 3, one member's business loss offsets another member's business and rental profit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 47 + self_employment_income: 18_000 + rental_income: 2_400 + person2: + age: 45 + sstb_self_employment_income: -5_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2] + state_code: WI + output: + # Manual 3.3.5: losses of one self-employment source offset profits of + # another, across household members, with rental income in the offset: + # 18,000 + 2,400 - 5,000 = 15,400. + wi_wheap_countable_income: 15_400 + +- name: Case 4, a net household business loss counts as zero and does not reduce wages. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 39 + employment_income: 30_000 + rental_income: 1_200 + person2: + age: 37 + self_employment_income: -8_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2] + state_code: WI + output: + # Business group: 1,200 - 8,000 = -6,800, counted as 0; Manual 3.3.5 bars + # offsetting wages with a self-employment loss. Wages 30,000. + wi_wheap_countable_income: 30_000 + +- name: Case 5, Social Security and Railroad Retirement count net of the Medicare Part B premium. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + social_security_retirement: 21_600 + medicare_part_b_premium: 2_434.80 + ssi: 0 + person2: + age: 68 + railroad_benefits: 14_400 + medicare_part_b_premium: 2_434.80 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2] + state_code: WI + output: + # Table 3.5: Medicare premiums are deducted from the benefit. 2026 Part B + # standard premium 202.90 x 12 = 2,434.80 each. + # (21,600 - 2,434.80) + (14,400 - 2,434.80) = 19,165.20 + 11,965.20. + wi_wheap_countable_income: 31_130.40 + +- name: Case 6, interest and dividends count at 120 dollars per member but not at 119 dollars. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 52 + employment_income: 35_000 + taxable_interest_income: 80 + qualified_dividend_income: 40 + person2: + age: 50 + taxable_interest_income: 119 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2] + state_code: WI + output: + # Manual 3.3.2 and Table 3.4: a member's combined interest and dividends + # count when at least 120 a year. person1 80 + 40 = 120 counts; + # person2 119 is ignored. 35,000 + 120 = 35,120. + wi_wheap_countable_income: 35_120 + +- name: Case 7, retirement account withdrawals count at age 73 but not at age 72. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 73 + taxable_ira_distributions: 9_000 + ssi: 0 + person2: + age: 72 + taxable_ira_distributions: 6_000 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2] + state_code: WI + output: + # Manual 3.3.2 item g: all withdrawals count for an owner 73 or older; + # under 73 non-regular withdrawals are asset drawdowns (model counts 73+ + # only). Only person1's 9,000 counts. + wi_wheap_countable_income: 9_000 + +- name: Case 8, child support received counts and court-ordered child support paid is deducted. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 38 + employment_income: 36_000 + child_support_received: 3_600 + person2: + age: 40 + employment_income: 28_000 + child_support_expense: 4_200 + person3: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2, person3] + state_code: WI + output: + # Manual 3.3.3 and Table 3.5: child support received counts; court-ordered + # child support paid is deducted. + # 36,000 + 28,000 + 3,600 - 4,200 = 63,400. + wi_wheap_countable_income: 63_400 + +- name: Case 9, unemployment compensation is ignored while gambling winnings and W-2 cash count. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 29 + unemployment_compensation: 4_200 + gambling_winnings: 650 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + tanf: 3_648 + snap: 0 + households: + household: + members: [person1, person2] + state_code: WI + output: + # Table 3.6 ignores unemployment insurance; Table 3.4 counts gambling + # winnings and W-2 cash grants (six months x 608 = 3,648). + # 650 + 3,648 = 4,298. + wi_wheap_countable_income: 4_298 + +- name: Case 10, child support paid above income leaves countable income at zero. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 44 + employment_income: 4_000 + child_support_expense: 6_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + tanf: 0 + snap: 0 + households: + household: + members: [person1] + state_code: WI + output: + # 4,000 - 6,000 = -2,000, floored at 0. + wi_wheap_countable_income: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.yaml new file mode 100644 index 00000000000..acb291e62f4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.yaml @@ -0,0 +1,211 @@ +# Wisconsin WHEAP benefit heating cost (FFY2026 HE+ Benefit Calculator, +# Computation C19-C35 and HT_Proxy A4:H28; PY2026 WHEAP Manual 2.3.1 and +# 4.4.13-4.4.28). Heat proxy = base + per-room amount x rooms by fuel class and +# dwelling type, rooms = bedrooms held between 1 and 9 (1 for a motel or +# camper). The reported cost, less a 60 percent baseload for electric heat, is +# held between 0.75 and 2 times the proxy; heat included in rent uses 0.75 +# times the proxy. + +- name: Case 1, natural gas cost inside the bounds is used as reported. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 950 + households: + household: + members: [person1] + state_code: WI + bedrooms: 3 + output: + # Dwelling type not supplied: SINGLE_FAMILY default. Proxy 354 + 60 x 3 = + # 534; bounds 400.50 to 1,068; 950 is inside. + wi_wheap_heating_cost: 950 + +- name: Case 2, cost below 0.75 times the proxy is raised to that floor. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 300 + wi_wheap_dwelling_type: TWO_TO_FOUR_UNITS + households: + household: + members: [person1] + state_code: WI + bedrooms: 2 + output: + # 2 to 4 units, natural gas: 297 + 65 x 2 = 427; 0.75 x 427 = 320.25 > 300. + wi_wheap_heating_cost: 320.25 + +- name: Case 3, cost above twice the proxy is capped. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + heating_expense: 2_400 + wi_wheap_dwelling_type: MOBILE_HOME + households: + household: + members: [person1] + state_code: WI + bedrooms: 2 + output: + # Mobile home, propane: 723 + 69 x 2 = 861; 2 x 861 = 1,722 < 2,400. + wi_wheap_heating_cost: 1_722 + +- name: Case 4, heat included in rent uses 0.75 times the proxy and a studio counts one room. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 0 + heat_expense_included_in_rent: true + wi_wheap_dwelling_type: APARTMENT + households: + household: + members: [person1] + state_code: WI + bedrooms: 0 + output: + # Manual 4.4.17 and calculator Benefits!C12: at least 1 room. + # Apartment, natural gas: 116 + 68 x 1 = 184; 0.75 x 184 = 138. + wi_wheap_heating_cost: 138 + +- name: Case 5, electric heat removes a 60 percent baseload from the whole electric bill. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + heating_expense: 1_200 + wi_wheap_dwelling_type: APARTMENT + households: + household: + members: [person1] + state_code: WI + bedrooms: 2 + output: + # Eligible cost 1,200 - 0.6 x 1,200 = 480. Apartment, electric: + # 88 + 111 x 2 = 310; bounds 232.50 to 620; 480 is inside. + wi_wheap_heating_cost: 480 + +- name: Case 6, a motel or camper counts one room regardless of bedrooms. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + heating_expense: 1_700 + wi_wheap_dwelling_type: MOTEL_OR_CAMPER + households: + household: + members: [person1] + state_code: WI + bedrooms: 2 + output: + # Manual 4.4.17: one room. Propane: 751 + 51 x 1 = 802; cap 1,604 < 1,700. + # Two rooms would give a cap of 1,706. + wi_wheap_heating_cost: 1_604 + +- name: Case 7, rooms above nine are capped at nine. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + spm_units: + spm_unit: + members: [person1] + heating_type: FUEL_OIL + heating_expense: 4_000 + wi_wheap_dwelling_type: SINGLE_FAMILY + households: + household: + members: [person1] + state_code: WI + bedrooms: 10 + output: + # Single family, fuel oil: 1,221 + 68 x 9 = 1,833; cap 3,666 < 4,000. + # Ten rooms would give a cap of 3,802. + wi_wheap_heating_cost: 3_666 + +- name: Case 8, kerosene uses the wood-or-other proxy. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + spm_units: + spm_unit: + members: [person1] + heating_type: KEROSENE + heating_expense: 1_800 + wi_wheap_dwelling_type: SINGLE_FAMILY + households: + household: + members: [person1] + state_code: WI + bedrooms: 3 + output: + # Manual 4.4.28.3 places kerosene in wood and other: 524 + 89 x 3 = 791; + # cap 1,582 < 1,800. The fuel oil proxy would allow 2,850. + wi_wheap_heating_cost: 1_582 + +- name: Case 9, solar heating has no proxy and gives no heating cost. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + spm_units: + spm_unit: + members: [person1] + heating_type: SOLAR + heating_expense: 600 + wi_wheap_dwelling_type: SINGLE_FAMILY + households: + household: + members: [person1] + state_code: WI + bedrooms: 3 + output: + # Manual 4.4.28.3: solar-only heat has no fuel record; no calculator class. + wi_wheap_heating_cost: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/wi_wheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/wi_wheap.yaml new file mode 100644 index 00000000000..eb3ea7ff058 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/wi_wheap.yaml @@ -0,0 +1,331 @@ +# Wisconsin WHEAP regular heating benefit (FFY2026 HE+ Benefit Calculator, +# Computation C5-C43; PY2026 WHEAP Manual 2.3.1; FFY2026 agency plan 2.6). +# Poverty ratio = income / 2025 poverty guideline for the eligible-member size +# (15,650 + 5,500 per added person), held between 0.60 and 2.15; a +# categorically eligible household above its limit uses the limit as income. +# Raw = (2.66667 - ratio) / 2.94269 x benefit heating cost, truncated to whole +# dollars, capped at 2,580 and paid only when at least 30. + +- name: Case 1, Milwaukee family heating a single-family home with natural gas. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 36 + employment_income: 39_520 + person2: + age: 34 + person3: + age: 8 + person4: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + gas_expense: 1_000 + wi_wheap_dwelling_type: SINGLE_FAMILY + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2, person3, person4] + state_code: WI + bedrooms: 3 + output: + # Nursing assistant at 19 an hour x 2,080 hours = 39,520; size 4 limit + # 73,888. Guideline 15,650 + 3 x 5,500 = 32,150; ratio 1.22924. + # The gas bill reaches heating_expense through the natural gas input. + # Proxy 354 + 60 x 3 = 534, bounds 400.50 to 1,068, cost 1,000. + # (2.66667 - 1.22924) / 2.94269 x 1,000 = 488.48 -> 488. + wi_wheap_household_size: 4 + wi_wheap_countable_income: 39_520 + wi_wheap_income_limit: 73_888 + wi_wheap_categorically_eligible: false + wi_wheap_eligible: true + wi_wheap_heating_cost: 1_000 + wi_wheap: 488 + +- name: Case 2, poverty ratio below 0.60 is raised to 0.60 and the raw amount is truncated. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 12_000 + person2: + age: 9 + person3: + age: 6 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + heating_expense: 800 + wi_wheap_dwelling_type: TWO_TO_FOUR_UNITS + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2, person3] + state_code: WI + bedrooms: 2 + output: + # Ratio 12,000 / 26,650 = 0.45 -> 0.60. Proxy 297 + 65 x 2 = 427, cost 800 + # inside 320.25 to 854. (2.66667 - 0.6) / 2.94269 x 800 = 561.85; + # TRUNC gives 561, not 562. + wi_wheap: 561 + +- name: Case 3, poverty ratio above 2.15 is held at 2.15. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 28 + employment_income: 36_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + heating_expense: 900 + wi_wheap_dwelling_type: APARTMENT + tanf: 0 + snap: 0 + households: + household: + members: [person1] + state_code: WI + bedrooms: 1 + output: + # 36,000 <= size 1 limit 38,421; ratio 36,000 / 15,650 = 2.30 -> 2.15. + # Electric cost 900 x 0.4 = 360, inside 149.25 to 398 (proxy 88 + 111). + # (2.66667 - 2.15) / 2.94269 x 360 = 63.21 -> 63. + wi_wheap: 63 + +- name: Case 4, categorically eligible household above its limit is paid as if income equaled the limit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 44 + employment_income: 64_000 + person2: + age: 42 + employment_income: 40_000 + person3: + age: 15 + person4: + age: 13 + person5: + age: 11 + person6: + age: 9 + person7: + age: 6 + person8: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + heating_type: NATURAL_GAS + heating_expense: 1_150 + wi_wheap_dwelling_type: SINGLE_FAMILY + snap: 1_200 + tanf: 0 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + state_code: WI + bedrooms: 4 + output: + # Manual 2.3.1 item 1: benefit income = size 8 limit 101,965, not 104,000. + # Guideline 15,650 + 7 x 5,500 = 54,150; ratio 1.88301. + # Proxy 354 + 60 x 4 = 594, cost 1,150 inside 445.50 to 1,188. + # (2.66667 - 1.88301) / 2.94269 x 1,150 = 306.25 -> 306 + # (actual income would give 291). + wi_wheap_countable_income: 104_000 + wi_wheap_income_limit: 101_965 + wi_wheap_categorically_eligible: true + wi_wheap_eligible: true + wi_wheap_heating_cost: 1_150 + wi_wheap: 306 + +- name: Case 5, poverty guideline uses the eligible-member size while all income counts. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 33 + immigration_status: UNDOCUMENTED + employment_income: 30_000 + person2: + age: 31 + immigration_status: LEGAL_PERMANENT_RESIDENT + person3: + age: 5 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + heating_expense: 700 + wi_wheap_dwelling_type: TWO_TO_FOUR_UNITS + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2, person3] + state_code: WI + bedrooms: 2 + output: + # Manual 4.4.26.2: size 2; income 30,000 counts. Guideline 21,150; + # ratio 1.41844. Cost 700 inside 320.25 to 854. + # (2.66667 - 1.41844) / 2.94269 x 700 = 296.93 -> 296 + # (size 3 would give 366). + wi_wheap_household_size: 2 + wi_wheap: 296 + +- name: Case 6, benefit of exactly 30 dollars is paid. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 24 + employment_income: 31_500 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 0 + heat_expense_included_in_rent: true + wi_wheap_dwelling_type: APARTMENT + tanf: 0 + snap: 0 + households: + household: + members: [person1] + state_code: WI + bedrooms: 0 + output: + # Studio with heat in rent: 0 bedrooms count as 1 room (Manual 4.4.17), + # cost 0.75 x (116 + 68) = 138. Ratio 31,500 / 15,650 = 2.01278. + # (2.66667 - 2.01278) / 2.94269 x 138 = 30.66 -> 30, which meets the + # 30 minimum. + wi_wheap: 30 + +- name: Case 7, raw amount of 29.77 dollars is truncated below the minimum and pays nothing. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 24 + employment_income: 31_800 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 0 + heat_expense_included_in_rent: true + wi_wheap_dwelling_type: APARTMENT + tanf: 0 + snap: 0 + households: + household: + members: [person1] + state_code: WI + bedrooms: 0 + output: + # Ratio 31,800 / 15,650 = 2.03195; (2.66667 - 2.03195) / 2.94269 x 138 = + # 29.77 -> 29 < 30, so 0 (Manual 2.3.1: no payment below 30). Rounding + # would have given 30. + wi_wheap_eligible: true + wi_wheap: 0 + +- name: Case 8, noncitizen with Temporary Protected Status who receives Social Security is counted and paid. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 63 + immigration_status: TPS + social_security_retirement: 14_400 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 900 + wi_wheap_dwelling_type: SINGLE_FAMILY + tanf: 0 + snap: 0 + households: + household: + members: [person1] + state_code: WI + bedrooms: 2 + output: + # Manual 3.2.2.4: a non-citizen receiving monthly Social Security is an + # eligible non-citizen, so size 1 (status alone would give size 0 and no + # case head). Early retirement benefit of 1,200 a month = 14,400; no + # Medicare Part B premium before 65. Size 1 limit 38,421. + # Ratio 14,400 / 15,650 = 0.92013. Proxy 354 + 60 x 2 = 474, bounds + # 355.50 to 948, cost 900. + # (2.66667 - 0.92013) / 2.94269 x 900 = 534.17 -> 534. + wi_wheap_household_size: 1 + wi_wheap_countable_income: 14_400 + wi_wheap_income_limit: 38_421 + wi_wheap_eligible: true + wi_wheap_heating_cost: 900 + wi_wheap: 534 + +- name: Case 9, Social Security of a spouse with Temporary Protected Status raises the size and limit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 60 + immigration_status: CITIZEN + employment_income: 30_000 + person2: + age: 63 + immigration_status: TPS + social_security_retirement: 14_400 + marital_units: + marital_unit: + members: [person1, person2] + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 900 + wi_wheap_dwelling_type: SINGLE_FAMILY + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2] + state_code: WI + bedrooms: 2 + output: + # Retail cashier at 15 an hour x 2,000 hours = 30,000. Size 2 + # (Manual 3.2.2.4), limit 50,243; income 30,000 + 14,400 = 44,400 is + # eligible (size 1 would give limit 38,421 and no benefit). + # Ratio 44,400 / 21,150 = 2.09929, inside 0.60 to 2.15; cost 900. + # (2.66667 - 2.09929) / 2.94269 x 900 = 173.53 -> 173. + wi_wheap_household_size: 2 + wi_wheap_countable_income: 44_400 + wi_wheap_income_limit: 50_243 + wi_wheap_eligible: true + wi_wheap: 173 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/wi_wheap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/wi_wheap_household_size.yaml new file mode 100644 index 00000000000..4ca3621a57e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/wi_wheap_household_size.yaml @@ -0,0 +1,34 @@ +# Wisconsin WHEAP household size counts citizens, qualified noncitizens and +# noncitizens receiving Social Security (PY2026 WHEAP Manual 3.2.2.4). +# Manual 4.4.26.2: "The HE+ System will not count ineligible non-citizens as +# a household members, but it will count their income"; 3.2.6.2 enters them +# with their status flagged. + +- name: Case 1, ineligible noncitizens are not counted in a mixed-status household. + period: 2026 + input: + people: + person1: + age: 45 + immigration_status: CITIZEN + person2: + age: 43 + immigration_status: LEGAL_PERMANENT_RESIDENT + person3: + age: 20 + immigration_status: DACA + person4: + age: 50 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + households: + household: + members: [person1, person2, person3, person4] + state_code: WI + output: + # Citizen and lawful permanent resident (qualified noncitizen) count; + # DACA and undocumented members do not: 2 of 4. + wi_wheap_household_size: 2 diff --git a/policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_categorically_eligible.py b/policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_categorically_eligible.py new file mode 100644 index 00000000000..1f32b8c8bec --- /dev/null +++ b/policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_categorically_eligible.py @@ -0,0 +1,44 @@ +from policyengine_us.model_api import * + + +class wi_wheap_categorically_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Wisconsin WHEAP categorical eligibility" + defined_for = StateCode.WI + reference = ( + "https://docs.legis.wisconsin.gov/statutes/statutes/16/i/27", + # Section 3.3.1 item 2, including item 2(d). + # PDF pages 45-46 + "https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=45", + "https://liheapch.acf.gov/docs/2026/state-plans/WI_Plan_2026.pdf#page=5", + ) + + def formula(spm_unit, period, parameters): + # Every member, including ineligible non-citizens, must receive W-2 + # cash, FoodShare or SSI. FoodShare covers only members of the SNAP + # unit, and W-2 only citizens and qualified non-citizens. Unit-level + # receipt approximates each member's group membership, and receipt in + # any month approximates receipt for the entire prior month. + person = spm_unit.members + # Monthly flags are read month by month rather than as an annual + # aggregate, which would cache a summed value under the annual key + # and change what other formulas read. + first_month = period.first_month + months = [first_month.offset(offset) for offset in range(MONTHS_IN_YEAR)] + reported_ssi = np.any( + [person("receives_ssi", month) for month in months], axis=0 + ) + receives_ssi = (add(person, period, ["ssi"]) > 0) | reported_ssi + in_snap_unit = np.any( + [~person("is_snap_excluded_member", month) for month in months], + axis=0, + ) + receives_snap = spm_unit("snap", period, options=[ADD]) > 0 + foodshare_member = in_snap_unit & spm_unit.project(receives_snap) + receives_w2_cash = spm_unit("tanf", period) > 0 + w2_member = person("is_citizen_or_legal_immigrant", period) & spm_unit.project( + receives_w2_cash + ) + return spm_unit.all(receives_ssi | foodshare_member | w2_member) diff --git a/policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_eligible.py b/policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_eligible.py new file mode 100644 index 00000000000..ea82a20b158 --- /dev/null +++ b/policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_eligible.py @@ -0,0 +1,52 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class wi_wheap_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Eligible for the Wisconsin WHEAP regular heating benefit" + defined_for = StateCode.WI + reference = ( + "https://docs.legis.wisconsin.gov/statutes/statutes/16/i/27", + # Sections 3.2.1, 3.2.5, 3.3.1 and 4.4.28.3. + # PDF pages 33, 41-43, 45-46, 87 + "https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=33", + "https://www.law.cornell.edu/uscode/text/42/8624#b_2_B", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.wi.doa.wheap.eligibility + # A positive size means a citizen or eligible non-citizen can serve as + # case head. + size = spm_unit("wi_wheap_household_size", period) + income = spm_unit("wi_wheap_countable_income", period) + limit = spm_unit("wi_wheap_income_limit", period) + # 42 U.S.C. 8624(b)(2)(B) bars excluding a household on income below + # 110% of the poverty guideline. + state_group = spm_unit.household("state_group_str", period) + guideline = fpg(size, state_group, period, parameters, year_lag=p.fpg_year_lag) + federal_floor = np.round( + guideline * parameters(period).gov.hhs.liheap.fpg_floor, 2 + ) + categorical = spm_unit("wi_wheap_categorically_eligible", period) + income_eligible = (income <= limit) | (income < federal_floor) | categorical + heating_type = spm_unit("heating_type", period) + fuel = heating_type.possible_values + heating_reported = (heating_type != fuel.UNSPECIFIED) & ( + heating_type != fuel.NONE + ) + # Heat included in rent is an energy burden unless the household + # receives rental assistance or lives in public housing. Section 42 + # tax credit units keep the burden but are not distinguishable, and + # shared-meter arrangements without payment are not identified. + 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) + ) + energy_burden = spm_unit("has_heating_expense", period) | ( + heat_in_rent & ~subsidized + ) + # No asset test applies to regular benefits. + return (size > 0) & income_eligible & heating_reported & energy_burden diff --git a/policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_income_limit.py b/policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_income_limit.py new file mode 100644 index 00000000000..e2bc5b47e25 --- /dev/null +++ b/policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_income_limit.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.hhs_smi import liheap_smi_limit + + +class wi_wheap_income_limit(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Wisconsin WHEAP annual income limit" + defined_for = StateCode.WI + reference = ( + "https://docs.legis.wisconsin.gov/statutes/statutes/16/i/27", + # Section 3.3.1 and Appendix E. + # PDF pages 45, 166 + "https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=45", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.wi.doa.wheap.eligibility + size = spm_unit("wi_wheap_household_size", period) + state = spm_unit.household("state_code_str", period) + # The annual limit approximates the annualized one-month test; twelve + # times each printed one-month limit is within 4 cents of it. + limit = liheap_smi_limit(size, state, p.smi_rate, period, parameters) + return where(size > 0, limit, 0) diff --git a/policyengine_us/variables/gov/states/wi/doa/wheap/income/wi_wheap_countable_income.py b/policyengine_us/variables/gov/states/wi/doa/wheap/income/wi_wheap_countable_income.py new file mode 100644 index 00000000000..b7f5dfc8895 --- /dev/null +++ b/policyengine_us/variables/gov/states/wi/doa/wheap/income/wi_wheap_countable_income.py @@ -0,0 +1,70 @@ +from policyengine_us.model_api import * + + +class wi_wheap_countable_income(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Wisconsin WHEAP countable income" + defined_for = StateCode.WI + reference = ( + # Sections 3.3.2-3.3.5 and Tables 3.4-3.6. + # PDF pages 45-63 + "https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=45", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.wi.doa.wheap.income + person = spm_unit.members + age = person("age", period) + # Earned income of minors and of high school students 18 and older is + # ignored; is_in_k12_school identifies the students. + excluded = (age < p.adult_age) | person("is_in_k12_school", period) + wages = 0 + for source in p.sources.earned: + wages = wages + max_(person(source, period), 0) + # Self-employment, rental and royalty profits and losses offset each + # other across sources and members; a net loss counts as zero and + # cannot offset other income. + business = where( + excluded, 0, add(person, period, p.sources.self_employment) + ) + add(person, period, p.sources.rental) + unearned = 0 + for source in p.sources.unearned: + unearned = unearned + max_(person(source, period, options=[ADD]), 0) + # All withdrawals count from age 73. Regular withdrawals before 73 + # also count but cannot be told apart from lump sums, which are + # ignored as asset drawdowns. + distributions = where( + age >= p.retirement_distribution_age, + max_(person("retirement_distributions", period), 0), + 0, + ) + # Social Security and Railroad Retirement count net of Medicare + # premiums; Part C and Part D premiums have no input. + retirement_benefits = max_(person("social_security", period), 0) + max_( + person("railroad_benefits", period), 0 + ) + net_retirement_benefits = max_( + retirement_benefits - person("medicare_part_b_premium", period), 0 + ) + investment = 0 + for source in p.sources.investment: + investment = investment + max_(person(source, period), 0) + counted_investment = where(investment >= p.investment_minimum, investment, 0) + personal = ( + where(excluded, 0, wages) + + unearned + + distributions + + net_retirement_benefits + + counted_investment + ) + net_business = max_(spm_unit.sum(business), 0) + # W-2 cash grants count; unemployment compensation is ignored. + w2_cash = max_(spm_unit("tanf", period), 0) + deductions = add(spm_unit, period, p.deductions) + # Annual amounts approximate the prior-month test period. The extra + # paycheck rule, the present-month option and tribal per capita + # payments are not modeled. + return max_(spm_unit.sum(personal) + net_business + w2_cash - deductions, 0) diff --git a/policyengine_us/variables/gov/states/wi/doa/wheap/payment/wi_wheap_dwelling_type.py b/policyengine_us/variables/gov/states/wi/doa/wheap/payment/wi_wheap_dwelling_type.py new file mode 100644 index 00000000000..c520f7d71b3 --- /dev/null +++ b/policyengine_us/variables/gov/states/wi/doa/wheap/payment/wi_wheap_dwelling_type.py @@ -0,0 +1,22 @@ +from policyengine_us.model_api import * + + +class WIWHEAPDwellingType(Enum): + SINGLE_FAMILY = "Single family" + TWO_TO_FOUR_UNITS = "2- to 4-unit building" + APARTMENT = "Apartment or multi-unit building" + MOBILE_HOME = "Mobile home" + MOTEL_OR_CAMPER = "Motel or camper/RV" + + +class wi_wheap_dwelling_type(Variable): + value_type = Enum + entity = SPMUnit + possible_values = WIWHEAPDwellingType + default_value = WIWHEAPDwellingType.SINGLE_FAMILY + definition_period = YEAR + label = "Wisconsin WHEAP housing type" + defined_for = StateCode.WI + # Section 4.4.13 housing types. + # PDF pages 76-77 + reference = "https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=76" diff --git a/policyengine_us/variables/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.py b/policyengine_us/variables/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.py new file mode 100644 index 00000000000..aba6cc2d587 --- /dev/null +++ b/policyengine_us/variables/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.py @@ -0,0 +1,85 @@ +from policyengine_us.model_api import * + + +class wi_wheap_heating_cost(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Wisconsin WHEAP benefit heating cost" + defined_for = StateCode.WI + reference = ( + # Section 2.3.1 and sections 4.4.17 and 4.4.28.2-4.4.28.3. + # PDF pages 26-27, 79, 86-87 + "https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=26", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.wi.doa.wheap.payment + dwelling = spm_unit("wi_wheap_dwelling_type", period) + dwelling_types = dwelling.possible_values + # Bedrooms approximate the room count, which also includes kitchens + # and living rooms. HE+ accepts 1 to 9 rooms (section 4.4.17; the + # calculator's Benefits!C12 rejects 0), so a studio counts one room. + # Motels and campers count one room. + bedrooms = spm_unit.household("bedrooms", period) + rooms = where( + dwelling == dwelling_types.MOTEL_OR_CAMPER, + 1, + clip(bedrooms, 1, p.proxy.max_rooms), + ) + heating_type = spm_unit("heating_type", period) + fuel = heating_type.possible_values + is_electricity = heating_type == fuel.ELECTRICITY + # Kerosene, coal and other fuels use the wood-or-other row. Solar has + # no fuel record and is unsupported. + fuel_classes = [ + heating_type == fuel.FUEL_OIL, + heating_type == fuel.NATURAL_GAS, + heating_type == fuel.PROPANE, + is_electricity, + (heating_type == fuel.WOOD) + | (heating_type == fuel.KEROSENE) + | (heating_type == fuel.COAL) + | (heating_type == fuel.OTHER), + ] + base = select( + fuel_classes, + [ + p.proxy.base.fuel_oil[dwelling], + p.proxy.base.natural_gas[dwelling], + p.proxy.base.propane[dwelling], + p.proxy.base.electricity[dwelling], + p.proxy.base.wood_other[dwelling], + ], + default=0, + ) + per_room = select( + fuel_classes, + [ + p.proxy.per_room.fuel_oil[dwelling], + p.proxy.per_room.natural_gas[dwelling], + p.proxy.per_room.propane[dwelling], + p.proxy.per_room.electricity[dwelling], + p.proxy.per_room.wood_other[dwelling], + ], + default=0, + ) + proxy = base + per_room * rooms + # An electric heating bill also covers non-heating use. + reported = spm_unit("heating_expense", period) + baseload = where(is_electricity, reported * p.electric_baseload_rate, 0) + eligible_cost = max_(reported - baseload, 0) + lower = p.proxy.floor_rate * proxy + upper = p.proxy.cap_rate * proxy + # Shared and business meters also use the proxy floor but cannot be + # identified from existing inputs. + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + cost = where( + heat_in_rent | (eligible_cost <= 0), + lower, + clip(eligible_cost, lower, upper), + ) + supported = np.any(fuel_classes, axis=0) + return where(supported, cost, 0) diff --git a/policyengine_us/variables/gov/states/wi/doa/wheap/wi_wheap.py b/policyengine_us/variables/gov/states/wi/doa/wheap/wi_wheap.py new file mode 100644 index 00000000000..0c908d24947 --- /dev/null +++ b/policyengine_us/variables/gov/states/wi/doa/wheap/wi_wheap.py @@ -0,0 +1,49 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class wi_wheap(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Wisconsin WHEAP regular heating benefit" + defined_for = "wi_wheap_eligible" + reference = ( + "https://docs.legis.wisconsin.gov/statutes/statutes/16/i/27", + # Section 2.3.1. + "https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=26", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx", + "https://liheapch.acf.gov/docs/2026/state-plans/WI_Plan_2026.pdf#page=5", + "https://web.archive.org/web/20260316195114/https://energyandhousing.wi.gov/Documents/WHEAP/LiheapPlan_FFY2026.pdf#page=7", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.wi.doa.wheap + payment = p.payment + size = spm_unit("wi_wheap_household_size", period) + state_group = spm_unit.household("state_group_str", period) + guideline = fpg( + size, state_group, period, parameters, year_lag=p.eligibility.fpg_year_lag + ) + income = spm_unit("wi_wheap_countable_income", period) + limit = spm_unit("wi_wheap_income_limit", period) + # Categorically eligible households above the limit are paid as if + # their income equaled it. + categorical = spm_unit("wi_wheap_categorically_eligible", period) + benefit_income = where(categorical, min_(income, limit), income) + poverty_ratio = np.clip( + benefit_income / guideline, + payment.poverty_ratio.minimum, + payment.poverty_ratio.maximum, + ) + heating_cost = spm_unit("wi_wheap_heating_cost", period) + formula_amount = ( + (payment.benefit_ratio - poverty_ratio) + / payment.funding_factor + * heating_cost + ) + amount = min_(np.floor(formula_amount), payment.maximum) + # The February 1 reduction, supplemental benefits and the separate + # non-heating electric benefit are not modeled. + return where(amount >= payment.minimum, amount, 0) diff --git a/policyengine_us/variables/gov/states/wi/doa/wheap/wi_wheap_household_size.py b/policyengine_us/variables/gov/states/wi/doa/wheap/wi_wheap_household_size.py new file mode 100644 index 00000000000..de380da566c --- /dev/null +++ b/policyengine_us/variables/gov/states/wi/doa/wheap/wi_wheap_household_size.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class wi_wheap_household_size(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Wisconsin WHEAP household size" + defined_for = StateCode.WI + # Sections 3.2.2.4, 3.2.6.2, 3.2.6.4 and 4.4.26.2. + # PDF pages 35, 44-45, 82 + reference = "https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=35" + + def formula(spm_unit, period, parameters): + person = spm_unit.members + # Citizens, qualified non-citizens and non-citizens receiving any + # Social Security are counted. Ineligible non-citizens are excluded + # from size, but their income counts. FoodShare receipt has no + # person-level input, so non-citizens in FoodShare are counted only + # through their immigration status. Court-placed foster children are + # counted because no input identifies a court placement. SPM + # membership approximates the economic unit on the application date. + eligible_member = person("is_citizen_or_legal_immigrant", period) | ( + person("social_security", period) > 0 + ) + return spm_unit.sum(eligible_member) From b8890aed36af506913c2930e7e01feeb4991bc53 Mon Sep 17 00:00:00 2001 From: Ziming Date: Wed, 28 Oct 2026 20:10:30 -0400 Subject: [PATCH 2/2] Address review round 1 for Wisconsin LIHEAP Apply DTrim99's round 1 review: tests that pin every income source, the rule and documentation fixes, and source-conflict candidates. Co-Authored-By: Claude Opus 5.5 --- .../wi/doa/wheap/income/sources/rental.yaml | 5 +- .../wi/doa/wheap/income/sources/unearned.yaml | 6 +- .../wheap/payment/electric_baseload_rate.yaml | 2 + .../states/wi/doa/wheap/payment/maximum.yaml | 5 +- .../wi_wheap_categorically_eligible.yaml | 31 ++++ .../income/wi_wheap_countable_income.yaml | 82 +++++++++++ .../wheap/payment/wi_wheap_heating_cost.yaml | 43 +++++- .../wheap/eligibility/wi_wheap_eligible.py | 1 + .../wheap/payment/wi_wheap_heating_cost.py | 9 +- .../gov/states/wi/doa/wheap/wi_wheap.py | 3 +- source_conflicts/candidates.yaml | 136 ++++++++++++++++++ 11 files changed, 308 insertions(+), 15 deletions(-) diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/rental.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/rental.yaml index 09396d01c35..b7307c8f87c 100644 --- a/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/rental.yaml +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/rental.yaml @@ -1,6 +1,7 @@ description: Wisconsin includes these rental and royalty sources in the self-employment offset under the Home Energy Assistance Program. -# The $120 land rent threshold and rent paid by other household members are -# not modeled. +# Land rent is entered as rental income type (R), which section 3.3.5 +# offsets. The $120 land rent threshold and rent paid by other household +# members are not modeled. values: 2025-10-01: - rental_income diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/unearned.yaml index 67a68361948..e66fbbb96bd 100644 --- a/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/income/sources/unearned.yaml @@ -1,9 +1,13 @@ description: Wisconsin counts these sources as unearned income under the Home Energy Assistance Program. # Gift income beyond financial_assistance, tribal per capita payments and land -# contract interest have no input. +# contract interest have no input. PY27 Table 3.6 ignores gambling winnings; +# FFY2027 is not encoded. values: 2025-10-01: - ssi + # The state SSI line excludes SSI-E (wi_ssi_e). + - wi_ssp_regular + - wi_cts - pension_income - military_retirement_income - veterans_benefits diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/electric_baseload_rate.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/electric_baseload_rate.yaml index 3d73b976c33..f321a3f2a4c 100644 --- a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/electric_baseload_rate.yaml +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/electric_baseload_rate.yaml @@ -1,4 +1,6 @@ description: Wisconsin removes this share of the reported electric bill as non-heating use when electricity is the heating fuel under the Home Energy Assistance Program. +# HE+ removes the baseload only when one meter serves heat and other uses +# (Computation!C22); heat-only meters are not identified. values: 2025-10-01: 0.6 metadata: diff --git a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/maximum.yaml b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/maximum.yaml index 44febcbfc5d..a5dfedea493 100644 --- a/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/maximum.yaml +++ b/policyengine_us/parameters/gov/states/wi/doa/wheap/payment/maximum.yaml @@ -1,6 +1,9 @@ description: Wisconsin limits the regular heating benefit to this amount under the Home Energy Assistance Program. # The FFY2026 proxy table and formula constants yield at most $2,574, so the -# cap does not bind. +# cap does not bind. Calculator Computation!C17 sets $2,575 and the ACF-posted +# plan item 2.6 prints $2,147, which would bind for single-family fuel oil +# homes of 5-9 rooms. FFY2027 plan item 2.6 sets $2,784 from 2026-10-01; +# FFY2027 is not encoded. values: 2025-10-01: 2_580 metadata: diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_categorically_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_categorically_eligible.yaml index d6365f20c76..9ab0a8e546e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_categorically_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/eligibility/wi_wheap_categorically_eligible.yaml @@ -186,3 +186,34 @@ # citizen spouse and child only. The refugee receives no SSI or W-2, so not # every member is a recipient (Manual 3.3.1 item 2(d)). wi_wheap_categorically_eligible: false + +- name: Case 8, W-2 household with an undocumented adult is not categorically eligible. + period: 2026 + input: + people: + person1: + age: 27 + immigration_status: CITIZEN + person2: + age: 29 + immigration_status: UNDOCUMENTED + person3: + age: 2 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + gas_expense: 900 + tanf: 7_296 + snap: 0 + households: + household: + members: [person1, person2, person3] + state_code: WI + output: + # W-2 Transitions grant 608 a month x 12 = 7,296 for the citizen parent + # and child. W-2 covers only citizens and qualified non-citizens, so the + # undocumented adult receives no qualifying benefit (Manual 3.3.1 item + # 2(d)). Without the citizenship condition every member would count. + wi_wheap_categorically_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/income/wi_wheap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/income/wi_wheap_countable_income.yaml index d31779af5e7..8ac8c55b545 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/income/wi_wheap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/income/wi_wheap_countable_income.yaml @@ -289,3 +289,85 @@ output: # 4,000 - 6,000 = -2,000, floored at 0. wi_wheap_countable_income: 0 + +- name: Case 11, every listed business, rental and unearned source counts, including the state SSI line and the Caretaker Supplement. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + # SSI 994 a month; state SSI line 83.78 a month in January-April and + # 92.16 from May; Caretaker Supplement 275 a month for one child. + ssi: 11_928 + wi_ssp_regular: 1_072.40 + wi_cts: 3_300 + person2: + age: 9 + person3: + age: 66 + ssi: 0 + farm_operations_income: 1_000 + # Enters partnership_s_corp_income. + partnership_income: 2_000 + farm_rent_income: 3_000 + # Enters pension_income. + taxable_private_pension_income: 4_000 + # Enters military_retirement_income. + military_retirement_pay: 5_000 + veterans_benefits: 6_000 + workers_compensation: 7_000 + disability_benefits: 8_000 + alimony_income: 9_000 + strike_benefits: 1_100 + general_assistance: 1_200 + estate_income: 1_300 + financial_assistance: 1_400 + survivor_benefits: 1_500 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2, person3] + state_code: WI + output: + # Business and rental offset group (person3): farm operations 1,000 + + # partnership 2,000 + farm rent 3,000 = 6,000. + # person3 unearned: pension 4,000 + military retirement 5,000 + veterans + # 6,000 + workers' compensation 7,000 + disability 8,000 + alimony 9,000 + # = 39,000; strike 1,100 + general assistance 1,200 + trust 1,300 + + # financial assistance 1,400 + survivor 1,500 = 6,500; 45,500 in all. + # person1, Table 3.4 federal and state SSI lines plus C-Supp: + # 994 x 12 = 11,928; 4 x 83.78 + 8 x 92.16 = 1,072.40; 275 x 12 = 3,300; + # 16,300.40 in all. person3's Part B premium nets only against Social + # Security and Railroad Retirement, both 0. + # 6,000 + 45,500 + 16,300.40 = 67,800.40. + wi_wheap_countable_income: 67_800.40 + +- name: Case 12, a trust loss counts as zero and does not reduce wages. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 52 + employment_income_before_lsr: 40_000 + estate_income: -2_500 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + tanf: 0 + snap: 0 + households: + household: + members: [person1] + state_code: WI + output: + # Each unearned source is floored at zero per member: + # 40,000 + max(-2,500, 0) = 40,000. Without the floor, 37,500. + wi_wheap_countable_income: 40_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.yaml index acb291e62f4..3ca2ae1cd6a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.yaml @@ -2,9 +2,9 @@ # Computation C19-C35 and HT_Proxy A4:H28; PY2026 WHEAP Manual 2.3.1 and # 4.4.13-4.4.28). Heat proxy = base + per-room amount x rooms by fuel class and # dwelling type, rooms = bedrooms held between 1 and 9 (1 for a motel or -# camper). The reported cost, less a 60 percent baseload for electric heat, is -# held between 0.75 and 2 times the proxy; heat included in rent uses 0.75 -# times the proxy. +# camper). The reported cost, less a 60 percent baseload for electric heat +# (solar heat uses the electric row), is held between 0.75 and 2 times the +# proxy; heat included in rent uses 0.75 times the proxy. - name: Case 1, natural gas cost inside the bounds is used as reported. period: 2026 @@ -188,7 +188,7 @@ # cap 1,582 < 1,800. The fuel oil proxy would allow 2,850. wi_wheap_heating_cost: 1_582 -- name: Case 9, solar heating has no proxy and gives no heating cost. +- name: Case 9, solar heat uses the electricity row and its baseload. period: 2026 absolute_error_margin: 0.01 input: @@ -199,7 +199,8 @@ spm_unit: members: [person1] heating_type: SOLAR - heating_expense: 600 + # heating_expense reads a grid-tied solar home's electric bill. + pre_subsidy_electricity_expense: 1_500 wi_wheap_dwelling_type: SINGLE_FAMILY households: household: @@ -207,5 +208,33 @@ state_code: WI bedrooms: 3 output: - # Manual 4.4.28.3: solar-only heat has no fuel record; no calculator class. - wi_wheap_heating_cost: 0 + # Eligible cost 1,500 - 0.6 x 1,500 = 600. Single family, electric: + # 261 + 108 x 3 = 585; bounds 438.75 to 1,170; 600 is inside. Without + # the baseload the 1,500 bill would be capped at 1,170. + wi_wheap_heating_cost: 600 + +- name: Case 10, heat included in rent uses 0.75 times the proxy even with a reported gas bill. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + # Gas for cooking and the water heater; heat is paid in the rent. + gas_expense: 360 + heat_expense_included_in_rent: true + wi_wheap_dwelling_type: APARTMENT + households: + household: + members: [person1] + state_code: WI + bedrooms: 2 + output: + # Apartment, natural gas: 116 + 68 x 2 = 252; 0.75 x 252 = 189. + # The 360 bill lies inside the bounds 189 to 504 and would be used if + # heat in rent were ignored. + wi_wheap_heating_cost: 189 diff --git a/policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_eligible.py b/policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_eligible.py index ea82a20b158..571792bc398 100644 --- a/policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_eligible.py +++ b/policyengine_us/variables/gov/states/wi/doa/wheap/eligibility/wi_wheap_eligible.py @@ -34,6 +34,7 @@ def formula(spm_unit, period, parameters): income_eligible = (income <= limit) | (income < federal_floor) | categorical heating_type = spm_unit("heating_type", period) fuel = heating_type.possible_values + # An application without a fuel type is denied (section 4.4.28.3). heating_reported = (heating_type != fuel.UNSPECIFIED) & ( heating_type != fuel.NONE ) diff --git a/policyengine_us/variables/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.py b/policyengine_us/variables/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.py index aba6cc2d587..690d014a1b3 100644 --- a/policyengine_us/variables/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.py +++ b/policyengine_us/variables/gov/states/wi/doa/wheap/payment/wi_wheap_heating_cost.py @@ -31,9 +31,12 @@ def formula(spm_unit, period, parameters): ) heating_type = spm_unit("heating_type", period) fuel = heating_type.possible_values - is_electricity = heating_type == fuel.ELECTRICITY - # Kerosene, coal and other fuels use the wood-or-other row. Solar has - # no fuel record and is unsupported. + is_electricity = (heating_type == fuel.ELECTRICITY) | ( + heating_type == fuel.SOLAR + ) + # Kerosene, coal and other fuels use the wood-or-other row. Solar heat + # uses the electricity row, as heating_expense reads a grid-tied solar + # home's electric bill. fuel_classes = [ heating_type == fuel.FUEL_OIL, heating_type == fuel.NATURAL_GAS, diff --git a/policyengine_us/variables/gov/states/wi/doa/wheap/wi_wheap.py b/policyengine_us/variables/gov/states/wi/doa/wheap/wi_wheap.py index 0c908d24947..06d28ac416f 100644 --- a/policyengine_us/variables/gov/states/wi/doa/wheap/wi_wheap.py +++ b/policyengine_us/variables/gov/states/wi/doa/wheap/wi_wheap.py @@ -29,7 +29,8 @@ def formula(spm_unit, period, parameters): income = spm_unit("wi_wheap_countable_income", period) limit = spm_unit("wi_wheap_income_limit", period) # Categorically eligible households above the limit are paid as if - # their income equaled it. + # their income equaled it. Calculator cell C40 computes these with + # actual income; this follows manual 2.3.1 item 1 and plan 1.6. categorical = spm_unit("wi_wheap_categorically_eligible", period) benefit_income = where(categorical, min_(income, limit), income) poverty_ratio = np.clip( diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 843ddcbe21c..050b159be9a 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -5665,6 +5665,142 @@ candidates: state: ruled note: An October 2026 ruling followed the worksheet; the statute-first rule requires including the repayment deduction if the statutory reading is confirmed. +- id: us-wi:liheap-categorical-benefit-income + program: liheap + years: + from: 2026 + to: 2026 + type: federal-fiscal + question: Is a categorically eligible Wisconsin WHEAP household above 60% of state median income paid as if its income + equaled the limit, as the manual and plan state, or on its actual income, as the HE+ calculator computes? + sources: + - kind: statute + citation: Wis. Stat. 16.27(6) + url: https://docs.legis.wisconsin.gov/statutes/statutes/16/i/27 + - kind: manual + citation: Wisconsin PY26 WHEAP Manual, section 2.3.1 item 1 + url: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=26 + - kind: plan + citation: Wisconsin FY2026 LIHEAP State Plan, section 1.6 + url: https://liheapch.acf.gov/docs/2026/state-plans/WI_Plan_2026.pdf#page=5 + - kind: calculator + citation: Wisconsin HE+ Benefit Calculator FFY2026, Computation!C13, C39 and C40 + url: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx + readings: + statute: Section 16.27(6) pays heating assistance under a benefit schedule established by the department based on + household income, family size and energy costs; manual 2.3.1 item 1 and plan 1.6 pay a categorically eligible + household over 60% SMI as if its income were 60% SMI. + other: Calculator cell C40 keeps the amount that C39 computes from the household's actual income. + example: An eight-person household with $104,000 of income, a $1,150 gas bill and four bedrooms receives $306 at the + $101,965 limit and $291 at its actual income. + pe_us: + follows: gap-fill + code: [] + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10434 + state: ruled + note: 'Ruled 2026-10-21 by the user: follow manual 2.3.1 item 1 and plan 1.6. Code not yet on main: wi_wheap.' +- id: us-wi:liheap-general-relief-and-lump-sums + program: liheap + years: + from: 2026 + to: 2026 + type: federal-fiscal + question: Does Wisconsin WHEAP count General Relief and ignore most non-recurring lump sums, as the manual lists, or + follow the plan's income checklist, which leaves General Assistance unchecked and checks one-time lump sums? + sources: + - kind: statute + citation: Wis. Stat. 16.27(5)(b) and (6) + url: https://docs.legis.wisconsin.gov/statutes/statutes/16/i/27 + - kind: manual + citation: Wisconsin PY26 WHEAP Manual, Table 3.4 General Relief and Table 3.6 Lump Sum Payments, Non-recurring, PDF + pages 55 and 61 + url: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=55 + - kind: plan + citation: Wisconsin FY2026 LIHEAP State Plan, section 1.9 + url: https://liheapch.acf.gov/docs/2026/state-plans/WI_Plan_2026.pdf#page=6 + readings: + statute: Section 16.27 does not define countable income; manual Table 3.4 counts General Relief, including Refugee + Cash Assistance, and Table 3.6 ignores most non-recurring lump sums such as tax refunds and rebates. + other: Plan 1.9 leaves General Assistance benefits unchecked and checks one-time lump-sum payments such as rebates, + credits, lottery winnings and refund deposits. + example: A single adult with $1,200 of General Relief and no other income has countable income of $1,200 under the + manual and $0 under the plan. + pe_us: + follows: gap-fill + code: [] + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10434 + state: ruled + note: 'Ruled 2026-10-06: follow the manual over the plan. No rebate, credit or refund deposit is counted. Code not + yet on main: gov.states.wi.doa.wheap.income.sources.unearned.' +- id: us-wi:liheap-maximum-benefit + program: liheap + years: + from: 2026 + to: 2026 + type: federal-fiscal + question: Is the Wisconsin WHEAP FFY2026 maximum regular heating benefit $2,580 as the agency-posted plan states, $2,147 + as the ACF-posted plan prints, or $2,575 as the HE+ calculator sets? + sources: + - kind: statute + citation: Wis. Stat. 16.27(6) + url: https://docs.legis.wisconsin.gov/statutes/statutes/16/i/27 + - kind: plan + citation: Wisconsin LIHEAP State Plan FFY2026, agency-posted edition, section 2.6 + url: https://web.archive.org/web/20260316195114/https://energyandhousing.wi.gov/Documents/WHEAP/LiheapPlan_FFY2026.pdf#page=7 + - kind: plan + citation: Wisconsin FY2026 LIHEAP State Plan as posted by ACF, section 2.6 + url: https://liheapch.acf.gov/docs/2026/state-plans/WI_Plan_2026.pdf#page=12 + - kind: calculator + citation: Wisconsin HE+ Benefit Calculator FFY2026, Computation!C17 + url: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx + readings: + statute: Section 16.27(6) leaves the benefit schedule to the department and sets no maximum; the agency-posted plan + item 2.6 sets $2,580. + other: The ACF-posted plan item 2.6 prints $2,147 and calculator cell C17 sets $2,575. + example: A single-family fuel oil home of five rooms with a poverty ratio of 0.60 and costs above the $3,122 cap receives + $2,192 under the $2,580 or $2,575 maximum and $2,147 under the ACF-posted figure. + pe_us: + follows: gap-fill + code: [] + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10434 + state: ruled + note: 'Ruled 2026-10-21 by the user: $2,580. Code not yet on main: gov.states.wi.doa.wheap.payment.maximum.' +- id: us-wi:liheap-proxy-cost-bounds + program: liheap + years: + from: 2026 + to: 2026 + type: federal-fiscal + question: Is the Wisconsin WHEAP heating cost capped at twice a heat proxy equal to 75% of the table's average cost, as + the manual describes, or at twice the table amount with a floor of 0.75 times it, as the HE+ calculator computes? + sources: + - kind: statute + citation: Wis. Stat. 16.27(6) + url: https://docs.legis.wisconsin.gov/statutes/statutes/16/i/27 + - kind: manual + citation: Wisconsin PY26 WHEAP Manual, section 2.3.1 item 3 + url: https://web.archive.org/web/20260428215048/https://energyandhousing.wi.gov/Documents/WHEAP/WheapManual_PY26.pdf#page=27 + - kind: calculator + citation: Wisconsin HE+ Benefit Calculator FFY2026, Computation!C29, C30 and C31 + url: https://liheapch.acf.gov/docs/2026/benefits-matricies/WI_BenefitMatrix_2026.xlsx + readings: + statute: Section 16.27(6) bases the benefit schedule on energy costs and leaves the cost bounds to the department; + manual 2.3.1 item 3 sets the heat proxy at 75% of the average cost and caps costs at two times the proxy, or 1.5 + times the table amount. + other: Calculator cell C30 caps costs at 2 times and C31 floors them at 0.75 times the table amount. + example: A propane mobile home with two bedrooms and a $2,400 bill has a $861 table amount; the calculator caps its cost + at $1,722 and the manual reading at $1,291.50. + pe_us: + follows: gap-fill + code: [] + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10434 + state: ruled + note: 'Ruled 2026-10-21 by the user: calculator bounds. Code not yet on main: wi_wheap_heating_cost, + gov.states.wi.doa.wheap.payment.proxy.cap_rate.' - id: us-wi:shares-home-value-and-vehicle-asset-tests program: child-care-assistance years: