From a84ad158f81ab890b077bc7453018e78400de5d1 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sat, 3 Oct 2026 19:26:46 -0400 Subject: [PATCH 01/10] Add Utah HEAT regular heating assistance --- changelog.d/ut-liheap-wave2b.added.md | 1 + .../dwf/heat/benefit/burden_point_amount.yaml | 10 + .../ut/dwf/heat/benefit/fuel_credit.yaml | 10 + .../dwf/heat/benefit/fuel_credit_types.yaml | 12 + .../ut/dwf/heat/benefit/income_component.yaml | 10 + .../states/ut/dwf/heat/benefit/maximum.yaml | 10 + .../ut/dwf/heat/benefit/maximum_burden.yaml | 10 + .../states/ut/dwf/heat/benefit/minimum.yaml | 10 + .../benefit/monthly_energy_cost_standard.yaml | 37 + .../dwf/heat/benefit/rounding_increment.yaml | 12 + .../ut/dwf/heat/benefit/target/amount.yaml | 10 + .../ut/dwf/heat/benefit/target/child_age.yaml | 10 + .../dwf/heat/benefit/target/elderly_age.yaml | 10 + .../ut/dwf/heat/eligibility/adult_age.yaml | 10 + .../immigration/conditional_statuses.yaml | 12 + .../immigration/eligible_statuses.yaml | 18 + .../immigration/waiting_period.yaml | 10 + .../ut/dwf/heat/eligibility/income_limit.yaml | 10 + .../heat/income/business_income_sources.yaml | 14 + .../ut/dwf/heat/income/child_income_age.yaml | 10 + .../dwf/heat/income/student_income_age.yaml | 10 + .../heat/income/unearned_income_sources.yaml | 33 + policyengine_us/programs.yaml | 10 +- .../gov/states/ut/dwf/heat/ut_heat.yaml | 1203 +++++++++++++++++ .../ut/dwf/heat/ut_heat_countable_income.yaml | 570 ++++++++ .../states/ut/dwf/heat/ut_heat_eligible.yaml | 328 +++++ .../ut/dwf/heat/ut_heat_eligible_person.yaml | 434 ++++++ .../ut/dwf/heat/ut_heat_household_size.yaml | 61 + .../ut/dwf/heat/ut_heat_income_limit.yaml | 237 ++++ .../dwf/heat/eligibility/ut_heat_eligible.py | 50 + .../eligibility/ut_heat_eligible_person.py | 36 + .../eligibility/ut_heat_household_size.py | 13 + .../heat/eligibility/ut_heat_income_limit.py | 17 + .../heat/income/ut_heat_countable_income.py | 69 + .../gov/states/ut/dwf/heat/ut_heat.py | 65 + .../ut/dwf/heat/ut_heat_dwelling_type.py | 17 + .../dwf/heat/ut_heat_energy_cost_standard.py | 41 + .../gov/states/ut/dwf/heat/ut_heat_fpg.py | 21 + 38 files changed, 3450 insertions(+), 1 deletion(-) create mode 100644 changelog.d/ut-liheap-wave2b.added.md create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/burden_point_amount.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit_types.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/income_component.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/maximum.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/maximum_burden.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/minimum.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/monthly_energy_cost_standard.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/rounding_increment.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/target/amount.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/target/child_age.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/target/elderly_age.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/adult_age.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/immigration/conditional_statuses.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/immigration/eligible_statuses.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/immigration/waiting_period.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/income_limit.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/income/business_income_sources.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/income/child_income_age.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/income/student_income_age.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible_person.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_household_size.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_income_limit.yaml create mode 100644 policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_eligible.py create mode 100644 policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_eligible_person.py create mode 100644 policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_household_size.py create mode 100644 policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_income_limit.py create mode 100644 policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py create mode 100644 policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py create mode 100644 policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_dwelling_type.py create mode 100644 policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_energy_cost_standard.py create mode 100644 policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_fpg.py diff --git a/changelog.d/ut-liheap-wave2b.added.md b/changelog.d/ut-liheap-wave2b.added.md new file mode 100644 index 00000000000..87067938db9 --- /dev/null +++ b/changelog.d/ut-liheap-wave2b.added.md @@ -0,0 +1 @@ +Add Utah HEAT regular heating assistance, with household eligibility, countable income, fuel and dwelling standards, and targeted supplements verified for the March 2026 policy snapshot. diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/burden_point_amount.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/burden_point_amount.yaml new file mode 100644 index 00000000000..5094f9abab0 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/burden_point_amount.yaml @@ -0,0 +1,10 @@ +description: Utah pays this amount for each whole energy burden percentage point under the Home Energy Assistance Target program. +values: + 2026-03-01: 10 +metadata: + unit: currency-USD + period: year + label: Utah HEAT amount per energy burden percentage point + reference: + - title: Utah HEAT Benefit Calculation Worksheet, Section 2 + href: https://jobs.utah.gov/infosource/heatpolicy/assets/docs/Training/FY26/3.1.26%20Benefit%20Calculation%20Worksheet.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit.yaml new file mode 100644 index 00000000000..5bb1cf1ca07 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit.yaml @@ -0,0 +1,10 @@ +description: Utah adds this amount for primary propane or fuel oil heating under the Home Energy Assistance Target program. +values: + 2023-03-01: 150 +metadata: + unit: currency-USD + period: year + label: Utah HEAT propane or fuel oil credit + reference: + - title: Utah HEAT Manual, Section 605 Propane or Fuel Oil + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/605_Propane_or_Fuel_Oil.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit_types.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit_types.yaml new file mode 100644 index 00000000000..df6ca996579 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit_types.yaml @@ -0,0 +1,12 @@ +description: Utah grants an additional benefit for these primary heating fuels under the Home Energy Assistance Target program. +values: + 2023-03-01: + - PROPANE + - FUEL_OIL +metadata: + unit: list + period: year + label: Utah HEAT fuels receiving an additional benefit + reference: + - title: Utah HEAT Manual, Section 605 Propane or Fuel Oil + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/605_Propane_or_Fuel_Oil.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/income_component.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/income_component.yaml new file mode 100644 index 00000000000..f13e657d873 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/income_component.yaml @@ -0,0 +1,10 @@ +description: Utah starts the income component at this amount before subtracting the poverty percentage under the Home Energy Assistance Target program. +values: + 2026-03-01: 250 +metadata: + unit: currency-USD + period: year + label: Utah HEAT initial income component + reference: + - title: Utah HEAT Benefit Calculation Worksheet, Section 1 + href: https://jobs.utah.gov/infosource/heatpolicy/assets/docs/Training/FY26/3.1.26%20Benefit%20Calculation%20Worksheet.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/maximum.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/maximum.yaml new file mode 100644 index 00000000000..eb4c63d5178 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/maximum.yaml @@ -0,0 +1,10 @@ +description: Utah provides up to this regular heating benefit to an eligible household under the Home Energy Assistance Target program. +values: + 2025-10-01: 800 +metadata: + unit: currency-USD + period: year + label: Utah HEAT maximum regular benefit + reference: + - title: Utah FY2026 LIHEAP State Plan, Section 2.6 + href: https://liheapch.acf.gov/docs/2026/state-plans/UT_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/maximum_burden.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/maximum_burden.yaml new file mode 100644 index 00000000000..15910949c48 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/maximum_burden.yaml @@ -0,0 +1,10 @@ +description: Utah caps the energy burden at this share of countable income under the Home Energy Assistance Target program. +values: + 2024-03-01: 0.25 +metadata: + unit: /1 + period: year + label: Utah HEAT maximum energy burden + reference: + - title: Utah HEAT Manual, Section 603 Energy Burden + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/603_Energy_Burden.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/minimum.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/minimum.yaml new file mode 100644 index 00000000000..abfb723d931 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/minimum.yaml @@ -0,0 +1,10 @@ +description: Utah provides at least this regular heating benefit to an eligible household under the Home Energy Assistance Target program. +values: + 2025-10-01: 140 +metadata: + unit: currency-USD + period: year + label: Utah HEAT minimum regular benefit + reference: + - title: Utah FY2026 LIHEAP State Plan, Section 2.6 + href: https://liheapch.acf.gov/docs/2026/state-plans/UT_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/monthly_energy_cost_standard.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/monthly_energy_cost_standard.yaml new file mode 100644 index 00000000000..795a809f7be --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/monthly_energy_cost_standard.yaml @@ -0,0 +1,37 @@ +description: Utah assigns these monthly energy cost standards by dwelling category and fuel under the Home Energy Assistance Target program. +HOUSE: + natural_gas: + 2023-03-01: 211 + propane: + 2023-03-01: 541 + fuel_oil: + 2023-03-01: 468 + electricity: + 2023-03-01: 287 + coal: + 2023-03-01: 92 + wood: + 2023-03-01: 343 +APARTMENT: + natural_gas: + 2023-03-01: 161 + propane: + 2023-03-01: 408 + fuel_oil: + 2023-03-01: 354 + electricity: + 2023-03-01: 218 + coal: + 2023-03-01: 71 + wood: + 2023-03-01: 260 +# Section 609 groups kerosene with propane and other/steam with coal. +metadata: + unit: currency-USD + period: month + label: Utah HEAT monthly energy cost standard + breakdown: + - ut_heat_dwelling_type + reference: + - title: Utah HEAT Manual, Section 609 Table III Monthly Energy Cost Standards + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/609_Table_III_Monthly_Energy_Cost_Standards.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/rounding_increment.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/rounding_increment.yaml new file mode 100644 index 00000000000..ebdd655d611 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/rounding_increment.yaml @@ -0,0 +1,12 @@ +description: Utah rounds the poverty and energy burden ratios in increments of this share under the Home Energy Assistance Target program. +values: + 2024-03-01: 0.01 +metadata: + unit: /1 + period: year + label: Utah HEAT ratio rounding increment + reference: + - title: Utah HEAT Manual, Section 602 Percent of Poverty + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/602_Percent_of_Poverty.htm + - title: Utah HEAT Manual, Section 603 Energy Burden + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/603_Energy_Burden.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/target/amount.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/target/amount.yaml new file mode 100644 index 00000000000..0f12eff0104 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/target/amount.yaml @@ -0,0 +1,10 @@ +description: Utah adds this amount once for a household containing an eligible target group member under the Home Energy Assistance Target program. +values: + 2025-03-01: 150 +metadata: + unit: currency-USD + period: year + label: Utah HEAT target group credit + reference: + - title: Utah HEAT Manual, Section 604 Target Groups + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_600/Obsolete_604_(3_1_25_to_9_30_26)_Target_Groups.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/target/child_age.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/target/child_age.yaml new file mode 100644 index 00000000000..3ddfc8597a3 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/target/child_age.yaml @@ -0,0 +1,10 @@ +description: Utah includes eligible children below this age in the target group under the Home Energy Assistance Target program. +values: + 2025-03-01: 6 +metadata: + unit: year + period: year + label: Utah HEAT target group child age + reference: + - title: Utah HEAT Manual, Section 604 Target Groups + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_600/Obsolete_604_(3_1_25_to_9_30_26)_Target_Groups.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/target/elderly_age.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/target/elderly_age.yaml new file mode 100644 index 00000000000..916d262c0f8 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/target/elderly_age.yaml @@ -0,0 +1,10 @@ +description: Utah includes eligible people of at least this age in the target group under the Home Energy Assistance Target program. +values: + 2025-03-01: 60 +metadata: + unit: year + period: year + label: Utah HEAT target group elderly age + reference: + - title: Utah HEAT Manual, Section 604 Target Groups + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_600/Obsolete_604_(3_1_25_to_9_30_26)_Target_Groups.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/adult_age.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/adult_age.yaml new file mode 100644 index 00000000000..d781d45cb34 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/adult_age.yaml @@ -0,0 +1,10 @@ +description: Utah requires an applicant of at least this age, except emancipated minors, under the Home Energy Assistance Target program. +values: + 2026-03-01: 18 +metadata: + unit: year + period: year + label: Utah HEAT minimum applicant age + reference: + - title: Utah HEAT Manual, Section 202 Eligibility Standards + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_200/Obsolete_202_(03_01_26_to_09_30_26)_Eligibility_Standards.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/immigration/conditional_statuses.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/immigration/conditional_statuses.yaml new file mode 100644 index 00000000000..99df48a035f --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/immigration/conditional_statuses.yaml @@ -0,0 +1,12 @@ +description: Utah requires an additional eligible criterion for these qualified immigration statuses under the Home Energy Assistance Target program. +values: + 2025-10-01: + - CONDITIONAL_ENTRANT + - PAROLED_ONE_YEAR +metadata: + unit: list + period: year + label: Utah HEAT immigration statuses requiring an additional criterion + reference: + - title: Utah HEAT Manual, Section 203-2B Qualified and Eligible Non-Citizens + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/200_Program_Standards/203_Citizenship_and_Non-Citizenship_Requirements/203-2B_Qualified_and_Eligible_Non-Citizens.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/immigration/eligible_statuses.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/immigration/eligible_statuses.yaml new file mode 100644 index 00000000000..dc12d6ac819 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/immigration/eligible_statuses.yaml @@ -0,0 +1,18 @@ +description: Utah treats these observable citizenship or immigration statuses as eligible without an additional waiting period under the Home Energy Assistance Target program. +values: + 2025-10-01: + - CITIZEN + - LEGAL_PERMANENT_RESIDENT + - REFUGEE + - ASYLEE + - DEPORTATION_WITHHELD + - CUBAN_HAITIAN_ENTRANT +metadata: + unit: list + period: year + label: Utah HEAT eligible citizenship and immigration statuses + reference: + - title: Utah HEAT Manual, Section 203-1 US Citizenship + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/200_Program_Standards/203_Citizenship_and_Non-Citizenship_Requirements/203-1_US_Citizenship.htm + - title: Utah HEAT Manual, Section 203-2B Qualified and Eligible Non-Citizens + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/200_Program_Standards/203_Citizenship_and_Non-Citizenship_Requirements/203-2B_Qualified_and_Eligible_Non-Citizens.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/immigration/waiting_period.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/immigration/waiting_period.yaml new file mode 100644 index 00000000000..cfa9e6a1a79 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/immigration/waiting_period.yaml @@ -0,0 +1,10 @@ +description: Utah treats this many cumulative years in qualified immigration status as an eligible criterion under the Home Energy Assistance Target program. +values: + 2025-10-01: 5 +metadata: + unit: year + period: year + label: Utah HEAT qualified immigration waiting period + reference: + - title: Utah HEAT Manual, Section 203-2B Eligible Non-Citizens + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/200_Program_Standards/203_Citizenship_and_Non-Citizenship_Requirements/203-2B_Qualified_and_Eligible_Non-Citizens.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/income_limit.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/income_limit.yaml new file mode 100644 index 00000000000..7603b423f1c --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/income_limit.yaml @@ -0,0 +1,10 @@ +description: Utah limits countable income to this share of the federal poverty guideline under the Home Energy Assistance Target program. +values: + 2026-03-01: 1.5 +metadata: + unit: /1 + period: year + label: Utah HEAT income limit as a share of the poverty guideline + reference: + - title: Utah HEAT Manual, Section 608 Table II Federal Poverty Level Monthly Income Limits + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/608_Table_II_Federal_Poverty_Level_(FPL)_Monthly_Income_Limits.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/business_income_sources.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/business_income_sources.yaml new file mode 100644 index 00000000000..1bec943486f --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/business_income_sources.yaml @@ -0,0 +1,14 @@ +description: Utah counts these reported business income sources under the Home Energy Assistance Target program. +values: + 2024-10-01: + - self_employment_income + - sstb_self_employment_income + - farm_operations_income + - partnership_s_corp_income +metadata: + unit: list + period: year + label: Utah HEAT business income sources + reference: + - title: Utah HEAT Manual, Section 404 Self-Employment + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/404_Self-Employment/404_Self-Employment.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/child_income_age.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/child_income_age.yaml new file mode 100644 index 00000000000..80adc23e54e --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/child_income_age.yaml @@ -0,0 +1,10 @@ +description: Utah excludes all income of children below this age under the Home Energy Assistance Target program. +values: + 2025-03-01: 18 +metadata: + unit: year + period: year + label: Utah HEAT child income exclusion age + reference: + - title: Utah HEAT Manual, Section 405 Income Exclusions + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_400/Obsolete_405_(3_1_25_to_9_30_26)_Income_Exclusions.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/student_income_age.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/student_income_age.yaml new file mode 100644 index 00000000000..d11eb3e2b07 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/student_income_age.yaml @@ -0,0 +1,10 @@ +description: Utah excludes all income of children remaining in secondary school below this age under the Home Energy Assistance Target program. +values: + 2025-03-01: 19 +metadata: + unit: year + period: year + label: Utah HEAT student income exclusion age + reference: + - title: Utah HEAT Manual, Section 405 Income Exclusions + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_400/Obsolete_405_(3_1_25_to_9_30_26)_Income_Exclusions.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml new file mode 100644 index 00000000000..e82b8c06184 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml @@ -0,0 +1,33 @@ +description: Utah counts these unearned income sources under the Home Energy Assistance Target program. +values: + 2024-10-01: + - social_security + - ssi + - railroad_benefits + - pension_income + - retirement_distributions + - survivor_benefits + - veterans_benefits + - unemployment_compensation + - workers_compensation + - disability_benefits + - strike_benefits + - financial_assistance + - alimony_income + - child_support_received + - ordinary_dividend_income + - estate_income + - gambling_winnings +# The cash contribution input represents private gifts. Section 405 exempts +# TANF/FEP/GA and nonprofit/government aid, ordinary bank interest, educational +# aid and enumerated nonrecurring receipts. Existing inputs do not separately +# represent adoption payments, passive royalties or foster-child elections. +metadata: + unit: list + period: year + label: Utah HEAT unearned income sources + reference: + - title: Utah HEAT Manual, Section 402 Unearned Income + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/402_Unearned_Income.htm + - title: Utah HEAT Manual, Section 405 Income Exclusions + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_400/Obsolete_405_(3_1_25_to_9_30_26)_Income_Exclusions.htm diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 756dc2c4988..2c327e36751 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -802,7 +802,7 @@ programs: category: Benefits agency: HHS status: partial - coverage: OR, DC, Riverside County, MA, IL, KS, MS, TX + coverage: OR, DC, Riverside County, MA, IL, KS, MS, TX, UT state_implementations: - state: OR status: in_progress @@ -839,6 +839,14 @@ programs: parameter_prefix: gov.states.ms.mdhs.liheap verified_years: "2026" notes: FY2026 regular assistance for heating fuel and electric bills, using the published matrix as printed. Crisis assistance and energy equipment are out of scope. + - state: UT + status: partial + name: Utah HEAT + full_name: Utah Home Energy Assistance Target Program + variable: ut_heat + parameter_prefix: gov.states.ut.dwf.heat + verified_years: "2026" + notes: Regular heating assistance using the March 2026 policy snapshot and current-year federal poverty guidelines. Existing annual inputs approximate application-month income, medical/support eligibility and household membership; solar/unknown fuel, finer immigration categories and administrative conditions remain unsupported. Earlier-year backfills are unverified historical estimates; crisis, cooling, weatherization and equipment aid are out of scope. - state: TX status: partial name: Texas CEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml new file mode 100644 index 00000000000..4733f19ba4e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml @@ -0,0 +1,1203 @@ +# Sources: Utah HEAT manual sections 602-605 and 609; FY2026 plan pages 8-9. +# The annual 2026 period represents the agreed March 2026 snapshot. + +# UT-15: income component 250 + zero-income burden 250. +- name: "Case 1, zero income yields the ordinary gas award." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat: 500 + +# UT-15: 250+250+150 target+150 propane=800. +- name: "Case 2, targeted propane household reaches the maximum." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 60 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat: 800 + +# UT-15: I 1330, P 1330; Q 100; gas burden round(15.8647)=16; 150+160=310. +- name: "Case 3, gas house at one hundred percent poverty." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 15_960 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat: 310 + ut_heat_countable_income: 15_960 + ut_heat_household_size: 1 + ut_heat_income_limit: 23_940 + +# UT-14/15: I 1330; apartment 161/1330*100 rounds 12; 150+120=270. +- name: "Case 4, gas apartment uses its lower cost standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 15_960 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: APARTMENT + households: + household: + members: [person1] + state_code: UT + output: + ut_heat: 270 + +# UT-13/15: I 1995; Q 150; 211/1995*100 rounds 11; 100+110=210. +- name: "Case 5, income at the inclusive one person limit." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 23_940 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat: 210 + +# UT-13: monthly 1995.01 exceeds 1995; no benefit minimum for ineligible households. +- name: "Case 6, income twelve cents above the annual limit is ineligible." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 23_940.12 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat: 0 + +# UT-15: I 1331; ceil(100*1331/1330)=101; burden 16; 149+160=309. +- name: "Case 7, poverty percentage rounds upward." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 15_972 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat: 309 + +# UT-15: I 2840, P 2277, Q 125; 100*71/2840=2.5=>3; 125+30=155. +- name: "Case 8, energy burden rounds half upward." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 34_080 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: COAL + heating_expense: 1_200 + ut_heat_dwelling_type: APARTMENT + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 155 + +# UT-15: I 2840.04; Q 125; burden just below 2.5=>2; 125+20=145. +- name: "Case 9, energy burden just below the half point rounds down." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 34_080.48 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: COAL + heating_expense: 1_200 + ut_heat_dwelling_type: APARTMENT + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 145 + +# UT-15: size 14, P 7484, I 11225, Q 150, burden 1; raw 110 is raised to 140. +- name: "Case 10, large household receives the sourced minimum." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 134_700 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person4: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person5: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person6: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person7: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person8: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person9: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person10: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person11: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person12: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person13: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person14: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13, person14] + heating_type: COAL + heating_expense: 1_200 + ut_heat_dwelling_type: APARTMENT + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13, person14] + state_code: UT + output: + ut_heat: 140 + ut_heat_household_size: 14 + ut_heat_income_limit: 134_700 + +# UT-15: zero income 500 plus one 150 target credit. +- name: "Case 11, target credit begins at age sixty." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 60 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat: 650 + +# UT-15: age 59 is below 60. +- name: "Case 12, age fifty nine alone gives no target credit." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 59 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat: 500 + +# UT-15: child 5 is below 6; target credit once. +- name: "Case 13, eligible child under six gives one target credit." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person2: + age: 5 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat: 650 + +# UT-15: child 6 is outside under 6 category. +- name: "Case 14, child at six gives no young child target credit." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person2: + age: 6 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat: 500 + +# UT-15: older, disabled and young members trigger one 150 credit. +- name: "Case 15, multiple target members give only one credit." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 60 + immigration_status: CITIZEN + ssi: 0 + is_disabled: true + person2: + age: 5 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat: 650 + +# UT-15: disability target independent of age. +- name: "Case 16, disabled adult gives target credit." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + is_disabled: true + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat: 650 + +# UT-05/15: ineligible resident excluded from size and target credit. +- name: "Case 17, ineligible older resident gives no target credit." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person2: + age: 65 + immigration_status: UNDOCUMENTED + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat: 500 + ut_heat_household_size: 1 + +# UT-05: size 1; adult income 15960 counts; 310 base+150 eligible child. +- name: "Case 18, eligible child supports mixed status household." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + ssi: 0 + pension_income: 15_960 + person2: + age: 5 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat: 460 + ut_heat_household_size: 1 + ut_heat_countable_income: 15_960 + +# UT-06/15: nonsubsidized heat-in-rent uses regular standard; no actual-bill cap. +- name: "Case 19, ordinary heat in rent qualifies without a direct bill." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + rent: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 0 + ut_heat_dwelling_type: HOUSE + heat_expense_included_in_rent: true + receives_housing_assistance: false + households: + household: + members: [person1] + state_code: UT + output: + ut_heat: 500 + +# UT-16: actual expense establishes responsibility; standards determine grant. +- name: "Case 20, small direct heating bill does not cap the scheduled award." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat: 500 + +# UT-12: 20400-2400-2040=15960, no 20% earnings disregard. +- name: "Case 21, medical and child support deductions reach exact poverty." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 20_400 + other_medical_expenses: 2_400 + child_support_expense: 2_040 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat: 310 + ut_heat_countable_income: 15_960 + +# UT-11/15: countable-income floor 0; maximum 800. +- name: "Case 22, negative net business income does not inflate the maximum." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 60 + immigration_status: CITIZEN + ssi: 0 + self_employment_income: -10_000 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat: 800 + ut_heat_countable_income: 0 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 211, rounded burden 7. Fuel addition 0; apply 140 minimum. +- name: "Case 23, natural gas house standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 188 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 161, rounded burden 5. Fuel addition 0; apply 140 minimum. +- name: "Case 24, natural gas apartment standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: APARTMENT + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 168 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 541, rounded burden 18. Fuel addition 150; apply 140 minimum. +- name: "Case 25, propane house standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: PROPANE + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 448 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 408, rounded burden 14. Fuel addition 150; apply 140 minimum. +- name: "Case 26, propane apartment standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: PROPANE + heating_expense: 1_200 + ut_heat_dwelling_type: APARTMENT + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 408 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 541, rounded burden 18. Fuel addition 0; apply 140 minimum. +- name: "Case 27, kerosene house standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: KEROSENE + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 298 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 408, rounded burden 14. Fuel addition 0; apply 140 minimum. +- name: "Case 28, kerosene apartment standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: KEROSENE + heating_expense: 1_200 + ut_heat_dwelling_type: APARTMENT + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 258 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 468, rounded burden 16. Fuel addition 150; apply 140 minimum. +- name: "Case 29, fuel oil house standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: FUEL_OIL + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 428 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 354, rounded burden 12. Fuel addition 150; apply 140 minimum. +- name: "Case 30, fuel oil apartment standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: FUEL_OIL + heating_expense: 1_200 + ut_heat_dwelling_type: APARTMENT + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 388 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 287, rounded burden 10. Fuel addition 0; apply 140 minimum. +- name: "Case 31, electricity house standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 218 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 218, rounded burden 7. Fuel addition 0; apply 140 minimum. +- name: "Case 32, electricity apartment standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + heating_expense: 1_200 + ut_heat_dwelling_type: APARTMENT + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 188 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 92, rounded burden 3. Fuel addition 0; apply 140 minimum. +- name: "Case 33, coal house standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: COAL + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 148 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 71, rounded burden 2. Fuel addition 0; apply 140 minimum. +- name: "Case 34, coal apartment standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: COAL + heating_expense: 1_200 + ut_heat_dwelling_type: APARTMENT + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 140 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 92, rounded burden 3. Fuel addition 0; apply 140 minimum. +- name: "Case 35, other house standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: OTHER + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 148 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 71, rounded burden 2. Fuel addition 0; apply 140 minimum. +- name: "Case 36, other apartment standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: OTHER + heating_expense: 1_200 + ut_heat_dwelling_type: APARTMENT + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 140 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 343, rounded burden 11. Fuel addition 0; apply 140 minimum. +- name: "Case 37, wood house standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: WOOD + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 228 + +# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 260, rounded burden 9. Fuel addition 0; apply 140 minimum. +- name: "Case 38, wood apartment standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: WOOD + heating_expense: 1_200 + ut_heat_dwelling_type: APARTMENT + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat: 208 + +# UT-05/15: separate ordinary gas, targeted propane, mixed-status young-child, and no-heating cases in one vector. +- name: "Case 39, mixed households preserve separate formulas and eligibility." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + employment_income: 15_960 + ssi: 0 + person2: + age: 60 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: UNDOCUMENTED + pension_income: 15_960 + ssi: 0 + person4: + age: 5 + immigration_status: CITIZEN + ssi: 0 + person5: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit1: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + spm_unit2: + members: [person2] + heating_type: PROPANE + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + spm_unit3: + members: [person3, person4] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + spm_unit4: + members: [person5] + heating_type: NONE + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household1: + members: [person1] + state_code: UT + household2: + members: [person2] + state_code: UT + household3: + members: [person3, person4] + state_code: UT + household4: + members: [person5] + state_code: UT + output: + ut_heat: [310, 800, 460, 0] + ut_heat_household_size: [1, 1, 1, 1] + ut_heat_countable_income: [15_960, 0, 15_960, 0] + ut_heat_eligible: [true, true, true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml new file mode 100644 index 00000000000..c982eb51c69 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml @@ -0,0 +1,570 @@ +# Sources: Utah HEAT sections 401-407 and archived March 2026 sections 405/407. +# Section 406 rescinded the general 20% earnings disregard. +# Medicare and prior-period business-loss reconstruction remain outside scope. + +# UT-08; section 406 rescinds disregard, the contrary line in the older matrix is not used. +- name: "Case 1, gross earnings have no twenty percent disregard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 18_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 18_000 + +# UT-10: exclusion covers earned and unearned child income. +- name: "Case 2, all income of a seventeen year old is excluded." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 15_960 + person2: + age: 17 + immigration_status: CITIZEN + ssi: 1_200 + employment_income: 12_000 + pension_income: 2_400 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat_countable_income: 15_960 + +# UT-10: age 18 remains in school. +- name: "Case 3, eighteen year old secondary student income is excluded." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 15_960 + person2: + age: 18 + immigration_status: CITIZEN + ssi: 0 + is_in_secondary_school: true + employment_income: 12_000 + pension_income: 2_400 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat_countable_income: 15_960 + +# UT-10: 15960+12000+2400; the exclusion ends when the person is not in school. +- name: "Case 4, eighteen year old out of school income counts." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 15_960 + person2: + age: 18 + immigration_status: CITIZEN + ssi: 0 + is_in_secondary_school: false + is_in_k12_school: false + employment_income: 12_000 + pension_income: 2_400 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat_countable_income: 30_360 + +# UT-10: the student exception ends after 18. +- name: "Case 5, nineteen year old student income counts." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 15_960 + person2: + age: 19 + immigration_status: CITIZEN + ssi: 0 + is_in_secondary_school: true + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat_countable_income: 27_960 + +# UT-10: support received is parent income. +- name: "Case 6, parent child support counts despite child income exclusion." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + child_support_received: 3_600 + person2: + age: 10 + immigration_status: CITIZEN + ssi: 0 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat_countable_income: 3_600 + +# UT-05: count income of all age-eligible members; no general 20% disregard. +- name: "Case 7, income of ineligible resident counts." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 12_000 + person2: + age: 40 + immigration_status: UNDOCUMENTED + ssi: 0 + employment_income: 6_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat_countable_income: 18_000 + +# UT-10: DWS government assistance and ordinary bank interest are exempt. +- name: "Case 8, FEP general assistance and bank interest are excluded." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + general_assistance: 12_000 + interest_income: 1_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + ut_fep: 6_000 + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 0 + +# UT-09: recurring cash gifts and ordinary dividends count; no excluded agency classification applies. +- name: "Case 9, ordinary dividends and cash contributions count." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + ordinary_dividend_income: 1_200 + financial_assistance: 2_400 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 3_600 + +# UT-09: annual SSI input 2400=200/month; count gross SSA and pension income. +- name: "Case 10, gross social security SSI and pension count once." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 2_400 + social_security: 12_000 + pension_income: 3_600 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 18_000 + +# UT-09: 1200+2400+3600+4800. +- name: "Case 11, supported unearned benefit sources count." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + unemployment_compensation: 1_200 + workers_compensation: 2_400 + veterans_benefits: 3_600 + alimony_income: 4_800 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 12_000 + +# UT-11: no additional percentage business deduction. +- name: "Case 12, net business and rental income use supplied net amounts." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + self_employment_income: 12_000 + rental_income: 3_600 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 15_600 + +# UT-11: user-authorized nonnegative rental income. +- name: "Case 13, negative rent does not offset earnings." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 15_960 + rental_income: -6_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 15_960 + +# UT-12: qualified paid expenses 20400-2400-2040. +- name: "Case 14, medical and support payments are deductible." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 20_400 + other_medical_expenses: 2_400 + child_support_expense: 2_040 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 15_960 + +# UT-12: medical payment by an excluded member is allowed; that member's income also counts. +- name: "Case 15, medical payments by ineligible member remain deductible." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 15_960 + person2: + age: 40 + immigration_status: UNDOCUMENTED + ssi: 0 + employment_income: 6_000 + other_medical_expenses: 6_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat_countable_income: 15_960 + +# Contract medical input rule: the supplied generic premium 1200 takes precedence over the component fallback 3600. +- name: "Case 16, reported premiums are not counted twice." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 18_000 + health_insurance_premiums: 1_200 + health_insurance_premiums_without_medicare_part_b: 3_600 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 16_800 + +# UT-12/contract: 18000-1200-840; no automatically derived Medicare amount. +- name: "Case 17, supplied non Medicare premiums are the fallback." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 18_000 + health_insurance_premiums: 0 + health_insurance_premiums_without_medicare_part_b: 1_200 + other_medical_expenses: 840 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 15_960 + +# UT-12: qualifying alimony paid outside the unit. +- name: "Case 18, alimony paid is deductible." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 18_400 + alimony_expense: 2_440 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 15_960 + +# UT-15 countable income is nonnegative. +- name: "Case 19, deductions floor countable income at zero." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 1_000 + other_medical_expenses: 2_400 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 0 + +# UT-12 enumerated deductions do not include ordinary child care. +- name: "Case 20, childcare is not a general qualified deduction." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 15_960 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + childcare_expenses: 2_400 + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 15_960 + +# UT-11/contract: signed current net business 15960-3960=12000; prior-period loss reconstruction unavailable. +- name: "Case 21, current net business loss offsets other income." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 15_960 + self_employment_income: -3_960 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 12_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml new file mode 100644 index 00000000000..a282dde2e04 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml @@ -0,0 +1,328 @@ +# Sources: Utah HEAT manual sections 202-204, 208 and March 2026 section 608. +# The annual 2026 period represents the agreed March 2026 snapshot. + +# UT-13: monthly income 1995 equals published 150% limit. +- name: "Case 1, inclusive income limit." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 23_940 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible: true + +# UT-13: monthly income 1995.01 exceeds limit. +- name: "Case 2, income just above limit." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 23_940.12 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible: false + +# UT-04: requires adult 18+; emancipation unavailable. +- name: "Case 3, no adult member." + period: 2026 + input: + people: + person1: + age: 17 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible: false + +# UT-04: adult threshold is inclusive 18. +- name: "Case 4, adult age eighteen." + period: 2026 + input: + people: + person1: + age: 18 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible: true + +# UT-04/05: at least one eligible person. +- name: "Case 5, no qualified member." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible: false + +# UT-05: an eligible child can qualify unit; the adult need not be eligible. +- name: "Case 6, citizen child with ineligible adult." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + ssi: 0 + person2: + age: 5 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat_eligible: true + +# UT-04: Utah residence required. +- name: "Case 7, wrong state." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: ID + output: + ut_heat_eligible: false + +# UT-06: NONE affirmatively denotes no home heat. +- name: "Case 8, no heating source." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NONE + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible: false + +# UT-06: no supplied responsibility. +- name: "Case 9, no energy costs or rent responsibility." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 0 + ut_heat_dwelling_type: HOUSE + pre_subsidy_electricity_expense: 0 + heat_expense_included_in_rent: false + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible: false + +# UT-06: households supplying own firewood are eligible. +- name: "Case 10, self supplied firewood qualifies." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: WOOD + heating_expense: 0 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible: true + +# UT-06: indirect energy responsibility. +- name: "Case 11, ordinary heat in rent." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + rent: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 0 + ut_heat_dwelling_type: HOUSE + heat_expense_included_in_rent: true + receives_housing_assistance: false + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible: true + +# UT-06: utility costs are wholly covered. +- name: "Case 12, subsidized rent with all utilities covered." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 0 + ut_heat_dwelling_type: HOUSE + heat_expense_included_in_rent: true + pre_subsidy_electricity_expense: 0 + receives_housing_assistance: true + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible: false + +# UT-06: separately paid utility in addition to subsidized rent. +- name: "Case 13, subsidized rent with separately paid electricity." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 0 + ut_heat_dwelling_type: HOUSE + heat_expense_included_in_rent: true + pre_subsidy_electricity_expense: 600 + receives_housing_assistance: true + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible: true + +# UT-06: subsidized housing does not itself bar utility responsibility. +- name: "Case 14, subsidized household directly pays heating." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + receives_housing_assistance: true + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible_person.yaml new file mode 100644 index 00000000000..f48049a511b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible_person.yaml @@ -0,0 +1,434 @@ +# Source: Utah HEAT section 203-2B, effective October 1, 2025. +# LPR is separately eligible; parole/conditional entry needs an eligible criterion. +# Service exceptions use actual marital, tax and family relationships. + +# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +- name: "Case 1, citizen." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + years_since_us_entry: 0 + is_veteran: false + is_military: false + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible_person: true + +# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +- name: "Case 2, recent permanent resident." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: LEGAL_PERMANENT_RESIDENT + ssi: 0 + years_since_us_entry: 0 + is_veteran: false + is_military: false + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible_person: true + +# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +- name: "Case 3, refugee." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: REFUGEE + ssi: 0 + years_since_us_entry: 0 + is_veteran: false + is_military: false + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible_person: true + +# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +- name: "Case 4, asylee." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: ASYLEE + ssi: 0 + years_since_us_entry: 0 + is_veteran: false + is_military: false + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible_person: true + +# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +- name: "Case 5, deportation withheld." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: DEPORTATION_WITHHELD + ssi: 0 + years_since_us_entry: 0 + is_veteran: false + is_military: false + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible_person: true + +# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +- name: "Case 6, Cuban Haitian entrant." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CUBAN_HAITIAN_ENTRANT + ssi: 0 + years_since_us_entry: 0 + is_veteran: false + is_military: false + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible_person: true + +# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +- name: "Case 7, parole below five years." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: PAROLED_ONE_YEAR + ssi: 0 + years_since_us_entry: 4.99 + is_veteran: false + is_military: false + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible_person: false + +# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +- name: "Case 8, parole at five years." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: PAROLED_ONE_YEAR + ssi: 0 + years_since_us_entry: 5 + is_veteran: false + is_military: false + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible_person: true + +# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +- name: "Case 9, conditional entrant below five years." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CONDITIONAL_ENTRANT + ssi: 0 + years_since_us_entry: 4.99 + is_veteran: false + is_military: false + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible_person: false + +# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +- name: "Case 10, conditional entrant at five years." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CONDITIONAL_ENTRANT + ssi: 0 + years_since_us_entry: 5 + is_veteran: false + is_military: false + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible_person: true + +# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +- name: "Case 11, undocumented resident." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + ssi: 0 + years_since_us_entry: 10 + is_veteran: false + is_military: false + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible_person: false + +# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +- name: "Case 12, DACA recipient." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: DACA + ssi: 0 + years_since_us_entry: 10 + is_veteran: false + is_military: false + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible_person: false + +# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +- name: "Case 13, TPS recipient." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: TPS + ssi: 0 + years_since_us_entry: 10 + is_veteran: false + is_military: false + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible_person: false + +# UT-05: the qualifying veteran exception has no time limit. +- name: "Case 14, recent parolee who is a veteran." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: PAROLED_ONE_YEAR + ssi: 0 + years_since_us_entry: 0 + is_veteran: true + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible_person: true + +# UT-05: an actual marital-unit connection; no synthetic SPM-wide veteran flag. +- name: "Case 15, actual spouse of veteran qualifies without five years." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + is_veteran: true + is_tax_unit_head: true + person2: + age: 38 + immigration_status: PAROLED_ONE_YEAR + ssi: 0 + years_since_us_entry: 0 + is_tax_unit_spouse: true + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + marital_units: + marital_unit: + members: [person1, person2] + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + output: + ut_heat_eligible_person: [true, true] + +# UT-05: share an SPM unit and household but have separate family, tax, and marital units. +- name: "Case 16, unrelated veteran does not confer immigration eligibility." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + is_veteran: true + is_tax_unit_head: true + person2: + age: 38 + immigration_status: PAROLED_ONE_YEAR + ssi: 0 + years_since_us_entry: 0 + is_tax_unit_head: true + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + marital_units: + marital_unit1: + members: [person1] + marital_unit2: + members: [person2] + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2] + families: + family1: + members: [person1] + family2: + members: [person2] + output: + ut_heat_eligible_person: [true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_household_size.yaml new file mode 100644 index 00000000000..399c55f1def --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_household_size.yaml @@ -0,0 +1,61 @@ +# Source: Utah HEAT section 204; exclude ineligible persons from household size. + +# UT-05: only the eligible child and refugee count. +- name: "Case 1, only qualified members enter size." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + ssi: 0 + person2: + age: 5 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: TPS + ssi: 0 + person4: + age: 40 + immigration_status: REFUGEE + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2, person3, person4] + state_code: UT + output: + ut_heat_household_size: 2 + +# UT-05: no eligible member. +- name: "Case 2, no eligible members gives zero size." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + ssi: 0 + person2: + age: 40 + immigration_status: DACA + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat_household_size: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_income_limit.yaml new file mode 100644 index 00000000000..eb5e599963a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_income_limit.yaml @@ -0,0 +1,237 @@ +# Source: Utah HEAT section 608 and the March 1, 2026 poverty table. +# Annual thresholds are 1.5 * (15,960 + 5,680 * (eligible members - 1)). + +# UT-13: 1.5*(15960+5680*(size-1)); above 14 published monthly increment 710. +- name: "Case 1, published March limit for 1 eligible members." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_income_limit: 23_940 + +# UT-13: 1.5*(15960+5680*(size-1)); above 14 published monthly increment 710. +- name: "Case 2, published March limit for 2 eligible members." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat_income_limit: 32_460 + +# UT-13: 1.5*(15960+5680*(size-1)); above 14 published monthly increment 710. +- name: "Case 3, published March limit for 3 eligible members." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat_income_limit: 40_980 + +# UT-13: 1.5*(15960+5680*(size-1)); above 14 published monthly increment 710. +- name: "Case 4, published March limit for 14 eligible members." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person4: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person5: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person6: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person7: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person8: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person9: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person10: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person11: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person12: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person13: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person14: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13, person14] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13, person14] + state_code: UT + output: + ut_heat_income_limit: 134_700 + +# UT-13: 1.5*(15960+5680*(size-1)); above 14 published monthly increment 710. +- name: "Case 5, published March limit for 15 eligible members." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person4: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person5: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person6: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person7: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person8: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person9: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person10: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person11: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person12: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person13: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person14: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person15: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13, person14, person15] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13, person14, person15] + state_code: UT + output: + ut_heat_income_limit: 143_220 diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_eligible.py b/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_eligible.py new file mode 100644 index 00000000000..b720e7e016b --- /dev/null +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_eligible.py @@ -0,0 +1,50 @@ +from policyengine_us.model_api import * + + +class ut_heat_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Utah HEAT regular heating assistance eligibility" + defined_for = StateCode.UT + reference = ( + "https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_200/Obsolete_202_(03_01_26_to_09_30_26)_Eligibility_Standards.htm", + "https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_200/Obsolete_208_(03_01_26_to_09_30_26)_Vulnerability.htm", + "https://liheapch.acf.gov/docs/2026/state-plans/UT_Plan_2026.pdf#page=8", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ut.dwf.heat.eligibility + has_adult = spm_unit.any(spm_unit.members("age", period) >= p.adult_age) + size = spm_unit("ut_heat_household_size", period) + income = spm_unit("ut_heat_countable_income", period) + limit = spm_unit("ut_heat_income_limit", period) + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + subsidized = spm_unit( + "receives_housing_assistance", period + ) | spm_unit.household("is_in_public_housing", period) + directly_paid = ( + spm_unit("has_heating_expense", period) + | (spm_unit("heating_expense", period) > 0) + | (spm_unit("pre_subsidy_electricity_expense", period) > 0) + ) + fuel = spm_unit("heating_type", period) + own_wood = fuel == fuel.possible_values.WOOD + responsible = directly_paid | (heat_in_rent & ~subsidized) | own_wood + housing_eligible = ~subsidized | directly_paid + supported_fuel = spm_unit("ut_heat_energy_cost_standard", period) > 0 + # The annual potential award assumes no prior seasonal payment or + # disqualifying utility credit balance. Emancipation, institutions, + # tribal duplicate assistance and special RV rules are unobserved. + # Self-supplied firewood qualifies without a bill. Subsidized housing + # requires a separately paid utility; subsidies alone are not expenses. + # The existing heating-cost flag can supply that separate obligation + # when the primary fuel bill does not capture it. + return ( + (size > 0) + & has_adult + & (income <= limit) + & responsible + & housing_eligible + & supported_fuel + ) diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_eligible_person.py b/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_eligible_person.py new file mode 100644 index 00000000000..8736f27a7a4 --- /dev/null +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_eligible_person.py @@ -0,0 +1,36 @@ +from policyengine_us.model_api import * + + +class ut_heat_eligible_person(Variable): + value_type = bool + entity = Person + definition_period = YEAR + label = "Meets the Utah HEAT citizenship or immigration requirement" + defined_for = StateCode.UT + reference = "https://jobs.utah.gov/infosource/heatpolicy/Policy/200_Program_Standards/203_Citizenship_and_Non-Citizenship_Requirements/203-2B_Qualified_and_Eligible_Non-Citizens.htm" + + def formula(person, period, parameters): + p = parameters(period).gov.states.ut.dwf.heat.eligibility.immigration + status = person("immigration_status", period).decode_to_str() + eligible_status = np.isin(status, p.eligible_statuses) + conditional_status = np.isin(status, p.conditional_statuses) + waited = person("years_since_us_entry", period) >= p.waiting_period + served = person("is_veteran", period) | person("is_military", period) + # Marital units identify spouses; tax-unit roles and the child link + # identify dependent children. Unrelated household members do not confer + # the exception. Supply these units/links explicitly for complex families. + self_or_spouse_served = person.marital_unit.any(served) + dependent_child = person("is_tax_unit_dependent", period) & person( + "is_child_of_tax_head", period + ) + parent_served = person.tax_unit.any( + served & person("is_tax_unit_head_or_spouse", period) + ) + military_connection = self_or_spouse_served | (dependent_child & parent_served) + # The existing service flags do not distinguish honorable discharge or + # full-time active duty from National Guard service. The entry clock + # stands for years in qualified status, not merely physical presence. + # COFA, trafficking, battered and special Afghan/Ukrainian statuses are + # not separately observable. LPR is expressly eligible in section 203-2B; + # SNAP's broader waiting-period and child-age rules are not imported. + return eligible_status | (conditional_status & (waited | military_connection)) diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_household_size.py b/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_household_size.py new file mode 100644 index 00000000000..7960734ad38 --- /dev/null +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_household_size.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class ut_heat_household_size(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Utah HEAT eligible household size" + defined_for = StateCode.UT + reference = "https://jobs.utah.gov/infosource/heatpolicy/Policy/200_Program_Standards/204_Eligibility_with_Ineligible_Resident.htm" + # SPM units approximate the shared residence and energy-cost unit. Temporary + # guests, absences, shared custody and the foster-child election are unobserved. + adds = ["ut_heat_eligible_person"] diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_income_limit.py b/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_income_limit.py new file mode 100644 index 00000000000..078cd3ccbd0 --- /dev/null +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_income_limit.py @@ -0,0 +1,17 @@ +from policyengine_us.model_api import * + + +class ut_heat_income_limit(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Utah HEAT annual income limit" + defined_for = StateCode.UT + reference = "https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/608_Table_II_Federal_Poverty_Level_(FPL)_Monthly_Income_Limits.htm" + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ut.dwf.heat.eligibility + # The published 150% column uses unrounded annual FPG, rather than + # multiplying the upward-rounded monthly 100% benefit denominator. + return spm_unit("ut_heat_fpg", period) * p.income_limit diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py b/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py new file mode 100644 index 00000000000..8ab8f06f2db --- /dev/null +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py @@ -0,0 +1,69 @@ +from policyengine_us.model_api import * + + +class ut_heat_countable_income(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Utah HEAT annual countable household income" + defined_for = StateCode.UT + reference = ( + "https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/401_Income/401_Income.htm", + "https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/402_Unearned_Income.htm", + "https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/404_Self-Employment/404_Self-Employment.htm", + "https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_400/Obsolete_405_(3_1_25_to_9_30_26)_Income_Exclusions.htm", + "https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_400/Obsolete_407_(3_1_26_to_9_30_26)_Qualified_Deductions.htm", + "https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/406_No_Longer_Applicable.htm", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ut.dwf.heat.income + person = spm_unit.members + age = person("age", period) + in_school = person("is_in_k12_school", period) | person( + "is_in_secondary_school", period + ) + excluded_child = (age < p.child_income_age) | ( + (age < p.student_income_age) & in_school + ) + # Annual inputs approximate the preceding eligibility month. All + # otherwise countable adult income counts regardless of immigration. + # Section 406 rescinded the general 20% disregard; section 401 and the + # March 2026 worksheet prevail over the stale section 204/XLSX line. + wages = max_(person("employment_income", period), 0) + business = add(person, period, p.business_income_sources) + rental = max_(person("rental_income", period), 0) + max_( + person("farm_rent_income", period), 0 + ) + # Existing net business inputs stand for current net amounts. They do + # not reconstruct HEAT expenses or isolate disallowed prior-period + # losses. Current reported losses enter the household total; rental + # losses do not. No business expense or insurance is deducted twice. + unearned = max_(add(person, period, p.unearned_income_sources), 0) + gross = spm_unit.sum( + where(excluded_child, 0, wages + business + rental + unearned) + ) + # Child support belongs on the receiving parent. TANF/FEP/GA, ordinary + # bank interest, educational aid and exempt nonrecurring receipts are + # excluded. Broad inputs cannot isolate all exempt gifts, settlements, + # combat pay, third-party payments, or cash withdrawals from trusts. + premiums = person("health_insurance_premiums", period) + reported_premiums = where( + premiums > 0, + premiums, + person("health_insurance_premiums_without_medicare_part_b", period), + ) + medical = spm_unit.sum( + max_(reported_premiums, 0) + + max_(person("other_medical_expenses", period), 0) + ) + support_paid = add( + spm_unit, period, ["child_support_expense", "alimony_expense"] + ) + # Reported medical costs approximate qualified uncompensated payments; + # Medicare premiums are not derived. Self-employed premiums must be + # included only once. Support to an included child, payment timing and + # medical-purpose restrictions are not identified by these inputs. + # The seasonal equipment deduction is outside the heating-bill scope. + return max_(gross - medical - max_(support_paid, 0), 0) diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py new file mode 100644 index 00000000000..e37da7d82af --- /dev/null +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py @@ -0,0 +1,65 @@ +from policyengine_us.model_api import * + + +class ut_heat(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Utah Home Energy Assistance Target regular heating benefit" + defined_for = "ut_heat_eligible" + reference = ( + "https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/602_Percent_of_Poverty.htm", + "https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/603_Energy_Burden.htm", + "https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_600/Obsolete_604_(3_1_25_to_9_30_26)_Target_Groups.htm", + "https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/605_Propane_or_Fuel_Oil.htm", + "https://liheapch.acf.gov/docs/2026/state-plans/UT_Plan_2026.pdf#page=8,9", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ut.dwf.heat.benefit + income = ( + spm_unit("ut_heat_countable_income", period).astype(float) / MONTHS_IN_YEAR + ) + poverty = np.ceil( + spm_unit("ut_heat_fpg", period).astype(float) / MONTHS_IN_YEAR + ) + poverty_percentage = np.divide( + income / p.rounding_increment, + poverty, + out=np.zeros_like(income), + where=poverty > 0, + ) + # Sections 602/603 require numeric rounding, not merely the stale + # XLSX's zero-decimal display. Percentage-point size is the policy's + # rounding increment; 0.5 implements nearest-whole, ties upward. + income_points = p.income_component - np.ceil(poverty_percentage) + standard = spm_unit("ut_heat_energy_cost_standard", period) / MONTHS_IN_YEAR + burden_percentage = np.divide( + standard / p.rounding_increment, + income, + out=np.zeros_like(income), + where=income > 0, + ) + burden_points = np.floor(burden_percentage + 0.5) + capped_burden = min_(burden_points, p.maximum_burden / p.rounding_increment) + burden_component = where( + income > 0, + capped_burden * p.burden_point_amount, + p.maximum_burden / p.rounding_increment * p.burden_point_amount, + ) + person = spm_unit.members + age = person("age", period) + target_member = person("ut_heat_eligible_person", period) & ( + (age < p.target.child_age) + | (age >= p.target.elderly_age) + | person("is_disabled", period) + ) + target_credit = spm_unit.any(target_member) * p.target.amount + fuel = spm_unit("heating_type", period) + fuel_credit = np.isin(fuel.decode_to_str(), p.fuel_credit_types) * p.fuel_credit + # Kerosene shares propane's cost standard, but section 605 names only + # propane and fuel oil for this addition. No actual-expense cap applies. + # Funding-dependent extra supplements and equipment/crisis are excluded. + amount = income_points + burden_component + target_credit + fuel_credit + return clip(amount, p.minimum, p.maximum) diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_dwelling_type.py b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_dwelling_type.py new file mode 100644 index 00000000000..b4efd92e2f0 --- /dev/null +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_dwelling_type.py @@ -0,0 +1,17 @@ +from policyengine_us.model_api import * + + +class UtHeatDwellingType(Enum): + APARTMENT = "Apartment, condominium, townhouse, or small trailer home" + HOUSE = "House, mobile home, basement apartment, or duplex" + + +class ut_heat_dwelling_type(Variable): + value_type = Enum + entity = SPMUnit + possible_values = UtHeatDwellingType + default_value = UtHeatDwellingType.HOUSE + definition_period = YEAR + label = "Utah HEAT dwelling category" + defined_for = StateCode.UT + reference = "https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/609_Table_III_Monthly_Energy_Cost_Standards.htm" diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_energy_cost_standard.py b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_energy_cost_standard.py new file mode 100644 index 00000000000..5f2525bdc1f --- /dev/null +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_energy_cost_standard.py @@ -0,0 +1,41 @@ +from policyengine_us.model_api import * + + +class ut_heat_energy_cost_standard(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Utah HEAT annual energy cost standard" + defined_for = StateCode.UT + reference = "https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/609_Table_III_Monthly_Energy_Cost_Standards.htm" + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ut.dwf.heat.benefit + dwelling = spm_unit("ut_heat_dwelling_type", period) + standard = p.monthly_energy_cost_standard[dwelling] + fuel = spm_unit("heating_type", period) + fuels = fuel.possible_values + monthly_standard = select( + [ + fuel == fuels.NATURAL_GAS, + (fuel == fuels.PROPANE) | (fuel == fuels.KEROSENE), + fuel == fuels.FUEL_OIL, + fuel == fuels.ELECTRICITY, + (fuel == fuels.COAL) | (fuel == fuels.OTHER), + fuel == fuels.WOOD, + ], + [ + standard.natural_gas, + standard.propane, + standard.fuel_oil, + standard.electricity, + standard.coal, + standard.wood, + ], + # UNSPECIFIED and SOLAR have no verified schedule; zero denotes + # unsupported model coverage, not a legal zero-cost standard. + # NONE means no home heating and never qualifies. + default=0, + ) + return monthly_standard * MONTHS_IN_YEAR diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_fpg.py b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_fpg.py new file mode 100644 index 00000000000..fc794371401 --- /dev/null +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_fpg.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class ut_heat_fpg(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Federal poverty guideline for the Utah HEAT household" + defined_for = StateCode.UT + reference = "https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/608_Table_II_Federal_Poverty_Level_(FPL)_Monthly_Income_Limits.htm" + + def formula(spm_unit, period, parameters): + size = spm_unit("ut_heat_household_size", period) + state_group = spm_unit.household("state_group_str", period) + # Annual 2026 represents the March 2026 snapshot: current-year FPG. + # Application dates and the March/October transitions are not observed. + # Parameter backfilling does not verify other historical or future years. + amount = fpg(max_(size, 1), state_group, period, parameters, year_lag=0) + return where(size > 0, amount, 0) From d8288c9b430e41982df3ac33f5e731c7815ddb2d Mon Sep 17 00:00:00 2001 From: Ziming Date: Sat, 3 Oct 2026 19:52:41 -0400 Subject: [PATCH 02/10] Count reported Part B premiums in Utah HEAT --- .../gov/states/ut/dwf/heat/ut_heat.yaml | 135 ++++++++++++++++++ .../ut/dwf/heat/ut_heat_countable_income.yaml | 88 ++++++++++++ .../heat/income/ut_heat_countable_income.py | 8 +- 3 files changed, 230 insertions(+), 1 deletion(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml index 4733f19ba4e..45aaa0f2c71 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml @@ -1201,3 +1201,138 @@ ut_heat_household_size: [1, 1, 1, 1] ut_heat_countable_income: [15_960, 0, 15_960, 0] ut_heat_eligible: [true, true, true, false] + +# Reported premium repair contract v2: supplied positive aggregate takes precedence. +# Components represent paid, unreimbursed premiums; no calculated Medicare premium is used. +# Source: Utah HEAT section 407, effective March 1 through September 30, 2026. +# Award arithmetic uses sections 602-604 and the March 2026 gas-house standard of 211. + +# 18,360 - 2,400 = 15,960; I=1,330, P=1,330, income points=150, burden=160, age credit=150; award=460. +- name: "Case 40, reported paid Part B premiums affect the regular heating award." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + immigration_status: "CITIZEN" + pension_income: 18_360 + ssi: 0 + medicare_enrolled: true + health_insurance_premiums: 0 + health_insurance_premiums_without_medicare_part_b: 0 + medicare_part_b_premiums_reported: 2_400 + msp_part_b_premium_coverage: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "NATURAL_GAS" + gas_expense: 1_000 + heat_expense_included_in_rent: false + ut_fep: 0 + ut_heat_dwelling_type: "HOUSE" + households: + household1: + members: ["person1"] + state_code: "UT" + output: + ut_heat_countable_income: 15_960 + ut_heat: 460 + +# 18,360 - 1,200 - 2,400 - 600 = 14,160; I=1,180, ceil(100*I/1,330)=89; 161+180+150=491. +- name: "Case 41, both premium components and other paid medical expenses count." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + immigration_status: "CITIZEN" + pension_income: 18_360 + ssi: 0 + medicare_enrolled: true + health_insurance_premiums: 0 + health_insurance_premiums_without_medicare_part_b: 1_200 + medicare_part_b_premiums_reported: 2_400 + other_medical_expenses: 600 + msp_part_b_premium_coverage: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "NATURAL_GAS" + gas_expense: 1_000 + heat_expense_included_in_rent: false + ut_fep: 0 + ut_heat_dwelling_type: "HOUSE" + households: + household1: + members: ["person1"] + state_code: "UT" + output: + ut_heat_countable_income: 14_160 + ut_heat: 491 + +# 18,360 - 1,200 aggregate - 600 other medical = 16,560; I=1,380; 146+150+150=446. +- name: "Case 42, a positive premium aggregate prevents component double counting." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + immigration_status: "CITIZEN" + pension_income: 18_360 + ssi: 0 + medicare_enrolled: true + health_insurance_premiums: 1_200 + health_insurance_premiums_without_medicare_part_b: 1_200 + medicare_part_b_premiums_reported: 2_400 + other_medical_expenses: 600 + msp_part_b_premium_coverage: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "NATURAL_GAS" + gas_expense: 1_000 + heat_expense_included_in_rent: false + ut_fep: 0 + ut_heat_dwelling_type: "HOUSE" + households: + household1: + members: ["person1"] + state_code: "UT" + output: + ut_heat_countable_income: 16_560 + ut_heat: 446 + +# Income remains 18,360; I=1,530, ceil(100*I/1,330)=116; 134+140+150=424. +- name: "Case 43, Medicare enrollment without reported premiums gives no deduction." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + immigration_status: "CITIZEN" + pension_income: 18_360 + ssi: 0 + medicare_enrolled: true + health_insurance_premiums: 0 + health_insurance_premiums_without_medicare_part_b: 0 + medicare_part_b_premiums_reported: 0 + msp_part_b_premium_coverage: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "NATURAL_GAS" + gas_expense: 1_000 + heat_expense_included_in_rent: false + ut_fep: 0 + ut_heat_dwelling_type: "HOUSE" + households: + household1: + members: ["person1"] + state_code: "UT" + output: + ut_heat_countable_income: 18_360 + ut_heat: 424 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml index c982eb51c69..80a76608d50 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml @@ -568,3 +568,91 @@ state_code: UT output: ut_heat_countable_income: 12_000 + +# Reported premium repair contract v2: supplied positive aggregate takes precedence. +# Components represent paid, unreimbursed premiums; no calculated Medicare premium is used. +# Source: Utah HEAT section 407, effective March 1 through September 30, 2026. +# Award arithmetic uses sections 602-604 and the March 2026 gas-house standard of 211. + +# Contract v2: 18,360 - 1,200 - 2,400 - 600 = 14,160 despite a negative aggregate report. +- name: "Case 22, a nonpositive aggregate falls back to both reported premium components." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + immigration_status: "CITIZEN" + pension_income: 18_360 + ssi: 0 + medicare_enrolled: true + health_insurance_premiums: -1 + health_insurance_premiums_without_medicare_part_b: 1_200 + medicare_part_b_premiums_reported: 2_400 + other_medical_expenses: 600 + msp_part_b_premium_coverage: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "NATURAL_GAS" + gas_expense: 1_000 + heat_expense_included_in_rent: false + ut_fep: 0 + ut_heat_dwelling_type: "HOUSE" + households: + household1: + members: ["person1"] + state_code: "UT" + output: + ut_heat_countable_income: 14_160 + +# Contract v2: negative component reports cannot reduce the other positive deduction; 18,360-2,400 and 18,360-1,200. +- name: "Case 23, reported premium components are independently nonnegative." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + immigration_status: "CITIZEN" + pension_income: 18_360 + ssi: 0 + medicare_enrolled: true + health_insurance_premiums: 0 + health_insurance_premiums_without_medicare_part_b: -300 + medicare_part_b_premiums_reported: 2_400 + msp_part_b_premium_coverage: 0 + person2: + age: 68 + immigration_status: "CITIZEN" + pension_income: 18_360 + ssi: 0 + medicare_enrolled: true + health_insurance_premiums: 0 + health_insurance_premiums_without_medicare_part_b: 1_200 + medicare_part_b_premiums_reported: -300 + msp_part_b_premium_coverage: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "NATURAL_GAS" + gas_expense: 1_000 + heat_expense_included_in_rent: false + ut_fep: 0 + ut_heat_dwelling_type: "HOUSE" + spm_unit2: + members: ["person2"] + heating_type: "NATURAL_GAS" + gas_expense: 1_000 + heat_expense_included_in_rent: false + ut_fep: 0 + ut_heat_dwelling_type: "HOUSE" + households: + household1: + members: ["person1"] + state_code: "UT" + household2: + members: ["person2"] + state_code: "UT" + output: + ut_heat_countable_income: [15_960, 17_160] diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py b/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py index 8ab8f06f2db..51fc3e58e07 100644 --- a/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py @@ -49,10 +49,16 @@ def formula(spm_unit, period, parameters): # excluded. Broad inputs cannot isolate all exempt gifts, settlements, # combat pay, third-party payments, or cash withdrawals from trusts. premiums = person("health_insurance_premiums", period) + # A positive aggregate premium already includes reported components. + # Otherwise use both reported components without deriving Medicare. reported_premiums = where( premiums > 0, premiums, - person("health_insurance_premiums_without_medicare_part_b", period), + max_( + person("health_insurance_premiums_without_medicare_part_b", period), + 0, + ) + + max_(person("medicare_part_b_premiums_reported", period), 0), ) medical = spm_unit.sum( max_(reported_premiums, 0) From 300699b895534d2d5833bbd1908e07a540318c0d Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 20 Oct 2026 20:24:36 -0400 Subject: [PATCH 03/10] Fix Utah HEAT income and poverty rounding --- changelog.d/ut-liheap-review-fixes.fixed.md | 1 + .../heat/income/unearned_income_sources.yaml | 2 +- .../gov/states/ut/dwf/heat/ut_heat.yaml | 182 ++++++++++++++++++ .../heat/income/ut_heat_countable_income.py | 5 +- .../gov/states/ut/dwf/heat/ut_heat.py | 30 ++- 5 files changed, 208 insertions(+), 12 deletions(-) create mode 100644 changelog.d/ut-liheap-review-fixes.fixed.md diff --git a/changelog.d/ut-liheap-review-fixes.fixed.md b/changelog.d/ut-liheap-review-fixes.fixed.md new file mode 100644 index 00000000000..6dfbb78e2b3 --- /dev/null +++ b/changelog.d/ut-liheap-review-fixes.fixed.md @@ -0,0 +1 @@ +Fix Utah HEAT income treatment and exact poverty-percentage rounding. diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml index e82b8c06184..34d99a29152 100644 --- a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml @@ -8,7 +8,7 @@ values: - retirement_distributions - survivor_benefits - veterans_benefits - - unemployment_compensation + - total_unemployment_compensation - workers_compensation - disability_benefits - strike_benefits diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml index 45aaa0f2c71..7100ad69e07 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml @@ -1336,3 +1336,185 @@ output: ut_heat_countable_income: 18_360 ut_heat: 424 + +# Section 402 counts UI payments. The state UI formula gives 764 per week for +# 14 weeks: 10,696 + 15,000 wages = 25,696, above the 23,940 income limit. +- name: "Case 44, modeled unemployment compensation counts toward heating eligibility." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + employment_income: 15_000 + ut_ui_base_period_wages: 40_000 + ut_ui_high_quarter_wages: 20_000 + ut_ui_quarters_with_wages: 4 + ut_ui_weeks_unemployed: 20 + ut_ui_gross_weekly_earnings: 0 + ut_ui_weekly_hours_worked: 0 + spm_units: + spm_unit1: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + ut_heat_dwelling_type: HOUSE + households: + household1: + members: [person1] + state_code: UT + output: + ut_ui: 10_696 + total_unemployment_compensation: 10_696 + ut_heat_countable_income: 25_696 + ut_heat_income_limit: 23_940 + ut_heat: 0 + +# Sections 402/404 distinguish unearned receipts from current net business losses. +# Estate loss: 15,000 + 12,000 = 27,000, above the 23,940 limit. +# Current business loss: 15,000 + 12,000 - 12,000 = 15,000; +# monthly income 1,250 gives 250 - ceil(100 * 1,250 / 1,330) + 170 = 326. +- name: "Case 45, estate losses cannot erase pensions while current business losses remain counted." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + employment_income: 15_000 + pension_income: 12_000 + estate_income: -12_000 + person2: + age: 40 + immigration_status: CITIZEN + employment_income: 15_000 + pension_income: 12_000 + self_employment_income: -12_000 + spm_units: + spm_unit1: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + ut_heat_dwelling_type: HOUSE + spm_unit2: + members: [person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + ut_heat_dwelling_type: HOUSE + households: + household1: + members: [person1] + state_code: UT + household2: + members: [person2] + state_code: UT + output: + ut_heat_countable_income: [27_000, 15_000] + ut_heat: [0, 326] + +# Section 407 permits household-paid, unreimbursed medical premiums. Pretax +# payroll premiums are disjoint from the alternative nonpretax aggregate/components. +# 24,000 - 1,200 = 22,800; monthly 1,900: 250 - 143 + 110 = 217. +# 24,000 - 1,200 pretax - 1,200 aggregate = 21,600; 250 - 136 + 120 = 234. +- name: "Case 46, pretax premiums are deducted once alongside reported nonpretax premiums." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + employment_income: 24_000 + pre_tax_health_insurance_premiums: 1_200 + health_insurance_premiums: 0 + health_insurance_premiums_without_medicare_part_b: 0 + medicare_part_b_premiums_reported: 0 + person2: + age: 40 + immigration_status: CITIZEN + employment_income: 24_000 + pre_tax_health_insurance_premiums: 1_200 + health_insurance_premiums: 1_200 + health_insurance_premiums_without_medicare_part_b: 1_200 + medicare_part_b_premiums_reported: 0 + spm_units: + spm_unit1: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + ut_heat_dwelling_type: HOUSE + spm_unit2: + members: [person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + ut_heat_dwelling_type: HOUSE + households: + household1: + members: [person1] + state_code: UT + household2: + members: [person2] + state_code: UT + output: + ut_heat_countable_income: [22_800, 21_600] + ut_heat: [217, 234] + +# Section 602 rounds poverty percentages upward. Annual 319.20 is exactly +# 2% of 15,960; cents below/equal/above give ceilings 2, 2, 3. Section 603's +# burden component is capped at 250, giving awards 498, 498, 497. +- name: "Case 47, exact poverty percentages and neighboring cents retain their correct ceilings." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + employment_income: 319.19 + person2: + age: 40 + immigration_status: CITIZEN + employment_income: 319.20 + person3: + age: 40 + immigration_status: CITIZEN + employment_income: 319.21 + spm_units: + spm_unit1: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + ut_heat_dwelling_type: HOUSE + spm_unit2: + members: [person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + ut_heat_dwelling_type: HOUSE + spm_unit3: + members: [person3] + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + ut_heat_dwelling_type: HOUSE + households: + household1: + members: [person1] + state_code: UT + household2: + members: [person2] + state_code: UT + household3: + members: [person3] + state_code: UT + output: + ut_heat_countable_income: [319.19, 319.20, 319.21] + ut_heat: [498, 498, 497] diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py b/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py index 51fc3e58e07..62f9fb53804 100644 --- a/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py @@ -40,7 +40,9 @@ def formula(spm_unit, period, parameters): # not reconstruct HEAT expenses or isolate disallowed prior-period # losses. Current reported losses enter the household total; rental # losses do not. No business expense or insurance is deducted twice. - unearned = max_(add(person, period, p.unearned_income_sources), 0) + unearned = sum( + max_(person(source, period), 0) for source in p.unearned_income_sources + ) gross = spm_unit.sum( where(excluded_child, 0, wages + business + rental + unearned) ) @@ -62,6 +64,7 @@ def formula(spm_unit, period, parameters): ) medical = spm_unit.sum( max_(reported_premiums, 0) + + max_(person("pre_tax_health_insurance_premiums", period), 0) + max_(person("other_medical_expenses", period), 0) ) support_paid = add( diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py index e37da7d82af..764f68f53c8 100644 --- a/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py @@ -13,27 +13,37 @@ class ut_heat(Variable): "https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/603_Energy_Burden.htm", "https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_600/Obsolete_604_(3_1_25_to_9_30_26)_Target_Groups.htm", "https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/605_Propane_or_Fuel_Oil.htm", - "https://liheapch.acf.gov/docs/2026/state-plans/UT_Plan_2026.pdf#page=8,9", + # PDF pages 8-9. + "https://liheapch.acf.gov/docs/2026/state-plans/UT_Plan_2026.pdf#page=8", ) def formula(spm_unit, period, parameters): p = parameters(period).gov.states.ut.dwf.heat.benefit - income = ( - spm_unit("ut_heat_countable_income", period).astype(float) / MONTHS_IN_YEAR - ) + annual_income = spm_unit("ut_heat_countable_income", period) + income = annual_income.astype(float) / MONTHS_IN_YEAR poverty = np.ceil( spm_unit("ut_heat_fpg", period).astype(float) / MONTHS_IN_YEAR ) - poverty_percentage = np.divide( - income / p.rounding_increment, - poverty, - out=np.zeros_like(income), - where=poverty > 0, + lower_poverty_percentage = np.floor( + np.divide( + income / p.rounding_increment, + poverty, + out=np.zeros_like(income), + where=poverty > 0, + ) ) + # Compare integer percentage boundaries at stored annual-income precision. + poverty_boundary = ( + lower_poverty_percentage * poverty * p.rounding_increment * MONTHS_IN_YEAR + ).astype(annual_income.dtype) # Sections 602/603 require numeric rounding, not merely the stale # XLSX's zero-decimal display. Percentage-point size is the policy's # rounding increment; 0.5 implements nearest-whole, ties upward. - income_points = p.income_component - np.ceil(poverty_percentage) + income_points = ( + p.income_component + - lower_poverty_percentage + - (annual_income > poverty_boundary) + ) standard = spm_unit("ut_heat_energy_cost_standard", period) / MONTHS_IN_YEAR burden_percentage = np.divide( standard / p.rounding_increment, From 60593edd8e20bee6ed8dec1f8c7c22f6bece1261 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 20 Oct 2026 20:27:46 -0400 Subject: [PATCH 04/10] Count ordinary military retirement pay in Utah HEAT --- changelog.d/ut-liheap-review-fixes.fixed.md | 2 +- .../heat/income/unearned_income_sources.yaml | 1 + .../gov/states/ut/dwf/heat/ut_heat.yaml | 33 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/changelog.d/ut-liheap-review-fixes.fixed.md b/changelog.d/ut-liheap-review-fixes.fixed.md index 6dfbb78e2b3..ef9ae6fec58 100644 --- a/changelog.d/ut-liheap-review-fixes.fixed.md +++ b/changelog.d/ut-liheap-review-fixes.fixed.md @@ -1 +1 @@ -Fix Utah HEAT income treatment and exact poverty-percentage rounding. +Fix Utah HEAT income treatment, including military retirement pay, and exact poverty-percentage rounding. diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml index 34d99a29152..451ef88b6cf 100644 --- a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml @@ -5,6 +5,7 @@ values: - ssi - railroad_benefits - pension_income + - military_retirement_pay - retirement_distributions - survivor_benefits - veterans_benefits diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml index 7100ad69e07..1882c311ecf 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml @@ -1518,3 +1518,36 @@ output: ut_heat_countable_income: [319.19, 319.20, 319.21] ut_heat: [498, 498, 497] + +# Sections 401/402 count ordinary DoD retirement pay separately from pensions +# and VA benefits. A veteran retiring after 20 years receives 36,000 per year, +# above the one-person 23,940 income limit; no ordinary heating award is due. +- name: "Case 48, ordinary military retirement pay counts toward heating eligibility." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + is_veteran: true + years_in_military: 20 + military_retirement_pay: 36_000 + pension_income: 0 + veterans_benefits: 0 + spm_units: + spm_unit1: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + ut_heat_dwelling_type: HOUSE + households: + household1: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 36_000 + ut_heat_income_limit: 23_940 + ut_heat_eligible: false + ut_heat: 0 From 1deb9aa2d710add9fe24a599f2ead2050d87f15c Mon Sep 17 00:00:00 2001 From: Ziming Date: Wed, 21 Oct 2026 01:15:47 -0400 Subject: [PATCH 05/10] Consolidate Utah LIHEAP changelog and registry note One added changelog fragment for the new program and a one- or two-sentence programs.yaml note; limitations stay in code comments and the PR body. Co-Authored-By: Claude Opus 5.5 --- changelog.d/ut-liheap-review-fixes.fixed.md | 1 - changelog.d/ut-liheap-wave2b.added.md | 2 +- policyengine_us/programs.yaml | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 changelog.d/ut-liheap-review-fixes.fixed.md diff --git a/changelog.d/ut-liheap-review-fixes.fixed.md b/changelog.d/ut-liheap-review-fixes.fixed.md deleted file mode 100644 index ef9ae6fec58..00000000000 --- a/changelog.d/ut-liheap-review-fixes.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix Utah HEAT income treatment, including military retirement pay, and exact poverty-percentage rounding. diff --git a/changelog.d/ut-liheap-wave2b.added.md b/changelog.d/ut-liheap-wave2b.added.md index 87067938db9..b3b62c2b0d6 100644 --- a/changelog.d/ut-liheap-wave2b.added.md +++ b/changelog.d/ut-liheap-wave2b.added.md @@ -1 +1 @@ -Add Utah HEAT regular heating assistance, with household eligibility, countable income, fuel and dwelling standards, and targeted supplements verified for the March 2026 policy snapshot. +Add Utah HEAT (ut_heat): 2026 regular heating benefit from energy-burden points and targeted supplements with the 150% federal poverty guideline income test. diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 00ab1ebcbe8..d12ccd5175e 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -967,7 +967,7 @@ programs: variable: ut_heat parameter_prefix: gov.states.ut.dwf.heat verified_years: "2026" - notes: Regular heating assistance using the March 2026 policy snapshot and current-year federal poverty guidelines. Existing annual inputs approximate application-month income, medical/support eligibility and household membership; solar/unknown fuel, finer immigration categories and administrative conditions remain unsupported. Earlier-year backfills are unverified historical estimates; crisis, cooling, weatherization and equipment aid are out of scope. + notes: Regular heating under the rules effective March 1, 2026, including the target-group and propane or fuel oil supplements. Crisis, cooling, weatherization and equipment assistance are out of scope. # --- SSA programs --- - id: ssi From 45ce4f41c87df8641510c93360afc1ac3c52ff23 Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 23 Oct 2026 22:46:02 -0400 Subject: [PATCH 06/10] Address review findings for Utah LIHEAP Co-Authored-By: Claude Opus 5.5 --- .../states/ut/dwf/heat/benefit/maximum.yaml | 10 -- .../states/ut/dwf/heat/benefit/minimum.yaml | 10 -- .../heat/income/unearned_income_sources.yaml | 2 +- .../gov/states/ut/dwf/heat/ut_heat.yaml | 127 +++++++++++------- .../ut/dwf/heat/ut_heat_countable_income.yaml | 48 +++---- .../states/ut/dwf/heat/ut_heat_eligible.yaml | 30 ++--- .../ut/dwf/heat/ut_heat_eligible_person.yaml | 32 ++--- .../ut/dwf/heat/ut_heat_household_size.yaml | 4 +- .../ut/dwf/heat/ut_heat_income_limit.yaml | 10 +- .../heat/income/ut_heat_countable_income.py | 9 +- .../gov/states/ut/dwf/heat/ut_heat.py | 7 +- source_conflicts/candidates.yaml | 43 ++++++ 12 files changed, 197 insertions(+), 135 deletions(-) delete mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/maximum.yaml delete mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/minimum.yaml diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/maximum.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/maximum.yaml deleted file mode 100644 index eb4c63d5178..00000000000 --- a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/maximum.yaml +++ /dev/null @@ -1,10 +0,0 @@ -description: Utah provides up to this regular heating benefit to an eligible household under the Home Energy Assistance Target program. -values: - 2025-10-01: 800 -metadata: - unit: currency-USD - period: year - label: Utah HEAT maximum regular benefit - reference: - - title: Utah FY2026 LIHEAP State Plan, Section 2.6 - href: https://liheapch.acf.gov/docs/2026/state-plans/UT_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/minimum.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/minimum.yaml deleted file mode 100644 index abfb723d931..00000000000 --- a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/minimum.yaml +++ /dev/null @@ -1,10 +0,0 @@ -description: Utah provides at least this regular heating benefit to an eligible household under the Home Energy Assistance Target program. -values: - 2025-10-01: 140 -metadata: - unit: currency-USD - period: year - label: Utah HEAT minimum regular benefit - reference: - - title: Utah FY2026 LIHEAP State Plan, Section 2.6 - href: https://liheapch.acf.gov/docs/2026/state-plans/UT_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml index 451ef88b6cf..1cfed647fe0 100644 --- a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml @@ -5,7 +5,7 @@ values: - ssi - railroad_benefits - pension_income - - military_retirement_pay + - military_retirement_income - retirement_distributions - survivor_benefits - veterans_benefits diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml index 1882c311ecf..91b3d0dd04d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml @@ -1,7 +1,7 @@ # Sources: Utah HEAT manual sections 602-605 and 609; FY2026 plan pages 8-9. -# The annual 2026 period represents the agreed March 2026 snapshot. +# The annual 2026 period represents the March 2026 snapshot. -# UT-15: income component 250 + zero-income burden 250. +# Income component 250 + zero-income burden 250. - name: "Case 1, zero income yields the ordinary gas award." period: 2026 absolute_error_margin: 0.01 @@ -24,8 +24,8 @@ output: ut_heat: 500 -# UT-15: 250+250+150 target+150 propane=800. -- name: "Case 2, targeted propane household reaches the maximum." +# 250+250+150 target+150 propane=800. +- name: "Case 2, targeted propane household reaches the highest formula award." period: 2026 absolute_error_margin: 0.01 input: @@ -47,7 +47,7 @@ output: ut_heat: 800 -# UT-15: I 1330, P 1330; Q 100; gas burden round(15.8647)=16; 150+160=310. +# I 1330, P 1330; Q 100; gas burden round(15.8647)=16; 150+160=310. - name: "Case 3, gas house at one hundred percent poverty." period: 2026 absolute_error_margin: 0.01 @@ -74,7 +74,7 @@ ut_heat_household_size: 1 ut_heat_income_limit: 23_940 -# UT-14/15: I 1330; apartment 161/1330*100 rounds 12; 150+120=270. +# I 1330; apartment 161/1330*100 rounds 12; 150+120=270. - name: "Case 4, gas apartment uses its lower cost standard." period: 2026 absolute_error_margin: 0.01 @@ -98,7 +98,7 @@ output: ut_heat: 270 -# UT-13/15: I 1995; Q 150; 211/1995*100 rounds 11; 100+110=210. +# I 1995; Q 150; 211/1995*100 rounds 11; 100+110=210. - name: "Case 5, income at the inclusive one person limit." period: 2026 absolute_error_margin: 0.01 @@ -122,7 +122,7 @@ output: ut_heat: 210 -# UT-13: monthly 1995.01 exceeds 1995; no benefit minimum for ineligible households. +# Monthly 1995.01 exceeds 1995, so the household is ineligible and receives 0. - name: "Case 6, income twelve cents above the annual limit is ineligible." period: 2026 absolute_error_margin: 0.01 @@ -146,7 +146,7 @@ output: ut_heat: 0 -# UT-15: I 1331; ceil(100*1331/1330)=101; burden 16; 149+160=309. +# I 1331; ceil(100*1331/1330)=101; burden 16; 149+160=309. - name: "Case 7, poverty percentage rounds upward." period: 2026 absolute_error_margin: 0.01 @@ -170,7 +170,7 @@ output: ut_heat: 309 -# UT-15: I 2840, P 2277, Q 125; 100*71/2840=2.5=>3; 125+30=155. +# I 2840, P 2277, Q 125; 100*71/2840=2.5=>3; 125+30=155. - name: "Case 8, energy burden rounds half upward." period: 2026 absolute_error_margin: 0.01 @@ -202,7 +202,7 @@ output: ut_heat: 155 -# UT-15: I 2840.04; Q 125; burden just below 2.5=>2; 125+20=145. +# I 2840.04; Q 125; burden just below 2.5=>2; 125+20=145. - name: "Case 9, energy burden just below the half point rounds down." period: 2026 absolute_error_margin: 0.01 @@ -234,8 +234,10 @@ output: ut_heat: 145 -# UT-15: size 14, P 7484, I 11225, Q 150, burden 1; raw 110 is raised to 140. -- name: "Case 10, large household receives the sourced minimum." +# Size 14, P 7484, I 11225; Q 150 => income 250-150=100. Coal apartment +# 71/11225*100 rounds to burden 1 => 10. Section 607 adds the components +# with no floor: 100+10=110. +- name: "Case 10, large household at the income limit receives the unfloored formula amount." period: 2026 absolute_error_margin: 0.01 input: @@ -308,11 +310,11 @@ members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13, person14] state_code: UT output: - ut_heat: 140 + ut_heat: 110 ut_heat_household_size: 14 ut_heat_income_limit: 134_700 -# UT-15: zero income 500 plus one 150 target credit. +# Zero income 500 plus one 150 target credit. - name: "Case 11, target credit begins at age sixty." period: 2026 absolute_error_margin: 0.01 @@ -335,7 +337,7 @@ output: ut_heat: 650 -# UT-15: age 59 is below 60. +# Age 59 is below 60. - name: "Case 12, age fifty nine alone gives no target credit." period: 2026 absolute_error_margin: 0.01 @@ -358,7 +360,7 @@ output: ut_heat: 500 -# UT-15: child 5 is below 6; target credit once. +# Child 5 is below 6; target credit once. - name: "Case 13, eligible child under six gives one target credit." period: 2026 absolute_error_margin: 0.01 @@ -385,7 +387,7 @@ output: ut_heat: 650 -# UT-15: child 6 is outside under 6 category. +# Child 6 is outside under 6 category. - name: "Case 14, child at six gives no young child target credit." period: 2026 absolute_error_margin: 0.01 @@ -412,7 +414,7 @@ output: ut_heat: 500 -# UT-15: older, disabled and young members trigger one 150 credit. +# Older, disabled and young members trigger one 150 credit. - name: "Case 15, multiple target members give only one credit." period: 2026 absolute_error_margin: 0.01 @@ -440,7 +442,7 @@ output: ut_heat: 650 -# UT-15: disability target independent of age. +# Disability target independent of age. - name: "Case 16, disabled adult gives target credit." period: 2026 absolute_error_margin: 0.01 @@ -464,7 +466,7 @@ output: ut_heat: 650 -# UT-05/15: ineligible resident excluded from size and target credit. +# Ineligible resident excluded from size and target credit. - name: "Case 17, ineligible older resident gives no target credit." period: 2026 absolute_error_margin: 0.01 @@ -492,7 +494,7 @@ ut_heat: 500 ut_heat_household_size: 1 -# UT-05: size 1; adult income 15960 counts; 310 base+150 eligible child. +# Size 1; adult income 15960 counts; 310 base+150 eligible child. - name: "Case 18, eligible child supports mixed status household." period: 2026 absolute_error_margin: 0.01 @@ -522,7 +524,7 @@ ut_heat_household_size: 1 ut_heat_countable_income: 15_960 -# UT-06/15: nonsubsidized heat-in-rent uses regular standard; no actual-bill cap. +# Nonsubsidized heat-in-rent uses regular standard; no actual-bill cap. - name: "Case 19, ordinary heat in rent qualifies without a direct bill." period: 2026 absolute_error_margin: 0.01 @@ -548,7 +550,7 @@ output: ut_heat: 500 -# UT-16: actual expense establishes responsibility; standards determine grant. +# Actual expense establishes responsibility; standards determine grant. - name: "Case 20, small direct heating bill does not cap the scheduled award." period: 2026 absolute_error_margin: 0.01 @@ -571,7 +573,7 @@ output: ut_heat: 500 -# UT-12: 20400-2400-2040=15960, no 20% earnings disregard. +# 20400-2400-2040=15960, no 20% earnings disregard. - name: "Case 21, medical and child support deductions reach exact poverty." period: 2026 absolute_error_margin: 0.01 @@ -598,8 +600,8 @@ ut_heat: 310 ut_heat_countable_income: 15_960 -# UT-11/15: countable-income floor 0; maximum 800. -- name: "Case 22, negative net business income does not inflate the maximum." +# Countable income floors at 0; 250+250+150 target+150 propane=800. +- name: "Case 22, negative net business income does not inflate the award." period: 2026 absolute_error_margin: 0.01 input: @@ -623,7 +625,7 @@ ut_heat: 800 ut_heat_countable_income: 0 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 211, rounded burden 7. Fuel addition 0; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 211, rounded burden 7. Fuel addition 0; 118+70=188. - name: "Case 23, natural gas house standard." period: 2026 absolute_error_margin: 0.01 @@ -655,7 +657,7 @@ output: ut_heat: 188 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 161, rounded burden 5. Fuel addition 0; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 161, rounded burden 5. Fuel addition 0; 118+50=168. - name: "Case 24, natural gas apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -687,7 +689,7 @@ output: ut_heat: 168 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 541, rounded burden 18. Fuel addition 150; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 541, rounded burden 18. Fuel addition 150; 118+180+150=448. - name: "Case 25, propane house standard." period: 2026 absolute_error_margin: 0.01 @@ -719,7 +721,7 @@ output: ut_heat: 448 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 408, rounded burden 14. Fuel addition 150; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 408, rounded burden 14. Fuel addition 150; 118+140+150=408. - name: "Case 26, propane apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -751,7 +753,7 @@ output: ut_heat: 408 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 541, rounded burden 18. Fuel addition 0; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 541, rounded burden 18. Fuel addition 0; 118+180=298. - name: "Case 27, kerosene house standard." period: 2026 absolute_error_margin: 0.01 @@ -783,7 +785,7 @@ output: ut_heat: 298 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 408, rounded burden 14. Fuel addition 0; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 408, rounded burden 14. Fuel addition 0; 118+140=258. - name: "Case 28, kerosene apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -815,7 +817,7 @@ output: ut_heat: 258 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 468, rounded burden 16. Fuel addition 150; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 468, rounded burden 16. Fuel addition 150; 118+160+150=428. - name: "Case 29, fuel oil house standard." period: 2026 absolute_error_margin: 0.01 @@ -847,7 +849,7 @@ output: ut_heat: 428 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 354, rounded burden 12. Fuel addition 150; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 354, rounded burden 12. Fuel addition 150; 118+120+150=388. - name: "Case 30, fuel oil apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -879,7 +881,7 @@ output: ut_heat: 388 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 287, rounded burden 10. Fuel addition 0; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 287, rounded burden 10. Fuel addition 0; 118+100=218. - name: "Case 31, electricity house standard." period: 2026 absolute_error_margin: 0.01 @@ -911,7 +913,7 @@ output: ut_heat: 218 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 218, rounded burden 7. Fuel addition 0; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 218, rounded burden 7. Fuel addition 0; 118+70=188. - name: "Case 32, electricity apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -943,7 +945,7 @@ output: ut_heat: 188 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 92, rounded burden 3. Fuel addition 0; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 92, rounded burden 3. Fuel addition 0; 118+30=148. - name: "Case 33, coal house standard." period: 2026 absolute_error_margin: 0.01 @@ -975,7 +977,7 @@ output: ut_heat: 148 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 71, rounded burden 2. Fuel addition 0; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 71, rounded burden 2. Fuel addition 0; 118+20=138. - name: "Case 34, coal apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -1005,9 +1007,9 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 140 + ut_heat: 138 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 92, rounded burden 3. Fuel addition 0; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 92, rounded burden 3. Fuel addition 0; 118+30=148. - name: "Case 35, other house standard." period: 2026 absolute_error_margin: 0.01 @@ -1039,7 +1041,7 @@ output: ut_heat: 148 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 71, rounded burden 2. Fuel addition 0; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 71, rounded burden 2. Fuel addition 0; 118+20=138. - name: "Case 36, other apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -1069,9 +1071,9 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 140 + ut_heat: 138 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 343, rounded burden 11. Fuel addition 0; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 343, rounded burden 11. Fuel addition 0; 118+110=228. - name: "Case 37, wood house standard." period: 2026 absolute_error_margin: 0.01 @@ -1103,7 +1105,7 @@ output: ut_heat: 228 -# UT-14/15: size 3, P 2277, I 3000, Q 132 => income 118; standard 260, rounded burden 9. Fuel addition 0; apply 140 minimum. +# Size 3, P 2277, I 3000, Q 132 => income 118; standard 260, rounded burden 9. Fuel addition 0; 118+90=208. - name: "Case 38, wood apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -1135,7 +1137,7 @@ output: ut_heat: 208 -# UT-05/15: separate ordinary gas, targeted propane, mixed-status young-child, and no-heating cases in one vector. +# Separate ordinary gas, targeted propane, mixed-status young-child, and no-heating cases in one vector. - name: "Case 39, mixed households preserve separate formulas and eligibility." period: 2026 input: @@ -1202,7 +1204,7 @@ ut_heat_countable_income: [15_960, 0, 15_960, 0] ut_heat_eligible: [true, true, true, false] -# Reported premium repair contract v2: supplied positive aggregate takes precedence. +# A supplied positive premium aggregate takes precedence over its components. # Components represent paid, unreimbursed premiums; no calculated Medicare premium is used. # Source: Utah HEAT section 407, effective March 1 through September 30, 2026. # Award arithmetic uses sections 602-604 and the March 2026 gas-house standard of 211. @@ -1551,3 +1553,34 @@ ut_heat_income_limit: 23_940 ut_heat_eligible: false ut_heat: 0 + +# Section 402 counts pensions and annuities as unearned income; section 405 +# exempts no uniformed-services survivor annuity. A surviving spouse receives +# a 26,000 Survivor Benefit Plan annuity: 26,000 / 12 = 2,166.67 per month, +# above the one-person 1,995 monthly limit (23,940 / 12); no award is due. +- name: "Case 49, military survivor annuity counts toward heating eligibility." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: CITIZEN + military_retirement_pay_survivors: 26_000 + spm_units: + spm_unit1: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + ut_heat_dwelling_type: HOUSE + households: + household1: + members: [person1] + state_code: UT + output: + military_retirement_income: 26_000 + ut_heat_countable_income: 26_000 + ut_heat_income_limit: 23_940 + ut_heat_eligible: false + ut_heat: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml index 80a76608d50..2d8a2179e7b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml @@ -2,7 +2,7 @@ # Section 406 rescinded the general 20% earnings disregard. # Medicare and prior-period business-loss reconstruction remain outside scope. -# UT-08; section 406 rescinds disregard, the contrary line in the older matrix is not used. +# Sections 401 and 406 and the March 2026 worksheet count gross earnings in full. - name: "Case 1, gross earnings have no twenty percent disregard." period: 2026 absolute_error_margin: 0.01 @@ -26,7 +26,7 @@ output: ut_heat_countable_income: 18_000 -# UT-10: exclusion covers earned and unearned child income. +# Exclusion covers earned and unearned child income. - name: "Case 2, all income of a seventeen year old is excluded." period: 2026 absolute_error_margin: 0.01 @@ -56,7 +56,7 @@ output: ut_heat_countable_income: 15_960 -# UT-10: age 18 remains in school. +# Age 18 remains in school. - name: "Case 3, eighteen year old secondary student income is excluded." period: 2026 absolute_error_margin: 0.01 @@ -87,7 +87,7 @@ output: ut_heat_countable_income: 15_960 -# UT-10: 15960+12000+2400; the exclusion ends when the person is not in school. +# 15960+12000+2400; the exclusion ends when the person is not in school. - name: "Case 4, eighteen year old out of school income counts." period: 2026 absolute_error_margin: 0.01 @@ -119,7 +119,7 @@ output: ut_heat_countable_income: 30_360 -# UT-10: the student exception ends after 18. +# The student exception ends after 18. - name: "Case 5, nineteen year old student income counts." period: 2026 absolute_error_margin: 0.01 @@ -149,7 +149,7 @@ output: ut_heat_countable_income: 27_960 -# UT-10: support received is parent income. +# Support received is parent income. - name: "Case 6, parent child support counts despite child income exclusion." period: 2026 absolute_error_margin: 0.01 @@ -178,7 +178,7 @@ output: ut_heat_countable_income: 3_600 -# UT-05: count income of all age-eligible members; no general 20% disregard. +# Count income of all age-eligible members; no general 20% disregard. - name: "Case 7, income of ineligible resident counts." period: 2026 absolute_error_margin: 0.01 @@ -207,7 +207,7 @@ output: ut_heat_countable_income: 18_000 -# UT-10: DWS government assistance and ordinary bank interest are exempt. +# DWS government assistance and ordinary bank interest are exempt. - name: "Case 8, FEP general assistance and bank interest are excluded." period: 2026 absolute_error_margin: 0.01 @@ -233,7 +233,7 @@ output: ut_heat_countable_income: 0 -# UT-09: recurring cash gifts and ordinary dividends count; no excluded agency classification applies. +# Recurring cash gifts and ordinary dividends count; no excluded agency classification applies. - name: "Case 9, ordinary dividends and cash contributions count." period: 2026 absolute_error_margin: 0.01 @@ -258,7 +258,7 @@ output: ut_heat_countable_income: 3_600 -# UT-09: annual SSI input 2400=200/month; count gross SSA and pension income. +# Annual SSI input 2400=200/month; count gross SSA and pension income. - name: "Case 10, gross social security SSI and pension count once." period: 2026 absolute_error_margin: 0.01 @@ -283,7 +283,7 @@ output: ut_heat_countable_income: 18_000 -# UT-09: 1200+2400+3600+4800. +# 1200+2400+3600+4800. - name: "Case 11, supported unearned benefit sources count." period: 2026 absolute_error_margin: 0.01 @@ -310,7 +310,7 @@ output: ut_heat_countable_income: 12_000 -# UT-11: no additional percentage business deduction. +# No additional percentage business deduction. - name: "Case 12, net business and rental income use supplied net amounts." period: 2026 absolute_error_margin: 0.01 @@ -335,7 +335,7 @@ output: ut_heat_countable_income: 15_600 -# UT-11: user-authorized nonnegative rental income. +# Rental income counts only when positive; a rental loss does not offset earnings. - name: "Case 13, negative rent does not offset earnings." period: 2026 absolute_error_margin: 0.01 @@ -360,7 +360,7 @@ output: ut_heat_countable_income: 15_960 -# UT-12: qualified paid expenses 20400-2400-2040. +# Qualified paid expenses 20400-2400-2040. - name: "Case 14, medical and support payments are deductible." period: 2026 absolute_error_margin: 0.01 @@ -386,7 +386,7 @@ output: ut_heat_countable_income: 15_960 -# UT-12: medical payment by an excluded member is allowed; that member's income also counts. +# Medical payment by an excluded member is allowed; that member's income also counts. - name: "Case 15, medical payments by ineligible member remain deductible." period: 2026 absolute_error_margin: 0.01 @@ -416,7 +416,7 @@ output: ut_heat_countable_income: 15_960 -# Contract medical input rule: the supplied generic premium 1200 takes precedence over the component fallback 3600. +# The supplied generic premium 1200 takes precedence over the component fallback 3600. - name: "Case 16, reported premiums are not counted twice." period: 2026 absolute_error_margin: 0.01 @@ -442,7 +442,7 @@ output: ut_heat_countable_income: 16_800 -# UT-12/contract: 18000-1200-840; no automatically derived Medicare amount. +# 18000-1200-840; no automatically derived Medicare amount. - name: "Case 17, supplied non Medicare premiums are the fallback." period: 2026 absolute_error_margin: 0.01 @@ -469,7 +469,7 @@ output: ut_heat_countable_income: 15_960 -# UT-12: qualifying alimony paid outside the unit. +# Qualifying alimony paid outside the unit. - name: "Case 18, alimony paid is deductible." period: 2026 absolute_error_margin: 0.01 @@ -494,7 +494,7 @@ output: ut_heat_countable_income: 15_960 -# UT-15 countable income is nonnegative. +# Countable income is nonnegative. - name: "Case 19, deductions floor countable income at zero." period: 2026 absolute_error_margin: 0.01 @@ -519,7 +519,7 @@ output: ut_heat_countable_income: 0 -# UT-12 enumerated deductions do not include ordinary child care. +# Enumerated deductions do not include ordinary child care. - name: "Case 20, childcare is not a general qualified deduction." period: 2026 absolute_error_margin: 0.01 @@ -544,7 +544,7 @@ output: ut_heat_countable_income: 15_960 -# UT-11/contract: signed current net business 15960-3960=12000; prior-period loss reconstruction unavailable. +# Signed current net business 15960-3960=12000; prior-period loss reconstruction unavailable. - name: "Case 21, current net business loss offsets other income." period: 2026 absolute_error_margin: 0.01 @@ -569,12 +569,12 @@ output: ut_heat_countable_income: 12_000 -# Reported premium repair contract v2: supplied positive aggregate takes precedence. +# A supplied positive premium aggregate takes precedence over its components. # Components represent paid, unreimbursed premiums; no calculated Medicare premium is used. # Source: Utah HEAT section 407, effective March 1 through September 30, 2026. # Award arithmetic uses sections 602-604 and the March 2026 gas-house standard of 211. -# Contract v2: 18,360 - 1,200 - 2,400 - 600 = 14,160 despite a negative aggregate report. +# 18,360 - 1,200 - 2,400 - 600 = 14,160 despite a negative aggregate report. - name: "Case 22, a nonpositive aggregate falls back to both reported premium components." period: 2026 absolute_error_margin: 0.01 @@ -606,7 +606,7 @@ output: ut_heat_countable_income: 14_160 -# Contract v2: negative component reports cannot reduce the other positive deduction; 18,360-2,400 and 18,360-1,200. +# Negative component reports cannot reduce the other positive deduction; 18,360-2,400 and 18,360-1,200. - name: "Case 23, reported premium components are independently nonnegative." period: 2026 absolute_error_margin: 0.01 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml index a282dde2e04..1280e4820da 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml @@ -1,7 +1,7 @@ # Sources: Utah HEAT manual sections 202-204, 208 and March 2026 section 608. -# The annual 2026 period represents the agreed March 2026 snapshot. +# The annual 2026 period represents the March 2026 snapshot. -# UT-13: monthly income 1995 equals published 150% limit. +# Monthly income 1995 equals published 150% limit. - name: "Case 1, inclusive income limit." period: 2026 input: @@ -24,7 +24,7 @@ output: ut_heat_eligible: true -# UT-13: monthly income 1995.01 exceeds limit. +# Monthly income 1995.01 exceeds limit. - name: "Case 2, income just above limit." period: 2026 input: @@ -47,7 +47,7 @@ output: ut_heat_eligible: false -# UT-04: requires adult 18+; emancipation unavailable. +# Requires adult 18+; emancipation unavailable. - name: "Case 3, no adult member." period: 2026 input: @@ -69,7 +69,7 @@ output: ut_heat_eligible: false -# UT-04: adult threshold is inclusive 18. +# Adult threshold is inclusive 18. - name: "Case 4, adult age eighteen." period: 2026 input: @@ -91,7 +91,7 @@ output: ut_heat_eligible: true -# UT-04/05: at least one eligible person. +# At least one eligible person. - name: "Case 5, no qualified member." period: 2026 input: @@ -113,7 +113,7 @@ output: ut_heat_eligible: false -# UT-05: an eligible child can qualify unit; the adult need not be eligible. +# An eligible child can qualify unit; the adult need not be eligible. - name: "Case 6, citizen child with ineligible adult." period: 2026 input: @@ -139,7 +139,7 @@ output: ut_heat_eligible: true -# UT-04: Utah residence required. +# Utah residence required. - name: "Case 7, wrong state." period: 2026 input: @@ -161,7 +161,7 @@ output: ut_heat_eligible: false -# UT-06: NONE affirmatively denotes no home heat. +# NONE affirmatively denotes no home heat. - name: "Case 8, no heating source." period: 2026 input: @@ -183,7 +183,7 @@ output: ut_heat_eligible: false -# UT-06: no supplied responsibility. +# No supplied responsibility. - name: "Case 9, no energy costs or rent responsibility." period: 2026 input: @@ -207,7 +207,7 @@ output: ut_heat_eligible: false -# UT-06: households supplying own firewood are eligible. +# Households supplying own firewood are eligible. - name: "Case 10, self supplied firewood qualifies." period: 2026 input: @@ -229,7 +229,7 @@ output: ut_heat_eligible: true -# UT-06: indirect energy responsibility. +# Indirect energy responsibility. - name: "Case 11, ordinary heat in rent." period: 2026 input: @@ -254,7 +254,7 @@ output: ut_heat_eligible: true -# UT-06: utility costs are wholly covered. +# Utility costs are wholly covered. - name: "Case 12, subsidized rent with all utilities covered." period: 2026 input: @@ -279,7 +279,7 @@ output: ut_heat_eligible: false -# UT-06: separately paid utility in addition to subsidized rent. +# Separately paid utility in addition to subsidized rent. - name: "Case 13, subsidized rent with separately paid electricity." period: 2026 input: @@ -304,7 +304,7 @@ output: ut_heat_eligible: true -# UT-06: subsidized housing does not itself bar utility responsibility. +# Subsidized housing does not itself bar utility responsibility. - name: "Case 14, subsidized household directly pays heating." period: 2026 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible_person.yaml index f48049a511b..84250478efb 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible_person.yaml @@ -2,7 +2,7 @@ # LPR is separately eligible; parole/conditional entry needs an eligible criterion. # Service exceptions use actual marital, tax and family relationships. -# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +# Section 203-2 B qualified and eligible categories, including the explicit LPR exception. - name: "Case 1, citizen." period: 2026 input: @@ -27,7 +27,7 @@ output: ut_heat_eligible_person: true -# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +# Section 203-2 B qualified and eligible categories, including the explicit LPR exception. - name: "Case 2, recent permanent resident." period: 2026 input: @@ -52,7 +52,7 @@ output: ut_heat_eligible_person: true -# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +# Section 203-2 B qualified and eligible categories, including the explicit LPR exception. - name: "Case 3, refugee." period: 2026 input: @@ -77,7 +77,7 @@ output: ut_heat_eligible_person: true -# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +# Section 203-2 B qualified and eligible categories, including the explicit LPR exception. - name: "Case 4, asylee." period: 2026 input: @@ -102,7 +102,7 @@ output: ut_heat_eligible_person: true -# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +# Section 203-2 B qualified and eligible categories, including the explicit LPR exception. - name: "Case 5, deportation withheld." period: 2026 input: @@ -127,7 +127,7 @@ output: ut_heat_eligible_person: true -# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +# Section 203-2 B qualified and eligible categories, including the explicit LPR exception. - name: "Case 6, Cuban Haitian entrant." period: 2026 input: @@ -152,7 +152,7 @@ output: ut_heat_eligible_person: true -# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +# Section 203-2 B qualified and eligible categories, including the explicit LPR exception. - name: "Case 7, parole below five years." period: 2026 input: @@ -177,7 +177,7 @@ output: ut_heat_eligible_person: false -# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +# Section 203-2 B qualified and eligible categories, including the explicit LPR exception. - name: "Case 8, parole at five years." period: 2026 input: @@ -202,7 +202,7 @@ output: ut_heat_eligible_person: true -# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +# Section 203-2 B qualified and eligible categories, including the explicit LPR exception. - name: "Case 9, conditional entrant below five years." period: 2026 input: @@ -227,7 +227,7 @@ output: ut_heat_eligible_person: false -# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +# Section 203-2 B qualified and eligible categories, including the explicit LPR exception. - name: "Case 10, conditional entrant at five years." period: 2026 input: @@ -252,7 +252,7 @@ output: ut_heat_eligible_person: true -# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +# Section 203-2 B qualified and eligible categories, including the explicit LPR exception. - name: "Case 11, undocumented resident." period: 2026 input: @@ -277,7 +277,7 @@ output: ut_heat_eligible_person: false -# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +# Section 203-2 B qualified and eligible categories, including the explicit LPR exception. - name: "Case 12, DACA recipient." period: 2026 input: @@ -302,7 +302,7 @@ output: ut_heat_eligible_person: false -# UT-05; section 203-2 B qualified and eligible categories, including the explicit LPR exception. +# Section 203-2 B qualified and eligible categories, including the explicit LPR exception. - name: "Case 13, TPS recipient." period: 2026 input: @@ -327,7 +327,7 @@ output: ut_heat_eligible_person: false -# UT-05: the qualifying veteran exception has no time limit. +# The qualifying veteran exception has no time limit. - name: "Case 14, recent parolee who is a veteran." period: 2026 input: @@ -351,7 +351,7 @@ output: ut_heat_eligible_person: true -# UT-05: an actual marital-unit connection; no synthetic SPM-wide veteran flag. +# An actual marital-unit connection; no synthetic SPM-wide veteran flag. - name: "Case 15, actual spouse of veteran qualifies without five years." period: 2026 input: @@ -388,7 +388,7 @@ output: ut_heat_eligible_person: [true, true] -# UT-05: share an SPM unit and household but have separate family, tax, and marital units. +# Share an SPM unit and household but have separate family, tax, and marital units. - name: "Case 16, unrelated veteran does not confer immigration eligibility." period: 2026 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_household_size.yaml index 399c55f1def..eec29338b43 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_household_size.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_household_size.yaml @@ -1,6 +1,6 @@ # Source: Utah HEAT section 204; exclude ineligible persons from household size. -# UT-05: only the eligible child and refugee count. +# Only the eligible child and refugee count. - name: "Case 1, only qualified members enter size." period: 2026 input: @@ -34,7 +34,7 @@ output: ut_heat_household_size: 2 -# UT-05: no eligible member. +# No eligible member. - name: "Case 2, no eligible members gives zero size." period: 2026 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_income_limit.yaml index eb5e599963a..ebe4566ad8a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_income_limit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_income_limit.yaml @@ -1,7 +1,7 @@ # Source: Utah HEAT section 608 and the March 1, 2026 poverty table. # Annual thresholds are 1.5 * (15,960 + 5,680 * (eligible members - 1)). -# UT-13: 1.5*(15960+5680*(size-1)); above 14 published monthly increment 710. +# 1.5*(15960+5680*(size-1)); above 14 published monthly increment 710. - name: "Case 1, published March limit for 1 eligible members." period: 2026 absolute_error_margin: 0.01 @@ -24,7 +24,7 @@ output: ut_heat_income_limit: 23_940 -# UT-13: 1.5*(15960+5680*(size-1)); above 14 published monthly increment 710. +# 1.5*(15960+5680*(size-1)); above 14 published monthly increment 710. - name: "Case 2, published March limit for 2 eligible members." period: 2026 absolute_error_margin: 0.01 @@ -51,7 +51,7 @@ output: ut_heat_income_limit: 32_460 -# UT-13: 1.5*(15960+5680*(size-1)); above 14 published monthly increment 710. +# 1.5*(15960+5680*(size-1)); above 14 published monthly increment 710. - name: "Case 3, published March limit for 3 eligible members." period: 2026 absolute_error_margin: 0.01 @@ -82,7 +82,7 @@ output: ut_heat_income_limit: 40_980 -# UT-13: 1.5*(15960+5680*(size-1)); above 14 published monthly increment 710. +# 1.5*(15960+5680*(size-1)); above 14 published monthly increment 710. - name: "Case 4, published March limit for 14 eligible members." period: 2026 absolute_error_margin: 0.01 @@ -157,7 +157,7 @@ output: ut_heat_income_limit: 134_700 -# UT-13: 1.5*(15960+5680*(size-1)); above 14 published monthly increment 710. +# 1.5*(15960+5680*(size-1)); above 14 published monthly increment 710. - name: "Case 5, published March limit for 15 eligible members." period: 2026 absolute_error_margin: 0.01 diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py b/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py index 62f9fb53804..0ff63a87215 100644 --- a/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py @@ -9,7 +9,7 @@ class ut_heat_countable_income(Variable): label = "Utah HEAT annual countable household income" defined_for = StateCode.UT reference = ( - "https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/401_Income/401_Income.htm", + "https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_400/Obsolete_401_(10_1_24_to_9_30_26)_Income.htm", "https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/402_Unearned_Income.htm", "https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/404_Self-Employment/404_Self-Employment.htm", "https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_400/Obsolete_405_(3_1_25_to_9_30_26)_Income_Exclusions.htm", @@ -29,8 +29,11 @@ def formula(spm_unit, period, parameters): ) # Annual inputs approximate the preceding eligibility month. All # otherwise countable adult income counts regardless of immigration. - # Section 406 rescinded the general 20% disregard; section 401 and the - # March 2026 worksheet prevail over the stale section 204/XLSX line. + # Earned income counts in full, following manual sections 401 and 406 + # (income disregards obsolete since September 30, 2023) and the March + # 2026 worksheet. Utah Admin. Code R982-403-6 disregards 20% of earned + # income, as do manual section 204 and the FY2026 benefit matrix + # workbook. wages = max_(person("employment_income", period), 0) business = add(person, period, p.business_income_sources) rental = max_(person("rental_income", period), 0) + max_( diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py index 764f68f53c8..d31c49fc4f9 100644 --- a/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py @@ -71,5 +71,8 @@ def formula(spm_unit, period, parameters): # Kerosene shares propane's cost standard, but section 605 names only # propane and fuel oil for this addition. No actual-expense cap applies. # Funding-dependent extra supplements and equipment/crisis are excluded. - amount = income_points + burden_component + target_credit + fuel_credit - return clip(amount, p.minimum, p.maximum) + # Section 607 and the March 2026 worksheet add the components with no + # floor or ceiling. Plan section 2.6 reports $140 and $800 as estimated + # benefit levels; $800 is the formula's maximum and $140 its one-person + # minimum, so larger households can receive less. + return income_points + burden_component + target_credit + fuel_credit diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 7d8c41658c6..7a72503f52e 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -5053,6 +5053,49 @@ candidates: state: open note: 'PE-US follows the regulation; resolve the pending conflict with the information letter. Code not yet on main: tx_ssi_state_supplement.' +- id: us-ut:heat-earned-income-disregard + program: liheap + years: + from: 2026 + to: 2026 + type: federal-fiscal + question: Does Utah HEAT disregard 20% of earned income, as Utah Admin. Code R982-403-6 provides, or count earned + income in full, as manual sections 401 and 406 and the March 2026 worksheet do? + sources: + - kind: regulation + citation: Utah Admin. Code R982-403-6(1), effective June 14, 2022 + url: https://adminrules.utah.gov/api/rule/4676/file/c1e409f0-8dd1-4548-b624-a00614e57fb2.html + - kind: manual + citation: Utah HEAT Manual section 204, effective October 1, 2024 + url: https://jobs.utah.gov/infosource/heatpolicy/Policy/200_Program_Standards/204_Eligibility_with_Ineligible_Resident.htm + - kind: calculator + citation: Utah FY2026 LIHEAP benefit matrix workbook, cell C8 + url: https://liheapch.acf.gov/docs/2026/benefits-matricies/UT_BenefitMatrix_2026.xlsx + - kind: manual + citation: Utah HEAT Manual section 401, effective October 1, 2024 to September 30, 2026 + url: https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_400/Obsolete_401_(10_1_24_to_9_30_26)_Income.htm + - kind: manual + citation: Utah HEAT Manual section 406, income disregards obsolete September 30, 2023 + url: https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/406_No_Longer_Applicable.htm + - kind: worksheet + citation: Utah HEAT Benefit Calculation Worksheet, effective March 1, 2026 + url: https://jobs.utah.gov/infosource/heatpolicy/assets/docs/Training/FY26/3.1.26%20Benefit%20Calculation%20Worksheet.pdf#page=1 + readings: + statute: No statute is supplied; R982-403-6(1) disregards 20% of earned income, including self-employment earned + income, and manual section 204 and the FY2026 workbook apply the same disregard. + other: Manual section 406 marks income disregards obsolete, and section 401 and the March 2026 worksheet compute + countable income with no disregard. + example: A one-person household with $27,000 annual wages heating a house with natural gas receives $0 in the PR + (monthly $2,250 exceeds the $1,995 limit) versus $234 with the disregard (monthly $1,800). + pe_us: + follows: conflicting-source + code: [] + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10344 + state: unreviewed + note: 'PE-US follows manual sections 401 and 406 and the March 2026 worksheet; R982-403 is a current rule with + its next five-year review due June 17, 2027, so decide whether it governs over the manual. Code not yet on main: + ut_heat_countable_income.' - id: us-va:claim-of-right-credit-gap-fill program: income-tax years: From 90af28c55ee2656973d7287407487e92b41ed0ac Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 23 Oct 2026 22:47:00 -0400 Subject: [PATCH 07/10] Sum Utah LIHEAP income without built-in sum Co-Authored-By: Claude Opus 5.5 --- .../states/ut/dwf/heat/income/ut_heat_countable_income.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py b/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py index 0ff63a87215..46296e4ad3f 100644 --- a/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py @@ -43,9 +43,9 @@ def formula(spm_unit, period, parameters): # not reconstruct HEAT expenses or isolate disallowed prior-period # losses. Current reported losses enter the household total; rental # losses do not. No business expense or insurance is deducted twice. - unearned = sum( - max_(person(source, period), 0) for source in p.unearned_income_sources - ) + unearned = 0 + for source in p.unearned_income_sources: + unearned = unearned + max_(person(source, period), 0) gross = spm_unit.sum( where(excluded_child, 0, wages + business + rental + unearned) ) From 1f79e33814c612a4847474bbb5c361a591d33fd7 Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 23 Oct 2026 23:21:35 -0400 Subject: [PATCH 08/10] Floor each Utah HEAT business income source at zero Co-Authored-By: Claude Opus 5.5 --- .../baseline/gov/states/ut/dwf/heat/ut_heat.yaml | 13 ++++++------- .../ut/dwf/heat/ut_heat_countable_income.yaml | 6 +++--- .../ut/dwf/heat/income/ut_heat_countable_income.py | 9 ++++++--- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml index 91b3d0dd04d..4d084d93d49 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml @@ -1374,11 +1374,10 @@ ut_heat_income_limit: 23_940 ut_heat: 0 -# Sections 402/404 distinguish unearned receipts from current net business losses. -# Estate loss: 15,000 + 12,000 = 27,000, above the 23,940 limit. -# Current business loss: 15,000 + 12,000 - 12,000 = 15,000; -# monthly income 1,250 gives 250 - ceil(100 * 1,250 / 1,330) + 170 = 326. -- name: "Case 45, estate losses cannot erase pensions while current business losses remain counted." +# Sections 402/404: each source is floored at zero, so neither an estate loss +# nor a current business loss offsets other income. +# Both households: 15,000 + 12,000 = 27,000, above the 23,940 limit. +- name: "Case 45, neither estate losses nor business losses erase wages and pensions." period: 2026 absolute_error_margin: 0.01 input: @@ -1416,8 +1415,8 @@ members: [person2] state_code: UT output: - ut_heat_countable_income: [27_000, 15_000] - ut_heat: [0, 326] + ut_heat_countable_income: [27_000, 27_000] + ut_heat: [0, 0] # Section 407 permits household-paid, unreimbursed medical premiums. Pretax # payroll premiums are disjoint from the alternative nonpretax aggregate/components. diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml index 2d8a2179e7b..c7ba548be89 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml @@ -544,8 +544,8 @@ output: ut_heat_countable_income: 15_960 -# Signed current net business 15960-3960=12000; prior-period loss reconstruction unavailable. -- name: "Case 21, current net business loss offsets other income." +# Each business source is floored at zero: 15,960 + 0 = 15,960; prior-period loss reconstruction unavailable. +- name: "Case 21, current net business loss does not offset other income." period: 2026 absolute_error_margin: 0.01 input: @@ -567,7 +567,7 @@ members: [person1] state_code: UT output: - ut_heat_countable_income: 12_000 + ut_heat_countable_income: 15_960 # A supplied positive premium aggregate takes precedence over its components. # Components represent paid, unreimbursed premiums; no calculated Medicare premium is used. diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py b/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py index 46296e4ad3f..4ce0943d272 100644 --- a/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py @@ -35,14 +35,17 @@ def formula(spm_unit, period, parameters): # income, as do manual section 204 and the FY2026 benefit matrix # workbook. wages = max_(person("employment_income", period), 0) - business = add(person, period, p.business_income_sources) + business = 0 + for source in p.business_income_sources: + business = business + max_(person(source, period), 0) rental = max_(person("rental_income", period), 0) + max_( person("farm_rent_income", period), 0 ) # Existing net business inputs stand for current net amounts. They do # not reconstruct HEAT expenses or isolate disallowed prior-period - # losses. Current reported losses enter the household total; rental - # losses do not. No business expense or insurance is deducted twice. + # losses. Each business and rental source is floored at zero, so a + # loss does not offset other income. No business expense or insurance + # is deducted twice. unearned = 0 for source in p.unearned_income_sources: unearned = unearned + max_(person(source, period), 0) From 3e16d526d3ffc5ba0d21c0cbaa27d05f5764898f Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 27 Oct 2026 15:41:49 -0400 Subject: [PATCH 09/10] Address review of Utah LIHEAP Co-Authored-By: Claude Opus 5.5 --- .../ut/dwf/heat/benefit/fuel_credit.yaml | 4 +- .../dwf/heat/benefit/fuel_credit_types.yaml | 9 + .../ut/dwf/heat/eligibility/income_limit.yaml | 4 + .../heat/income/business_income_sources.yaml | 14 - .../ut/dwf/heat/income/child_income_age.yaml | 5 + .../heat/income/earned_income_disregard.yaml | 19 + .../heat/income/earned_income_sources.yaml | 34 ++ .../heat/income/unearned_income_sources.yaml | 22 +- .../gov/states/ut/dwf/heat/ut_heat.yaml | 416 +++++++++++++----- .../ut/dwf/heat/ut_heat_countable_income.yaml | 236 ++++++---- .../states/ut/dwf/heat/ut_heat_eligible.yaml | 96 +++- .../ut/dwf/heat/ut_heat_eligible_person.yaml | 76 ++++ .../dwf/heat/eligibility/ut_heat_eligible.py | 4 + .../heat/income/ut_heat_countable_income.py | 69 ++- .../gov/states/ut/dwf/heat/ut_heat.py | 5 +- .../dwf/heat/ut_heat_energy_cost_standard.py | 27 +- source_conflicts/candidates.yaml | 119 ++++- 17 files changed, 883 insertions(+), 276 deletions(-) delete mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/income/business_income_sources.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/income/earned_income_disregard.yaml create mode 100644 policyengine_us/parameters/gov/states/ut/dwf/heat/income/earned_income_sources.yaml diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit.yaml index 5bb1cf1ca07..127c6992744 100644 --- a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit.yaml +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit.yaml @@ -1,10 +1,10 @@ -description: Utah adds this amount for primary propane or fuel oil heating under the Home Energy Assistance Target program. +description: Utah adds this amount for primary propane, kerosene or fuel oil heating under the Home Energy Assistance Target program. values: 2023-03-01: 150 metadata: unit: currency-USD period: year - label: Utah HEAT propane or fuel oil credit + label: Utah HEAT propane, kerosene or fuel oil credit reference: - title: Utah HEAT Manual, Section 605 Propane or Fuel Oil href: https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/605_Propane_or_Fuel_Oil.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit_types.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit_types.yaml index df6ca996579..7f4e06c3bc6 100644 --- a/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit_types.yaml +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/benefit/fuel_credit_types.yaml @@ -2,7 +2,12 @@ description: Utah grants an additional benefit for these primary heating fuels u values: 2023-03-01: - PROPANE + - KEROSENE - FUEL_OIL +# Section 605 names propane and fuel oil. Table III groups kerosene with +# propane, and the March 2026 worksheet's fuel list (GAS, PROPANE, OIL, +# ELECTRICITY, COAL/STEAM, WOOD) has no kerosene entry, so a kerosene household +# is entered as propane or oil, both of which carry the credit. metadata: unit: list period: year @@ -10,3 +15,7 @@ metadata: reference: - title: Utah HEAT Manual, Section 605 Propane or Fuel Oil href: https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/605_Propane_or_Fuel_Oil.htm + - title: Utah HEAT Manual, Section 609 Table III Monthly Energy Cost Standards + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/609_Table_III_Monthly_Energy_Cost_Standards.htm + - title: Utah HEAT Benefit Calculation Worksheet, effective March 1, 2026, Sheet2 fuel list + href: https://jobs.utah.gov/infosource/heatpolicy/assets/docs/Training/FY26/3.1.26%20Benefit%20Calculation%20Worksheet.xlsx diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/income_limit.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/income_limit.yaml index 7603b423f1c..14b8941fa6d 100644 --- a/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/income_limit.yaml +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/eligibility/income_limit.yaml @@ -8,3 +8,7 @@ metadata: reference: - title: Utah HEAT Manual, Section 608 Table II Federal Poverty Level Monthly Income Limits href: https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/608_Table_II_Federal_Poverty_Level_(FPL)_Monthly_Income_Limits.htm + - title: Utah FY2026 LIHEAP State Plan, Section 2.1 + href: https://liheapch.acf.gov/docs/2026/state-plans/UT_Plan_2026.pdf#page=8 + - title: Utah Admin. Code R982-403-1 + href: https://www.law.cornell.edu/regulations/utah/Utah-Admin-Code-R982-403-1 diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/business_income_sources.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/business_income_sources.yaml deleted file mode 100644 index 1bec943486f..00000000000 --- a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/business_income_sources.yaml +++ /dev/null @@ -1,14 +0,0 @@ -description: Utah counts these reported business income sources under the Home Energy Assistance Target program. -values: - 2024-10-01: - - self_employment_income - - sstb_self_employment_income - - farm_operations_income - - partnership_s_corp_income -metadata: - unit: list - period: year - label: Utah HEAT business income sources - reference: - - title: Utah HEAT Manual, Section 404 Self-Employment - href: https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/404_Self-Employment/404_Self-Employment.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/child_income_age.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/child_income_age.yaml index 80adc23e54e..3f28dcdee24 100644 --- a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/child_income_age.yaml +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/child_income_age.yaml @@ -1,6 +1,9 @@ description: Utah excludes all income of children below this age under the Home Energy Assistance Target program. values: 2025-03-01: 18 +# Section 405 excludes earned and unearned income of a child; R982-403-5(1) +# excludes the earned income of an unemancipated household member (candidate +# us-ut:heat-child-unearned-income). metadata: unit: year period: year @@ -8,3 +11,5 @@ metadata: reference: - title: Utah HEAT Manual, Section 405 Income Exclusions href: https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_400/Obsolete_405_(3_1_25_to_9_30_26)_Income_Exclusions.htm + - title: Utah Admin. Code R982-403-5(1) + href: https://www.law.cornell.edu/regulations/utah/Utah-Admin-Code-R982-403-5 diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/earned_income_disregard.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/earned_income_disregard.yaml new file mode 100644 index 00000000000..5e465a74fb0 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/earned_income_disregard.yaml @@ -0,0 +1,19 @@ +description: Utah excludes this share of earned income from countable income under the Home Energy Assistance Target program. +values: + 2022-06-14: 0.2 +# R982-403-6(2) applies the disregard to self-employment income after the +# cost of doing business; the business inputs are already net amounts. +# Worksheet cell C8 ("20% disregard") is B8*0.2, and TOTAL INCOME B11 is +# B8+B9-C8-B10. The worksheet's print area is A1:B67, so the PDF copy omits +# column C. +metadata: + unit: /1 + period: year + label: Utah HEAT earned income disregard rate + reference: + - title: Utah Admin. Code R982-403-6 + href: https://www.law.cornell.edu/regulations/utah/Utah-Admin-Code-R982-403-6 + - title: Utah HEAT Benefit Calculation Worksheet, effective March 1, 2026, cells C8 and B11 + href: https://jobs.utah.gov/infosource/heatpolicy/assets/docs/Training/FY26/3.1.26%20Benefit%20Calculation%20Worksheet.xlsx + - title: Utah HEAT Manual, Section 204 Eligibility with Ineligible Resident + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/200_Program_Standards/204_Eligibility_with_Ineligible_Resident.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/earned_income_sources.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/earned_income_sources.yaml new file mode 100644 index 00000000000..f4c41c4db1c --- /dev/null +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/earned_income_sources.yaml @@ -0,0 +1,34 @@ +description: Utah uses these earned income sources under the Home Energy Assistance Target program. +values: + 2024-10-01: + - employment_income + # R982-403-4(2)(a): "wages, including military base pay"; section 403 + # counts military pay as earned income. + - military_basic_pay + - self_employment_income + - sstb_self_employment_income + - farm_operations_income + - partnership_s_corp_income + # Section 403 treats rental income as self-employment. R982-403-4(2)(k) + # makes rent earned when the client manages the property and + # R982-403-3(2)(m) makes it unearned otherwise; the inputs do not + # identify who manages the property, so all rent is earned here. + - rental_income + - farm_rent_income +# The business inputs are current net amounts and do not reconstruct HEAT +# business expenses or isolate disallowed prior-period losses. Section 404 +# counts passive S corporation income as unearned, which +# partnership_s_corp_income does not separate. +metadata: + unit: list + period: year + label: Utah HEAT earned income sources + reference: + - title: Utah Admin. Code R982-403-4(2) + href: https://www.law.cornell.edu/regulations/utah/Utah-Admin-Code-R982-403-4 + - title: Utah Admin. Code R982-403-8 + href: https://www.law.cornell.edu/regulations/utah/Utah-Admin-Code-R982-403-8 + - title: Utah HEAT Manual, Section 403 Earned Income + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/403_Earned_Income.htm + - title: Utah HEAT Manual, Section 404 Self-Employment + href: https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/404_Self-Employment/404_Self-Employment.htm diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml index 1cfed647fe0..dea5449aedd 100644 --- a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml @@ -16,19 +16,31 @@ values: - financial_assistance - alimony_income - child_support_received - - ordinary_dividend_income + # Trust payments, which R982-403-3(2)(h) counts. - estate_income - gambling_winnings -# The cash contribution input represents private gifts. Section 405 exempts -# TANF/FEP/GA and nonprofit/government aid, ordinary bank interest, educational -# aid and enumerated nonrecurring receipts. Existing inputs do not separately -# represent adoption payments, passive royalties or foster-child elections. +# R982-403-5(12) excludes interest and dividend income, and plan item 1.9 +# leaves "Interest, dividends, or royalties" unchecked, so ordinary dividends +# are not listed. The cash contribution input represents private gifts. +# Section 405 exempts TANF/FEP/GA, financial payments made by the Department +# of Workforce Services and financial assistance from other government +# agencies, so the Utah state supplement (ut_ssp) is not listed either +# (candidate us-ut:heat-tanf-dws-payments). Section 405 also exempts ordinary +# bank interest, educational aid and enumerated nonrecurring receipts. +# Existing inputs do not separately represent adoption payments, passive +# royalties or foster-child elections. metadata: unit: list period: year label: Utah HEAT unearned income sources reference: + - title: Utah Admin. Code R982-403-3 + href: https://www.law.cornell.edu/regulations/utah/Utah-Admin-Code-R982-403-3 + - title: Utah Admin. Code R982-403-5 + href: https://www.law.cornell.edu/regulations/utah/Utah-Admin-Code-R982-403-5 - title: Utah HEAT Manual, Section 402 Unearned Income href: https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/402_Unearned_Income.htm - title: Utah HEAT Manual, Section 405 Income Exclusions href: https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_400/Obsolete_405_(3_1_25_to_9_30_26)_Income_Exclusions.htm + - title: Utah FY2026 LIHEAP State Plan, Section 1.9 + href: https://liheapch.acf.gov/docs/2026/state-plans/UT_Plan_2026.pdf#page=6 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml index 4d084d93d49..b5c6214bfec 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat.yaml @@ -1,5 +1,6 @@ # Sources: Utah HEAT manual sections 602-605 and 609; FY2026 plan pages 8-9. # The annual 2026 period represents the March 2026 snapshot. +# Countable earnings take the 20% disregard of Utah Admin. Code R982-403-6. # Income component 250 + zero-income burden 250. - name: "Case 1, zero income yields the ordinary gas award." @@ -47,8 +48,9 @@ output: ut_heat: 800 -# I 1330, P 1330; Q 100; gas burden round(15.8647)=16; 150+160=310. -- name: "Case 3, gas house at one hundred percent poverty." +# Wages 15,960 x 0.8 = 12,768; I 1064, P 1330; Q 80 => 170; +# gas burden round(19.83)=20; 170+200=370. +- name: "Case 3, gas house with wages at the poverty guideline after the earnings disregard." period: 2026 absolute_error_margin: 0.01 input: @@ -69,12 +71,13 @@ members: [person1] state_code: UT output: - ut_heat: 310 - ut_heat_countable_income: 15_960 + ut_heat: 370 + ut_heat_countable_income: 12_768 ut_heat_household_size: 1 ut_heat_income_limit: 23_940 -# I 1330; apartment 161/1330*100 rounds 12; 150+120=270. +# I 1064 (15,960 x 0.8 / 12); Q 80 => 170; apartment 161/1064*100 rounds 15; +# 170+150=320. - name: "Case 4, gas apartment uses its lower cost standard." period: 2026 absolute_error_margin: 0.01 @@ -96,9 +99,9 @@ members: [person1] state_code: UT output: - ut_heat: 270 + ut_heat: 320 -# I 1995; Q 150; 211/1995*100 rounds 11; 100+110=210. +# Wages 29,925 x 0.8 = 23,940, I 1995; Q 150; 211/1995*100 rounds 11; 100+110=210. - name: "Case 5, income at the inclusive one person limit." period: 2026 absolute_error_margin: 0.01 @@ -108,7 +111,7 @@ age: 40 immigration_status: CITIZEN ssi: 0 - employment_income: 23_940 + employment_income: 29_925 spm_units: spm_unit: members: [person1] @@ -122,8 +125,9 @@ output: ut_heat: 210 -# Monthly 1995.01 exceeds 1995, so the household is ineligible and receives 0. -- name: "Case 6, income twelve cents above the annual limit is ineligible." +# Wages 29,925.15 x 0.8 = 23,940.12; monthly 1995.01 exceeds 1995, so the +# household is ineligible and receives 0. +- name: "Case 6, countable income twelve cents above the annual limit is ineligible." period: 2026 absolute_error_margin: 0.01 input: @@ -132,7 +136,7 @@ age: 40 immigration_status: CITIZEN ssi: 0 - employment_income: 23_940.12 + employment_income: 29_925.15 spm_units: spm_unit: members: [person1] @@ -146,7 +150,8 @@ output: ut_heat: 0 -# I 1331; ceil(100*1331/1330)=101; burden 16; 149+160=309. +# Wages 15,972 x 0.8 = 12,777.60; I 1064.80; ceil(100*1064.80/1330)=ceil(80.06)=81 +# => 169; burden round(19.82)=20; 169+200=369. - name: "Case 7, poverty percentage rounds upward." period: 2026 absolute_error_margin: 0.01 @@ -168,9 +173,9 @@ members: [person1] state_code: UT output: - ut_heat: 309 + ut_heat: 369 -# I 2840, P 2277, Q 125; 100*71/2840=2.5=>3; 125+30=155. +# Wages 42,600 x 0.8 = 34,080; I 2840, P 2277, Q 125; 100*71/2840=2.5=>3; 125+30=155. - name: "Case 8, energy burden rounds half upward." period: 2026 absolute_error_margin: 0.01 @@ -180,7 +185,7 @@ age: 40 immigration_status: CITIZEN ssi: 0 - employment_income: 34_080 + employment_income: 42_600 person2: age: 40 immigration_status: CITIZEN @@ -202,7 +207,8 @@ output: ut_heat: 155 -# I 2840.04; Q 125; burden just below 2.5=>2; 125+20=145. +# Wages 42,600.60 x 0.8 = 34,080.48; I 2840.04; Q 125; burden just below 2.5=>2; +# 125+20=145. - name: "Case 9, energy burden just below the half point rounds down." period: 2026 absolute_error_margin: 0.01 @@ -212,7 +218,7 @@ age: 40 immigration_status: CITIZEN ssi: 0 - employment_income: 34_080.48 + employment_income: 42_600.60 person2: age: 40 immigration_status: CITIZEN @@ -234,9 +240,9 @@ output: ut_heat: 145 -# Size 14, P 7484, I 11225; Q 150 => income 250-150=100. Coal apartment -# 71/11225*100 rounds to burden 1 => 10. Section 607 adds the components -# with no floor: 100+10=110. +# Size 14, P 7484; wages 168,375 x 0.8 = 134,700, I 11225; Q 150 => income +# 250-150=100. Coal apartment 71/11225*100 rounds to burden 1 => 10. +# Section 607 adds the components with no floor: 100+10=110. - name: "Case 10, large household at the income limit receives the unfloored formula amount." period: 2026 absolute_error_margin: 0.01 @@ -246,7 +252,7 @@ age: 40 immigration_status: CITIZEN ssi: 0 - employment_income: 134_700 + employment_income: 168_375 person2: age: 40 immigration_status: CITIZEN @@ -573,7 +579,7 @@ output: ut_heat: 500 -# 20400-2400-2040=15960, no 20% earnings disregard. +# Wages 25,500 x 0.8 = 20,400; 20,400-2,400-2,040=15,960; I 1330; 150+160=310. - name: "Case 21, medical and child support deductions reach exact poverty." period: 2026 absolute_error_margin: 0.01 @@ -583,7 +589,7 @@ age: 40 immigration_status: CITIZEN ssi: 0 - employment_income: 20_400 + employment_income: 25_500 other_medical_expenses: 2_400 child_support_expense: 2_040 spm_units: @@ -625,7 +631,8 @@ ut_heat: 800 ut_heat_countable_income: 0 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 211, rounded burden 7. Fuel addition 0; 118+70=188. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 211, rounded burden 9. +# Fuel addition 0; 144+90=234. - name: "Case 23, natural gas house standard." period: 2026 absolute_error_margin: 0.01 @@ -655,9 +662,10 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 188 + ut_heat: 234 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 161, rounded burden 5. Fuel addition 0; 118+50=168. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 161, rounded burden 7. +# Fuel addition 0; 144+70=214. - name: "Case 24, natural gas apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -687,9 +695,10 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 168 + ut_heat: 214 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 541, rounded burden 18. Fuel addition 150; 118+180+150=448. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 541, rounded burden 23. +# Fuel addition 150; 144+230+150=524. - name: "Case 25, propane house standard." period: 2026 absolute_error_margin: 0.01 @@ -719,9 +728,10 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 448 + ut_heat: 524 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 408, rounded burden 14. Fuel addition 150; 118+140+150=408. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 408, rounded burden 17. +# Fuel addition 150; 144+170+150=464. - name: "Case 26, propane apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -751,9 +761,11 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 408 + ut_heat: 464 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 541, rounded burden 18. Fuel addition 0; 118+180=298. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 541, rounded burden 23. +# Kerosene shares propane's standard and its addition. +# Fuel addition 150; 144+230+150=524. - name: "Case 27, kerosene house standard." period: 2026 absolute_error_margin: 0.01 @@ -783,9 +795,11 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 298 + ut_heat: 524 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 408, rounded burden 14. Fuel addition 0; 118+140=258. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 408, rounded burden 17. +# Kerosene shares propane's standard and its addition. +# Fuel addition 150; 144+170+150=464. - name: "Case 28, kerosene apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -815,9 +829,11 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 258 + ut_heat: 464 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 468, rounded burden 16. Fuel addition 150; 118+160+150=428. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 468, 468/2400*100=19.5 rounds +# half upward to burden 20. +# Fuel addition 150; 144+200+150=494. - name: "Case 29, fuel oil house standard." period: 2026 absolute_error_margin: 0.01 @@ -847,9 +863,10 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 428 + ut_heat: 494 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 354, rounded burden 12. Fuel addition 150; 118+120+150=388. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 354, rounded burden 15. +# Fuel addition 150; 144+150+150=444. - name: "Case 30, fuel oil apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -879,9 +896,10 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 388 + ut_heat: 444 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 287, rounded burden 10. Fuel addition 0; 118+100=218. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 287, rounded burden 12. +# Fuel addition 0; 144+120=264. - name: "Case 31, electricity house standard." period: 2026 absolute_error_margin: 0.01 @@ -911,9 +929,10 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 218 + ut_heat: 264 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 218, rounded burden 7. Fuel addition 0; 118+70=188. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 218, rounded burden 9. +# Fuel addition 0; 144+90=234. - name: "Case 32, electricity apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -943,9 +962,10 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 188 + ut_heat: 234 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 92, rounded burden 3. Fuel addition 0; 118+30=148. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 92, rounded burden 4. +# Fuel addition 0; 144+40=184. - name: "Case 33, coal house standard." period: 2026 absolute_error_margin: 0.01 @@ -975,9 +995,10 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 148 + ut_heat: 184 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 71, rounded burden 2. Fuel addition 0; 118+20=138. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 71, rounded burden 3. +# Fuel addition 0; 144+30=174. - name: "Case 34, coal apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -1007,9 +1028,10 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 138 + ut_heat: 174 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 92, rounded burden 3. Fuel addition 0; 118+30=148. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 92, rounded burden 4. +# Fuel addition 0; 144+40=184. - name: "Case 35, other house standard." period: 2026 absolute_error_margin: 0.01 @@ -1039,9 +1061,10 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 148 + ut_heat: 184 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 71, rounded burden 2. Fuel addition 0; 118+20=138. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 71, rounded burden 3. +# Fuel addition 0; 144+30=174. - name: "Case 36, other apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -1071,9 +1094,10 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 138 + ut_heat: 174 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 343, rounded burden 11. Fuel addition 0; 118+110=228. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 343, rounded burden 14. +# Fuel addition 0; 144+140=284. - name: "Case 37, wood house standard." period: 2026 absolute_error_margin: 0.01 @@ -1103,9 +1127,10 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 228 + ut_heat: 284 -# Size 3, P 2277, I 3000, Q 132 => income 118; standard 260, rounded burden 9. Fuel addition 0; 118+90=208. +# Size 3, P 2277, I 2400 (36,000 x 0.8 / 12), Q 106 => income 144; standard 260, rounded burden 11. +# Fuel addition 0; 144+110=254. - name: "Case 38, wood apartment standard." period: 2026 absolute_error_margin: 0.01 @@ -1135,9 +1160,12 @@ members: [person1, person2, person3] state_code: UT output: - ut_heat: 208 + ut_heat: 254 # Separate ordinary gas, targeted propane, mixed-status young-child, and no-heating cases in one vector. +# Unit 1 matches Case 3 (wages 15,960 x 0.8 = 12,768: 370); unit 3 counts the +# undocumented parent's pension in full (unearned: 310 + 150 = 460); unit 4 has +# no heating and no electric bill, so no energy source (section 208). - name: "Case 39, mixed households preserve separate formulas and eligibility." period: 2026 input: @@ -1183,7 +1211,7 @@ spm_unit4: members: [person5] heating_type: NONE - heating_expense: 1_200 + pre_subsidy_electricity_expense: 0 ut_heat_dwelling_type: HOUSE households: household1: @@ -1199,18 +1227,22 @@ members: [person5] state_code: UT output: - ut_heat: [310, 800, 460, 0] + ut_heat: [370, 800, 460, 0] ut_heat_household_size: [1, 1, 1, 1] - ut_heat_countable_income: [15_960, 0, 15_960, 0] + ut_heat_countable_income: [12_768, 0, 15_960, 0] ut_heat_eligible: [true, true, true, false] -# A supplied positive premium aggregate takes precedence over its components. -# Components represent paid, unreimbursed premiums; no calculated Medicare premium is used. +# Section 407 and R982-403-7(1)(b) deduct health insurance premiums. A nonzero +# direct premium input replaces the components; otherwise non-Medicare premiums +# and the modeled Part B premium for an enrollee count. Cases 40-43 supply no +# Medicare Savings Program coverage, so the modeled Part B premium is +# 202.90 x 12 = 2,434.80. # Source: Utah HEAT section 407, effective March 1 through September 30, 2026. # Award arithmetic uses sections 602-604 and the March 2026 gas-house standard of 211. -# 18,360 - 2,400 = 15,960; I=1,330, P=1,330, income points=150, burden=160, age credit=150; award=460. -- name: "Case 40, reported paid Part B premiums affect the regular heating award." +# The reported 2,400 replaces the modeled 2,434.80: 18,360 - 2,400 = 15,960; +# I=1,330, P=1,330, income points=150, burden=160, age credit=150; award=460. +- name: "Case 40, a reported premium replaces the modeled Part B premium." period: 2026 absolute_error_margin: 0.01 input: @@ -1221,9 +1253,8 @@ pension_income: 18_360 ssi: 0 medicare_enrolled: true - health_insurance_premiums: 0 + health_insurance_premiums: 2_400 health_insurance_premiums_without_medicare_part_b: 0 - medicare_part_b_premiums_reported: 2_400 msp_part_b_premium_coverage: 0 spm_units: spm_unit1: @@ -1238,11 +1269,15 @@ members: ["person1"] state_code: "UT" output: + medicare_part_b_premium: 2_434.80 + medical_expense_health_insurance_premiums: 2_400 ut_heat_countable_income: 15_960 ut_heat: 460 -# 18,360 - 1,200 - 2,400 - 600 = 14,160; I=1,180, ceil(100*I/1,330)=89; 161+180+150=491. -- name: "Case 41, both premium components and other paid medical expenses count." +# 18,360 - (1,200 + 2,434.80) - 600 = 14,125.20; I=1,177.10, +# ceil(100*I/1,330)=ceil(88.50)=89 => 161; 211/1,177.10=17.93 => 18 => 180; +# 161+180+150=491. +- name: "Case 41, non-Medicare premiums, the modeled Part B premium and other medical expenses all count." period: 2026 absolute_error_margin: 0.01 input: @@ -1255,7 +1290,6 @@ medicare_enrolled: true health_insurance_premiums: 0 health_insurance_premiums_without_medicare_part_b: 1_200 - medicare_part_b_premiums_reported: 2_400 other_medical_expenses: 600 msp_part_b_premium_coverage: 0 spm_units: @@ -1271,9 +1305,11 @@ members: ["person1"] state_code: "UT" output: - ut_heat_countable_income: 14_160 + medicare_part_b_premium: 2_434.80 + ut_heat_countable_income: 14_125.20 ut_heat: 491 +# The 1,200 direct aggregate replaces 1,200 + 2,434.80 of components: # 18,360 - 1,200 aggregate - 600 other medical = 16,560; I=1,380; 146+150+150=446. - name: "Case 42, a positive premium aggregate prevents component double counting." period: 2026 @@ -1288,7 +1324,6 @@ medicare_enrolled: true health_insurance_premiums: 1_200 health_insurance_premiums_without_medicare_part_b: 1_200 - medicare_part_b_premiums_reported: 2_400 other_medical_expenses: 600 msp_part_b_premium_coverage: 0 spm_units: @@ -1304,11 +1339,14 @@ members: ["person1"] state_code: "UT" output: + medicare_part_b_premium: 2_434.80 + medical_expense_health_insurance_premiums: 1_200 ut_heat_countable_income: 16_560 ut_heat: 446 -# Income remains 18,360; I=1,530, ceil(100*I/1,330)=116; 134+140+150=424. -- name: "Case 43, Medicare enrollment without reported premiums gives no deduction." +# 18,360 - 2,434.80 = 15,925.20; I=1,327.10, ceil(100*I/1,330)=ceil(99.78)=100 +# => 150; 211/1,327.10=15.90 => 16 => 160; 150+160+150=460. +- name: "Case 43, Medicare enrollment deducts the modeled Part B premium." period: 2026 absolute_error_margin: 0.01 input: @@ -1321,7 +1359,6 @@ medicare_enrolled: true health_insurance_premiums: 0 health_insurance_premiums_without_medicare_part_b: 0 - medicare_part_b_premiums_reported: 0 msp_part_b_premium_coverage: 0 spm_units: spm_unit1: @@ -1336,12 +1373,15 @@ members: ["person1"] state_code: "UT" output: - ut_heat_countable_income: 18_360 - ut_heat: 424 + medicare_part_b_premium: 2_434.80 + ut_heat_countable_income: 15_925.20 + ut_heat: 460 # Section 402 counts UI payments. The state UI formula gives 764 per week for -# 14 weeks: 10,696 + 15,000 wages = 25,696, above the 23,940 income limit. -- name: "Case 44, modeled unemployment compensation counts toward heating eligibility." +# 14 weeks: 10,696. Wages 15,000 x 0.8 = 12,000; 12,000 + 10,696 = 22,696, +# within the 23,940 limit. I=1,891.33, ceil(142.21)=143 => 107; +# 211/1,891.33=11.16 => 11 => 110; 107+110=217. +- name: "Case 44, modeled unemployment compensation counts as unearned income." period: 2026 absolute_error_margin: 0.01 input: @@ -1370,13 +1410,13 @@ output: ut_ui: 10_696 total_unemployment_compensation: 10_696 - ut_heat_countable_income: 25_696 + ut_heat_countable_income: 22_696 ut_heat_income_limit: 23_940 - ut_heat: 0 + ut_heat: 217 # Sections 402/404: each source is floored at zero, so neither an estate loss # nor a current business loss offsets other income. -# Both households: 15,000 + 12,000 = 27,000, above the 23,940 limit. +# Both households: 15,000 x 0.8 + 12,000 = 24,000, above the 23,940 limit. - name: "Case 45, neither estate losses nor business losses erase wages and pensions." period: 2026 absolute_error_margin: 0.01 @@ -1415,13 +1455,15 @@ members: [person2] state_code: UT output: - ut_heat_countable_income: [27_000, 27_000] + ut_heat_countable_income: [24_000, 24_000] ut_heat: [0, 0] # Section 407 permits household-paid, unreimbursed medical premiums. Pretax # payroll premiums are disjoint from the alternative nonpretax aggregate/components. -# 24,000 - 1,200 = 22,800; monthly 1,900: 250 - 143 + 110 = 217. -# 24,000 - 1,200 pretax - 1,200 aggregate = 21,600; 250 - 136 + 120 = 234. +# 24,000 x 0.8 = 19,200; less 1,200 pretax = 18,000; monthly 1,500: +# 250 - 113 + 140 = 277. +# 19,200 - 1,200 pretax - 1,200 aggregate = 16,800; monthly 1,400: +# 250 - 106 + 150 = 294. - name: "Case 46, pretax premiums are deducted once alongside reported nonpretax premiums." period: 2026 absolute_error_margin: 0.01 @@ -1434,7 +1476,6 @@ pre_tax_health_insurance_premiums: 1_200 health_insurance_premiums: 0 health_insurance_premiums_without_medicare_part_b: 0 - medicare_part_b_premiums_reported: 0 person2: age: 40 immigration_status: CITIZEN @@ -1442,7 +1483,6 @@ pre_tax_health_insurance_premiums: 1_200 health_insurance_premiums: 1_200 health_insurance_premiums_without_medicare_part_b: 1_200 - medicare_part_b_premiums_reported: 0 spm_units: spm_unit1: members: [person1] @@ -1464,29 +1504,30 @@ members: [person2] state_code: UT output: - ut_heat_countable_income: [22_800, 21_600] - ut_heat: [217, 234] + ut_heat_countable_income: [18_000, 16_800] + ut_heat: [277, 294] -# Section 602 rounds poverty percentages upward. Annual 319.20 is exactly -# 2% of 15,960; cents below/equal/above give ceilings 2, 2, 3. Section 603's +# Section 602 rounds poverty percentages upward. Wages of 399 give countable +# income 399 x 0.8 = 319.20, exactly 2% of 15,960; a cent of wages below or +# above gives 319.192 or 319.208, so the ceilings are 2, 2, 3. Section 603's # burden component is capped at 250, giving awards 498, 498, 497. - name: "Case 47, exact poverty percentages and neighboring cents retain their correct ceilings." period: 2026 - absolute_error_margin: 0.01 + absolute_error_margin: 0.001 input: people: person1: age: 40 immigration_status: CITIZEN - employment_income: 319.19 + employment_income: 398.99 person2: age: 40 immigration_status: CITIZEN - employment_income: 319.20 + employment_income: 399 person3: age: 40 immigration_status: CITIZEN - employment_income: 319.21 + employment_income: 399.01 spm_units: spm_unit1: members: [person1] @@ -1517,7 +1558,7 @@ members: [person3] state_code: UT output: - ut_heat_countable_income: [319.19, 319.20, 319.21] + ut_heat_countable_income: [319.192, 319.20, 319.208] ut_heat: [498, 498, 497] # Sections 401/402 count ordinary DoD retirement pay separately from pensions @@ -1554,10 +1595,14 @@ ut_heat: 0 # Section 402 counts pensions and annuities as unearned income; section 405 -# exempts no uniformed-services survivor annuity. A surviving spouse receives -# a 26,000 Survivor Benefit Plan annuity: 26,000 / 12 = 2,166.67 per month, -# above the one-person 1,995 monthly limit (23,940 / 12); no award is due. -- name: "Case 49, military survivor annuity counts toward heating eligibility." +# exempts no uniformed-services survivor annuity. A surviving spouse aged 70 +# receives a 26,000 Survivor Benefit Plan annuity. Medicare Savings Program +# countable income of 26,000 / 12 - 20 = 2,146.67 a month exceeds the QI limit +# of 1,795.50, so the enrollee pays the 2,434.80 Part B premium, which is +# deducted: 26,000 - 2,434.80 = 23,565.20, within the 23,940 limit. +# I=1,963.77, ceil(147.65)=148 => 102; 211/1,963.77=10.74 => 11 => 110; +# 102+110+150 age credit=362. +- name: "Case 49, military survivor annuity counts as income for a Medicare enrollee." period: 2026 absolute_error_margin: 0.01 input: @@ -1579,7 +1624,178 @@ state_code: UT output: military_retirement_income: 26_000 - ut_heat_countable_income: 26_000 + medicare_part_b_premium: 2_434.80 + ut_heat_countable_income: 23_565.20 ut_heat_income_limit: 23_940 - ut_heat_eligible: false - ut_heat: 0 + ut_heat_eligible: true + ut_heat: 362 + +# Section 407 and R982-403-7(1)(b): an enrollee's Part B premium is a +# deductible health insurance premium. Medicare Savings Program countable +# income is 24,500 / 12 - 20 = 2,021.67 a month, above the QI limit of +# 1,330 x 1.35 = 1,795.50, so the enrollee pays 202.90 x 12 = 2,434.80. +# 24,500 - 2,434.80 = 22,065.20, within the 23,940 limit. I=1,838.77, +# ceil(138.25)=139 => 111; 211/1,838.77=11.48 => 11 => 110; +# 111+110+150 age credit=371. +- name: "Case 50, a Social Security retiree's modeled Part B premium is deducted." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + immigration_status: CITIZEN + ssi: 0 + social_security_retirement: 24_500 + medicare_enrolled: true + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + msp_part_b_premium_coverage: 0 + medicare_part_b_premium: 2_434.80 + ut_heat_countable_income: 22_065.20 + ut_heat_income_limit: 23_940 + ut_heat: 371 + +# R982-403-4(2)(a) and section 403 count military base pay as earned income: +# 30,000 x 0.8 = 24,000, within the size-2 limit of 32,460. +# P=ceil(21,640 / 12)=1,804; I=2,000, ceil(110.86)=111 => 139; +# 211/2,000=10.55 => 11 => 110; 139+110=249. +- name: "Case 51, military basic pay is earned income subject to the disregard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + immigration_status: CITIZEN + ssi: 0 + is_military: true + military_basic_pay: 30_000 + person2: + age: 30 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT + output: + ut_heat_household_size: 2 + ut_heat_countable_income: 24_000 + ut_heat_income_limit: 32_460 + ut_heat: 249 + +# R982-403-5(12) excludes interest and dividend income, and plan item 1.9 +# leaves dividends unchecked, so only the 15,000 pension counts (with the +# dividends, 24,000 would exceed the 23,940 limit). I=1,250, +# ceil(93.98)=94 => 156; 211/1,250=16.88 => 17 => 170; 156+170=326. +- name: "Case 52, ordinary dividends do not count toward heating eligibility." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 50 + immigration_status: CITIZEN + ssi: 0 + pension_income: 15_000 + ordinary_dividend_income: 9_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + heat_expense_included_in_rent: false + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 15_000 + ut_heat: 326 + +# Plan item 1.1 and section 207: HEAT is one benefit a program year covering +# heating and cooling, and section 603 takes the standard for the household's +# primary energy source. A home with no heating that pays an electric bill +# uses the electricity house standard of 287 (3,444 a year). +# Wages 15,960 x 0.8 = 12,768; I=1,064, Q 80 => 170; 287/1,064=26.97 => 27, +# capped at 25 => 250; 170+250=420. +- name: "Case 53, a home with no heating that pays an electric bill uses the electricity standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 15_960 + spm_units: + spm_unit: + members: [person1] + heating_type: NONE + pre_subsidy_electricity_expense: 900 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_energy_cost_standard: 3_444 + ut_heat_eligible: true + ut_heat_countable_income: 12_768 + ut_heat: 420 + +# A solar home is taken as grid-tied with electric supplemental heat, so it +# uses the electricity house standard of 287 (3,444 a year), as Case 31 does: +# size 3, I=2,400 (36,000 x 0.8 / 12), Q 106 => 144; 287/2,400=11.96 => 12 +# => 120; 144+120=264. +- name: "Case 54, solar heating uses the electricity standard." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 36_000 + person2: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: SOLAR + pre_subsidy_electricity_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + ut_heat_energy_cost_standard: 3_444 + ut_heat: 264 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml index c7ba548be89..12ba09e030a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml @@ -1,9 +1,11 @@ -# Sources: Utah HEAT sections 401-407 and archived March 2026 sections 405/407. -# Section 406 rescinded the general 20% earnings disregard. -# Medicare and prior-period business-loss reconstruction remain outside scope. +# Sources: Utah Admin. Code R982-403, Utah HEAT sections 401-407 and archived +# March 2026 sections 405/407. +# R982-403-6 and the March 2026 worksheet (cell C8) disregard 20% of earned +# income, including net self-employment and rental income (section 403). +# Prior-period business-loss reconstruction remains outside scope. -# Sections 401 and 406 and the March 2026 worksheet count gross earnings in full. -- name: "Case 1, gross earnings have no twenty percent disregard." +# R982-403-6(1): 18,000 x (1 - 0.2) = 14,400. +- name: "Case 1, twenty percent of gross earnings is disregarded." period: 2026 absolute_error_margin: 0.01 input: @@ -24,9 +26,9 @@ members: [person1] state_code: UT output: - ut_heat_countable_income: 18_000 + ut_heat_countable_income: 14_400 -# Exclusion covers earned and unearned child income. +# Exclusion covers earned and unearned child income; 15,960 x 0.8 = 12,768. - name: "Case 2, all income of a seventeen year old is excluded." period: 2026 absolute_error_margin: 0.01 @@ -54,9 +56,9 @@ members: [person1, person2] state_code: UT output: - ut_heat_countable_income: 15_960 + ut_heat_countable_income: 12_768 -# Age 18 remains in school. +# Age 18 remains in school; 15,960 x 0.8 = 12,768. - name: "Case 3, eighteen year old secondary student income is excluded." period: 2026 absolute_error_margin: 0.01 @@ -85,9 +87,10 @@ members: [person1, person2] state_code: UT output: - ut_heat_countable_income: 15_960 + ut_heat_countable_income: 12_768 -# 15960+12000+2400; the exclusion ends when the person is not in school. +# (15,960 + 12,000) x 0.8 + 2,400 = 24,768; the exclusion ends when the person +# is not in school. - name: "Case 4, eighteen year old out of school income counts." period: 2026 absolute_error_margin: 0.01 @@ -117,9 +120,9 @@ members: [person1, person2] state_code: UT output: - ut_heat_countable_income: 30_360 + ut_heat_countable_income: 24_768 -# The student exception ends after 18. +# The student exception ends after 18; (15,960 + 12,000) x 0.8 = 22,368. - name: "Case 5, nineteen year old student income counts." period: 2026 absolute_error_margin: 0.01 @@ -147,7 +150,7 @@ members: [person1, person2] state_code: UT output: - ut_heat_countable_income: 27_960 + ut_heat_countable_income: 22_368 # Support received is parent income. - name: "Case 6, parent child support counts despite child income exclusion." @@ -178,7 +181,8 @@ output: ut_heat_countable_income: 3_600 -# Count income of all age-eligible members; no general 20% disregard. +# Count income of all age-eligible members; section 204 allows the disregard on +# the ineligible resident's earnings too: (12,000 + 6,000) x 0.8 = 14,400. - name: "Case 7, income of ineligible resident counts." period: 2026 absolute_error_margin: 0.01 @@ -205,7 +209,7 @@ members: [person1, person2] state_code: UT output: - ut_heat_countable_income: 18_000 + ut_heat_countable_income: 14_400 # DWS government assistance and ordinary bank interest are exempt. - name: "Case 8, FEP general assistance and bank interest are excluded." @@ -233,8 +237,9 @@ output: ut_heat_countable_income: 0 -# Recurring cash gifts and ordinary dividends count; no excluded agency classification applies. -- name: "Case 9, ordinary dividends and cash contributions count." +# R982-403-5(12) excludes dividend income and plan item 1.9 leaves dividends +# unchecked; the 2,400 of recurring cash contributions counts. +- name: "Case 9, cash contributions count but ordinary dividends do not." period: 2026 absolute_error_margin: 0.01 input: @@ -256,7 +261,7 @@ members: [person1] state_code: UT output: - ut_heat_countable_income: 3_600 + ut_heat_countable_income: 2_400 # Annual SSI input 2400=200/month; count gross SSA and pension income. - name: "Case 10, gross social security SSI and pension count once." @@ -310,8 +315,9 @@ output: ut_heat_countable_income: 12_000 -# No additional percentage business deduction. -- name: "Case 12, net business and rental income use supplied net amounts." +# Section 403 treats rental income as self-employment, so both supplied net +# amounts are earned income: (12,000 + 3,600) x 0.8 = 12,480. +- name: "Case 12, net business and rental income are earned income subject to the disregard." period: 2026 absolute_error_margin: 0.01 input: @@ -333,9 +339,10 @@ members: [person1] state_code: UT output: - ut_heat_countable_income: 15_600 + ut_heat_countable_income: 12_480 -# Rental income counts only when positive; a rental loss does not offset earnings. +# Rental income counts only when positive; a rental loss does not offset +# earnings: 15,960 x 0.8 = 12,768. - name: "Case 13, negative rent does not offset earnings." period: 2026 absolute_error_margin: 0.01 @@ -358,9 +365,9 @@ members: [person1] state_code: UT output: - ut_heat_countable_income: 15_960 + ut_heat_countable_income: 12_768 -# Qualified paid expenses 20400-2400-2040. +# 20,400 x 0.8 = 16,320, less paid medical 2,400 and child support 2,040 = 11,880. - name: "Case 14, medical and support payments are deductible." period: 2026 absolute_error_margin: 0.01 @@ -384,9 +391,10 @@ members: [person1] state_code: UT output: - ut_heat_countable_income: 15_960 + ut_heat_countable_income: 11_880 -# Medical payment by an excluded member is allowed; that member's income also counts. +# Medical payment by an excluded member is allowed; that member's income also +# counts: (15,960 + 6,000) x 0.8 - 6,000 = 11,568. - name: "Case 15, medical payments by ineligible member remain deductible." period: 2026 absolute_error_margin: 0.01 @@ -414,9 +422,10 @@ members: [person1, person2] state_code: UT output: - ut_heat_countable_income: 15_960 + ut_heat_countable_income: 11_568 -# The supplied generic premium 1200 takes precedence over the component fallback 3600. +# The supplied generic premium 1,200 takes precedence over the component +# fallback 3,600: 18,000 x 0.8 - 1,200 = 13,200. - name: "Case 16, reported premiums are not counted twice." period: 2026 absolute_error_margin: 0.01 @@ -440,9 +449,9 @@ members: [person1] state_code: UT output: - ut_heat_countable_income: 16_800 + ut_heat_countable_income: 13_200 -# 18000-1200-840; no automatically derived Medicare amount. +# 18,000 x 0.8 - 1,200 - 840 = 12,360; a person under 65 has no Medicare premium. - name: "Case 17, supplied non Medicare premiums are the fallback." period: 2026 absolute_error_margin: 0.01 @@ -467,9 +476,9 @@ members: [person1] state_code: UT output: - ut_heat_countable_income: 15_960 + ut_heat_countable_income: 12_360 -# Qualifying alimony paid outside the unit. +# Qualifying alimony paid outside the unit: 18,400 x 0.8 - 2,440 = 12,280. - name: "Case 18, alimony paid is deductible." period: 2026 absolute_error_margin: 0.01 @@ -492,9 +501,9 @@ members: [person1] state_code: UT output: - ut_heat_countable_income: 15_960 + ut_heat_countable_income: 12_280 -# Countable income is nonnegative. +# Countable income is nonnegative: 1,000 x 0.8 - 2,400 floors at 0. - name: "Case 19, deductions floor countable income at zero." period: 2026 absolute_error_margin: 0.01 @@ -519,7 +528,7 @@ output: ut_heat_countable_income: 0 -# Enumerated deductions do not include ordinary child care. +# Enumerated deductions do not include ordinary child care: 15,960 x 0.8 = 12,768. - name: "Case 20, childcare is not a general qualified deduction." period: 2026 absolute_error_margin: 0.01 @@ -542,9 +551,10 @@ members: [person1] state_code: UT output: - ut_heat_countable_income: 15_960 + ut_heat_countable_income: 12_768 -# Each business source is floored at zero: 15,960 + 0 = 15,960; prior-period loss reconstruction unavailable. +# Each earned source is floored at zero: (15,960 + 0) x 0.8 = 12,768; +# prior-period loss reconstruction unavailable. - name: "Case 21, current net business loss does not offset other income." period: 2026 absolute_error_margin: 0.01 @@ -567,15 +577,18 @@ members: [person1] state_code: UT output: - ut_heat_countable_income: 15_960 + ut_heat_countable_income: 12_768 -# A supplied positive premium aggregate takes precedence over its components. -# Components represent paid, unreimbursed premiums; no calculated Medicare premium is used. +# Section 407 and R982-403-7(1)(b) deduct health insurance premiums. A nonzero +# direct premium input replaces the components; otherwise non-Medicare premiums +# and the modeled Part B premium for an enrollee (net of Medicare Savings +# Program coverage) count, and any Part A premium is added once. # Source: Utah HEAT section 407, effective March 1 through September 30, 2026. -# Award arithmetic uses sections 602-604 and the March 2026 gas-house standard of 211. -# 18,360 - 1,200 - 2,400 - 600 = 14,160 despite a negative aggregate report. -- name: "Case 22, a nonpositive aggregate falls back to both reported premium components." +# No direct premium and no Medicare Savings Program coverage, so the modeled +# Part B premium is 202.90 x 12 = 2,434.80. +# 18,360 - (1,200 + 2,434.80) - 600 = 14,125.20. +- name: "Case 22, a zero direct premium falls back to non-Medicare premiums plus the modeled Part B premium." period: 2026 absolute_error_margin: 0.01 input: @@ -586,9 +599,8 @@ pension_income: 18_360 ssi: 0 medicare_enrolled: true - health_insurance_premiums: -1 + health_insurance_premiums: 0 health_insurance_premiums_without_medicare_part_b: 1_200 - medicare_part_b_premiums_reported: 2_400 other_medical_expenses: 600 msp_part_b_premium_coverage: 0 spm_units: @@ -604,55 +616,113 @@ members: ["person1"] state_code: "UT" output: - ut_heat_countable_income: 14_160 + medicare_part_b_premium: 2_434.80 + medical_expense_health_insurance_premiums: 3_634.80 + ut_heat_countable_income: 14_125.20 -# Negative component reports cannot reduce the other positive deduction; 18,360-2,400 and 18,360-1,200. -- name: "Case 23, reported premium components are independently nonnegative." +# A retiree with 35 quarters of covered work pays the reduced Part A premium: +# 311 x 12 = 3,732. Medicare Savings Program countable income is +# 18,360 / 12 - 20 = 1,510 a month: above the QMB limit of 1,330 (no Part A +# coverage) but below the QI limit of 1,330 x 1.35 = 1,795.50 with no assets, +# so the program pays the 2,434.80 Part B premium. +# 18,360 - 3,732 - 0 = 14,628. +- name: "Case 23, a Part A premium is deducted and Medicare Savings Program coverage leaves no Part B premium." period: 2026 absolute_error_margin: 0.01 input: people: person1: age: 68 - immigration_status: "CITIZEN" - pension_income: 18_360 - ssi: 0 - medicare_enrolled: true - health_insurance_premiums: 0 - health_insurance_premiums_without_medicare_part_b: -300 - medicare_part_b_premiums_reported: 2_400 - msp_part_b_premium_coverage: 0 - person2: - age: 68 - immigration_status: "CITIZEN" + immigration_status: CITIZEN pension_income: 18_360 ssi: 0 medicare_enrolled: true - health_insurance_premiums: 0 - health_insurance_premiums_without_medicare_part_b: 1_200 - medicare_part_b_premiums_reported: -300 - msp_part_b_premium_coverage: 0 + medicare_quarters_of_coverage: 35 spm_units: - spm_unit1: - members: ["person1"] - heating_type: "NATURAL_GAS" - gas_expense: 1_000 - heat_expense_included_in_rent: false - ut_fep: 0 - ut_heat_dwelling_type: "HOUSE" - spm_unit2: - members: ["person2"] - heating_type: "NATURAL_GAS" + spm_unit: + members: [person1] + heating_type: NATURAL_GAS gas_expense: 1_000 heat_expense_included_in_rent: false - ut_fep: 0 - ut_heat_dwelling_type: "HOUSE" + ut_heat_dwelling_type: HOUSE households: - household1: - members: ["person1"] - state_code: "UT" - household2: - members: ["person2"] - state_code: "UT" + household: + members: [person1] + state_code: UT + output: + medicare_part_a_premium: 3_732 + msp_part_b_premium_coverage: 2_434.80 + medicare_part_b_premium: 0 + ut_heat_countable_income: 14_628 + +# Every earned and unearned list entry not exercised elsewhere, for an adult. +# Earned: (5,000 SSTB self-employment + 2,500 farm operations + 2,000 +# partnership/S corporation + 1,000 farm rent) x 0.8 = 8,400. +# Unearned: 1,200 railroad + 2,400 retirement distributions + 3,600 survivor +# + 4,800 disability + 600 strike + 300 gambling + 1,800 estate = 14,700. +# 8,400 + 14,700 = 23,100. +- name: "Case 24, remaining earned and unearned income sources count." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 50 + immigration_status: CITIZEN + ssi: 0 + sstb_self_employment_income: 5_000 + farm_operations_income: 2_500 + partnership_s_corp_income: 2_000 + farm_rent_income: 1_000 + railroad_benefits: 1_200 + retirement_distributions: 2_400 + survivor_benefits: 3_600 + disability_benefits: 4_800 + strike_benefits: 600 + gambling_winnings: 300 + estate_income: 1_800 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_countable_income: 23_100 + +# Section 405 excludes the income of an 18-year-old still in school; K-12 +# enrollment qualifies as well as secondary school: 15,960 x 0.8 = 12,768. +- name: "Case 25, eighteen year old K-12 student income is excluded." + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 15_960 + person2: + age: 18 + immigration_status: CITIZEN + ssi: 0 + is_in_k12_school: true + is_in_secondary_school: false + employment_income: 12_000 + pension_income: 2_400 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2] + state_code: UT output: - ut_heat_countable_income: [15_960, 17_160] + ut_heat_countable_income: 12_768 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml index 1280e4820da..3dc589246b9 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml @@ -1,7 +1,7 @@ # Sources: Utah HEAT manual sections 202-204, 208 and March 2026 section 608. # The annual 2026 period represents the March 2026 snapshot. -# Monthly income 1995 equals published 150% limit. +# Wages 29,925 x 0.8 = 23,940; monthly income 1995 equals published 150% limit. - name: "Case 1, inclusive income limit." period: 2026 input: @@ -10,7 +10,7 @@ age: 40 immigration_status: CITIZEN ssi: 0 - employment_income: 23_940 + employment_income: 29_925 spm_units: spm_unit: members: [person1] @@ -24,7 +24,7 @@ output: ut_heat_eligible: true -# Monthly income 1995.01 exceeds limit. +# Wages 29,925.15 x 0.8 = 23,940.12; monthly income 1995.01 exceeds limit. - name: "Case 2, income just above limit." period: 2026 input: @@ -33,7 +33,7 @@ age: 40 immigration_status: CITIZEN ssi: 0 - employment_income: 23_940.12 + employment_income: 29_925.15 spm_units: spm_unit: members: [person1] @@ -161,8 +161,9 @@ output: ut_heat_eligible: false -# NONE affirmatively denotes no home heat. -- name: "Case 8, no heating source." +# NONE affirmatively denotes no home heat; with no electric bill the home is +# not connected to an energy source (section 208). +- name: "Case 8, no heating source and no electric bill." period: 2026 input: people: @@ -174,7 +175,7 @@ spm_unit: members: [person1] heating_type: NONE - heating_expense: 1_200 + pre_subsidy_electricity_expense: 0 ut_heat_dwelling_type: HOUSE households: household: @@ -326,3 +327,84 @@ state_code: UT output: ut_heat_eligible: true + +# Section 208 and plan item 2.3: public housing counts as subsidized housing, +# and heat included in subsidized rent is not a household energy cost. +- name: "Case 15, public housing with heat in rent and no utility bill." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 0 + ut_heat_dwelling_type: HOUSE + heat_expense_included_in_rent: true + pre_subsidy_electricity_expense: 0 + receives_housing_assistance: false + households: + household: + members: [person1] + state_code: UT + is_in_public_housing: true + output: + ut_heat_eligible: false + +# A public housing tenant who pays an electric bill directly is responsible for +# an energy cost (section 208). +- name: "Case 16, public housing with a separately paid electric bill." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 0 + ut_heat_dwelling_type: HOUSE + heat_expense_included_in_rent: true + pre_subsidy_electricity_expense: 600 + receives_housing_assistance: false + households: + household: + members: [person1] + state_code: UT + is_in_public_housing: true + output: + ut_heat_eligible: true + +# The household reports paying for heat without a bill amount; the heating-cost +# flag alone establishes responsibility (section 208). Case 9 is the same +# household without the flag. +- name: "Case 17, reported heating costs without a bill amount qualify." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 0 + ut_heat_dwelling_type: HOUSE + pre_subsidy_electricity_expense: 0 + heat_expense_included_in_rent: false + has_heating_expense: true + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible_person.yaml index 84250478efb..fe2a4448e7f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible_person.yaml @@ -432,3 +432,79 @@ members: [person2] output: ut_heat_eligible_person: [true, false] + +# Section 203-2B: active-duty service is an eligible criterion for a parolee +# with no time in qualified status. +- name: "Case 17, recent parolee on active duty qualifies." + period: 2026 + input: + people: + person1: + age: 30 + immigration_status: PAROLED_ONE_YEAR + ssi: 0 + years_since_us_entry: 0 + is_veteran: false + is_military: true + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_eligible_person: true + +# Section 203-2B extends the service criterion to a veteran's dependent child. +# The 10-year-old is a dependent in the veteran's tax unit and qualifies; the +# 25-year-old child files a separate return, is not a dependent, and has no +# qualifying service or five years in status, so does not. +- name: "Case 18, a veteran's dependent child qualifies but a non-dependent adult child does not." + period: 2026 + input: + people: + person1: + age: 45 + immigration_status: CITIZEN + ssi: 0 + is_veteran: true + is_tax_unit_head: true + person2: + age: 10 + immigration_status: PAROLED_ONE_YEAR + ssi: 0 + years_since_us_entry: 0 + person3: + age: 25 + immigration_status: PAROLED_ONE_YEAR + ssi: 0 + years_since_us_entry: 0 + is_tax_unit_head: true + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + heating_expense: 1_200 + ut_heat_dwelling_type: HOUSE + households: + household: + members: [person1, person2, person3] + state_code: UT + marital_units: + marital_unit1: + members: [person1] + marital_unit2: + members: [person2] + marital_unit3: + members: [person3] + tax_units: + tax_unit1: + members: [person1, person2] + tax_unit2: + members: [person3] + output: + ut_heat_eligible_person: [true, true, false] diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_eligible.py b/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_eligible.py index b720e7e016b..ca70a4fa5d7 100644 --- a/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_eligible.py +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/eligibility/ut_heat_eligible.py @@ -32,6 +32,10 @@ def formula(spm_unit, period, parameters): own_wood = fuel == fuel.possible_values.WOOD responsible = directly_paid | (heat_in_rent & ~subsidized) | own_wood housing_eligible = ~subsidized | directly_paid + # A household with no heating (NONE) has a standard, and so + # qualifies, only when it pays an electric bill; one with no bill is + # not connected to an energy source (section 208). UNSPECIFIED has no + # standard. supported_fuel = spm_unit("ut_heat_energy_cost_standard", period) > 0 # The annual potential award assumes no prior seasonal payment or # disqualifying utility credit balance. Emancipation, institutions, diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py b/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py index 4ce0943d272..5db7f77b495 100644 --- a/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py @@ -9,12 +9,18 @@ class ut_heat_countable_income(Variable): label = "Utah HEAT annual countable household income" defined_for = StateCode.UT reference = ( + "https://www.law.cornell.edu/regulations/utah/Utah-Admin-Code-R982-403-2", + "https://www.law.cornell.edu/regulations/utah/Utah-Admin-Code-R982-403-6", + "https://www.law.cornell.edu/regulations/utah/Utah-Admin-Code-R982-403-7", "https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_400/Obsolete_401_(10_1_24_to_9_30_26)_Income.htm", "https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/402_Unearned_Income.htm", + "https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/403_Earned_Income.htm", "https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/404_Self-Employment/404_Self-Employment.htm", "https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_400/Obsolete_405_(3_1_25_to_9_30_26)_Income_Exclusions.htm", "https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_400/Obsolete_407_(3_1_26_to_9_30_26)_Qualified_Deductions.htm", - "https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/406_No_Longer_Applicable.htm", + "https://jobs.utah.gov/infosource/heatpolicy/Policy/200_Program_Standards/204_Eligibility_with_Ineligible_Resident.htm", + "https://jobs.utah.gov/infosource/heatpolicy/assets/docs/Training/FY26/3.1.26%20Benefit%20Calculation%20Worksheet.xlsx", + "https://liheapch.acf.gov/docs/2026/state-plans/UT_Plan_2026.pdf#page=6", ) def formula(spm_unit, period, parameters): @@ -29,56 +35,39 @@ def formula(spm_unit, period, parameters): ) # Annual inputs approximate the preceding eligibility month. All # otherwise countable adult income counts regardless of immigration. - # Earned income counts in full, following manual sections 401 and 406 - # (income disregards obsolete since September 30, 2023) and the March - # 2026 worksheet. Utah Admin. Code R982-403-6 disregards 20% of earned - # income, as do manual section 204 and the FY2026 benefit matrix - # workbook. - wages = max_(person("employment_income", period), 0) - business = 0 - for source in p.business_income_sources: - business = business + max_(person(source, period), 0) - rental = max_(person("rental_income", period), 0) + max_( - person("farm_rent_income", period), 0 - ) - # Existing net business inputs stand for current net amounts. They do - # not reconstruct HEAT expenses or isolate disallowed prior-period - # losses. Each business and rental source is floored at zero, so a - # loss does not offset other income. No business expense or insurance - # is deducted twice. + # Each source is floored at zero, so a business or rental loss does + # not offset other income. R982-403-6 disregards 20% of earned income, + # including self-employment income after business costs; section 204 + # and the March 2026 worksheet (cell C8) apply the same disregard. + earned = 0 + for source in p.earned_income_sources: + earned = earned + max_(person(source, period), 0) unearned = 0 for source in p.unearned_income_sources: unearned = unearned + max_(person(source, period), 0) - gross = spm_unit.sum( - where(excluded_child, 0, wages + business + rental + unearned) - ) + counted = earned * (1 - p.earned_income_disregard) + unearned + income = spm_unit.sum(where(excluded_child, 0, counted)) # Child support belongs on the receiving parent. TANF/FEP/GA, ordinary # bank interest, educational aid and exempt nonrecurring receipts are # excluded. Broad inputs cannot isolate all exempt gifts, settlements, # combat pay, third-party payments, or cash withdrawals from trusts. - premiums = person("health_insurance_premiums", period) - # A positive aggregate premium already includes reported components. - # Otherwise use both reported components without deriving Medicare. - reported_premiums = where( - premiums > 0, - premiums, - max_( - person("health_insurance_premiums_without_medicare_part_b", period), - 0, - ) - + max_(person("medicare_part_b_premiums_reported", period), 0), - ) + # Section 407 and R982-403-7(1)(b) count health insurance premiums as + # medical expenses. The premium aggregate takes a nonzero direct + # premium input, or else non-Medicare premiums plus the modeled Part B + # premium for enrollees net of Medicare Savings Program coverage; Part A + # and pretax payroll premiums are added once each. medical = spm_unit.sum( - max_(reported_premiums, 0) + max_(person("medical_expense_health_insurance_premiums", period), 0) + max_(person("pre_tax_health_insurance_premiums", period), 0) + + person("medicare_part_a_premium", period) + max_(person("other_medical_expenses", period), 0) ) support_paid = add( spm_unit, period, ["child_support_expense", "alimony_expense"] ) - # Reported medical costs approximate qualified uncompensated payments; - # Medicare premiums are not derived. Self-employed premiums must be - # included only once. Support to an included child, payment timing and - # medical-purpose restrictions are not identified by these inputs. - # The seasonal equipment deduction is outside the heating-bill scope. - return max_(gross - medical - max_(support_paid, 0), 0) + # Reported medical costs approximate qualified uncompensated payments. + # Self-employed premiums must be included only once. Support to an + # included child, payment timing and medical-purpose restrictions are + # not identified by these inputs. The seasonal equipment deduction is + # outside the heating-bill scope. + return max_(income - medical - max_(support_paid, 0), 0) diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py index d31c49fc4f9..9af75fd2d54 100644 --- a/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat.py @@ -68,8 +68,9 @@ def formula(spm_unit, period, parameters): target_credit = spm_unit.any(target_member) * p.target.amount fuel = spm_unit("heating_type", period) fuel_credit = np.isin(fuel.decode_to_str(), p.fuel_credit_types) * p.fuel_credit - # Kerosene shares propane's cost standard, but section 605 names only - # propane and fuel oil for this addition. No actual-expense cap applies. + # Kerosene shares propane's cost standard and its addition: the March + # 2026 worksheet has no kerosene fuel, so a kerosene household is + # entered as propane or oil. No actual-expense cap applies. # Funding-dependent extra supplements and equipment/crisis are excluded. # Section 607 and the March 2026 worksheet add the components with no # floor or ceiling. Plan section 2.6 reports $140 and $800 as estimated diff --git a/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_energy_cost_standard.py b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_energy_cost_standard.py index 5f2525bdc1f..20ff981e3ac 100644 --- a/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_energy_cost_standard.py +++ b/policyengine_us/variables/gov/states/ut/dwf/heat/ut_heat_energy_cost_standard.py @@ -8,7 +8,12 @@ class ut_heat_energy_cost_standard(Variable): unit = USD label = "Utah HEAT annual energy cost standard" defined_for = StateCode.UT - reference = "https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/609_Table_III_Monthly_Energy_Cost_Standards.htm" + reference = ( + "https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/609_Table_III_Monthly_Energy_Cost_Standards.htm", + "https://jobs.utah.gov/infosource/heatpolicy/Policy/600_Payment_Calculation/603_Energy_Burden.htm", + "https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_200/Obsolete_207_(01_01_25_to_09_30_26)_Eligible_Households-copy.htm", + "https://liheapch.acf.gov/docs/2026/state-plans/UT_Plan_2026.pdf#page=4", + ) def formula(spm_unit, period, parameters): p = parameters(period).gov.states.ut.dwf.heat.benefit @@ -16,12 +21,23 @@ def formula(spm_unit, period, parameters): standard = p.monthly_energy_cost_standard[dwelling] fuel = spm_unit("heating_type", period) fuels = fuel.possible_values + # HEAT is one benefit a program year covering heating and cooling + # (plan item 1.1, section 207), and section 603 takes the standard for + # the household's primary energy source. A home with no heating that + # pays an electric bill therefore uses the electricity standard. Solar + # homes are taken as grid-tied with electric supplemental heat, as + # heating_expense reads their electricity bill. + electric_without_heating = (fuel == fuels.NONE) & ( + spm_unit("pre_subsidy_electricity_expense", period) > 0 + ) monthly_standard = select( [ fuel == fuels.NATURAL_GAS, (fuel == fuels.PROPANE) | (fuel == fuels.KEROSENE), fuel == fuels.FUEL_OIL, - fuel == fuels.ELECTRICITY, + (fuel == fuels.ELECTRICITY) + | (fuel == fuels.SOLAR) + | electric_without_heating, (fuel == fuels.COAL) | (fuel == fuels.OTHER), fuel == fuels.WOOD, ], @@ -33,9 +49,10 @@ def formula(spm_unit, period, parameters): standard.coal, standard.wood, ], - # UNSPECIFIED and SOLAR have no verified schedule; zero denotes - # unsupported model coverage, not a legal zero-cost standard. - # NONE means no home heating and never qualifies. + # UNSPECIFIED has no verified schedule; zero denotes unsupported + # model coverage, not a legal zero-cost standard. A NONE household + # with no electric bill is not connected to an energy source + # (section 208) and never qualifies. default=0, ) return monthly_standard * MONTHS_IN_YEAR diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 3435ac0b58f..1bed681ad12 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -5310,6 +5310,42 @@ candidates: state: open note: 'PE-US follows the regulation; resolve the pending conflict with the information letter. Code not yet on main: tx_ssi_state_supplement.' +- id: us-ut:heat-child-unearned-income + program: liheap + years: + from: 2026 + to: 2026 + type: federal-fiscal + question: Does Utah HEAT count a child's unearned income, as Utah Admin. Code R982-403-5(1) implies by excluding only + an unemancipated member's earned income, or exclude all income of children, as manual section 405 does? + sources: + - kind: regulation + citation: Utah Admin. Code R982-403-5(1), effective June 14, 2022 (the linked Cornell LII copy carries + the 2014 wording, unchanged in substance for this provision) + url: https://www.law.cornell.edu/regulations/utah/Utah-Admin-Code-R982-403-5 + - kind: manual + citation: Utah HEAT Manual section 405, effective March 1, 2025 to September 30, 2026 + url: https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_400/Obsolete_405_(3_1_25_to_9_30_26)_Income_Exclusions.htm + readings: + statute: No statute is supplied; R982-403-5(1) excludes the "earned income of an unemancipated household member", + so a child's unearned income, such as Social Security survivor benefits, counts under R982-403-3. + other: Manual section 405 reads "Do not count any income of a child (earned or unearned), 17 years old or younger. + Do not count income of an 18 year old unless they have dropped out of school or graduated." + example: A parent aged 35 with $12,000 annual wages and a child aged 10 with $6,000 of Social Security survivor + benefits, heating a house with natural gas, receive $455 under the manual (monthly countable income $800 after + the earned income disregard) versus $337 under the rule (monthly $1,300). + pe_us: + follows: conflicting-source + code: + - policyengine_us/parameters/gov/states/ut/dwf/heat/income/child_income_age.yaml + - policyengine_us/parameters/gov/states/ut/dwf/heat/income/student_income_age.yaml + - policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10344 + state: open + note: PE-US follows manual section 405 and excludes all income of members younger than 18 and of 18-year-olds in + secondary school (child_income_age.yaml, student_income_age.yaml, ut_heat_countable_income.py). R982-403 is a + current rule with its next five-year review due June 17, 2027, so decide whether it governs over the manual. - id: us-ut:heat-earned-income-disregard program: liheap years: @@ -5317,16 +5353,20 @@ candidates: to: 2026 type: federal-fiscal question: Does Utah HEAT disregard 20% of earned income, as Utah Admin. Code R982-403-6 provides, or count earned - income in full, as manual sections 401 and 406 and the March 2026 worksheet do? + income in full, as the retired manual section 406 implies? sources: - kind: regulation - citation: Utah Admin. Code R982-403-6(1), effective June 14, 2022 - url: https://adminrules.utah.gov/api/rule/4676/file/c1e409f0-8dd1-4548-b624-a00614e57fb2.html + citation: Utah Admin. Code R982-403-2 and R982-403-6, effective June 14, 2022 (the linked Cornell LII + copy carries the 2014 wording, unchanged in substance for these provisions) + url: https://www.law.cornell.edu/regulations/utah/Utah-Admin-Code-R982-403-6 - kind: manual citation: Utah HEAT Manual section 204, effective October 1, 2024 url: https://jobs.utah.gov/infosource/heatpolicy/Policy/200_Program_Standards/204_Eligibility_with_Ineligible_Resident.htm + - kind: worksheet + citation: Utah HEAT Benefit Calculation Worksheet, effective March 1, 2026, cells C8 and B11 + url: https://jobs.utah.gov/infosource/heatpolicy/assets/docs/Training/FY26/3.1.26%20Benefit%20Calculation%20Worksheet.xlsx - kind: calculator - citation: Utah FY2026 LIHEAP benefit matrix workbook, cell C8 + citation: Utah FY2026 LIHEAP benefit matrix workbook, cells C8 and B11 url: https://liheapch.acf.gov/docs/2026/benefits-matricies/UT_BenefitMatrix_2026.xlsx - kind: manual citation: Utah HEAT Manual section 401, effective October 1, 2024 to September 30, 2026 @@ -5334,25 +5374,68 @@ candidates: - kind: manual citation: Utah HEAT Manual section 406, income disregards obsolete September 30, 2023 url: https://jobs.utah.gov/infosource/heatpolicy/Policy/400_Income_Standards/406_No_Longer_Applicable.htm - - kind: worksheet - citation: Utah HEAT Benefit Calculation Worksheet, effective March 1, 2026 - url: https://jobs.utah.gov/infosource/heatpolicy/assets/docs/Training/FY26/3.1.26%20Benefit%20Calculation%20Worksheet.pdf#page=1 readings: - statute: No statute is supplied; R982-403-6(1) disregards 20% of earned income, including self-employment earned - income, and manual section 204 and the FY2026 workbook apply the same disregard. - other: Manual section 406 marks income disregards obsolete, and section 401 and the March 2026 worksheet compute - countable income with no disregard. - example: A one-person household with $27,000 annual wages heating a house with natural gas receives $0 in the PR - (monthly $2,250 exceeds the $1,995 limit) versus $234 with the disregard (monthly $1,800). + statute: No statute is supplied; R982-403-2 makes countable income "gross income minus exclusions, disregards, and + deductions", and R982-403-6 disregards 20% of earned income, including self-employment earned income after the + cost of doing business. Manual section 204 ("Allow the 20% disregard on the earned income of all household + members"), the March 2026 worksheet (C8 = B8*0.2, "20% disregard"; B11 = B8+B9-C8-B10) and the FY2026 workbook + apply the same disregard. + other: Manual section 406 marks income disregards no longer applicable from September 30, 2023, and sections 401 + and 603 describe countable income as gross income minus exclusions and deductions without naming a disregard. + example: A one-person household with $27,000 annual wages heating a house with natural gas receives $234 with the + disregard (monthly $1,800) and $0 without it (monthly $2,250 exceeds the $1,995 limit). pe_us: - follows: conflicting-source - code: [] + follows: statute + code: + - policyengine_us/parameters/gov/states/ut/dwf/heat/income/earned_income_disregard.yaml + - policyengine_us/variables/gov/states/ut/dwf/heat/income/ut_heat_countable_income.py prs: - https://github.com/PolicyEngine/policyengine-us/pull/10344 state: unreviewed - note: 'PE-US follows manual sections 401 and 406 and the March 2026 worksheet; R982-403 is a current rule with - its next five-year review due June 17, 2027, so decide whether it governs over the manual. Code not yet on main: - ut_heat_countable_income.' + note: PE-US follows R982-403-6, manual section 204 and the March 2026 worksheet and disregards 20% of earned income + after per-source zero floors (earned_income_disregard.yaml, ut_heat_countable_income.py). The worksheet PDF prints + only A1:B67, so its disregard column C does not appear there. The open question is whether retired section 406 + signals a practice of counting earnings in full; no current DWS source applies one. +- id: us-ut:heat-tanf-dws-payments + program: liheap + years: + from: 2026 + to: 2026 + type: federal-fiscal + question: Does Utah HEAT count TANF and other financial payments made by the Department of Workforce Services, as + Utah Admin. Code R982-403-3(2)(l) and (n) list them, or exclude them, as manual section 405 and plan item 1.9 do? + sources: + - kind: regulation + citation: Utah Admin. Code R982-403-3(2)(l) and (n), effective June 14, 2022 (the linked Cornell LII + copy carries the 2014 wording, unchanged in substance for these provisions) + url: https://www.law.cornell.edu/regulations/utah/Utah-Admin-Code-R982-403-3 + - kind: manual + citation: Utah HEAT Manual section 405, effective March 1, 2025 to September 30, 2026 + url: https://jobs.utah.gov/infosource/heatpolicy/Policy/Obsolete_Policy/Obsolete_400/Obsolete_405_(3_1_25_to_9_30_26)_Income_Exclusions.htm + - kind: plan + citation: Utah FY2026 LIHEAP State Plan, item 1.9 + url: https://liheapch.acf.gov/docs/2026/state-plans/UT_Plan_2026.pdf#page=6 + readings: + statute: No statute is supplied; R982-403-3(2) lists "(l) financial payments made by the Department" and "(n) + Temporary Assistance to Needy Families" as countable unearned income. + other: Manual section 405 excludes "Temporary Assistance to Needy Families (TANF), Family Employment Program (FEP), + General Assistance (GA), Transitional Cash Assistance, financial payments made by the Department of Workforce + Services, or other financial assistance from non-profits / other government agencies", and plan item 1.9 leaves + TANF and General Assistance benefits unchecked. + example: A parent aged 30 with $12,000 annual wages, $4,800 of Family Employment Program cash assistance and a child + aged 4, heating a house with natural gas, receives $605 under the manual and plan (monthly countable income $800 + after the earned income disregard) versus $513 under the rule (monthly $1,200). + pe_us: + follows: conflicting-source + code: + - policyengine_us/parameters/gov/states/ut/dwf/heat/income/unearned_income_sources.yaml + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10344 + state: open + note: PE-US follows manual section 405 and plan item 1.9, so unearned_income_sources.yaml lists neither tanf nor the + Utah state supplement (ut_ssp, which adds ut_dws_ssp, a Department of Workforce Services payment, and ut_dhhs_ssp, + another agency payment that section 405 excludes in the same item). R982-403 is a current rule with its next + five-year review due June 17, 2027, so decide whether it governs over the manual and plan. - id: us-va:claim-of-right-credit-gap-fill program: income-tax years: From 97a8f486a004112a9506161ebedf36193a0565db Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 27 Oct 2026 17:03:12 -0400 Subject: [PATCH 10/10] Pin Utah HEAT zero energy cost standards Eligibility Case 8 now outputs the zero standard for a NONE household with no electric bill, and new Case 18 pins the zero standard and ineligibility for an UNSPECIFIED household with heat in rent. Correct the Medicare Savings Program category in countable Case 23 (SLMB) and note that military basic pay belongs in one input only. Co-Authored-By: Claude Opus 5.5 --- .../heat/income/earned_income_sources.yaml | 3 +- .../ut/dwf/heat/ut_heat_countable_income.yaml | 2 +- .../states/ut/dwf/heat/ut_heat_eligible.yaml | 32 ++++++++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/earned_income_sources.yaml b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/earned_income_sources.yaml index f4c41c4db1c..fb4a1b46aa9 100644 --- a/policyengine_us/parameters/gov/states/ut/dwf/heat/income/earned_income_sources.yaml +++ b/policyengine_us/parameters/gov/states/ut/dwf/heat/income/earned_income_sources.yaml @@ -3,7 +3,8 @@ values: 2024-10-01: - employment_income # R982-403-4(2)(a): "wages, including military base pay"; section 403 - # counts military pay as earned income. + # counts military pay as earned income. Basic pay entered here should not + # also be included in employment_income, or it is counted twice. - military_basic_pay - self_employment_income - sstb_self_employment_income diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml index 12ba09e030a..7863e7546bf 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_countable_income.yaml @@ -623,7 +623,7 @@ # A retiree with 35 quarters of covered work pays the reduced Part A premium: # 311 x 12 = 3,732. Medicare Savings Program countable income is # 18,360 / 12 - 20 = 1,510 a month: above the QMB limit of 1,330 (no Part A -# coverage) but below the QI limit of 1,330 x 1.35 = 1,795.50 with no assets, +# coverage) but within the SLMB limit of 1,330 x 1.2 = 1,596 with no assets, # so the program pays the 2,434.80 Part B premium. # 18,360 - 3,732 - 0 = 14,628. - name: "Case 23, a Part A premium is deducted and Medicare Savings Program coverage leaves no Part B premium." diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml index 3dc589246b9..83517c343ff 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/dwf/heat/ut_heat_eligible.yaml @@ -162,7 +162,8 @@ ut_heat_eligible: false # NONE affirmatively denotes no home heat; with no electric bill the home is -# not connected to an energy source (section 208). +# not connected to an energy source (section 208) and has no energy cost +# standard. Case 53 in ut_heat.yaml covers a NONE household with a bill. - name: "Case 8, no heating source and no electric bill." period: 2026 input: @@ -182,6 +183,7 @@ members: [person1] state_code: UT output: + ut_heat_energy_cost_standard: 0 ut_heat_eligible: false # No supplied responsibility. @@ -408,3 +410,31 @@ state_code: UT output: ut_heat_eligible: true + +# Case 11 with no heating type reported. Heat in unsubsidized rent makes the +# household responsible for an energy cost, but an unspecified fuel has no +# energy cost standard, so the household is not eligible. +- name: "Case 18, heat in rent with no heating type reported." + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + rent: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: UNSPECIFIED + heating_expense: 0 + ut_heat_dwelling_type: HOUSE + heat_expense_included_in_rent: true + receives_housing_assistance: false + households: + household: + members: [person1] + state_code: UT + output: + ut_heat_energy_cost_standard: 0 + ut_heat_eligible: false