From b3ef74a93ba48b22caa9cdeaaf885665c4f100e0 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sat, 3 Oct 2026 23:37:04 -0400 Subject: [PATCH 1/8] Add Iowa LIHEAP Regular Assistance (ia_liheap) Encode Iowa's FY2026 regular heating benefit from the FY26 LIHEAP Policy and Procedures Manual: household size of citizens and qualified noncitizens, gross countable income of members aged 18 and over less child support paid, a 200 percent federal poverty guideline limit on the prior-year guideline through the shared fpg() helper, the energy burden and housing rules of Appendix D, and the Appendix I payment matrix (poverty-level points, nine targeting factors, fuel points, $40 per point between $80 and $800, flat $800 for liquid propane and fuel oil). Adds the ia_liheap_dwelling_type input the matrix requires and registers the program with verified_years "2026". Crisis assistance, cooling and weatherization are out of scope. Co-Authored-By: Claude Fable 5.1 --- changelog.d/ia-liheap-wave1c.added.md | 1 + .../ia/hhs/liheap/eligibility/fpg_rate.yaml | 15 + .../hhs/liheap/eligibility/fpg_year_lag.yaml | 18 + .../hhs/liheap/income/countable_min_age.yaml | 20 + .../ia/hhs/liheap/income/sources/earned.yaml | 31 ++ .../ia/hhs/liheap/income/sources/fixed.yaml | 25 + .../hhs/liheap/income/sources/unearned.yaml | 60 +++ .../hhs/liheap/payment/amount_per_point.yaml | 13 + .../payment/deliverable_fuel_amount.yaml | 22 + .../ia/hhs/liheap/payment/fuel_points.yaml | 31 ++ .../ia/hhs/liheap/payment/max_benefit.yaml | 16 + .../ia/hhs/liheap/payment/min_benefit.yaml | 13 + .../ia/hhs/liheap/payment/poverty_points.yaml | 42 ++ .../payment/targeting/detached_dwelling.yaml | 13 + .../liheap/payment/targeting/disabled.yaml | 13 + .../hhs/liheap/payment/targeting/elderly.yaml | 13 + .../liheap/payment/targeting/elderly_age.yaml | 11 + .../liheap/payment/targeting/five_plex.yaml | 13 + .../payment/targeting/fixed_income.yaml | 13 + .../payment/targeting/heat_in_rent.yaml | 13 + .../hhs/liheap/payment/targeting/savings.yaml | 13 + .../payment/targeting/savings_threshold.yaml | 19 + .../payment/targeting/subsidized_housing.yaml | 13 + .../liheap/payment/targeting/young_child.yaml | 13 + .../payment/targeting/young_child_age.yaml | 11 + policyengine_us/programs.yaml | 10 +- .../eligibility/ia_liheap_eligible.yaml | 265 +++++++++++ .../hhs/liheap/eligibility/ia_liheap_fpg.yaml | 110 +++++ .../ia_liheap_has_energy_burden.yaml | 209 ++++++++ .../ia_liheap_income_eligible.yaml | 122 +++++ .../eligibility/ia_liheap_income_limit.yaml | 134 ++++++ .../gov/states/ia/hhs/liheap/ia_liheap.yaml | 360 ++++++++++++++ .../hhs/liheap/ia_liheap_fuel_category.yaml | 119 +++++ .../hhs/liheap/ia_liheap_household_size.yaml | 126 +++++ .../ia_liheap_countable_earned_income.yaml | 146 ++++++ .../income/ia_liheap_countable_income.yaml | 197 ++++++++ .../ia_liheap_countable_unearned_income.yaml | 193 ++++++++ .../income/ia_liheap_fixed_income_only.yaml | 223 +++++++++ .../gov/states/ia/hhs/liheap/integration.yaml | 340 +++++++++++++ .../liheap/payment/ia_liheap_fuel_points.yaml | 107 +++++ .../payment/ia_liheap_poverty_points.yaml | 253 ++++++++++ .../payment/ia_liheap_targeting_points.yaml | 449 ++++++++++++++++++ .../liheap/eligibility/ia_liheap_eligible.py | 47 ++ .../hhs/liheap/eligibility/ia_liheap_fpg.py | 30 ++ .../ia_liheap_has_energy_burden.py | 49 ++ .../eligibility/ia_liheap_income_eligible.py | 25 + .../eligibility/ia_liheap_income_limit.py | 27 ++ .../gov/states/ia/hhs/liheap/ia_liheap.py | 52 ++ .../ia/hhs/liheap/ia_liheap_dwelling_type.py | 36 ++ .../ia/hhs/liheap/ia_liheap_fuel_category.py | 62 +++ .../ia/hhs/liheap/ia_liheap_household_size.py | 28 ++ .../ia_liheap_countable_earned_income.py | 49 ++ .../income/ia_liheap_countable_income.py | 38 ++ .../ia_liheap_countable_unearned_income.py | 44 ++ .../income/ia_liheap_fixed_income_only.py | 41 ++ .../liheap/payment/ia_liheap_fuel_points.py | 24 + .../ia/hhs/liheap/payment/ia_liheap_points.py | 27 ++ .../payment/ia_liheap_poverty_points.py | 40 ++ .../payment/ia_liheap_targeting_points.py | 68 +++ 59 files changed, 4514 insertions(+), 1 deletion(-) create mode 100644 changelog.d/ia-liheap-wave1c.added.md create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_year_lag.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/income/countable_min_age.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/earned.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/fixed.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/amount_per_point.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/deliverable_fuel_amount.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/fuel_points.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/max_benefit.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/min_benefit.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/poverty_points.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/detached_dwelling.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/disabled.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly_age.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/five_plex.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/fixed_income.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/heat_in_rent.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings_threshold.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/subsidized_housing.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child_age.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_fpg.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_has_energy_burden.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_limit.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap_fuel_category.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap_household_size.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/integration.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_fuel_points.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_targeting_points.yaml create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_fpg.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_has_energy_burden.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_limit.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_dwelling_type.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_fuel_category.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_household_size.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_fuel_points.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_points.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.py create mode 100644 policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_targeting_points.py diff --git a/changelog.d/ia-liheap-wave1c.added.md b/changelog.d/ia-liheap-wave1c.added.md new file mode 100644 index 00000000000..c5760816dc2 --- /dev/null +++ b/changelog.d/ia-liheap-wave1c.added.md @@ -0,0 +1 @@ +Add Iowa LIHEAP Regular Assistance (ia_liheap): FY2026 regular heating benefit from the points matrix with the 200% federal poverty guideline income test. diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_rate.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_rate.yaml new file mode 100644 index 00000000000..dba34e3e8b1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_rate.yaml @@ -0,0 +1,15 @@ +description: Iowa limits annual countable household income to this multiple of the federal poverty guidelines under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 2 + +metadata: + unit: /1 + period: year + label: Iowa LIHEAP federal poverty guideline rate + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Section 5.10 Eligibility Criteria (income 200% or below of the federal poverty guidelines), Appendix B Poverty Guidelines and Appendix I Payment Matrix (over 200% ineligible), pages 18, 89 and 137 + href: https://hhs.iowa.gov/media/17262/download?inline#page=18 + - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 2.1 note (threshold is 200% FPG; the printed 150.00% is an OLDC form limitation), page 9 + href: https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9 + - title: Iowa HHS Low-Income Home Energy Assistance program page (at or below 200% of the 2025 federal poverty guidelines) + href: https://hhs.iowa.gov/programs/programs-and-services/liheap diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_year_lag.yaml new file mode 100644 index 00000000000..cebd5b86987 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_year_lag.yaml @@ -0,0 +1,18 @@ +description: Iowa uses federal poverty guidelines from this many years before the modeled heating season for the income limit and poverty-level points under the Low Income Home Energy Assistance Program. +# Verified for FY26 only: the October 1, 2025 to September 30, 2026 limits +# ($31,300 for one person plus $11,000 per additional member) equal exactly +# 200% of the 2025 HHS guideline. Model period 2026 therefore reads the 2025 +# guideline. This is a relative year selection, not a fixed guideline year; +# earlier and later Iowa policy remains unverified. +values: + 2025-10-01: 1 + +metadata: + unit: year + period: year + label: Iowa LIHEAP federal poverty guideline year lag + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix B Poverty Guidelines (FY26 October 1, 2025, to September 30, 2026 annual gross income table), page 89 + href: https://hhs.iowa.gov/media/17262/download?inline#page=89 + - title: Iowa HHS Low-Income Home Energy Assistance program page (200% of the 2025 federal poverty guidelines for October 1, 2025 to September 30, 2026) + href: https://hhs.iowa.gov/programs/programs-and-services/liheap diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/countable_min_age.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/countable_min_age.yaml new file mode 100644 index 00000000000..597996ee74d --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/countable_min_age.yaml @@ -0,0 +1,20 @@ +description: Iowa counts the income of household members at or above this age under the Low Income Home Energy Assistance Program. +# Section 6.20 counts "the gross income of all household members aged 18 and +# over"; Appendix C records Social Security paid to members under 18 at $0.00 +# and leaves full-time earnings of under-18 members "NOT included as income". +# Section 6.00 (page 24) states an exception for minors "working full-time"; +# that conflict is resolved in favor of Sections 6.20 and Appendix C and the +# FFY2026 plan, which leaves "Earned income of a child under the age of 18" +# unchecked. +values: + 2025-10-01: 18 + +metadata: + unit: year + period: year + label: Iowa LIHEAP minimum age for countable income + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Section 6.20 Calculating Income (members aged 18 and over), Section 6.00 Income, Appendix C Income Earned by HH Members under the age of 18 and Social Security Benefits (only count for members 18 and older), pages 26, 24, 102 and 114 + href: https://hhs.iowa.gov/media/17262/download?inline#page=26 + - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 1.9 (earned income of a child under the age of 18 not counted), page 6 + href: https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/earned.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/earned.yaml new file mode 100644 index 00000000000..6ee27dad892 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/earned.yaml @@ -0,0 +1,31 @@ +description: Iowa counts these earned income sources toward gross household income under the Low Income Home Energy Assistance Program. +# Appendix C counts wages, salaries, tips, commissions, bonuses, severance, PTO +# and per diem (Employment Income), military pay excluding the housing +# allowance, self-employment and contract income, and farm income. Appendix C +# Self-Employment Income: "The LIHEAP program does NOT deduct business +# expenses", so Iowa counts gross receipts. The model's business inputs are +# net amounts; each is floored at zero in the countable earned income formula +# so that a loss does not offset other income. Using net income understates +# Iowa's gross-receipts basis when expenses exist (documented limitation). +values: + 2025-10-01: + - employment_income + # Appendix C Military Pay: counted, "excluding housing allowance". + - military_basic_pay + - self_employment_income + - sstb_self_employment_income + - partnership_s_corp_income + # Appendix C Farm Income: sale of crops, livestock and land rental is + # counted (the X is in the Yes column although the code column reads NA). + - farm_operations_income + - farm_rent_income + +metadata: + unit: list + period: year + label: Iowa LIHEAP countable earned income sources + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix C Income Guidelines, Employment Income, Farm Income, Military Pay and Self-Employment Income rows, pages 97, 98, 108 and 113 + href: https://hhs.iowa.gov/media/17262/download?inline#page=113 + - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Sections 1.8-1.9 (gross income; wages, self-employment income, contract income and commissions counted), pages 5-6 + href: https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/fixed.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/fixed.yaml new file mode 100644 index 00000000000..567eb5885e1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/fixed.yaml @@ -0,0 +1,25 @@ +description: Iowa treats these income sources as fixed income for the fixed-income-only targeting factor under the Low Income Home Energy Assistance Program. +# Definitions, Fixed Income Household: "A household that receives only one or +# more of the following income types ... Social Security Administration (SSA) +# Benefits, Supplemental Security Income (SSI), Social Security Disability +# Insurance (SSDI), Veterans' Assistance, VA Service-Connected Disability +# Pension, VA Non-Service-Connected Disability Pension, and pensions. Only mark +# 'fixed income' when that is the ONLY source(s) of income." Appendix C notes +# that child support, alimony and variable annuities are not fixed income. +# Keep this list in the same relative order as sources/unearned.yaml: the +# fixed-income-only test compares two sums, and the same order keeps them +# bit-identical when every other source is zero. +values: + 2025-10-01: + - social_security + - ssi + - pension_income + - veterans_benefits + +metadata: + unit: list + period: year + label: Iowa LIHEAP fixed income sources + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Definitions, Fixed Income Household, page 81, and Appendix I Payment Matrix, Fixed Income Only targeting factor, page 137 + href: https://hhs.iowa.gov/media/17262/download?inline#page=81 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml new file mode 100644 index 00000000000..fb18e64b037 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml @@ -0,0 +1,60 @@ +description: Iowa counts these unearned income sources toward gross household income under the Low Income Home Energy Assistance Program. +# Not counted (Appendix C, not-counted column; plan Section 1.9 unchecked): +# FIP (Iowa TANF), child support received, SNAP, WIC, Medicaid, housing +# assistance and utility allowances, bank-account interest, capital gains, +# cash gifts, loans, grants and scholarships, jury duty pay, foster care and +# adoption assistance, inheritances and life insurance, AmeriCorps and VISTA, +# reimbursements, rebates and tax refunds, in-kind income, short-term +# disability, VA education and housing allowances, and savings balances. +values: + 2025-10-01: + # Appendix C Social Security Benefits (SSA, SSI, SSDI): "Count the net + # amount as income (gross minus benefits repaid to SSA)" and the amount + # "paid by check or direct deposit"; the plan checks "Excluding MediCare + # deduction". The model counts social_security gross because Medicare + # premiums and SSA repayments are not separable (documented limitation). + - social_security + # ssi is a monthly variable; add() sums the twelve months. + - ssi + - pension_income + # Appendix C IRA and Retirement rows: regular distributions count; + # irregular withdrawals are not separable from the model inputs. + # retirement_distributions already includes taxable_ira_distributions + # (through taxable_retirement_distributions), so IRA distributions are not + # listed separately; listing both would count them twice. + - retirement_distributions + # Appendix C Unemployment: counted; the limited-duration annualization + # rule of the Definitions (Annualized Income, page 79) is not modeled. + - unemployment_compensation + - workers_compensation + - strike_benefits + # Appendix C Disability Insurance: long-term private disability counts, + # short-term does not; the model input does not split them. + - disability_benefits + # Appendix C Veterans Benefits: compensation and pensions count; VA + # educational and housing allowances and non-recurring lump sums do not + # and are not separable from the model input. + - veterans_benefits + - alimony_income + - rental_income + # Appendix C Interest (Bonds, Investments) is counted while Interest + # Bearing Bank Accounts is not; the model input does not split them, so + # all interest is counted (documented approximation). + - interest_income + # Appendix C Dividends: counted; "if the annual combined total earned is + # less than $120, ignore the income". The $120 rule is not applied + # (documented approximation). ordinary_dividend_income is the canonical + # input; dividend_income is a deprecated alias. + - ordinary_dividend_income + # Appendix C Gambling: taxable gambling winnings count. + - gambling_winnings + +metadata: + unit: list + period: year + label: Iowa LIHEAP countable unearned income sources + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix C Income Guidelines (Alimony, Annuities, Disability Insurance, Dividends, Gambling, Interest, IRA, Pensions, Rental Income, Retirement, Social Security Benefits, Strike Benefits, Unemployment, Veterans Benefits and Workers Compensation rows), pages 90-121, and Section 6.10, page 25 + href: https://hhs.iowa.gov/media/17262/download?inline#page=114 + - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 1.9 (countable income sources), page 6 + href: https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/amount_per_point.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/amount_per_point.yaml new file mode 100644 index 00000000000..f2d9940b65d --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/amount_per_point.yaml @@ -0,0 +1,13 @@ +description: Iowa pays this amount per payment matrix point under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 40 + +metadata: + unit: currency-USD + period: year + label: Iowa LIHEAP benefit per matrix point + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums, Award Calculation ($40 per point), page 137 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/deliverable_fuel_amount.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/deliverable_fuel_amount.yaml new file mode 100644 index 00000000000..5202ed76bf8 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/deliverable_fuel_amount.yaml @@ -0,0 +1,22 @@ +description: Iowa pays this flat Regular Assistance benefit to households heating primarily with liquid propane or fuel oil under the Low Income Home Energy Assistance Program. +# Appendix I: "The minimum Regular Assistance benefit for liquid propane and +# fuel oil LIHEAP customers is $800". Section 11.00: if the matrix benefit "is +# less than the annual state-set amount for deliverable benefits, the +# deliverable fuel benefit will be raised to meet the state-set benefit +# amount". The FY26 public comment draft plan describes it as "a flat amount +# for deliverable fuel customers that serves as both a minimum and maximum +# benefit for liquid propane and fuel oil LIHEAP customers. For FY26 that flat +# amount is $800." The three statements agree numerically for FY26 because the +# general maximum is also $800. +values: + 2025-10-01: 800 + +metadata: + unit: currency-USD + period: year + label: Iowa LIHEAP deliverable fuel benefit + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums (minimum benefit for liquid propane and fuel oil customers $800), page 137, and Section 11.00 Payments (deliverable fuel benefit raised to the state-set amount), page 42 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa LIHEAP FFY2026 Public Comment Draft State Plan, Payment Matrix attachment (flat $800 for deliverable fuel customers), page 48 + href: https://hhs.iowa.gov/media/16690/download?inline#page=48 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/fuel_points.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/fuel_points.yaml new file mode 100644 index 00000000000..48151eeb8ea --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/fuel_points.yaml @@ -0,0 +1,31 @@ +description: Iowa awards these payment matrix points by primary heating fuel category under the Low Income Home Energy Assistance Program. +# Appendix I item 3, Fuel/Energy Type: Natural Gas 4, Electric 4, Liquid +# Propane (LP) 5, Fuel Oil 5, Solid Fuel (Wood/Coal/Corn) 2. The matrix +# publishes no points for kerosene or other fuels; kerosene is mapped to the +# fuel oil category in ia_liheap_fuel_category (an inference), and OTHER +# carries zero points. +metadata: + propagate_metadata_to_children: true + unit: /1 + period: year + label: Iowa LIHEAP fuel type points + breakdown: + - ia_liheap_fuel_category + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums, Regular Assistance Payment Matrix, item 3 Fuel/Energy Type, page 137 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 + +NATURAL_GAS: + 2025-10-01: 4 +ELECTRICITY: + 2025-10-01: 4 +PROPANE: + 2025-10-01: 5 +FUEL_OIL: + 2025-10-01: 5 +SOLID_FUEL: + 2025-10-01: 2 +OTHER: + 2025-10-01: 0 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/max_benefit.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/max_benefit.yaml new file mode 100644 index 00000000000..dd018778081 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/max_benefit.yaml @@ -0,0 +1,16 @@ +description: Iowa pays at most this Regular Assistance benefit under the Low Income Home Energy Assistance Program. +# With FY26 values the matrix cannot reach this cap: the largest total is +# 8 poverty + 5 targeting + 5 fuel = 18 points, or $720. The cap is kept as the +# published rule; only the flat deliverable fuel amount equals it. +values: + 2025-10-01: 800 + +metadata: + unit: currency-USD + period: year + label: Iowa LIHEAP maximum benefit + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums, Award Calculation (maximum benefit $800), page 137 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 2.6 (maximum benefit $800), page 9 + href: https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/min_benefit.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/min_benefit.yaml new file mode 100644 index 00000000000..18432fc5629 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/min_benefit.yaml @@ -0,0 +1,13 @@ +description: Iowa pays at least this Regular Assistance benefit to an approved household under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 80 + +metadata: + unit: currency-USD + period: year + label: Iowa LIHEAP minimum benefit + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums, Award Calculation (minimum benefit $80), page 137 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 2.6 (minimum benefit $80), page 9 + href: https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/poverty_points.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/poverty_points.yaml new file mode 100644 index 00000000000..59b33a19a92 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/poverty_points.yaml @@ -0,0 +1,42 @@ +description: Iowa awards these payment matrix points based on the ratio of annual countable household income to the federal poverty guideline under the Low Income Home Energy Assistance Program. +# Appendix I prints closed percent bands: 0-75% gives 8, 76-100% gives 6, +# 101-125% gives 5, 126-200% gives 4, over 200% is ineligible, with the note +# "Poverty levels of .01% are rounded up to the next percentage". A ratio of +# exactly 0.75 is 75% (8 points) and 0.7501 rounds up to 76% (6 points), so +# the bands are keyed at their tops and looked up with right=True; the first +# threshold is -.inf so that zero income scores the first band. The zero +# amount above 2.00 is unreachable for eligible households, which the income +# test already limits to 200%. +brackets: + - threshold: + 2025-10-01: -.inf + amount: + 2025-10-01: 8 + - threshold: + 2025-10-01: 0.75 + amount: + 2025-10-01: 6 + - threshold: + 2025-10-01: 1 + amount: + 2025-10-01: 5 + - threshold: + 2025-10-01: 1.25 + amount: + 2025-10-01: 4 + - threshold: + 2025-10-01: 2 + amount: + 2025-10-01: 0 + +metadata: + type: single_amount + threshold_unit: /1 + amount_unit: /1 + period: year + label: Iowa LIHEAP poverty level points + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums, Regular Assistance Payment Matrix, item 1 Poverty Level, page 137 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/detached_dwelling.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/detached_dwelling.yaml new file mode 100644 index 00000000000..07f39203254 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/detached_dwelling.yaml @@ -0,0 +1,13 @@ +description: Iowa adds these payment matrix points for a household occupying a detached dwelling under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 1 + +metadata: + unit: /1 + period: year + label: Iowa LIHEAP detached dwelling points + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Detached Dwelling, page 137, and Definitions, Detached Household/Single Family Home (a single-unit dwelling), page 80 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/disabled.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/disabled.yaml new file mode 100644 index 00000000000..65306c8bcec --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/disabled.yaml @@ -0,0 +1,13 @@ +description: Iowa adds these payment matrix points for a household with a disabled member under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 1 + +metadata: + unit: /1 + period: year + label: Iowa LIHEAP disabled household points + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Disabled, page 137, and Definitions, Disabled (applicants self-declare a disability), page 80 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly.yaml new file mode 100644 index 00000000000..2328571dcb1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly.yaml @@ -0,0 +1,13 @@ +description: Iowa adds these payment matrix points for a household with an elderly member under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 1 + +metadata: + unit: /1 + period: year + label: Iowa LIHEAP elderly household points + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Elderly (60 years of age and older), page 137 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly_age.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly_age.yaml new file mode 100644 index 00000000000..4eacddb3505 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly_age.yaml @@ -0,0 +1,11 @@ +description: Iowa treats household members at or above this age as elderly under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 60 + +metadata: + unit: year + period: year + label: Iowa LIHEAP elderly age + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Definitions, Elderly (60 years of age or older as of the application date), page 80, and Appendix I Payment Matrix, Elderly (60 years of age and older), page 137 + href: https://hhs.iowa.gov/media/17262/download?inline#page=80 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/five_plex.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/five_plex.yaml new file mode 100644 index 00000000000..8810d495a04 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/five_plex.yaml @@ -0,0 +1,13 @@ +description: Iowa adjusts payment matrix points by this amount for a household in a building of five or more units under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: -1 + +metadata: + unit: /1 + period: year + label: Iowa LIHEAP five-plex or larger building points + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, 5-Plexes (or more than 5), page 137 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/fixed_income.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/fixed_income.yaml new file mode 100644 index 00000000000..b4b9728b1d1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/fixed_income.yaml @@ -0,0 +1,13 @@ +description: Iowa adds these payment matrix points for a household whose only income is fixed income under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 1 + +metadata: + unit: /1 + period: year + label: Iowa LIHEAP fixed income only points + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Fixed Income Only (no other sources of income), page 137, and Definitions, Fixed Income Household, page 81 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/heat_in_rent.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/heat_in_rent.yaml new file mode 100644 index 00000000000..f76f86b2216 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/heat_in_rent.yaml @@ -0,0 +1,13 @@ +description: Iowa adjusts payment matrix points by this amount for a household whose heating cost is included in its rent under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: -4 + +metadata: + unit: /1 + period: year + label: Iowa LIHEAP heating included in rent points + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Heating Included in Rent, page 137, and Section 5.41 Eligible Dwellings, page 20 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings.yaml new file mode 100644 index 00000000000..a254705b3e9 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings.yaml @@ -0,0 +1,13 @@ +description: Iowa adjusts payment matrix points by this amount for a household with savings above the savings threshold under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: -4 + +metadata: + unit: /1 + period: year + label: Iowa LIHEAP savings points + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Over $50,000 in Savings, page 137 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings_threshold.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings_threshold.yaml new file mode 100644 index 00000000000..79ef2d1b0af --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings_threshold.yaml @@ -0,0 +1,19 @@ +description: Iowa applies the savings points adjustment to households whose savings exceed this amount under the Low Income Home Energy Assistance Program. +# Section 4.40 item 4 describes the savings test as "Interest and dividend- +# bearing accounts (this includes high-interest bank accounts, cash on hand, +# CDs, and other investments that can be accessed without penalty to the +# household)". Appendix C excludes IAble accounts, trusts and penalty-locked +# retirement accounts from savings. The model maps savings to +# spm_unit_cash_assets (bank, stock and bond assets). +values: + 2025-10-01: 50_000 + +metadata: + unit: currency-USD + period: year + label: Iowa LIHEAP savings threshold + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, Over $50,000 in Savings, page 137, Section 4.40 Automatically Eligible Households item 4, page 14, and Appendix C Savings Account and IAble Savings Plan rows, pages 112 and 102 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/subsidized_housing.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/subsidized_housing.yaml new file mode 100644 index 00000000000..1ad1de5ecab --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/subsidized_housing.yaml @@ -0,0 +1,13 @@ +description: Iowa adjusts payment matrix points by this amount for a household in subsidized housing under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: -2 + +metadata: + unit: /1 + period: year + label: Iowa LIHEAP subsidized housing points + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Subsidized Housing, page 137, and Definitions, Subsidized Housing, pages 84-85 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child.yaml new file mode 100644 index 00000000000..35a2c295b67 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child.yaml @@ -0,0 +1,13 @@ +description: Iowa adds these payment matrix points for a household with a young child under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 1 + +metadata: + unit: /1 + period: year + label: Iowa LIHEAP young child household points + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Children (under 6 years of age in the household), page 137 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child_age.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child_age.yaml new file mode 100644 index 00000000000..71f80f667cd --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child_age.yaml @@ -0,0 +1,11 @@ +description: Iowa treats household members below this age as young children under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 6 + +metadata: + unit: year + period: year + label: Iowa LIHEAP young child age limit + reference: + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Definitions, Young Child (a person under six years of age), page 85, and Appendix I Payment Matrix, Children (under 6 years of age in the household), page 137 + href: https://hhs.iowa.gov/media/17262/download?inline#page=85 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 756dc2c4988..4c2b0aefdfa 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, IA, TX 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: IA + status: partial + name: Iowa LIHEAP + full_name: Iowa Low Income Home Energy Assistance Program + variable: ia_liheap + parameter_prefix: gov.states.ia.hhs.liheap + verified_years: "2026" + notes: FY2026 Regular Assistance heating benefit from the points matrix, with a flat amount for propane and fuel oil. Crisis assistance, cooling and weatherization are out of scope. - state: TX status: partial name: Texas CEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.yaml new file mode 100644 index 00000000000..0653ec6a8d0 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.yaml @@ -0,0 +1,265 @@ +# Iowa LIHEAP Regular Assistance eligibility (REQ-002, REQ-004, REQ-006, +# REQ-027, REQ-028): Iowa residence (FY26 LIHEAP Policy and Procedures Manual +# section 5.30 p. 18), at least one counted member (section 7.30 p. 34), income +# at or below 200 percent of the guideline (section 5.10 p. 18), an energy +# burden (section 5.50 p. 22 and Appendix D pp. 122-123, where electricity from +# solar cells, wind, a generator or an extension cord is not a burden), and a +# heating fuel: https://hhs.iowa.gov/media/17262/download?inline#page=18 +# Modeling: heating_type UNSPECIFIED pays nothing under the all-state +# convention (callers must send the fuel), NONE means the home has no heating, +# SOLAR is ineligible under Appendix D p. 123. + +- name: Case 1, single renter paying a natural gas vendor is eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # 28,000 <= 31,300; pays vendor; natural gas. + ia_liheap_eligible: true + +- name: Case 2, unspecified heating type pays nothing even with a gas bill. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: UNSPECIFIED + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # heating_expense is zero for UNSPECIFIED, and the fuel gate excludes it. + ia_liheap_has_energy_burden: false + ia_liheap_eligible: false + +- name: Case 3, a home with no heating is not eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NONE + households: + household: + members: [person1] + state_code: IA + output: + ia_liheap_eligible: false + +- name: Case 4, solar-heated home is not eligible despite an electric bill. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: SOLAR + pre_subsidy_electricity_expense: 600 + households: + household: + members: [person1] + state_code: IA + output: + # Appendix D p. 123: electricity from solar cells has no energy burden. + # The bill-based burden flag is true; the fuel gate makes it ineligible. + ia_liheap_has_energy_burden: true + ia_liheap_eligible: false + +- name: Case 5, household of only undocumented members is not eligible. + period: 2026 + input: + people: + person1: + age: 30 + immigration_status: UNDOCUMENTED + employment_income: 22_000 + person2: + age: 28 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_000 + households: + household: + members: [person1, person2] + state_code: IA + output: + # Section 7.30 p. 34: household size 0. + ia_liheap_eligible: false + +- name: Case 6, income over 200 percent of the guideline is not eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 35_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # 35,000 > 31,300. + ia_liheap_eligible: false + +- name: Case 7, the same household in Nebraska is not eligible for the Iowa program. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: NE + output: + ia_liheap_eligible: false + +- name: Case 8, unspecified fuel with unsubsidized heat in rent is not eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: UNSPECIFIED + heat_expense_included_in_rent: true + households: + household: + members: [person1] + state_code: IA + output: + # The energy burden holds (Appendix D p. 122) but the fuel is required + # because the matrix needs the building's fuel points. + ia_liheap_has_energy_burden: true + ia_liheap_eligible: false + +- name: Case 9, Section 8 tenant with heat and electric in rent is not eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + pre_subsidy_electricity_expense: 0 + households: + household: + members: [person1] + state_code: IA + output: + # Appendix D p. 123; section 5.41 p. 20. + ia_liheap_eligible: false + +- name: Case 10, an "other" heating fuel with a bill is eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: OTHER + other_heating_fuel_expense: 700 + households: + household: + members: [person1] + state_code: IA + output: + # Vendors supply "other fuels" (sections 12.00-12.20 pp. 46-47); the + # household pays a vendor so it has a burden. Fuel points are zero. + ia_liheap_eligible: true + +- name: Case 11, kerosene heat paid to a vendor is eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: KEROSENE + fuel_oil_expense: 1_600 + households: + household: + members: [person1] + state_code: IA + output: + ia_liheap_eligible: true + +- name: Case 12, undocumented parent with a citizen child is eligible on the child's count. + period: 2026 + input: + people: + person1: + age: 30 + immigration_status: UNDOCUMENTED + employment_income: 24_000 + person2: + age: 4 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_000 + households: + household: + members: [person1, person2] + state_code: IA + output: + # Section 7.30 p. 34: an ineligible adult may apply for eligible children; + # size 1, income 24,000 <= 31,300. + ia_liheap_household_size: 1 + ia_liheap_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_fpg.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_fpg.yaml new file mode 100644 index 00000000000..5819a4abb2f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_fpg.yaml @@ -0,0 +1,110 @@ +# Iowa LIHEAP poverty guideline for the household (REQ-003). The FY26 season +# (October 1, 2025 to April 30, 2026) uses the 2025 HHS guideline: 15,650 for +# one person plus 5,500 per additional person in the contiguous states +# (parameters/gov/hhs/fpg.yaml at 2025-01-01). Period 2026 reads it through a +# one-year lag. The FY26 table of 2 x the guideline is Appendix B p. 89 of the +# FY26 LIHEAP Policy and Procedures Manual: +# https://hhs.iowa.gov/media/17262/download?inline#page=89 +# The guideline uses ia_liheap_household_size (citizen or qualified members, +# section 7.30 p. 34) and is zero when that size is zero. + +- name: Case 1, one-person household reads the 2025 guideline. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # 2025 guideline, size 1 = 15,650. + ia_liheap_fpg: 15_650 + +- name: Case 2, family of four adds 5,500 per additional person. + period: 2026 + input: + people: + person1: + age: 38 + employment_income: 42_000 + person2: + age: 36 + employment_income: 18_000 + person3: + age: 9 + person4: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + gas_expense: 1_250 + households: + household: + members: [person1, person2, person3, person4] + state_code: IA + output: + # 15,650 + 3 x 5,500 = 32,150. + ia_liheap_fpg: 32_150 + +- name: Case 3, mixed-status household of three uses the size-two guideline. + period: 2026 + input: + people: + person1: + age: 35 + immigration_status: UNDOCUMENTED + employment_income: 24_000 + person2: + age: 33 + immigration_status: CITIZEN + employment_income: 14_000 + person3: + age: 5 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1, person2, person3] + state_code: IA + output: + # Size 2 (the undocumented adult is not counted): 15,650 + 5,500 = 21,150. + ia_liheap_household_size: 2 + ia_liheap_fpg: 21_150 + +- name: Case 4, household with no counted member has a zero guideline. + period: 2026 + input: + people: + person1: + age: 30 + immigration_status: UNDOCUMENTED + employment_income: 22_000 + person2: + age: 28 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_000 + households: + household: + members: [person1, person2] + state_code: IA + output: + # Size 0 is guarded to zero rather than 15,650 - 5,500 = 10,150. + ia_liheap_household_size: 0 + ia_liheap_fpg: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_has_energy_burden.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_has_energy_burden.yaml new file mode 100644 index 00000000000..1802a1de5f1 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_has_energy_burden.yaml @@ -0,0 +1,209 @@ +# Iowa LIHEAP energy burden (REQ-025 to REQ-027). The household must have an +# energy burden on the date of application (FY26 LIHEAP Policy and Procedures +# Manual section 5.50 p. 22). Appendix D pp. 122-123: paying a vendor directly +# is a burden; unsubsidized households with all utilities in the rent are +# eligible for Regular Assistance; subsidized households with heat in the rent +# qualify only through a secondary burden with a utility vendor (an electric +# account), and subsidized housing with all utilities in the rent is not a +# burden (sections 5.41-5.42 pp. 20-21): +# https://hhs.iowa.gov/media/17262/download?inline#page=122 +# FFY2026 plan section 2.3 p. 8 (subsidized housing and utilities in rent): +# https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=8 +# Modeling: subsidized = receives_housing_assistance or is_in_public_housing; +# the secondary burden is pre_subsidy_electricity_expense > 0; electric heat +# reads pre_subsidy_electricity_expense because heating_expense uses it. + +- name: Case 1, paying a natural gas vendor directly is an energy burden. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # Appendix D p. 122: pays vendor directly, yes. + ia_liheap_has_energy_burden: true + +- name: Case 2, unsubsidized heat included in rent is an energy burden. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + households: + household: + members: [person1] + state_code: IA + output: + # Appendix D p. 122: all utilities in rent, non-subsidized, yes (Regular + # Assistance only); section 5.41 p. 20. + ia_liheap_has_energy_burden: true + +- name: Case 3, Section 8 tenant with heat in rent and an electric account has a burden. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + pre_subsidy_electricity_expense: 900 + households: + household: + members: [person1] + state_code: IA + output: + # Appendix D p. 122: heat in rent, billed directly for electric, yes. + ia_liheap_has_energy_burden: true + +- name: Case 4, Section 8 tenant with heat and electric both in rent has no burden. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + pre_subsidy_electricity_expense: 0 + households: + household: + members: [person1] + state_code: IA + output: + # Appendix D p. 123: subsidized housing, all utilities in rent, no. + ia_liheap_has_energy_burden: false + +- name: Case 5, public housing with heat in rent and no electric bill has no burden. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + pre_subsidy_electricity_expense: 0 + households: + household: + members: [person1] + state_code: IA + is_in_public_housing: true + output: + # Public housing is subsidized housing (Definitions pp. 84-85). + ia_liheap_has_energy_burden: false + +- name: Case 6, public housing with heat in rent and an electric account has a burden. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + pre_subsidy_electricity_expense: 840 + households: + household: + members: [person1] + state_code: IA + is_in_public_housing: true + output: + ia_liheap_has_energy_burden: true + +- name: Case 7, a known fuel with no bill and heat not in rent is no burden. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: false + households: + household: + members: [person1] + state_code: IA + output: + # Appendix D p. 123: not charged by anyone for utilities, no. + ia_liheap_has_energy_burden: false + +- name: Case 8, subsidized tenant paying the gas vendor directly has a burden. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 950 + receives_housing_assistance: true + households: + household: + members: [person1] + state_code: IA + output: + # Appendix D p. 122: subsidized housing, pays vendor directly (utility + # allowance), yes. + ia_liheap_has_energy_burden: true + +- name: Case 9, electric heat paid to the utility is a burden. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_500 + households: + household: + members: [person1] + state_code: IA + output: + ia_liheap_has_energy_burden: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.yaml new file mode 100644 index 00000000000..64f3dfa45c8 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.yaml @@ -0,0 +1,122 @@ +# Iowa LIHEAP income eligibility (REQ-002, REQ-003): countable income at or +# below 200 percent of the 2025 poverty guideline. FY26 LIHEAP Policy and +# Procedures Manual section 5.10 p. 18 ("200% or below"), Appendix B p. 89 +# (31,300 for one person plus 11,000 per additional person) and Appendix I +# p. 137 ("Over 200%" ineligible; poverty levels of .01 percent are rounded up, +# so exactly 200 percent is still eligible): +# https://hhs.iowa.gov/media/17262/download?inline#page=18 + +- name: Case 1, one person exactly at 31,300 is income eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 31_300 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # 31,300 <= 2.0 x 15,650 = 31,300. + ia_liheap_countable_income: 31_300 + ia_liheap_income_limit: 31_300 + ia_liheap_income_eligible: true + +- name: Case 2, one person at 31,301 is one dollar over the limit. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 31_301 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # 31,301 > 31,300. + ia_liheap_income_eligible: false + +- name: Case 3, family of four exactly at 64,300 is income eligible. + period: 2026 + input: + people: + person1: + age: 38 + employment_income: 44_300 + person2: + age: 36 + employment_income: 20_000 + person3: + age: 9 + person4: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + gas_expense: 1_250 + households: + household: + members: [person1, person2, person3, person4] + state_code: IA + output: + # 44,300 + 20,000 = 64,300 <= 2.0 x 32,150 = 64,300 (Appendix B, size 4). + ia_liheap_income_eligible: true + +- name: Case 4, family of four at 70,000 is over the limit. + period: 2026 + input: + people: + person1: + age: 38 + employment_income: 48_000 + person2: + age: 36 + employment_income: 22_000 + person3: + age: 9 + person4: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + gas_expense: 1_250 + households: + household: + members: [person1, person2, person3, person4] + state_code: IA + output: + # 48,000 + 22,000 = 70,000 > 64,300. + ia_liheap_income_eligible: false + +- name: Case 5, a household with no countable income is income eligible. + period: 2026 + input: + people: + person1: + age: 29 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 800 + households: + household: + members: [person1] + state_code: IA + output: + # 0 <= 31,300. + ia_liheap_income_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_limit.yaml new file mode 100644 index 00000000000..90960e2d169 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_limit.yaml @@ -0,0 +1,134 @@ +# Iowa LIHEAP income limit (REQ-002, REQ-003): 200 percent of the 2025 HHS +# poverty guideline for the counted household size, with no rounding. The FY26 +# published table is 31,300 for one person plus 11,000 per additional person +# (Appendix B p. 89 of the FY26 LIHEAP Policy and Procedures Manual, repeated +# in Appendix A p. 88): https://hhs.iowa.gov/media/17262/download?inline#page=89 +# Agency program page, "at or below 200% of the 2025 federal poverty +# guidelines": https://hhs.iowa.gov/programs/programs-and-services/liheap + +- name: Case 1, one-person limit is 31,300. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # 2.0 x 15,650 = 31,300 (Appendix B, size 1). + ia_liheap_income_limit: 31_300 + +- name: Case 2, four-person limit is 64,300. + period: 2026 + input: + people: + person1: + age: 38 + employment_income: 42_000 + person2: + age: 36 + employment_income: 18_000 + person3: + age: 9 + person4: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + gas_expense: 1_250 + households: + household: + members: [person1, person2, person3, person4] + state_code: IA + output: + # 2.0 x (15,650 + 3 x 5,500) = 2.0 x 32,150 = 64,300 (Appendix B, size 4). + ia_liheap_income_limit: 64_300 + +- name: Case 3, eight-person limit is 108,300. + period: 2026 + input: + people: + person1: + age: 44 + employment_income: 60_000 + person2: + age: 42 + employment_income: 30_000 + person3: + age: 17 + person4: + age: 15 + person5: + age: 12 + person6: + age: 10 + person7: + age: 7 + person8: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + heating_type: NATURAL_GAS + gas_expense: 1_600 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + state_code: IA + output: + # 2.0 x (15,650 + 7 x 5,500) = 2.0 x 54,150 = 108,300 (Appendix B, size 8); + # 108,300 - 97,300 = 11,000, the published each-additional-person step. + ia_liheap_income_limit: 108_300 + +- name: Case 4, household with no counted member has a zero limit. + period: 2026 + input: + people: + person1: + age: 30 + immigration_status: UNDOCUMENTED + employment_income: 22_000 + person2: + age: 28 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_000 + households: + household: + members: [person1, person2] + state_code: IA + output: + # 2.0 x 0 = 0. + ia_liheap_income_limit: 0 + +- name: Case 5, the same household in Illinois has a zero Iowa limit. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IL + output: + # ia_liheap_household_size is zero outside Iowa, so 2.0 x 0 = 0. + ia_liheap_income_limit: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap.yaml new file mode 100644 index 00000000000..d359d4afa53 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap.yaml @@ -0,0 +1,360 @@ +# Iowa LIHEAP Regular Assistance benefit (REQ-029 to REQ-031). Appendix I +# p. 137 of the FY26 LIHEAP Policy and Procedures Manual: $40 per point of the +# total matrix points (poverty points + targeting factors + fuel points), a +# minimum of $80 and a maximum of $800 for an approved customer, and a minimum +# of $800 for liquid propane and fuel oil customers; section 11.00 p. 42 raises +# a deliverable-fuel matrix result to the state-set amount; the benefit does +# not depend on the amount owed (section 5.41 p. 20): +# https://hhs.iowa.gov/media/17262/download?inline#page=137 +# FY26 plan hearing draft p. 48: the flat amount is both minimum and maximum +# for liquid propane and fuel oil, $800 for FY26: +# https://hhs.iowa.gov/media/16690/download?inline#page=48 +# FFY2026 plan section 2.6 p. 9 ($80 minimum, $800 maximum): +# https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9 +# Modeling: 800 for PROPANE and FUEL_OIL (not KEROSENE, which the flat rule +# does not name); otherwise clip(40 x points, 80, 800). The largest total a +# non-deliverable household can reach is 8 + 5 + 5 = 18 points (720), so the +# 800 cap binds only through the flat amount. + +- name: Case 1, retiree on Social Security in a detached gas-heated house receives 480. + period: 2026 + input: + people: + person1: + age: 65 + social_security: 18_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + spm_unit_cash_assets: 5_000 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1] + state_code: IA + output: + # 18,000 / 15,650 = 1.1502 -> 5 poverty points; fixed income +1, elderly +1, + # detached +1 = 3; natural gas 4; 12 points x 40 = 480. + ia_liheap: 480 + +- name: Case 2, working couple with a toddler in a subsidized eight-unit building with heat in rent receives the 80 minimum. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 20_000 + person2: + age: 32 + employment_income: 14_000 + person3: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + pre_subsidy_electricity_expense: 900 + ia_liheap_dwelling_type: ATTACHED_5_OR_MORE_UNITS + households: + household: + members: [person1, person2, person3] + state_code: IA + output: + # 34,000 / 26,650 = 1.2758 -> 4; young child +1, five-plex -1, + # subsidized -2, heat in rent -4 = -6; natural gas 4; 2 points x 40 = 80, + # which equals the 80 minimum. + ia_liheap: 80 + +- name: Case 3, negative total points pay the 80 minimum. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + pre_subsidy_electricity_expense: 900 + spm_unit_cash_assets: 60_000 + ia_liheap_dwelling_type: ATTACHED_5_OR_MORE_UNITS + households: + household: + members: [person1] + state_code: IA + output: + # 28,000 / 15,650 = 1.789 -> 4; five-plex -1, subsidized -2, + # heat in rent -4, savings -4 = -11; natural gas 4; total -3; + # 40 x -3 = -120 -> raised to the 80 minimum. + ia_liheap_points: -3 + ia_liheap: 80 + +- name: Case 4, propane household receives the flat 800 regardless of points. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + bottled_gas_expense: 2_200 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1] + state_code: IA + output: + # 28,000 / 15,650 = 1.789 -> 4; detached +1; propane 5; 10 points x 40 = + # 400, raised to the 800 liquid propane amount (Appendix I p. 137). + ia_liheap_points: 10 + ia_liheap: 800 + +- name: Case 5, the same household heating with kerosene receives the matrix 400. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: KEROSENE + fuel_oil_expense: 1_600 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1] + state_code: IA + output: + # Kerosene takes the fuel-oil 5 points but the flat 800 names only liquid + # propane and fuel oil: 4 + 1 + 5 = 10 points x 40 = 400. + ia_liheap_points: 10 + ia_liheap: 400 + +- name: Case 6, retiree heating with fuel oil receives the flat 800. + period: 2026 + input: + people: + person1: + age: 70 + social_security: 20_000 + spm_units: + spm_unit: + members: [person1] + heating_type: FUEL_OIL + fuel_oil_expense: 2_100 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1] + state_code: IA + output: + # 20,000 / 15,650 = 1.278 -> 4; fixed income +1, elderly +1, detached +1 = + # 3; fuel oil 5; 12 points x 40 = 480, raised to 800. + ia_liheap: 800 + +- name: Case 7, the maximum 18 matrix points pay 720, below the 800 cap. + period: 2026 + input: + people: + person1: + age: 68 + is_disabled: true + social_security: 15_000 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: KEROSENE + fuel_oil_expense: 1_600 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1, person2] + state_code: IA + output: + # 15,000 / 21,150 = 0.7092 -> 8; fixed income +1, elderly +1, disabled +1, + # young child +1, detached +1 = 5; kerosene 5; 18 points x 40 = 720. + ia_liheap_points: 18 + ia_liheap: 720 + +- name: Case 8, income over 200 percent of poverty receives nothing. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 35_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1] + state_code: IA + output: + # 35,000 > 31,300: not eligible. + ia_liheap: 0 + +- name: Case 9, the benefit is not capped at a small heating bill. + period: 2026 + input: + people: + person1: + age: 65 + social_security: 18_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 200 + spm_unit_cash_assets: 5_000 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1] + state_code: IA + output: + # Same 12 points as Case 1 with a 200 bill: 480 (section 5.41 p. 20, the + # benefit does not depend on the amount owed). + ia_liheap: 480 + +- name: Case 10, zero total points pay the 80 minimum. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + spm_unit_cash_assets: 60_000 + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1] + state_code: IA + output: + # 4 poverty points; heat in rent -4, savings -4 = -8; natural gas 4; + # total 0; 40 x 0 = 0 -> 80 minimum. Unsubsidized heat in rent is eligible. + ia_liheap_points: 0 + ia_liheap: 80 + +- name: Case 11, wood-heated detached house receives 280. + period: 2026 + input: + people: + person1: + age: 45 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: WOOD + wood_expense: 700 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1] + state_code: IA + output: + # 30,000 / 15,650 = 1.917 -> 4; detached +1; solid fuel 2; 7 points x 40 = + # 280. + ia_liheap: 280 + +- name: Case 12, unspecified heating type pays nothing even with heat in unsubsidized rent. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 20_000 + spm_units: + spm_unit: + members: [person1] + heating_type: UNSPECIFIED + heat_expense_included_in_rent: true + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1] + state_code: IA + output: + ia_liheap_eligible: false + ia_liheap: 0 + +- name: Case 13, family of four in an all-electric detached house receives 400. + period: 2026 + input: + people: + person1: + age: 38 + employment_income: 42_000 + person2: + age: 36 + employment_income: 18_000 + person3: + age: 9 + person4: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1, person2, person3, person4] + state_code: IA + output: + # 60,000 / 32,150 = 1.866 -> 4; young child +1, detached +1 = 2; + # electric 4; 10 points x 40 = 400. + ia_liheap: 400 + +- name: Case 14, one total point pays the 80 minimum. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + pre_subsidy_electricity_expense: 900 + ia_liheap_dwelling_type: ATTACHED_5_OR_MORE_UNITS + households: + household: + members: [person1] + state_code: IA + output: + # 4; five-plex -1, subsidized -2, heat in rent -4 = -7; natural gas 4; + # 1 point x 40 = 40 -> 80 minimum. + ia_liheap_points: 1 + ia_liheap: 80 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap_fuel_category.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap_fuel_category.yaml new file mode 100644 index 00000000000..1abd0b737b4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap_fuel_category.yaml @@ -0,0 +1,119 @@ +# Iowa LIHEAP fuel category derived from heating_type (REQ-028). Appendix I +# p. 137 of the FY26 LIHEAP Policy and Procedures Manual lists natural gas, +# electric, liquid propane, fuel oil and solid fuel (wood, coal, corn); the +# primary heating fuel is the one used most (Definitions p. 84); vendors also +# supply kerosene and "other fuels" (sections 12.00-12.20 pp. 46-47): +# https://hhs.iowa.gov/media/17262/download?inline#page=137 +# Modeling: KEROSENE takes the fuel-oil category by inference (the manual +# groups LP, heating oil, wood, kerosene and corn as deliverable fuels, p. 47, +# and heating_expense already pairs fuel oil with kerosene); OTHER, SOLAR, +# UNSPECIFIED and NONE have no published points and fall in OTHER. + +- name: Case 1, natural gas. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: NATURAL_GAS + gas_expense: 1_100 + output: + ia_liheap_fuel_category: NATURAL_GAS + +- name: Case 2, electricity. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_500 + output: + ia_liheap_fuel_category: ELECTRICITY + +- name: Case 3, propane. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: PROPANE + bottled_gas_expense: 2_200 + output: + ia_liheap_fuel_category: PROPANE + +- name: Case 4, fuel oil. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: FUEL_OIL + fuel_oil_expense: 2_000 + output: + ia_liheap_fuel_category: FUEL_OIL + +- name: Case 5, kerosene is treated as fuel oil. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: KEROSENE + fuel_oil_expense: 1_600 + output: + # Inference: deliverable fuel grouped with heating oil (manual p. 47). + ia_liheap_fuel_category: FUEL_OIL + +- name: Case 6, wood is solid fuel. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: WOOD + wood_expense: 700 + output: + ia_liheap_fuel_category: SOLID_FUEL + +- name: Case 7, coal is solid fuel. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: COAL + coal_expense: 900 + output: + ia_liheap_fuel_category: SOLID_FUEL + +- name: Case 8, other fuel has no published category. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: OTHER + other_heating_fuel_expense: 700 + output: + ia_liheap_fuel_category: OTHER + +- name: Case 9, solar falls in other. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: SOLAR + pre_subsidy_electricity_expense: 600 + output: + ia_liheap_fuel_category: OTHER + +- name: Case 10, unspecified falls in other. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: UNSPECIFIED + output: + ia_liheap_fuel_category: OTHER + +- name: Case 11, no heating falls in other. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: NONE + output: + ia_liheap_fuel_category: OTHER diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap_household_size.yaml new file mode 100644 index 00000000000..8446ff1dd93 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap_household_size.yaml @@ -0,0 +1,126 @@ +# Iowa LIHEAP household size (REQ-004). Each member must verify citizenship or +# SSN documentation to be counted toward the household size; an ineligible +# member is excluded from the count but that member's income is counted in +# full, and a household made up only of ineligible members is ineligible. +# FY26 LIHEAP Policy and Procedures Manual section 7.00 p. 31 and section 7.30 +# p. 34: https://hhs.iowa.gov/media/17262/download?inline#page=31 +# Approximated with is_citizen_or_legal_immigrant (the Kansas pattern); Iowa's +# test is documentary (SSN or I-94), not the federal qualified-noncitizen list. + +- name: Case 1, single citizen counts as one. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + ia_liheap_household_size: 1 + +- name: Case 2, family of four citizens counts four. + period: 2026 + input: + people: + person1: + age: 38 + employment_income: 42_000 + person2: + age: 36 + employment_income: 18_000 + person3: + age: 9 + person4: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + gas_expense: 1_250 + households: + household: + members: [person1, person2, person3, person4] + state_code: IA + output: + ia_liheap_household_size: 4 + +- name: Case 3, mixed-status household excludes the undocumented adult from the count. + period: 2026 + input: + people: + person1: + age: 35 + immigration_status: UNDOCUMENTED + employment_income: 24_000 + person2: + age: 33 + immigration_status: CITIZEN + employment_income: 14_000 + person3: + age: 5 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1, person2, person3] + state_code: IA + output: + # Manual section 7.30 p. 34: 3 members - 1 ineligible member = 2; the + # ineligible member's 24,000 still counts (see ia_liheap_countable_income). + ia_liheap_household_size: 2 + +- name: Case 4, household of two undocumented adults counts zero. + period: 2026 + input: + people: + person1: + age: 30 + immigration_status: UNDOCUMENTED + employment_income: 22_000 + person2: + age: 28 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_000 + households: + household: + members: [person1, person2] + state_code: IA + output: + # Manual section 7.30 p. 34: a household of only ineligible members. + ia_liheap_household_size: 0 + +- name: Case 5, the same single citizen in Illinois is outside the Iowa program. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IL + output: + # defined_for = StateCode.IA returns zero outside Iowa. + ia_liheap_household_size: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.yaml new file mode 100644 index 00000000000..cc4b3522e9f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.yaml @@ -0,0 +1,146 @@ +# Iowa LIHEAP countable earned income per person (REQ-009 to REQ-011, +# REQ-016). Gross income of members aged 18 and over is counted (FY26 LIHEAP +# Policy and Procedures Manual section 6.20 p. 26); Appendix C counts wages, +# tips, bonuses and severance (p. 97), farm income (p. 98), military pay +# excluding the housing allowance (p. 108) and self-employment on gross +# receipts with no business-expense deduction (p. 113), and records income of +# members under 18 as not included (p. 102): +# https://hhs.iowa.gov/media/17262/download?inline#page=26 +# FFY2026 plan section 1.9 pp. 5-6 leaves "earned income of a child under the +# age of 18" unchecked: +# https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=5 +# Modeling: the model's net self-employment and farm inputs are floored at zero +# per source so a loss cannot offset other income (Kansas pattern). All income +# of members under 18 is excluded (user decision; section 6.00 p. 24 is in +# conflict and is recorded on the variable). + +- name: Case 1, adult wages are counted in full. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 36_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + ia_liheap_countable_earned_income: 36_000 + +- name: Case 2, a 16-year-old's wages are excluded. + period: 2026 + input: + people: + person1: + age: 44 + employment_income: 38_000 + person2: + age: 16 + employment_income: 6_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1, person2] + state_code: IA + output: + # Appendix C p. 102: income of members under 18 is not included. + ia_liheap_countable_earned_income: [38_000, 0] + +- name: Case 3, an 18-year-old's wages are counted. + period: 2026 + input: + people: + person1: + age: 46 + employment_income: 30_000 + person2: + age: 18 + employment_income: 8_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1, person2] + state_code: IA + output: + # Section 6.20 p. 26: members aged 18 and over. + ia_liheap_countable_earned_income: [30_000, 8_000] + +- name: Case 4, self-employment and farm losses are floored at zero per source. + period: 2026 + input: + people: + person1: + age: 52 + employment_income: 20_000 + self_employment_income: -5_000 + farm_operations_income: -3_000 + farm_rent_income: 2_000 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + bottled_gas_expense: 2_200 + households: + household: + members: [person1] + state_code: IA + output: + # 20,000 + max(-5,000, 0) + max(-3,000, 0) + 2,000 = 22,000 + # (Appendix C p. 113: no business-expense deduction; p. 98 farm income). + ia_liheap_countable_earned_income: 22_000 + +- name: Case 5, Social Security is not earned income. + period: 2026 + input: + people: + person1: + age: 67 + social_security: 18_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + ia_liheap_countable_earned_income: 0 + +- name: Case 6, military basic pay, partnership and SSTB self-employment income are earned. + period: 2026 + input: + people: + person1: + age: 31 + military_basic_pay: 28_000 + partnership_s_corp_income: 4_000 + sstb_self_employment_income: 3_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_500 + households: + household: + members: [person1] + state_code: IA + output: + # 28,000 + 4,000 + 3,000 = 35,000 (Appendix C p. 108 military pay + # excluding housing allowance; p. 113 self-employment and contract income). + ia_liheap_countable_earned_income: 35_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.yaml new file mode 100644 index 00000000000..b5c961af50e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.yaml @@ -0,0 +1,197 @@ +# Iowa LIHEAP countable income for the household (REQ-009, REQ-010, REQ-014, +# REQ-004). Gross income of all members aged 18 and over is counted (FY26 +# LIHEAP Policy and Procedures Manual section 6.20 p. 26); child support paid +# by a household member is deducted on proof of payment (Appendix C pp. 92-93); +# an ineligible member is excluded from the household size but that member's +# income is counted in full (section 7.30 p. 34): +# https://hhs.iowa.gov/media/17262/download?inline#page=26 +# Modeling: max(earned + unearned - child support paid, 0); all income of +# members under 18 is excluded (user decision; section 6.00 p. 24 is in +# conflict and is recorded on the earned-income variable). + +- name: Case 1, two adults' wages are summed. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 20_000 + person2: + age: 32 + employment_income: 14_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_000 + households: + household: + members: [person1, person2] + state_code: IA + output: + # 20,000 + 14,000 = 34,000. + ia_liheap_countable_income: 34_000 + +- name: Case 2, child support paid is deducted. + period: 2026 + input: + people: + person1: + age: 38 + employment_income: 30_000 + child_support_expense: 3_600 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # 30,000 - 3,600 = 26,400 (Appendix C p. 92, child support notes column). + ia_liheap_countable_income: 26_400 + +- name: Case 3, child support paid above income floors countable income at zero. + period: 2026 + input: + people: + person1: + age: 41 + employment_income: 2_400 + child_support_expense: 3_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: IA + output: + # max(2,400 - 3,000, 0) = 0. + ia_liheap_countable_income: 0 + +- name: Case 4, mixed-status household counts both incomes against the size-one limit. + period: 2026 + input: + people: + person1: + age: 35 + immigration_status: UNDOCUMENTED + employment_income: 24_000 + person2: + age: 33 + immigration_status: CITIZEN + employment_income: 14_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1, person2] + state_code: IA + output: + # Section 7.30 p. 34: size 1 (the undocumented adult is not counted) but + # 24,000 + 14,000 = 38,000 is counted; 38,000 > 2.0 x 15,650 = 31,300. + ia_liheap_household_size: 1 + ia_liheap_countable_income: 38_000 + ia_liheap_income_limit: 31_300 + ia_liheap_income_eligible: false + +- name: Case 5, wages and Social Security of children are excluded. + period: 2026 + input: + people: + person1: + age: 44 + employment_income: 28_000 + person2: + age: 16 + employment_income: 5_000 + person3: + age: 10 + social_security: 8_000 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + gas_expense: 1_200 + households: + household: + members: [person1, person2, person3] + state_code: IA + output: + # 28,000 + 0 + 0 = 28,000 (section 6.20 p. 26; Appendix C pp. 102, 114). + ia_liheap_countable_income: 28_000 + +- name: Case 6, a self-employment loss cannot offset Social Security. + period: 2026 + input: + people: + person1: + age: 62 + self_employment_income: -12_000 + social_security: 18_000 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + bottled_gas_expense: 2_000 + households: + household: + members: [person1] + state_code: IA + output: + # max(-12,000, 0) + 18,000 = 18,000 (Appendix C p. 113, gross receipts). + ia_liheap_countable_income: 18_000 + +- name: Case 7, earned and unearned income are combined. + period: 2026 + input: + people: + person1: + age: 47 + employment_income: 24_000 + unemployment_compensation: 5_000 + alimony_income: 3_600 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # 24,000 + 5,000 + 3,600 = 32,600. + ia_liheap_countable_income: 32_600 + +- name: Case 8, child support paid by a second adult is deducted from the household total. + period: 2026 + input: + people: + person1: + age: 36 + employment_income: 30_000 + person2: + age: 39 + employment_income: 12_000 + child_support_expense: 2_400 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1, person2] + state_code: IA + output: + # 30,000 + 12,000 - 2,400 = 39,600. + ia_liheap_countable_income: 39_600 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml new file mode 100644 index 00000000000..a6e110d7284 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml @@ -0,0 +1,193 @@ +# Iowa LIHEAP countable unearned income per person (REQ-012, REQ-013, +# REQ-015). Appendix C of the FY26 LIHEAP Policy and Procedures Manual counts +# Social Security and SSI for members 18 and over (p. 114), pensions, IRA and +# regular retirement distributions (pp. 103, 109, 111), unemployment (p. 117), +# workers' compensation (p. 121), strike benefits (p. 115), long-term private +# disability (p. 94), VA compensation and pensions (pp. 118-119), alimony +# (p. 90), rental income (p. 110), investment interest (p. 103), dividends +# (p. 96) and taxable gambling winnings (p. 99); it excludes child support +# received (pp. 92-93) and capital gains (p. 92): +# https://hhs.iowa.gov/media/17262/download?inline#page=114 +# Modeling: Social Security is counted gross (Iowa nets Medicare premiums); +# bank interest and the under-$120 dividend rule are approximations. ssi is a +# MONTH variable, so the twelve months are summed. + +- name: Case 1, Social Security and a pension are counted. + period: 2026 + input: + people: + person1: + age: 67 + social_security: 18_000 + pension_income: 6_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # 18,000 + 6,000 = 24,000. + ia_liheap_countable_unearned_income: 24_000 + +- name: Case 2, SSI is summed over the twelve months. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + social_security: 3_000 + # 764 a month: 994 federal benefit rate - (250 - 20) countable. + ssi: 9_168 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: IA + output: + # 3,000 + 9,168 = 12,168 (Appendix C p. 114: SSA benefits and SSI counted). + ia_liheap_countable_unearned_income: 12_168 + +- name: Case 3, Social Security paid to a child is excluded. + period: 2026 + input: + people: + person1: + age: 42 + social_security: 20_000 + person2: + age: 10 + social_security: 9_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1, person2] + state_code: IA + output: + # Appendix C p. 114: Social Security for members under 18 is recorded at 0. + ia_liheap_countable_unearned_income: [20_000, 0] + +- name: Case 4, wages are not unearned income. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + ia_liheap_countable_unearned_income: 0 + +- name: Case 5, unemployment, workers' compensation, alimony, rent, interest, dividends and gambling are counted. + period: 2026 + input: + people: + person1: + age: 50 + unemployment_compensation: 9_000 + workers_compensation: 4_000 + alimony_income: 6_000 + rental_income: 7_200 + interest_income: 300 + ordinary_dividend_income: 500 + gambling_winnings: 1_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # 9,000 + 4,000 + 6,000 + 7,200 + 300 + 500 + 1,000 = 28,000. + ia_liheap_countable_unearned_income: 28_000 + +- name: Case 6, child support received and capital gains are not counted. + period: 2026 + input: + people: + person1: + age: 35 + child_support_received: 4_800 + capital_gains: 2_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # Appendix C pp. 92-93 (child support received) and p. 92 (capital gains). + ia_liheap_countable_unearned_income: 0 + +- name: Case 7, veterans' benefits, disability, strike benefits and retirement distributions are counted. + period: 2026 + input: + people: + person1: + age: 58 + veterans_benefits: 15_000 + disability_benefits: 6_000 + strike_benefits: 1_500 + retirement_distributions: 2_000 + spm_units: + spm_unit: + members: [person1] + heating_type: FUEL_OIL + fuel_oil_expense: 2_000 + households: + household: + members: [person1] + state_code: IA + output: + # 15,000 + 6,000 + 1,500 + 2,000 = 24,500. + ia_liheap_countable_unearned_income: 24_500 + +- name: Case 8, an IRA distribution is counted once. + period: 2026 + input: + people: + person1: + age: 66 + social_security: 16_000 + taxable_ira_distributions: 3_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # 16,000 + 3,000 = 19,000 (Appendix C p. 103: regular IRA distributions + # are one income item). retirement_distributions already contains + # taxable_ira_distributions through taxable_retirement_distributions, so + # reading both sources would count the 3,000 twice (22,000). + ia_liheap_countable_unearned_income: 19_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml new file mode 100644 index 00000000000..76555478ac4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml @@ -0,0 +1,223 @@ +# Iowa LIHEAP "Fixed Income Only (no other sources of income)" targeting flag +# (REQ-018). Appendix I p. 137 of the FY26 LIHEAP Policy and Procedures Manual +# awards +1 point; the Definitions (p. 81) limit fixed income to SSA benefits, +# SSI, SSDI, Veterans' Assistance and pensions and say "Only mark 'fixed +# income' when that is the ONLY source(s)"; Appendix C notes alimony (p. 90) +# and earnings are not fixed income: +# https://hhs.iowa.gov/media/17262/download?inline#page=137 +# Modeling: true when the household's countable gross income (members aged 18 +# and over, before the child-support deduction) is positive and equals the sum +# of social_security, ssi, veterans_benefits and pension_income. + +- name: Case 1, Social Security alone is fixed income. + period: 2026 + input: + people: + person1: + age: 67 + social_security: 18_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + households: + household: + members: [person1] + state_code: IA + output: + # gross 18,000 = fixed 18,000 > 0. + ia_liheap_fixed_income_only: true + +- name: Case 2, Social Security plus part-time wages is not fixed income only. + period: 2026 + input: + people: + person1: + age: 67 + social_security: 18_000 + employment_income: 3_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + households: + household: + members: [person1] + state_code: IA + output: + # gross 21,000, fixed 18,000. + ia_liheap_fixed_income_only: false + +- name: Case 3, Social Security, a pension and VA benefits together are fixed income. + period: 2026 + input: + people: + person1: + age: 72 + social_security: 14_000 + pension_income: 6_000 + veterans_benefits: 4_000 + spm_units: + spm_unit: + members: [person1] + heating_type: FUEL_OIL + fuel_oil_expense: 2_100 + households: + household: + members: [person1] + state_code: IA + output: + # gross 14,000 + 6,000 + 4,000 = 24,000 = fixed 24,000. + ia_liheap_fixed_income_only: true + +- name: Case 4, SSI and Social Security together are fixed income. + period: 2026 + input: + people: + person1: + age: 70 + social_security: 3_000 + # 764 a month: 994 federal benefit rate - (250 - 20) countable. + ssi: 9_168 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: IA + output: + # gross 3,000 + 9,168 = 12,168 = fixed 12,168. + ia_liheap_fixed_income_only: true + +- name: Case 5, alimony is not fixed income. + period: 2026 + input: + people: + person1: + age: 66 + social_security: 18_000 + alimony_income: 6_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + households: + household: + members: [person1] + state_code: IA + output: + # Appendix C p. 90: alimony is not fixed income; gross 24,000, fixed 18,000. + ia_liheap_fixed_income_only: false + +- name: Case 6, a household with no income is not fixed income only. + period: 2026 + input: + people: + person1: + age: 35 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 800 + households: + household: + members: [person1] + state_code: IA + output: + # gross 0 is not positive. + ia_liheap_fixed_income_only: false + +- name: Case 7, child support paid does not change the fixed-income test. + period: 2026 + input: + people: + person1: + age: 65 + social_security: 18_000 + child_support_expense: 2_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + households: + household: + members: [person1] + state_code: IA + output: + # The test compares gross sources before the deduction: 18,000 = 18,000. + ia_liheap_fixed_income_only: true + +- name: Case 8, a child's excluded wages do not break fixed income only. + period: 2026 + input: + people: + person1: + age: 67 + social_security: 18_000 + person2: + age: 16 + employment_income: 4_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + households: + household: + members: [person1, person2] + state_code: IA + output: + # The 16-year-old's wages are not countable, so gross 18,000 = fixed 18,000. + ia_liheap_fixed_income_only: true + +- name: Case 9, interest income is not fixed income. + period: 2026 + input: + people: + person1: + age: 67 + social_security: 18_000 + interest_income: 200 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + households: + household: + members: [person1] + state_code: IA + output: + # gross 18,200, fixed 18,000. + ia_liheap_fixed_income_only: false + +- name: Case 10, a retired couple on Social Security and a pension is fixed income only. + period: 2026 + input: + people: + person1: + age: 67 + social_security: 18_000 + person2: + age: 65 + pension_income: 9_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_300 + households: + household: + members: [person1, person2] + state_code: IA + output: + # gross 18,000 + 9,000 = 27,000 = fixed 27,000. + ia_liheap_fixed_income_only: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/integration.yaml new file mode 100644 index 00000000000..e43b7953052 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/integration.yaml @@ -0,0 +1,340 @@ +# Iowa LIHEAP Regular Assistance end-to-end integration tests, FY26 season +# (October 1, 2025 to April 30, 2026; model period 2026). +# FY26 LIHEAP Policy and Procedures Manual: income limit section 5.10 p. 18; +# household size and ineligible members section 7.30 p. 34; countable income +# section 6.20 p. 26 and Appendix C pp. 90-121; energy burden Appendix D +# pp. 122-123; 200 percent table Appendix B p. 89; points matrix and award +# Appendix I p. 137: https://hhs.iowa.gov/media/17262/download?inline#page=137 +# FFY2026 Detailed Model Plan sections 1.8-1.9 pp. 5-6 and 2.3-2.6 pp. 8-9: +# https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9 +# FY26 plan hearing draft p. 48 (flat 800 for liquid propane and fuel oil): +# https://hhs.iowa.gov/media/16690/download?inline#page=48 +# 2025 guideline (contiguous states): 15,650 + 5,500 per additional person; +# limit = 2 x guideline: 31,300 (1) / 42,300 (2) / 53,300 (3) / 64,300 (4). +# Poverty points by income / guideline: <= 0.75 -> 8, <= 1.00 -> 6, +# <= 1.25 -> 5, <= 2.00 -> 4. Benefit = 40 x points clipped to [80, 800]; +# propane and fuel oil households receive the flat 800. + +- name: Case 1, retiree on Social Security in a detached gas-heated house. + period: 2026 + input: + people: + person1: + age: 65 + social_security: 18_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + spm_unit_cash_assets: 5_000 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1] + state_code: IA + output: + ia_liheap_household_size: 1 + ia_liheap_fpg: 15_650 + # 2.0 x 15,650 + ia_liheap_income_limit: 31_300 + # 18,000 Social Security; 1,500 a month exceeds the SSI federal benefit + # rate, so no SSI is added. + ia_liheap_countable_income: 18_000 + ia_liheap_income_eligible: true + ia_liheap_fixed_income_only: true + ia_liheap_has_energy_burden: true + ia_liheap_eligible: true + ia_liheap_fuel_category: NATURAL_GAS + # 18,000 / 15,650 = 1.1502 -> 116 percent -> 101 to 125 band + ia_liheap_poverty_points: 5 + # fixed income +1, elderly +1, detached +1 + ia_liheap_targeting_points: 3 + ia_liheap_fuel_points: 4 + # 5 + 3 + 4 + ia_liheap_points: 12 + # 40 x 12 + ia_liheap: 480 + +- name: Case 2, working couple with a toddler in a Section 8 eight-unit building with heat in rent and an electric account. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 20_000 + person2: + age: 32 + employment_income: 14_000 + person3: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3] + # The building's fuel is still required for the fuel points. + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + pre_subsidy_electricity_expense: 900 + ia_liheap_dwelling_type: ATTACHED_5_OR_MORE_UNITS + households: + household: + members: [person1, person2, person3] + state_code: IA + output: + ia_liheap_household_size: 3 + # 15,650 + 2 x 5,500 + ia_liheap_fpg: 26_650 + # 2.0 x 26,650 + ia_liheap_income_limit: 53_300 + # 20,000 + 14,000 + ia_liheap_countable_income: 34_000 + ia_liheap_income_eligible: true + ia_liheap_fixed_income_only: false + # Subsidized heat in rent with an electric account (Appendix D p. 122). + ia_liheap_has_energy_burden: true + ia_liheap_eligible: true + # 34,000 / 26,650 = 1.2758 -> 128 percent -> 126 to 200 band + ia_liheap_poverty_points: 4 + # young child +1, five-plex -1, subsidized -2, heat in rent -4 + ia_liheap_targeting_points: -6 + ia_liheap_fuel_points: 4 + # 4 - 6 + 4 + ia_liheap_points: 2 + # 40 x 2 = 80, at the 80 minimum + ia_liheap: 80 + +- name: Case 3, farm couple heating with propane and holding savings over 50,000. + period: 2026 + input: + people: + person1: + age: 55 + farm_operations_income: 22_000 + person2: + age: 53 + farm_rent_income: 6_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: PROPANE + bottled_gas_expense: 2_400 + spm_unit_cash_assets: 60_000 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1, person2] + state_code: IA + output: + ia_liheap_household_size: 2 + # 15,650 + 5,500 + ia_liheap_fpg: 21_150 + # 2.0 x 21,150 + ia_liheap_income_limit: 42_300 + # 22,000 + 6,000 (Appendix C p. 98 farm income); no asset test + ia_liheap_countable_income: 28_000 + ia_liheap_income_eligible: true + ia_liheap_eligible: true + ia_liheap_fuel_category: PROPANE + # 28,000 / 21,150 = 1.3239 -> 133 percent -> 126 to 200 band + ia_liheap_poverty_points: 4 + # detached +1, savings over 50,000 -4 + ia_liheap_targeting_points: -3 + ia_liheap_fuel_points: 5 + # 4 - 3 + 5 + ia_liheap_points: 6 + # 40 x 6 = 240, raised to the 800 liquid propane amount + ia_liheap: 800 + +- name: Case 4, mixed-status family counts both parents' wages against the size-two limit. + period: 2026 + input: + people: + person1: + age: 35 + immigration_status: UNDOCUMENTED + employment_income: 26_000 + person2: + age: 33 + immigration_status: CITIZEN + employment_income: 12_000 + person3: + age: 5 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + gas_expense: 1_100 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1, person2, person3] + state_code: IA + output: + # Section 7.30 p. 34: the undocumented parent is not counted. + ia_liheap_household_size: 2 + ia_liheap_fpg: 21_150 + ia_liheap_income_limit: 42_300 + # 26,000 + 12,000, the ineligible member's income counts in full + ia_liheap_countable_income: 38_000 + # 38,000 <= 42,300 + ia_liheap_income_eligible: true + ia_liheap_eligible: true + # 38,000 / 21,150 = 1.7967 -> 180 percent -> 126 to 200 band + ia_liheap_poverty_points: 4 + # young child +1, detached +1 + ia_liheap_targeting_points: 2 + ia_liheap_fuel_points: 4 + # 4 + 2 + 4 + ia_liheap_points: 10 + # 40 x 10 + ia_liheap: 400 + +- name: Case 5, disabled worker paying child support in an all-electric duplex. + period: 2026 + input: + people: + person1: + age: 45 + is_disabled: true + employment_income: 26_000 + child_support_expense: 4_800 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_500 + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1] + state_code: IA + output: + ia_liheap_household_size: 1 + ia_liheap_income_limit: 31_300 + # 26,000 - 4,800 child support paid (Appendix C p. 92) + ia_liheap_countable_income: 21_200 + ia_liheap_income_eligible: true + ia_liheap_fixed_income_only: false + ia_liheap_has_energy_burden: true + ia_liheap_eligible: true + ia_liheap_fuel_category: ELECTRICITY + # 21,200 / 15,650 = 1.3546 -> 136 percent -> 126 to 200 band + ia_liheap_poverty_points: 4 + # disabled +1; a two-to-four-unit building scores 0 + ia_liheap_targeting_points: 1 + ia_liheap_fuel_points: 4 + # 4 + 1 + 4 + ia_liheap_points: 9 + # 40 x 9 + ia_liheap: 360 + +- name: Case 6, unspecified heating type with heat in unsubsidized rent pays nothing. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 20_000 + spm_units: + spm_unit: + members: [person1] + heating_type: UNSPECIFIED + heat_expense_included_in_rent: true + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1] + state_code: IA + output: + ia_liheap_household_size: 1 + ia_liheap_countable_income: 20_000 + # 20,000 <= 31,300 + ia_liheap_income_eligible: true + # Unsubsidized heat in rent is a burden (Appendix D p. 122) ... + ia_liheap_has_energy_burden: true + ia_liheap_fuel_category: OTHER + ia_liheap_fuel_points: 0 + # ... but the fuel must be sent: UNSPECIFIED pays nothing. + ia_liheap_eligible: false + ia_liheap: 0 + +- name: Case 7, disabled grandparent raising a preschooler in a kerosene-heated house reaches the 18-point maximum. + period: 2026 + input: + people: + person1: + age: 68 + is_disabled: true + social_security: 15_000 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: KEROSENE + fuel_oil_expense: 1_600 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1, person2] + state_code: IA + output: + ia_liheap_household_size: 2 + ia_liheap_fpg: 21_150 + ia_liheap_income_limit: 42_300 + # 15,000 Social Security; 1,250 a month exceeds the SSI federal benefit + # rate, so no SSI is added. + ia_liheap_countable_income: 15_000 + ia_liheap_fixed_income_only: true + ia_liheap_eligible: true + # Kerosene takes the fuel-oil category by inference. + ia_liheap_fuel_category: FUEL_OIL + # 15,000 / 21,150 = 0.7092 -> 71 percent -> 0 to 75 band + ia_liheap_poverty_points: 8 + # fixed income +1, elderly +1, disabled +1, young child +1, detached +1 + ia_liheap_targeting_points: 5 + ia_liheap_fuel_points: 5 + # 8 + 5 + 5, the largest total the matrix allows + ia_liheap_points: 18 + # 40 x 18 = 720, below the 800 cap; kerosene does not take the flat 800 + ia_liheap: 720 + +- name: Case 8, Section 8 tenant with heat in rent, large savings and an electric account has negative points and receives the 80 minimum. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + pre_subsidy_electricity_expense: 900 + spm_unit_cash_assets: 60_000 + ia_liheap_dwelling_type: ATTACHED_5_OR_MORE_UNITS + households: + household: + members: [person1] + state_code: IA + output: + ia_liheap_household_size: 1 + ia_liheap_countable_income: 28_000 + ia_liheap_income_eligible: true + ia_liheap_has_energy_burden: true + ia_liheap_eligible: true + # 28,000 / 15,650 = 1.789 -> 179 percent -> 126 to 200 band + ia_liheap_poverty_points: 4 + # five-plex -1, subsidized -2, heat in rent -4, savings -4 + ia_liheap_targeting_points: -11 + ia_liheap_fuel_points: 4 + # 4 - 11 + 4 + ia_liheap_points: -3 + # 40 x -3 = -120 -> raised to the 80 minimum + ia_liheap: 80 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_fuel_points.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_fuel_points.yaml new file mode 100644 index 00000000000..c82f0b91943 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_fuel_points.yaml @@ -0,0 +1,107 @@ +# Iowa LIHEAP fuel points (REQ-028). Appendix I p. 137 of the FY26 LIHEAP +# Policy and Procedures Manual, "Fuel/Energy Type": Natural Gas 4, Electric 4, +# Liquid Propane 5, Fuel Oil 5, Solid Fuel (Wood/Coal/Corn) 2: +# https://hhs.iowa.gov/media/17262/download?inline#page=137 +# Identical Clearinghouse copy of the matrix: +# https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 +# Modeling: kerosene takes the fuel-oil points by inference; OTHER, SOLAR, +# UNSPECIFIED and NONE have no published points and score 0. + +- name: Case 1, natural gas scores 4. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: NATURAL_GAS + gas_expense: 1_100 + output: + ia_liheap_fuel_points: 4 + +- name: Case 2, electricity scores 4. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_500 + output: + ia_liheap_fuel_points: 4 + +- name: Case 3, propane scores 5. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: PROPANE + bottled_gas_expense: 2_200 + output: + ia_liheap_fuel_points: 5 + +- name: Case 4, fuel oil scores 5. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: FUEL_OIL + fuel_oil_expense: 2_000 + output: + ia_liheap_fuel_points: 5 + +- name: Case 5, kerosene scores the fuel-oil 5. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: KEROSENE + fuel_oil_expense: 1_600 + output: + ia_liheap_fuel_points: 5 + +- name: Case 6, wood scores 2. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: WOOD + wood_expense: 700 + output: + ia_liheap_fuel_points: 2 + +- name: Case 7, coal scores 2. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: COAL + coal_expense: 900 + output: + ia_liheap_fuel_points: 2 + +- name: Case 8, other fuel scores 0. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: OTHER + other_heating_fuel_expense: 700 + output: + ia_liheap_fuel_points: 0 + +- name: Case 9, unspecified fuel scores 0. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: UNSPECIFIED + output: + ia_liheap_fuel_points: 0 + +- name: Case 10, solar scores 0. + period: 2026 + input: + state_code: IA + age: 40 + heating_type: SOLAR + pre_subsidy_electricity_expense: 600 + output: + ia_liheap_fuel_points: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.yaml new file mode 100644 index 00000000000..89fdeb3cdf6 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.yaml @@ -0,0 +1,253 @@ +# Iowa LIHEAP poverty-level points (REQ-017). Appendix I p. 137 of the FY26 +# LIHEAP Policy and Procedures Manual: 0 to 75 percent of poverty 8 points, +# 76 to 100 percent 6, 101 to 125 percent 5, 126 to 200 percent 4, over 200 +# percent ineligible; "Poverty levels of .01% are rounded up to the next +# percentage", so the bands are inclusive at their tops (a ratio of exactly +# 0.75 scores 8, 0.7501 becomes 76 percent and scores 6): +# https://hhs.iowa.gov/media/17262/download?inline#page=137 +# The guideline dividing the income is the 2025 guideline of Appendix B p. 89 +# (15,650 plus 5,500 per additional person), inferred because Appendix I names +# no other table. Modeling: countable income / ia_liheap_fpg, looked up in a +# single_amount scale keyed at the band tops with right=True; zero when the +# guideline is zero. + +- name: Case 1, no countable income is 0 percent of poverty and scores 8. + period: 2026 + input: + people: + person1: + age: 29 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 800 + households: + household: + members: [person1] + state_code: IA + output: + # 0 / 15,650 = 0 -> 0 to 75 percent. + ia_liheap_poverty_points: 8 + +- name: Case 2, income at exactly 75 percent of poverty scores 8. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 11_737.5 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: IA + output: + # 11,737.5 / 15,650 = 0.75 exactly (0.75 x 15,650 = 11,737.5) -> 75 percent, + # the top of the 0 to 75 band. + ia_liheap_poverty_points: 8 + +- name: Case 3, one dollar above 75 percent of poverty rounds up to 76 percent and scores 6. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 11_738 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: IA + output: + # 11,738 / 15,650 = 0.750032 -> 75.0032 percent rounds up to 76 percent. + ia_liheap_poverty_points: 6 + +- name: Case 4, income at exactly 100 percent of poverty scores 6. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 15_650 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: IA + output: + # 15,650 / 15,650 = 1.00 -> top of the 76 to 100 band. + ia_liheap_poverty_points: 6 + +- name: Case 5, income just above 100 percent of poverty scores 5. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 15_700 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + households: + household: + members: [person1] + state_code: IA + output: + # 15,700 / 15,650 = 1.0032 -> 101 percent. + ia_liheap_poverty_points: 5 + +- name: Case 6, family of four at exactly 125 percent of poverty scores 5. + period: 2026 + input: + people: + person1: + age: 38 + employment_income: 24_000 + person2: + age: 36 + employment_income: 16_187.5 + person3: + age: 9 + person4: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + gas_expense: 1_250 + households: + household: + members: [person1, person2, person3, person4] + state_code: IA + output: + # 24,000 + 16,187.5 = 40,187.5; guideline 15,650 + 3 x 5,500 = 32,150; + # 40,187.5 / 32,150 = 1.25 exactly (1.25 x 32,150 = 40,187.5). + ia_liheap_poverty_points: 5 + +- name: Case 7, family of four just above 125 percent of poverty scores 4. + period: 2026 + input: + people: + person1: + age: 38 + employment_income: 24_000 + person2: + age: 36 + employment_income: 16_500 + person3: + age: 9 + person4: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + gas_expense: 1_250 + households: + household: + members: [person1, person2, person3, person4] + state_code: IA + output: + # 40,500 / 32,150 = 1.2597 -> 126 percent. + ia_liheap_poverty_points: 4 + +- name: Case 8, income at exactly 200 percent of poverty scores 4. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 31_300 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # 31,300 / 15,650 = 2.00 -> top of the 126 to 200 band. + ia_liheap_poverty_points: 4 + +- name: Case 9, income above 200 percent of poverty scores no points. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 31_301 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # 31,301 / 15,650 = 2.00006 -> 201 percent, "Over 200%" (ineligible). + ia_liheap_poverty_points: 0 + ia_liheap_eligible: false + +- name: Case 10, household with no counted member has a zero guideline and no points. + period: 2026 + input: + people: + person1: + age: 30 + immigration_status: UNDOCUMENTED + employment_income: 22_000 + person2: + age: 28 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_000 + households: + household: + members: [person1, person2] + state_code: IA + output: + # Guideline 0: the division is guarded and scores 0. + ia_liheap_fpg: 0 + ia_liheap_poverty_points: 0 + +- name: Case 11, retiree at 115 percent of poverty scores 5. + period: 2026 + input: + people: + person1: + age: 65 + social_security: 18_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + households: + household: + members: [person1] + state_code: IA + output: + # 18,000 / 15,650 = 1.1502 -> 116 percent -> 101 to 125 band. + ia_liheap_poverty_points: 5 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_targeting_points.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_targeting_points.yaml new file mode 100644 index 00000000000..e0714af6f23 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_targeting_points.yaml @@ -0,0 +1,449 @@ +# Iowa LIHEAP targeting points (REQ-018 to REQ-025, REQ-032). Appendix I +# p. 137 of the FY26 LIHEAP Policy and Procedures Manual: Fixed Income Only +1, +# Elderly (60 and older) +1, Disabled +1, Children under 6 +1, Detached +# Dwelling +1, Subsidized Housing -2, 5-Plexes (or more than 5) -1, Heating +# Included in Rent -4, Over $50,000 in Savings -4. Definitions pp. 79-85 +# (attached and detached dwellings, disabled, elderly, subsidized housing, +# young child); savings are interest or dividend-bearing accounts, cash, CDs +# and investments accessible without penalty (section 4.40 p. 14, Appendix C +# p. 112): https://hhs.iowa.gov/media/17262/download?inline#page=137 +# Modeling: one adjustment per factor per household; subsidized housing is +# receives_housing_assistance or is_in_public_housing; savings use +# spm_unit_cash_assets; the dwelling input defaults to DETACHED. + +- name: Case 1, working-age renter in a duplex with no factors scores 0. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_000 + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1] + state_code: IA + output: + # Buildings of two to four units take neither the +1 nor the -1. + ia_liheap_targeting_points: 0 + +- name: Case 2, detached house scores 1. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1] + state_code: IA + output: + # Detached Dwelling +1 (Definitions p. 80, single-unit dwelling). + ia_liheap_targeting_points: 1 + +- name: Case 3, member aged exactly 60 scores the elderly point. + period: 2026 + input: + people: + person1: + age: 60 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_000 + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1] + state_code: IA + output: + # Elderly (60 years of age and older) +1; wages are not fixed income. + ia_liheap_targeting_points: 1 + +- name: Case 4, member aged 59 does not score the elderly point. + period: 2026 + input: + people: + person1: + age: 59 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_000 + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1] + state_code: IA + output: + ia_liheap_targeting_points: 0 + +- name: Case 5, disabled member scores 1. + period: 2026 + input: + people: + person1: + age: 45 + is_disabled: true + employment_income: 26_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_500 + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1] + state_code: IA + output: + # Disabled +1 (Definitions p. 80, self-declared). + ia_liheap_targeting_points: 1 + +- name: Case 6, child aged 5 scores the young-child point. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 30_000 + person2: + age: 5 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_000 + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1, person2] + state_code: IA + output: + # Children (under 6 years of age in the household) +1 (Definitions p. 85). + ia_liheap_targeting_points: 1 + +- name: Case 7, child aged 6 does not score the young-child point. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 30_000 + person2: + age: 6 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_000 + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1, person2] + state_code: IA + output: + ia_liheap_targeting_points: 0 + +- name: Case 8, apartment in a building of five or more units scores -1. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + ia_liheap_dwelling_type: ATTACHED_5_OR_MORE_UNITS + households: + household: + members: [person1] + state_code: IA + output: + # 5-Plexes (or more than 5) -1. + ia_liheap_targeting_points: -1 + +- name: Case 9, Section 8 tenant in a duplex scores -2. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + receives_housing_assistance: true + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1] + state_code: IA + output: + # Subsidized Housing -2 (Definitions pp. 84-85 include Section 8). + ia_liheap_targeting_points: -2 + +- name: Case 10, public housing tenant in a duplex scores -2. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1] + state_code: IA + is_in_public_housing: true + output: + # Subsidized Housing -2 (Definitions pp. 84-85 include public housing). + ia_liheap_targeting_points: -2 + +- name: Case 11, unsubsidized heat included in rent scores -4. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1] + state_code: IA + output: + # Heating Included in Rent -4. + ia_liheap_targeting_points: -4 + +- name: Case 12, savings of exactly 50,000 are not over the threshold. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_000 + spm_unit_cash_assets: 50_000 + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1] + state_code: IA + output: + # Appendix C p. 112: "only count if Savings are over $50,000". + ia_liheap_targeting_points: 0 + +- name: Case 13, savings of 50,001 score -4. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_000 + spm_unit_cash_assets: 50_001 + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1] + state_code: IA + output: + # Over $50,000 in Savings -4. + ia_liheap_targeting_points: -4 + +- name: Case 14, veteran living only on VA compensation scores the fixed-income point. + period: 2026 + input: + people: + person1: + age: 45 + veterans_benefits: 20_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_000 + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1] + state_code: IA + output: + # Fixed Income Only +1 (Definitions p. 81: VA benefits are fixed income); + # not elderly, no disability declared. + ia_liheap_fixed_income_only: true + ia_liheap_targeting_points: 1 + +- name: Case 15, two elderly disabled grandparents with two young grandchildren score each factor once. + period: 2026 + input: + people: + person1: + age: 67 + is_disabled: true + social_security: 18_000 + person2: + age: 66 + is_disabled: true + social_security: 14_000 + person3: + age: 3 + person4: + age: 5 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + gas_expense: 1_300 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1, person2, person3, person4] + state_code: IA + output: + # fixed income +1, elderly +1, disabled +1, young child +1, detached +1 = 5; + # two qualifying members per factor do not double the points (REQ-032). + ia_liheap_targeting_points: 5 + +- name: Case 16, subsidized five-plex tenant with heat in rent and large savings scores -11. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + pre_subsidy_electricity_expense: 900 + spm_unit_cash_assets: 60_000 + ia_liheap_dwelling_type: ATTACHED_5_OR_MORE_UNITS + households: + household: + members: [person1] + state_code: IA + output: + # five-plex -1, subsidized -2, heat in rent -4, savings -4 = -11. + ia_liheap_targeting_points: -11 + +- name: Case 17, working couple with a toddler in a subsidized eight-unit building with heat in rent scores -6. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 20_000 + person2: + age: 32 + employment_income: 14_000 + person3: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + gas_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + pre_subsidy_electricity_expense: 900 + ia_liheap_dwelling_type: ATTACHED_5_OR_MORE_UNITS + households: + household: + members: [person1, person2, person3] + state_code: IA + output: + # young child +1, five-plex -1, subsidized -2, heat in rent -4 = -6. + ia_liheap_targeting_points: -6 + +- name: Case 18, retiree on Social Security in a detached house scores 3. + period: 2026 + input: + people: + person1: + age: 65 + social_security: 18_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + spm_unit_cash_assets: 5_000 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1] + state_code: IA + output: + # fixed income +1, elderly +1, detached +1 = 3; 5,000 savings is not over + # 50,000. + ia_liheap_targeting_points: 3 + +- name: Case 19, farm couple with savings over 50,000 in a detached house scores -3. + period: 2026 + input: + people: + person1: + age: 55 + farm_operations_income: 22_000 + person2: + age: 53 + farm_rent_income: 6_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: PROPANE + bottled_gas_expense: 2_400 + spm_unit_cash_assets: 60_000 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1, person2] + state_code: IA + output: + # detached +1, savings -4 = -3. + ia_liheap_targeting_points: -3 diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.py new file mode 100644 index 00000000000..03a59845c7c --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.py @@ -0,0 +1,47 @@ +from policyengine_us.model_api import * + + +class ia_liheap_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Eligible for Iowa LIHEAP Regular Assistance" + documentation = ( + "Iowa household with at least one counted member, countable income at " + "or below 200 percent of the federal poverty guidelines, an energy " + "burden, and a known primary heating fuel. Section 5.30 requires " + "applicants to be 'living in Iowa, in their primary residence'. Iowa has " + "no categorical eligibility and no assets test. Application timing, " + "first-come first-served funding, the once-per-program-year limit and " + "the automatically eligible verification waiver are not modeled." + ) + reference = ( + # Section 5.10 Eligibility Criteria and 5.30 Who Can Apply (page 18), + # Section 5.50 Energy Burden (page 22), Section 7.30 (page 34) and + # Appendix D (pages 122-123). + "https://hhs.iowa.gov/media/17262/download?inline#page=18", + # Sections 1.4-1.7 (no categorical eligibility) and 2.3 (no assets + # test), pages 5 and 8. + "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=8", + ) + defined_for = StateCode.IA + + def formula(spm_unit, period, parameters): + # Section 7.30: "A household comprised entirely of ineligible members + # is ineligible to receive LIHEAP." + has_counted_member = spm_unit("ia_liheap_household_size", period) > 0 + income_eligible = spm_unit("ia_liheap_income_eligible", period) + energy_burden = spm_unit("ia_liheap_has_energy_burden", period) + heating_type = spm_unit("heating_type", period) + types = heating_type.possible_values + # UNSPECIFIED pays nothing under the all-state convention (callers must + # send the fuel; the fuel row of the matrix cannot be scored without + # it). NONE means the home has no heating. Appendix D: a household whose + # heat "is obtained from solar cells, wind power, an extension cord, or + # a generator" has no energy burden. + fuel_known = ( + (heating_type != types.UNSPECIFIED) + & (heating_type != types.NONE) + & (heating_type != types.SOLAR) + ) + return has_counted_member & income_eligible & energy_burden & fuel_known diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_fpg.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_fpg.py new file mode 100644 index 00000000000..81621dfdc39 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_fpg.py @@ -0,0 +1,30 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class ia_liheap_fpg(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Iowa LIHEAP federal poverty guideline" + documentation = ( + "Annual 100 percent federal poverty guideline for the Iowa LIHEAP " + "household size, from the guideline year the program adopts for the " + "season. The FY26 Appendix B table equals 200 percent of the 2025 " + "guideline, so model period 2026 reads the guideline one year back. " + "Zero when no member counts toward household size." + ) + reference = ( + # Appendix B, Poverty Guidelines (page 89). + "https://hhs.iowa.gov/media/17262/download?inline#page=89", + "https://hhs.iowa.gov/programs/programs-and-services/liheap", + ) + defined_for = StateCode.IA + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ia.hhs.liheap.eligibility + size = spm_unit("ia_liheap_household_size", period) + state_group = spm_unit.household("state_group_str", period) + guideline = fpg(size, state_group, period, parameters, year_lag=p.fpg_year_lag) + return where(size > 0, guideline, 0) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_has_energy_burden.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_has_energy_burden.py new file mode 100644 index 00000000000..89565db6c16 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_has_energy_burden.py @@ -0,0 +1,49 @@ +from policyengine_us.model_api import * + + +class ia_liheap_has_energy_burden(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Iowa LIHEAP household has an energy burden" + documentation = ( + "Section 5.50: 'the household must have an energy burden on the date of " + "application', meaning it 'must be vulnerable to increases in energy " + "costs'. True when the household pays a vendor for its heating fuel, " + "when heat is included in unsubsidized rent, or when heat is included " + "in subsidized rent and the household has its own electric account, " + "proxied by a positive pre-subsidy electricity expense. Direct-pay " + "routing, landlord bill shares, disconnect notices and the 'not charged " + "by anyone' case of Appendix D are not identified by existing inputs." + ) + reference = ( + # Section 5.41-5.42 (pages 20-21), Section 5.50 (page 22) and Appendix + # D, Energy Burden Table (pages 122-123). + "https://hhs.iowa.gov/media/17262/download?inline#page=122", + # Section 2.3, subsidized housing and utilities-in-rent policies. + "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=8", + ) + defined_for = StateCode.IA + + def formula(spm_unit, period, parameters): + # Appendix D: "The LIHEAP customer pays a vendor directly for utilities" + # has an energy burden. + pays_for_heat = spm_unit("has_heating_expense", period) + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + # Definitions, Subsidized Housing: assistance "provided by a government + # agency or a non-profit program", including Section 8, TBRA, Section + # 202, public housing, VASH and income-restricted apartments. + subsidized = spm_unit( + "receives_housing_assistance", period + ) | spm_unit.household("is_in_public_housing", period) + # Section 5.41: "Non-subsidized households where all energy utilities + # are included in the rent are eligible for LIHEAP Regular Assistance + # because the benefit amount is determined by a matrix". "Subsidized + # households where heat is included in the rent are eligible ... + # provided they have a secondary energy burden with a utility vendor"; + # those "where both heat and electric are included in the rent are + # ineligible". Appendix D requires the household to be "billed directly + # for electric"; a positive electricity bill is the proxy. + has_electric_account = spm_unit("pre_subsidy_electricity_expense", period) > 0 + heat_in_rent_eligible = heat_in_rent & (~subsidized | has_electric_account) + return pays_for_heat | heat_in_rent_eligible diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.py new file mode 100644 index 00000000000..a9a3a49a2c3 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class ia_liheap_income_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Iowa LIHEAP income eligible" + documentation = ( + "Annual countable household income is at or below the income limit. " + "Section 5.10 ties financial eligibility to income 'being 200% or " + "below' of the federal poverty guidelines, and the payment matrix makes " + "only income 'Over 200%' ineligible, so income exactly at the limit " + "qualifies." + ) + reference = ( + # Section 5.10 (page 18) and Appendix I (page 137). + "https://hhs.iowa.gov/media/17262/download?inline#page=18", + ) + defined_for = StateCode.IA + + def formula(spm_unit, period, parameters): + income = spm_unit("ia_liheap_countable_income", period) + limit = spm_unit("ia_liheap_income_limit", period) + return income <= limit diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_limit.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_limit.py new file mode 100644 index 00000000000..a37af5c070c --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_limit.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class ia_liheap_income_limit(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Iowa LIHEAP annual income limit" + documentation = ( + "200 percent of the household's federal poverty guideline, with no " + "rounding: $31,300 for one person plus $11,000 for each additional " + "member in FY26." + ) + reference = ( + # Section 5.10 (page 18) and Appendix B (page 89). + "https://hhs.iowa.gov/media/17262/download?inline#page=89", + # Section 2.1 note: the threshold is 200% FPG; 150.00% is a form + # artifact. + "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9", + "https://hhs.iowa.gov/programs/programs-and-services/liheap", + ) + defined_for = StateCode.IA + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ia.hhs.liheap.eligibility + return p.fpg_rate * spm_unit("ia_liheap_fpg", period) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap.py new file mode 100644 index 00000000000..32e8714208c --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap.py @@ -0,0 +1,52 @@ +from policyengine_us.model_api import * + + +class ia_liheap(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Iowa LIHEAP Regular Assistance" + documentation = ( + "Annual Iowa Low Income Home Energy Assistance Program Regular " + "Assistance heating benefit: the per-point amount times total payment " + "matrix points, between the minimum and maximum benefit, or the flat " + "deliverable fuel amount for liquid propane and fuel oil households. " + "The benefit does not depend on the heating bill. Verified for FY2026; " + "earlier years use model parameter backfilling and are unverified " + "historical estimates. Crisis assistance, cooling, heating equipment " + "repair and weatherization are out of scope." + ) + defined_for = "ia_liheap_eligible" + reference = ( + # Appendix I, Award Calculation (page 137), Section 11.00 Payments + # (page 42) and Section 5.41 (page 20). + "https://hhs.iowa.gov/media/17262/download?inline#page=137", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1", + # Section 2.6, minimum $80 and maximum $800. + "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9", + # Payment Matrix attachment: flat deliverable fuel amount. + "https://hhs.iowa.gov/media/16690/download?inline#page=48", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ia.hhs.liheap.payment + points = spm_unit("ia_liheap_points", period) + heating_type = spm_unit("heating_type", period) + types = heating_type.possible_values + # Appendix I: "The minimum Regular Assistance benefit for liquid + # propane and fuel oil LIHEAP customers is $800"; Section 11.00 raises + # a lower matrix result "to meet the state-set benefit amount", and the + # FY26 draft plan calls it "a flat amount ... that serves as both a + # minimum and maximum benefit for liquid propane and fuel oil". The + # rule names only those two fuels, so kerosene households receive the + # matrix amount. + deliverable_fuel = (heating_type == types.PROPANE) | ( + heating_type == types.FUEL_OIL + ) + # Appendix I: "$40 per point"; "The minimum benefit an approved LIHEAP + # customer may receive is $80, and the maximum is $800." Section 5.41: + # the benefit "is determined by a matrix and does not depend on the + # actual amount owed by the household", so there is no bill cap. + matrix_benefit = clip(p.amount_per_point * points, p.min_benefit, p.max_benefit) + return where(deliverable_fuel, p.deliverable_fuel_amount, matrix_benefit) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_dwelling_type.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_dwelling_type.py new file mode 100644 index 00000000000..47d8fbd75c6 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_dwelling_type.py @@ -0,0 +1,36 @@ +from policyengine_us.model_api import * + + +class IALIHEAPDwellingType(Enum): + DETACHED = "Detached dwelling (single-unit structure)" + ATTACHED_2_TO_4_UNITS = "Building of two to four units" + ATTACHED_5_OR_MORE_UNITS = "Building of five or more units" + + +class ia_liheap_dwelling_type(Variable): + value_type = Enum + entity = SPMUnit + possible_values = IALIHEAPDwellingType + default_value = IALIHEAPDwellingType.DETACHED + definition_period = YEAR + label = "Iowa LIHEAP dwelling type" + documentation = ( + "Dwelling category of the Iowa LIHEAP payment matrix: a detached " + "dwelling (+1 point), a building of two to four units (no adjustment), " + "or a building of five or more units (-1 point). The manual defines a " + "detached household as one 'occupying a single-unit dwelling (i.e., not " + "more than one household residing in the complete structure)' and an " + "attached household as 'more than one household ... within a single " + "structure (i.e., multi-unit dwellings such as duplexes, apartments, " + "etc.)'. Mobile and manufactured homes occupied by one household are " + "not placed in the matrix; they meet the single-unit definition and are " + "treated as DETACHED by inference. Defaults to DETACHED." + ) + reference = ( + # Appendix I matrix (page 137); Definitions, Attached Household (page + # 79), Detached Household/Single Family Home (page 80) and + # Mobile/Manufactured Home (page 82). + "https://hhs.iowa.gov/media/17262/download?inline#page=137", + # Section 2.5, dwelling type checked as a benefit variable. + "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9", + ) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_fuel_category.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_fuel_category.py new file mode 100644 index 00000000000..922d48bf764 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_fuel_category.py @@ -0,0 +1,62 @@ +from policyengine_us.model_api import * + + +class IALIHEAPFuelCategory(Enum): + NATURAL_GAS = "Natural gas" + ELECTRICITY = "Electricity" + PROPANE = "Liquid propane" + FUEL_OIL = "Fuel oil" + SOLID_FUEL = "Solid fuel (wood, coal or corn)" + OTHER = "Other" + + +class ia_liheap_fuel_category(Variable): + value_type = Enum + entity = SPMUnit + possible_values = IALIHEAPFuelCategory + default_value = IALIHEAPFuelCategory.OTHER + definition_period = YEAR + label = "Iowa LIHEAP primary heating fuel category" + documentation = ( + "Payment matrix fuel row derived from the canonical heating_type input: " + "natural gas; electricity; liquid propane; fuel oil (including " + "kerosene); solid fuel for wood and coal; and other for every remaining " + "fuel, solar, no heating and an unspecified fuel, which carry no " + "published points." + ) + reference = ( + # Appendix I item 3, Fuel/Energy Type (page 137); Section 12.00 and + # 12.20 vendor fuel lists (pages 46-47); Definitions, Primary Heating + # Fuel Type (page 84). + "https://hhs.iowa.gov/media/17262/download?inline#page=137", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1", + ) + defined_for = StateCode.IA + + def formula(spm_unit, period, parameters): + heating_type = spm_unit("heating_type", period) + types = heating_type.possible_values + natural_gas = heating_type == types.NATURAL_GAS + electricity = heating_type == types.ELECTRICITY + propane = heating_type == types.PROPANE + # Appendix I prints points for "Fuel Oil" but none for kerosene. + # Section 12.20 groups "LP, heating oil, wood, kerosene, and corn" as + # deliverable fuels, and heating_expense already pairs kerosene with + # fuel oil, so kerosene takes the fuel oil points. This is an + # inference, not an express rule; the flat deliverable fuel benefit in + # ia_liheap still applies only to liquid propane and fuel oil. + fuel_oil = (heating_type == types.FUEL_OIL) | (heating_type == types.KEROSENE) + # Appendix I: "Solid Fuel (Wood/Coal/Corn)". The model has no corn + # heating type. + solid_fuel = (heating_type == types.WOOD) | (heating_type == types.COAL) + return select( + [natural_gas, electricity, propane, fuel_oil, solid_fuel], + [ + IALIHEAPFuelCategory.NATURAL_GAS, + IALIHEAPFuelCategory.ELECTRICITY, + IALIHEAPFuelCategory.PROPANE, + IALIHEAPFuelCategory.FUEL_OIL, + IALIHEAPFuelCategory.SOLID_FUEL, + ], + default=IALIHEAPFuelCategory.OTHER, + ) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_household_size.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_household_size.py new file mode 100644 index 00000000000..c0af11376d7 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_household_size.py @@ -0,0 +1,28 @@ +from policyengine_us.model_api import * + + +class ia_liheap_household_size(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Iowa LIHEAP household size" + documentation = ( + "Number of household members counted as application participants. " + "Section 7.00 requires each member to verify United States citizenship, " + "primarily by Social Security Number, 'to be counted towards the total " + "size of the household'; Section 7.30 excludes a member without the " + "required documents from the participant count while 'the ineligible " + "member's income must be counted'. Iowa's test is documentary (SSN, " + "I-94 or approved alternative documents); is_citizen_or_legal_immigrant " + "approximates it. The SPM unit approximates the housing unit, so " + "dual-household children, roomers, live-in caregivers, incarcerated " + "members and students away from home are not identified." + ) + reference = ( + # Section 7.00 (page 31) and Section 7.30 (page 34); Definitions, + # Household/Customer (page 82). + "https://hhs.iowa.gov/media/17262/download?inline#page=34", + ) + defined_for = StateCode.IA + + adds = ["is_citizen_or_legal_immigrant"] diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.py new file mode 100644 index 00000000000..e108038a4b0 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.py @@ -0,0 +1,49 @@ +from policyengine_us.model_api import * + + +class ia_liheap_countable_earned_income(Variable): + value_type = float + entity = Person + definition_period = YEAR + unit = USD + label = "Iowa LIHEAP countable earned income" + documentation = ( + "Gross earned income of household members aged 18 and over: wages, " + "military basic pay, self-employment and farm income. Appendix C counts " + "self-employment on gross receipts ('The LIHEAP program does NOT deduct " + "business expenses'); the model's net business and farm inputs are each " + "floored at zero so a loss does not offset other income, and they " + "understate Iowa's gross basis when expenses exist. All income of " + "members under 18 is excluded." + ) + reference = ( + # Section 6.00 (page 24), Section 6.20 (page 26), Appendix C Employment + # Income (page 97), Farm Income (page 98), Income Earned by HH Members + # under the age of 18 (page 102), Military Pay (page 108) and + # Self-Employment Income (page 113). + "https://hhs.iowa.gov/media/17262/download?inline#page=113", + # Sections 1.8-1.9, pages 5-6. + "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=5", + ) + defined_for = StateCode.IA + + def formula(person, period, parameters): + p = parameters(period).gov.states.ia.hhs.liheap.income + earned = 0 + for source in p.sources.earned: + # Appendix C, Self-Employment Income: gross receipts are counted + # and business expenses are not deducted, so a reported loss + # cannot reduce other income. + earned = earned + max_(person(source, period), 0) + age = person("age", period) + # Section 6.20 counts "the gross income of all household members aged + # 18 and over". Section 6.00 (page 24) says income of individuals under + # 18 is exempt "unless working full-time or the individual is + # emancipated and applying as head of household", but Appendix C (page + # 102) says earnings of under-18 members who "work full-time" are "NOT + # included as income" and the FFY2026 plan leaves "Earned income of a + # child under the age of 18" unchecked. The model follows Section 6.20, + # Appendix C and the plan: all under-18 income is excluded (user + # decision of 2026-10-04); the Section 6.00 wording is a recorded + # conflict. + return where(age >= p.countable_min_age, earned, 0) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.py new file mode 100644 index 00000000000..ebd39aed16d --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.py @@ -0,0 +1,38 @@ +from policyengine_us.model_api import * + + +class ia_liheap_countable_income(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Iowa LIHEAP countable income" + documentation = ( + "Annual gross countable income of all household members aged 18 and " + "over, including members excluded from household size, less child " + "support paid by a household member, floored at zero. Annual model " + "amounts stand in for the 30-day, 12-month or calendar-year " + "verification period the household elects under Section 6.10." + ) + reference = ( + # Section 6.10 (page 24), Section 6.20 (page 26), Section 7.30 (page + # 34) and Appendix C Child Support notes (pages 92-93). + "https://hhs.iowa.gov/media/17262/download?inline#page=92", + ) + defined_for = StateCode.IA + + def formula(spm_unit, period, parameters): + gross = add( + spm_unit, + period, + [ + "ia_liheap_countable_earned_income", + "ia_liheap_countable_unearned_income", + ], + ) + # Appendix C, Child Support: "For the party paying child support, a + # payment record is required as proof of payment in order for it to be + # deducted from countable income". Child support received is not + # counted and is not in the income source lists. + child_support_paid = add(spm_unit, period, ["child_support_expense"]) + return max_(gross - child_support_paid, 0) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py new file mode 100644 index 00000000000..fd12f075259 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py @@ -0,0 +1,44 @@ +from policyengine_us.model_api import * + + +class ia_liheap_countable_unearned_income(Variable): + value_type = float + entity = Person + definition_period = YEAR + unit = USD + label = "Iowa LIHEAP countable unearned income" + documentation = ( + "Unearned income of household members aged 18 and over counted under " + "Appendix C: Social Security, SSI, pensions and retirement " + "distributions, unemployment and workers' compensation, strike " + "benefits, disability and veterans' benefits, alimony, rental income, " + "interest, dividends and gambling winnings. Approximations: Social " + "Security is counted gross although Iowa counts the amount paid net of " + "Medicare premiums and SSA repayments; all interest is counted although " + "Iowa excludes interest on bank accounts; dividends are counted in full " + "although Iowa ignores a combined annual total under $120. Social " + "Security paid to members under 18 is recorded at $0.00 and is " + "excluded here with all other under-18 income." + ) + reference = ( + # Section 6.10 (page 25), Section 6.20 (page 26) and Appendix C rows + # for Alimony (page 90), Disability Insurance (page 94), Dividends + # (page 96), Gambling (page 99), Interest (page 103), IRA (page 103), + # Pensions (page 109), Rental Income (page 110), Retirement (page 111), + # Social Security Benefits (page 114), Strike Benefits (page 115), + # Unemployment (page 117), Veterans Benefits (pages 118-119) and + # Workers Compensation (page 121). + "https://hhs.iowa.gov/media/17262/download?inline#page=114", + # Section 1.9, page 6. + "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=6", + ) + defined_for = StateCode.IA + + def formula(person, period, parameters): + p = parameters(period).gov.states.ia.hhs.liheap.income + # ssi is a monthly variable; add() sums the twelve months. + unearned = add(person, period, p.sources.unearned) + age = person("age", period) + # Appendix C, Social Security Benefits: "Only count for HH members 18 + # and older"; payments to members under 18 are recorded at $0.00. + return where(age >= p.countable_min_age, unearned, 0) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py new file mode 100644 index 00000000000..fae4c3824a6 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py @@ -0,0 +1,41 @@ +from policyengine_us.model_api import * + + +class ia_liheap_fixed_income_only(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Iowa LIHEAP household has fixed income only" + documentation = ( + "Whether every dollar of the household's countable income before the " + "child support deduction comes from Social Security, SSI, veterans' " + "benefits or pensions. Definitions, Fixed Income Household: 'Only mark " + '"fixed income" when that is the ONLY source(s) of income.\' A ' + "household with no countable income is not fixed income only." + ) + reference = ( + # Definitions, Fixed Income Household (page 81); Appendix I, Fixed + # Income Only (no other sources of income) (page 137); Appendix C notes + # that alimony, variable annuities and child support are not fixed + # income (pages 90-92). + "https://hhs.iowa.gov/media/17262/download?inline#page=81", + ) + defined_for = StateCode.IA + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ia.hhs.liheap.income + gross = add( + spm_unit, + period, + [ + "ia_liheap_countable_earned_income", + "ia_liheap_countable_unearned_income", + ], + ) + person = spm_unit.members + adult = person("age", period) >= p.countable_min_age + # sources/fixed.yaml lists the fixed sources in the same relative order + # as sources/unearned.yaml, so the two sums are bit-identical when every + # other source is zero. + fixed = spm_unit.sum(where(adult, add(person, period, p.sources.fixed), 0)) + return (gross > 0) & (fixed == gross) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_fuel_points.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_fuel_points.py new file mode 100644 index 00000000000..4986551943d --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_fuel_points.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class ia_liheap_fuel_points(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Iowa LIHEAP fuel type points" + documentation = ( + "Payment matrix points for the household's primary heating fuel " + "category: natural gas and electricity 4, liquid propane and fuel oil " + "5, solid fuel 2, other fuels 0." + ) + reference = ( + # Appendix I item 3, Fuel/Energy Type (page 137). + "https://hhs.iowa.gov/media/17262/download?inline#page=137", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1", + ) + defined_for = StateCode.IA + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ia.hhs.liheap.payment + category = spm_unit("ia_liheap_fuel_category", period) + return p.fuel_points[category] diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_points.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_points.py new file mode 100644 index 00000000000..df8703f46ae --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_points.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class ia_liheap_points(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Iowa LIHEAP total payment matrix points" + documentation = ( + "Poverty level points plus targeting factor points plus fuel type " + "points. The total can be zero or negative; the minimum benefit then " + "applies." + ) + reference = ( + # Appendix I, Award Calculation (page 137) and Section 11.00 (page 42): + # "the amount of payment determined by the total number of matrix + # points". + "https://hhs.iowa.gov/media/17262/download?inline#page=137", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1", + ) + defined_for = StateCode.IA + + adds = [ + "ia_liheap_poverty_points", + "ia_liheap_targeting_points", + "ia_liheap_fuel_points", + ] diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.py new file mode 100644 index 00000000000..d87b82799fe --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.py @@ -0,0 +1,40 @@ +from policyengine_us.model_api import * + + +class ia_liheap_poverty_points(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Iowa LIHEAP poverty level points" + documentation = ( + "Payment matrix points for the ratio of annual countable income to the " + "household's federal poverty guideline: 8 points at or below 75 " + "percent, 6 through 100 percent, 5 through 125 percent and 4 through " + "200 percent. Appendix I does not say which guideline divides the " + "income; the manual's only poverty table is Appendix B, so the same " + "lagged guideline as the income limit is used (an inference)." + ) + reference = ( + # Appendix I item 1, Poverty Level (page 137) and Appendix B (page 89). + "https://hhs.iowa.gov/media/17262/download?inline#page=137", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1", + ) + defined_for = StateCode.IA + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ia.hhs.liheap.payment + # Model arrays are float32; a float32 quotient can land just above a + # float64 band top and flip an exact 75% into the 76-100% band, so the + # ratio is formed in float64 before the lookup. Households with no + # guideline (no counted member) get a zero ratio; they are ineligible. + income = spm_unit("ia_liheap_countable_income", period).astype(np.float64) + guideline = spm_unit("ia_liheap_fpg", period).astype(np.float64) + ratio = np.divide( + income, guideline, out=np.zeros_like(income), where=guideline > 0 + ) + # Appendix I prints "0 – 75%", "76 – 100%", "101 – 125%", "126 – 200%" + # with the note "Poverty levels of .01% are rounded up to the next + # percentage": a ratio of exactly 0.75 is 75% (8 points) and 0.7501 + # becomes 76% (6 points). The scale is keyed at the band tops with a + # -.inf first threshold, so right=True reproduces the rounding exactly. + return p.poverty_points.calc(ratio, right=True) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_targeting_points.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_targeting_points.py new file mode 100644 index 00000000000..70c78d75ee5 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_targeting_points.py @@ -0,0 +1,68 @@ +from policyengine_us.model_api import * + + +class ia_liheap_targeting_points(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Iowa LIHEAP targeting factor points" + documentation = ( + "Net payment matrix points from the nine targeting factors: fixed " + "income only, elderly, disabled, young child and detached dwelling add " + "points; a building of five or more units, subsidized housing, heating " + "included in rent and savings above the threshold subtract points. One " + "adjustment applies per factor per household however many members " + "qualify. Disability is self-declared in Iowa and is approximated by " + "is_disabled; savings are approximated by bank, stock and bond assets." + ) + reference = ( + # Appendix I item 2, Targeting Factors (page 137); Definitions, Disabled + # and Elderly (page 80), Fixed Income Household (page 81), Subsidized + # Housing (pages 84-85) and Young Child (page 85); Section 4.40 item 4 + # savings description (page 14). + "https://hhs.iowa.gov/media/17262/download?inline#page=137", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1", + # Section 2.4: the matrix "awards additional benefit amounts for + # households with members that are elderly, disabled, and/or have + # young children". + "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9", + ) + defined_for = StateCode.IA + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ia.hhs.liheap.payment.targeting + age = spm_unit.members("age", period) + fixed_income_only = spm_unit("ia_liheap_fixed_income_only", period) + # Definitions: Elderly is "60 years of age or older"; Young Child is + # "under six years of age"; Disabled: "applicants self-declare a + # disability". + has_elderly = spm_unit.any(age >= p.elderly_age) + has_disabled = add(spm_unit, period, ["is_disabled"]) > 0 + has_young_child = spm_unit.any(age < p.young_child_age) + dwelling = spm_unit("ia_liheap_dwelling_type", period) + dwellings = dwelling.possible_values + detached = dwelling == dwellings.DETACHED + five_plex = dwelling == dwellings.ATTACHED_5_OR_MORE_UNITS + # Definitions, Subsidized Housing: assistance "provided by a government + # agency or a non-profit program" (Section 8, TBRA, Section 202, public + # housing, VASH, income-restricted apartments). + subsidized = spm_unit( + "receives_housing_assistance", period + ) | spm_unit.household("is_in_public_housing", period) + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + # Section 4.40 item 4 describes the savings test as interest and + # dividend-bearing accounts, cash on hand, CDs and "other investments + # that can be accessed without penalty". + high_savings = spm_unit("spm_unit_cash_assets", period) > p.savings_threshold + # Appendix I lists each factor once with a single point value, so each + # factor adjusts the household total once (stacking is not addressed). + points = p.fixed_income * fixed_income_only + points = points + p.elderly * has_elderly + points = points + p.disabled * has_disabled + points = points + p.young_child * has_young_child + points = points + p.detached_dwelling * detached + points = points + p.five_plex * five_plex + points = points + p.subsidized_housing * subsidized + points = points + p.heat_in_rent * heat_in_rent + points = points + p.savings * high_savings + return points From 65ca8c1b511a8cd9d067246d9816c27d3ae26577 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sun, 4 Oct 2026 00:02:35 -0400 Subject: [PATCH 2/8] Apply Iowa LIHEAP review fixes Count railroad retirement benefits as unearned income per Appendix C, score zero poverty-level points when a household has no counted member and so no guideline, and test the fixed-income-only factor on the household's recorded income sources across all members so a minor's Social Security still identifies a fixed-income household while staying excluded from the countable amount. Reword the self-employment and Medicare comments to match the manual's two-route and net-of-premium rules. Co-Authored-By: Claude Fable 5.1 --- .../ia/hhs/liheap/income/sources/earned.yaml | 13 +++--- .../hhs/liheap/income/sources/unearned.yaml | 12 +++-- .../ia_liheap_income_eligible.yaml | 23 ++++++++++ .../ia_liheap_countable_unearned_income.yaml | 21 +++++++++ .../income/ia_liheap_fixed_income_only.yaml | 37 +++++++++++++-- .../payment/ia_liheap_poverty_points.yaml | 4 +- .../ia_liheap_countable_earned_income.py | 20 +++++---- .../ia_liheap_countable_unearned_income.py | 20 +++++---- .../income/ia_liheap_fixed_income_only.py | 45 ++++++++++--------- .../payment/ia_liheap_poverty_points.py | 13 +++--- 10 files changed, 153 insertions(+), 55 deletions(-) diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/earned.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/earned.yaml index 6ee27dad892..7ad2e57d5e7 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/earned.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/earned.yaml @@ -2,11 +2,14 @@ description: Iowa counts these earned income sources toward gross household inco # Appendix C counts wages, salaries, tips, commissions, bonuses, severance, PTO # and per diem (Employment Income), military pay excluding the housing # allowance, self-employment and contract income, and farm income. Appendix C -# Self-Employment Income: "The LIHEAP program does NOT deduct business -# expenses", so Iowa counts gross receipts. The model's business inputs are -# net amounts; each is floored at zero in the countable earned income formula -# so that a loss does not offset other income. Using net income understates -# Iowa's gross-receipts basis when expenses exist (documented limitation). +# Self-Employment Income has two routes: "Income from a business, less +# business expenses. Filed Federal tax return required if deducting business +# expense, otherwise gross income is used. The LIHEAP program does NOT deduct +# business expenses." Filers are assessed on the return's net business income +# and non-filers on gross income. The model's net business inputs follow the +# filer route; the approximations are the per-source zero floor applied in +# the countable earned income formula (a loss does not offset other income) +# and the absence of the non-filer gross route. values: 2025-10-01: - employment_income diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml index fb18e64b037..7031c6669f0 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml @@ -11,8 +11,9 @@ values: # Appendix C Social Security Benefits (SSA, SSI, SSDI): "Count the net # amount as income (gross minus benefits repaid to SSA)" and the amount # "paid by check or direct deposit"; the plan checks "Excluding MediCare - # deduction". The model counts social_security gross because Medicare - # premiums and SSA repayments are not separable (documented limitation). + # deduction". The model counts social_security gross: netting the Medicare + # Part B premium (the model has medicare_part_b_premium) is deferred under + # coordinator ruling D6 for minimal code, and SSA repayments have no input. - social_security # ssi is a monthly variable; add() sums the twelve months. - ssi @@ -23,6 +24,11 @@ values: # (through taxable_retirement_distributions), so IRA distributions are not # listed separately; listing both would count them twice. - retirement_distributions + # Appendix C Railroad Retirement: counted; "Tax Statement Form RRB-1099 + # ... Count box #5 as income" and "RRB-1099-R ... Count box #7 as income". + # Not a fixed income source: the Definitions list names only SSA, SSI, + # SSDI, veterans' benefits and pensions. + - railroad_benefits # Appendix C Unemployment: counted; the limited-duration annualization # rule of the Definitions (Annualized Income, page 79) is not modeled. - unemployment_compensation @@ -54,7 +60,7 @@ metadata: period: year label: Iowa LIHEAP countable unearned income sources reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix C Income Guidelines (Alimony, Annuities, Disability Insurance, Dividends, Gambling, Interest, IRA, Pensions, Rental Income, Retirement, Social Security Benefits, Strike Benefits, Unemployment, Veterans Benefits and Workers Compensation rows), pages 90-121, and Section 6.10, page 25 + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix C Income Guidelines (Alimony, Annuities, Disability Insurance, Dividends, Gambling, Interest, IRA, Pensions, Railroad Retirement, Rental Income, Retirement, Social Security Benefits, Strike Benefits, Unemployment, Veterans Benefits and Workers Compensation rows), pages 90-121 (Railroad Retirement page 110, Social Security Benefits page 114), and Section 6.10, page 25 href: https://hhs.iowa.gov/media/17262/download?inline#page=114 - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 1.9 (countable income sources), page 6 href: https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=6 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.yaml index 64f3dfa45c8..a0c2ec06e66 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.yaml @@ -120,3 +120,26 @@ output: # 0 <= 31,300. ia_liheap_income_eligible: true + +- name: Case 6, railroad retiree with 35,000 alone is over the one-person limit. + period: 2026 + input: + people: + person1: + age: 66 + railroad_benefits: 35_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + households: + household: + members: [person1] + state_code: IA + output: + # Appendix C p. 110 counts railroad retirement: 35,000 > 2.0 x 15,650 = + # 31,300, so the household is not income eligible and receives nothing. + ia_liheap_countable_income: 35_000 + ia_liheap_income_eligible: false + ia_liheap_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml index a6e110d7284..6ff4db031e8 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml @@ -191,3 +191,24 @@ # taxable_ira_distributions through taxable_retirement_distributions, so # reading both sources would count the 3,000 twice (22,000). ia_liheap_countable_unearned_income: 19_000 + +- name: Case 9, railroad retirement is counted. + period: 2026 + input: + people: + person1: + age: 68 + railroad_benefits: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + households: + household: + members: [person1] + state_code: IA + output: + # Appendix C p. 110, Railroad Retirement: RRB-1099 box 5 and RRB-1099-R + # box 7 are counted as income; 24,000. + ia_liheap_countable_unearned_income: 24_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml index 76555478ac4..e28da65613f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml @@ -155,7 +155,7 @@ # The test compares gross sources before the deduction: 18,000 = 18,000. ia_liheap_fixed_income_only: true -- name: Case 8, a child's excluded wages do not break fixed income only. +- name: Case 8, a child's wages are another source of income even though they are not countable. period: 2026 input: people: @@ -175,8 +175,12 @@ members: [person1, person2] state_code: IA output: - # The 16-year-old's wages are not countable, so gross 18,000 = fixed 18,000. - ia_liheap_fixed_income_only: true + # The source test reads every member's recorded sources regardless of age + # (Definitions p. 81, "no other sources of income"): fixed 18,000 but all + # sources 18,000 + 4,000 = 22,000. The 16-year-old's wages stay out of the + # countable amount (18,000). + ia_liheap_countable_income: 18_000 + ia_liheap_fixed_income_only: false - name: Case 9, interest income is not fixed income. period: 2026 @@ -221,3 +225,30 @@ output: # gross 18,000 + 9,000 = 27,000 = fixed 27,000. ia_liheap_fixed_income_only: true + +- name: Case 11, a child's Social Security survivor benefits as the only household source is fixed income only. + period: 2026 + input: + people: + person1: + age: 35 + person2: + age: 10 + social_security: 9_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_000 + households: + household: + members: [person1, person2] + state_code: IA + output: + # Appendix C p. 114 margin note: when the only source of household income + # is Social Security for members under 18, record the source for the fixed + # income criteria but the amount as 0.00 (also section 4.40(3)(a) p. 14). + # All-member fixed sources 9,000 = all-member sources 9,000 > 0 -> true; + # the countable amount excludes the child's 9,000 -> 0. + ia_liheap_countable_income: 0 + ia_liheap_fixed_income_only: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.yaml index 89fdeb3cdf6..67cf0903202 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.yaml @@ -228,7 +228,9 @@ members: [person1, person2] state_code: IA output: - # Guideline 0: the division is guarded and scores 0. + # Guideline 0: the formula returns 0 without a band lookup (a zero ratio + # would otherwise score 8, as in Case 1); the household is ineligible at + # size 0 in any event. ia_liheap_fpg: 0 ia_liheap_poverty_points: 0 diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.py index e108038a4b0..4672a13a664 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.py @@ -9,12 +9,13 @@ class ia_liheap_countable_earned_income(Variable): label = "Iowa LIHEAP countable earned income" documentation = ( "Gross earned income of household members aged 18 and over: wages, " - "military basic pay, self-employment and farm income. Appendix C counts " - "self-employment on gross receipts ('The LIHEAP program does NOT deduct " - "business expenses'); the model's net business and farm inputs are each " - "floored at zero so a loss does not offset other income, and they " - "understate Iowa's gross basis when expenses exist. All income of " - "members under 18 is excluded." + "military basic pay, self-employment and farm income. Appendix C " + "assesses self-employment on the federal return's net business income " + "for filers and on gross income for non-filers ('The LIHEAP program " + "does NOT deduct business expenses'); the model's net business and farm " + "inputs follow the filer route and are each floored at zero so a loss " + "does not offset other income. The non-filer gross route is not " + "represented. All income of members under 18 is excluded." ) reference = ( # Section 6.00 (page 24), Section 6.20 (page 26), Appendix C Employment @@ -31,9 +32,10 @@ def formula(person, period, parameters): p = parameters(period).gov.states.ia.hhs.liheap.income earned = 0 for source in p.sources.earned: - # Appendix C, Self-Employment Income: gross receipts are counted - # and business expenses are not deducted, so a reported loss - # cannot reduce other income. + # Appendix C, Self-Employment Income: filers are assessed on the + # return's net business income and "The LIHEAP program does NOT + # deduct business expenses" further, so a reported loss is floored + # per source and cannot reduce other income. earned = earned + max_(person(source, period), 0) age = person("age", period) # Section 6.20 counts "the gross income of all household members aged diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py index fd12f075259..f7a365752be 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py @@ -10,11 +10,13 @@ class ia_liheap_countable_unearned_income(Variable): documentation = ( "Unearned income of household members aged 18 and over counted under " "Appendix C: Social Security, SSI, pensions and retirement " - "distributions, unemployment and workers' compensation, strike " - "benefits, disability and veterans' benefits, alimony, rental income, " - "interest, dividends and gambling winnings. Approximations: Social " - "Security is counted gross although Iowa counts the amount paid net of " - "Medicare premiums and SSA repayments; all interest is counted although " + "distributions, railroad retirement, unemployment and workers' " + "compensation, strike benefits, disability and veterans' benefits, " + "alimony, rental income, interest, dividends and gambling winnings. " + "Approximations: Social Security is counted gross; Iowa counts the " + "amount paid net of Medicare premiums and SSA repayments, and netting " + "the Medicare Part B premium is deferred under coordinator ruling D6 " + "while SSA repayments have no input. All interest is counted although " "Iowa excludes interest on bank accounts; dividends are counted in full " "although Iowa ignores a combined annual total under $120. Social " "Security paid to members under 18 is recorded at $0.00 and is " @@ -24,10 +26,10 @@ class ia_liheap_countable_unearned_income(Variable): # Section 6.10 (page 25), Section 6.20 (page 26) and Appendix C rows # for Alimony (page 90), Disability Insurance (page 94), Dividends # (page 96), Gambling (page 99), Interest (page 103), IRA (page 103), - # Pensions (page 109), Rental Income (page 110), Retirement (page 111), - # Social Security Benefits (page 114), Strike Benefits (page 115), - # Unemployment (page 117), Veterans Benefits (pages 118-119) and - # Workers Compensation (page 121). + # Pensions (page 109), Railroad Retirement (page 110), Rental Income + # (page 110), Retirement (page 111), Social Security Benefits (page + # 114), Strike Benefits (page 115), Unemployment (page 117), Veterans + # Benefits (pages 118-119) and Workers Compensation (page 121). "https://hhs.iowa.gov/media/17262/download?inline#page=114", # Section 1.9, page 6. "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=6", diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py index fae4c3824a6..92190af0081 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py @@ -7,35 +7,40 @@ class ia_liheap_fixed_income_only(Variable): definition_period = YEAR label = "Iowa LIHEAP household has fixed income only" documentation = ( - "Whether every dollar of the household's countable income before the " - "child support deduction comes from Social Security, SSI, veterans' " - "benefits or pensions. Definitions, Fixed Income Household: 'Only mark " - '"fixed income" when that is the ONLY source(s) of income.\' A ' - "household with no countable income is not fixed income only." + "Whether every recorded income source of the household, across all " + "members regardless of age, is Social Security, SSI, veterans' benefits " + "or pensions. Definitions, Fixed Income Household: 'Only mark \"fixed " + "income\" when that is the ONLY source(s) of income.' Under-18 income " + "is excluded from the countable amount, but a minor's Social Security " + "still identifies the household's income source: Appendix C records " + "such payments at $0.00 'to capture source of income for AE fixed " + "income criteria', and Section 4.40(3)(a) says they 'can still qualify " + "the household' when that is the only source of income. A household " + "with no recorded income source is not fixed income only." ) reference = ( - # Definitions, Fixed Income Household (page 81); Appendix I, Fixed - # Income Only (no other sources of income) (page 137); Appendix C notes - # that alimony, variable annuities and child support are not fixed - # income (pages 90-92). + # Definitions, Fixed Income Household (page 81); Section 4.40 item 3a + # (page 14); Appendix C Social Security Benefits margin note (page 114); + # Appendix I, Fixed Income Only (no other sources of income) (page 137); + # Appendix C notes that alimony, variable annuities and child support + # are not fixed income (pages 90-92). "https://hhs.iowa.gov/media/17262/download?inline#page=81", ) defined_for = StateCode.IA def formula(spm_unit, period, parameters): p = parameters(period).gov.states.ia.hhs.liheap.income - gross = add( - spm_unit, - period, - [ - "ia_liheap_countable_earned_income", - "ia_liheap_countable_unearned_income", - ], - ) person = spm_unit.members - adult = person("age", period) >= p.countable_min_age + # Every recorded source of every member counts toward the test, with + # no age gate; earned sources are floored per source as in the + # countable amount. + earned = 0 + for source in p.sources.earned: + earned = earned + max_(person(source, period), 0) + unearned = add(person, period, p.sources.unearned) + recorded = spm_unit.sum(earned + unearned) # sources/fixed.yaml lists the fixed sources in the same relative order # as sources/unearned.yaml, so the two sums are bit-identical when every # other source is zero. - fixed = spm_unit.sum(where(adult, add(person, period, p.sources.fixed), 0)) - return (gross > 0) & (fixed == gross) + fixed = spm_unit.sum(add(person, period, p.sources.fixed)) + return (fixed > 0) & (fixed == recorded) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.py index d87b82799fe..ef44d257147 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.py @@ -12,7 +12,8 @@ class ia_liheap_poverty_points(Variable): "percent, 6 through 100 percent, 5 through 125 percent and 4 through " "200 percent. Appendix I does not say which guideline divides the " "income; the manual's only poverty table is Appendix B, so the same " - "lagged guideline as the income limit is used (an inference)." + "lagged guideline as the income limit is used (an inference). A " + "household with no counted member has no guideline and scores zero." ) reference = ( # Appendix I item 1, Poverty Level (page 137) and Appendix B (page 89). @@ -25,16 +26,18 @@ def formula(spm_unit, period, parameters): p = parameters(period).gov.states.ia.hhs.liheap.payment # Model arrays are float32; a float32 quotient can land just above a # float64 band top and flip an exact 75% into the 76-100% band, so the - # ratio is formed in float64 before the lookup. Households with no - # guideline (no counted member) get a zero ratio; they are ineligible. + # ratio is formed in float64 before the lookup. income = spm_unit("ia_liheap_countable_income", period).astype(np.float64) guideline = spm_unit("ia_liheap_fpg", period).astype(np.float64) + has_guideline = guideline > 0 ratio = np.divide( - income, guideline, out=np.zeros_like(income), where=guideline > 0 + income, guideline, out=np.zeros_like(income), where=has_guideline ) # Appendix I prints "0 – 75%", "76 – 100%", "101 – 125%", "126 – 200%" # with the note "Poverty levels of .01% are rounded up to the next # percentage": a ratio of exactly 0.75 is 75% (8 points) and 0.7501 # becomes 76% (6 points). The scale is keyed at the band tops with a # -.inf first threshold, so right=True reproduces the rounding exactly. - return p.poverty_points.calc(ratio, right=True) + # A household with no guideline (no counted member) has no poverty + # level and scores zero points; it is ineligible in any case. + return where(has_guideline, p.poverty_points.calc(ratio, right=True), 0) From d2eb2382aabeec98ff2ddbf796b01d34dabac2b5 Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 19 Oct 2026 17:10:25 -0400 Subject: [PATCH 3/8] Keep the Iowa railroad retirement test households out of SSI The SSI unearned income list does not include railroad_benefits, so a railroad retiree aged 65 or older with no other income receives the full SSI federal benefit, which Iowa then counts. Age 62 keeps the two cases on the railroad retirement rule they test. Co-Authored-By: Claude Fable 5.1 --- .../ia/hhs/liheap/eligibility/ia_liheap_income_eligible.yaml | 3 ++- .../hhs/liheap/income/ia_liheap_countable_unearned_income.yaml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.yaml index a0c2ec06e66..0b2f530cb0b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.yaml @@ -126,7 +126,8 @@ input: people: person1: - age: 66 + # Age 62 is below the SSI aged threshold, so the model imputes no SSI. + age: 62 railroad_benefits: 35_000 spm_units: spm_unit: diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml index 6ff4db031e8..39c990c8d1a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml @@ -197,7 +197,8 @@ input: people: person1: - age: 68 + # Age 62 is below the SSI aged threshold, so the model imputes no SSI. + age: 62 railroad_benefits: 24_000 spm_units: spm_unit: From a5d1fea397759f3125d72c47dc3bc44825b0b139 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 20 Oct 2026 01:09:41 -0400 Subject: [PATCH 4/8] Fix Iowa LIHEAP review findings - Fixed income only: recorded non-countable sources that Appendix C says are not fixed income (child support received, FIP) now cancel the point, through a new other_recorded source list; railroad retirement counts as a pension; the test no longer depends on float summation order. - Kerosene follows fuel oil for both the fuel points and the flat $800. - References follow the repo convention for PDF pages. - Replace internal workflow wording with the policy reasons. - Tests: new fixed-income, child support and kerosene cases (153 total). Co-Authored-By: Claude Opus 5.5 --- .../ia/hhs/liheap/eligibility/fpg_rate.yaml | 4 +- .../hhs/liheap/eligibility/fpg_year_lag.yaml | 2 +- .../hhs/liheap/income/countable_min_age.yaml | 6 +- .../ia/hhs/liheap/income/sources/earned.yaml | 6 +- .../ia/hhs/liheap/income/sources/fixed.yaml | 10 +- .../liheap/income/sources/other_recorded.yaml | 22 +++ .../hhs/liheap/income/sources/unearned.yaml | 17 +- .../hhs/liheap/payment/amount_per_point.yaml | 4 +- .../payment/deliverable_fuel_amount.yaml | 9 +- .../ia/hhs/liheap/payment/fuel_points.yaml | 4 +- .../ia/hhs/liheap/payment/max_benefit.yaml | 4 +- .../ia/hhs/liheap/payment/min_benefit.yaml | 4 +- .../ia/hhs/liheap/payment/poverty_points.yaml | 4 +- .../payment/targeting/detached_dwelling.yaml | 6 +- .../liheap/payment/targeting/disabled.yaml | 6 +- .../hhs/liheap/payment/targeting/elderly.yaml | 4 +- .../liheap/payment/targeting/elderly_age.yaml | 2 +- .../liheap/payment/targeting/five_plex.yaml | 4 +- .../payment/targeting/fixed_income.yaml | 6 +- .../payment/targeting/heat_in_rent.yaml | 6 +- .../hhs/liheap/payment/targeting/savings.yaml | 4 +- .../payment/targeting/savings_threshold.yaml | 6 +- .../payment/targeting/subsidized_housing.yaml | 6 +- .../liheap/payment/targeting/young_child.yaml | 4 +- .../payment/targeting/young_child_age.yaml | 2 +- .../gov/states/ia/hhs/liheap/ia_liheap.yaml | 61 +++++-- .../ia_liheap_countable_earned_income.yaml | 5 +- .../income/ia_liheap_countable_income.yaml | 5 +- .../income/ia_liheap_fixed_income_only.yaml | 159 ++++++++++++++++-- .../gov/states/ia/hhs/liheap/integration.yaml | 14 +- .../liheap/eligibility/ia_liheap_eligible.py | 11 +- .../hhs/liheap/eligibility/ia_liheap_fpg.py | 2 +- .../ia_liheap_has_energy_burden.py | 6 +- .../eligibility/ia_liheap_income_eligible.py | 3 +- .../eligibility/ia_liheap_income_limit.py | 5 +- .../gov/states/ia/hhs/liheap/ia_liheap.py | 29 ++-- .../ia/hhs/liheap/ia_liheap_dwelling_type.py | 8 +- .../ia/hhs/liheap/ia_liheap_fuel_category.py | 14 +- .../ia/hhs/liheap/ia_liheap_household_size.py | 6 +- .../ia_liheap_countable_earned_income.py | 18 +- .../income/ia_liheap_countable_income.py | 6 +- .../ia_liheap_countable_unearned_income.py | 23 ++- .../income/ia_liheap_fixed_income_only.py | 60 ++++--- .../liheap/payment/ia_liheap_fuel_points.py | 2 +- .../ia/hhs/liheap/payment/ia_liheap_points.py | 8 +- .../payment/ia_liheap_poverty_points.py | 5 +- .../payment/ia_liheap_targeting_points.py | 10 +- 47 files changed, 415 insertions(+), 197 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/other_recorded.yaml diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_rate.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_rate.yaml index dba34e3e8b1..04a55b03bce 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_rate.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_rate.yaml @@ -7,9 +7,9 @@ metadata: period: year label: Iowa LIHEAP federal poverty guideline rate reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Section 5.10 Eligibility Criteria (income 200% or below of the federal poverty guidelines), Appendix B Poverty Guidelines and Appendix I Payment Matrix (over 200% ineligible), pages 18, 89 and 137 + - title: "Iowa LIHEAP FY26 Policy and Procedures Manual, Section 5.10 Eligibility Criteria (income 200% or below of the federal poverty guidelines), Appendix B Poverty Guidelines and Appendix I Payment Matrix (over 200% ineligible) #page 18,89,137" href: https://hhs.iowa.gov/media/17262/download?inline#page=18 - - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 2.1 note (threshold is 200% FPG; the printed 150.00% is an OLDC form limitation), page 9 + - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 2.1 note (threshold is 200% FPG; the printed 150.00% is an OLDC form limitation) href: https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9 - title: Iowa HHS Low-Income Home Energy Assistance program page (at or below 200% of the 2025 federal poverty guidelines) href: https://hhs.iowa.gov/programs/programs-and-services/liheap diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_year_lag.yaml index cebd5b86987..b2c563e08d7 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_year_lag.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/eligibility/fpg_year_lag.yaml @@ -12,7 +12,7 @@ metadata: period: year label: Iowa LIHEAP federal poverty guideline year lag reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix B Poverty Guidelines (FY26 October 1, 2025, to September 30, 2026 annual gross income table), page 89 + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix B Poverty Guidelines (FY26 October 1, 2025, to September 30, 2026 annual gross income table) href: https://hhs.iowa.gov/media/17262/download?inline#page=89 - title: Iowa HHS Low-Income Home Energy Assistance program page (200% of the 2025 federal poverty guidelines for October 1, 2025 to September 30, 2026) href: https://hhs.iowa.gov/programs/programs-and-services/liheap diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/countable_min_age.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/countable_min_age.yaml index 597996ee74d..c22f5b68b8a 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/countable_min_age.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/countable_min_age.yaml @@ -14,7 +14,7 @@ metadata: period: year label: Iowa LIHEAP minimum age for countable income reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Section 6.20 Calculating Income (members aged 18 and over), Section 6.00 Income, Appendix C Income Earned by HH Members under the age of 18 and Social Security Benefits (only count for members 18 and older), pages 26, 24, 102 and 114 - href: https://hhs.iowa.gov/media/17262/download?inline#page=26 - - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 1.9 (earned income of a child under the age of 18 not counted), page 6 + - title: "Iowa LIHEAP FY26 Policy and Procedures Manual, Section 6.00 Income, Section 6.20 Calculating Income (members aged 18 and over), Appendix C Income Earned by HH Members under the age of 18 and Social Security Benefits (only count for members 18 and older) #page 24,26,102,114" + href: https://hhs.iowa.gov/media/17262/download?inline#page=24 + - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 1.9 (earned income of a child under the age of 18 not counted) href: https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/earned.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/earned.yaml index 7ad2e57d5e7..e2ef1b39169 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/earned.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/earned.yaml @@ -28,7 +28,7 @@ metadata: period: year label: Iowa LIHEAP countable earned income sources reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix C Income Guidelines, Employment Income, Farm Income, Military Pay and Self-Employment Income rows, pages 97, 98, 108 and 113 - href: https://hhs.iowa.gov/media/17262/download?inline#page=113 - - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Sections 1.8-1.9 (gross income; wages, self-employment income, contract income and commissions counted), pages 5-6 + - title: "Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix C Income Source Guide, Employment Income, Farm Income, Military Pay and Self-Employment Income rows #page 97-98,108,113" + href: https://hhs.iowa.gov/media/17262/download?inline#page=97 + - title: "Iowa LIHEAP FFY2026 Detailed Model Plan, Sections 1.8-1.9 (gross income; wages, self-employment income, contract income and commissions counted) #page 5-6" href: https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/fixed.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/fixed.yaml index 567eb5885e1..035cb353dfd 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/fixed.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/fixed.yaml @@ -6,14 +6,16 @@ description: Iowa treats these income sources as fixed income for the fixed-inco # Pension, VA Non-Service-Connected Disability Pension, and pensions. Only mark # 'fixed income' when that is the ONLY source(s) of income." Appendix C notes # that child support, alimony and variable annuities are not fixed income. -# Keep this list in the same relative order as sources/unearned.yaml: the -# fixed-income-only test compares two sums, and the same order keeps them -# bit-identical when every other source is zero. values: 2025-10-01: - social_security - ssi - pension_income + # Appendix C defines Pensions as assistance "paid at regular intervals ... + # in consideration of past services, age" and labels Form RRB-1099-R + # "Annuities or Pensions by the Railroad Retirement Board", so railroad + # retirement annuities are pensions in the Definitions' fixed income list. + - railroad_benefits - veterans_benefits metadata: @@ -21,5 +23,5 @@ metadata: period: year label: Iowa LIHEAP fixed income sources reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Definitions, Fixed Income Household, page 81, and Appendix I Payment Matrix, Fixed Income Only targeting factor, page 137 + - title: "Iowa LIHEAP FY26 Policy and Procedures Manual, Definitions, Fixed Income Household, Appendix C Pensions and Railroad Retirement rows, and Appendix I Payment Matrix, Fixed Income Only targeting factor #page 81,109-110,137" href: https://hhs.iowa.gov/media/17262/download?inline#page=81 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/other_recorded.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/other_recorded.yaml new file mode 100644 index 00000000000..74107b89833 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/other_recorded.yaml @@ -0,0 +1,22 @@ +description: Iowa excludes households with these uncounted income sources from the fixed income factor under the Low Income Home Energy Assistance Program. +# Definitions, Fixed Income Household: "Only mark 'fixed income' when that is +# the ONLY source(s) of income." Appendix C leaves these rows out of countable +# income and adds that they are not fixed: Child Support, "Child support is +# not considered a fixed income source", and FIP, "FIP is not considered fixed +# income because the household composition is likely to change". Recorded FIP +# is read from tanf, the take-up-adjusted cash assistance amount (equal to +# ia_fip for a household that takes up FIP), as in the Oregon, Kentucky and +# New Jersey LIHEAP income formulas. The AFLAC row (page 90) carries the same +# note but has no model input. +values: + 2025-10-01: + - child_support_received + - tanf + +metadata: + unit: list + period: year + label: Iowa LIHEAP recorded income sources that are neither countable nor fixed + reference: + - title: "Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix C Income Source Guide, Child Support and FIP (Family Investment Program) rows #page 92,98" + href: https://hhs.iowa.gov/media/17262/download?inline#page=92 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml index 7031c6669f0..05e29a2c51c 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml @@ -11,9 +11,10 @@ values: # Appendix C Social Security Benefits (SSA, SSI, SSDI): "Count the net # amount as income (gross minus benefits repaid to SSA)" and the amount # "paid by check or direct deposit"; the plan checks "Excluding MediCare - # deduction". The model counts social_security gross: netting the Medicare - # Part B premium (the model has medicare_part_b_premium) is deferred under - # coordinator ruling D6 for minimal code, and SSA repayments have no input. + # deduction". The model counts social_security gross as a documented + # approximation: netting the Medicare Part B premium (the model has + # medicare_part_b_premium) is deferred, as in Kansas, and SSA repayments + # have no input. - social_security # ssi is a monthly variable; add() sums the twelve months. - ssi @@ -26,8 +27,8 @@ values: - retirement_distributions # Appendix C Railroad Retirement: counted; "Tax Statement Form RRB-1099 # ... Count box #5 as income" and "RRB-1099-R ... Count box #7 as income". - # Not a fixed income source: the Definitions list names only SSA, SSI, - # SSDI, veterans' benefits and pensions. + # Railroad retirement annuities are pensions for the fixed income factor + # (see sources/fixed.yaml). - railroad_benefits # Appendix C Unemployment: counted; the limited-duration annualization # rule of the Definitions (Annualized Income, page 79) is not modeled. @@ -60,7 +61,7 @@ metadata: period: year label: Iowa LIHEAP countable unearned income sources reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix C Income Guidelines (Alimony, Annuities, Disability Insurance, Dividends, Gambling, Interest, IRA, Pensions, Railroad Retirement, Rental Income, Retirement, Social Security Benefits, Strike Benefits, Unemployment, Veterans Benefits and Workers Compensation rows), pages 90-121 (Railroad Retirement page 110, Social Security Benefits page 114), and Section 6.10, page 25 - href: https://hhs.iowa.gov/media/17262/download?inline#page=114 - - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 1.9 (countable income sources), page 6 + - title: "Iowa LIHEAP FY26 Policy and Procedures Manual, Section 6.10 Eligibility Period to Qualify for Assistance and Appendix C Income Source Guide (Alimony, Annuities, Disability Insurance, Dividends, Gambling, Interest, IRA, Pensions, Railroad Retirement, Rental Income, Retirement, Social Security Benefits, Strike Benefits, Unemployment, Veterans Benefits and Workers Compensation rows) #page 25,90-91,94-96,99,103,109-111,114-115,117-119,121" + href: https://hhs.iowa.gov/media/17262/download?inline#page=25 + - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 1.9 (countable income sources) href: https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/amount_per_point.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/amount_per_point.yaml index f2d9940b65d..071b27e3af2 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/amount_per_point.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/amount_per_point.yaml @@ -7,7 +7,7 @@ metadata: period: year label: Iowa LIHEAP benefit per matrix point reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums, Award Calculation ($40 per point), page 137 + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums, Award Calculation ($40 per point) href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/deliverable_fuel_amount.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/deliverable_fuel_amount.yaml index 5202ed76bf8..a66861b24d3 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/deliverable_fuel_amount.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/deliverable_fuel_amount.yaml @@ -7,7 +7,8 @@ description: Iowa pays this flat Regular Assistance benefit to households heatin # for deliverable fuel customers that serves as both a minimum and maximum # benefit for liquid propane and fuel oil LIHEAP customers. For FY26 that flat # amount is $800." The three statements agree numerically for FY26 because the -# general maximum is also $800. +# general maximum is also $800. Kerosene follows fuel oil through +# ia_liheap_fuel_category (an inference; the manual has no kerosene row). values: 2025-10-01: 800 @@ -16,7 +17,7 @@ metadata: period: year label: Iowa LIHEAP deliverable fuel benefit reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums (minimum benefit for liquid propane and fuel oil customers $800), page 137, and Section 11.00 Payments (deliverable fuel benefit raised to the state-set amount), page 42 - href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa LIHEAP FFY2026 Public Comment Draft State Plan, Payment Matrix attachment (flat $800 for deliverable fuel customers), page 48 + - title: "Iowa LIHEAP FY26 Policy and Procedures Manual, Section 11.00 Payments (deliverable fuel benefit raised to the state-set amount) and Appendix I Benefit Maximums (minimum benefit for liquid propane and fuel oil customers $800) #page 42,137" + href: https://hhs.iowa.gov/media/17262/download?inline#page=42 + - title: Iowa LIHEAP FFY2026 Public Comment Draft State Plan, Payment Matrix attachment (flat $800 for deliverable fuel customers) href: https://hhs.iowa.gov/media/16690/download?inline#page=48 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/fuel_points.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/fuel_points.yaml index 48151eeb8ea..b615edba18a 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/fuel_points.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/fuel_points.yaml @@ -12,9 +12,9 @@ metadata: breakdown: - ia_liheap_fuel_category reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums, Regular Assistance Payment Matrix, item 3 Fuel/Energy Type, page 137 + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums, Regular Assistance Payment Matrix, item 3 Fuel/Energy Type href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 NATURAL_GAS: diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/max_benefit.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/max_benefit.yaml index dd018778081..137e81e17a6 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/max_benefit.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/max_benefit.yaml @@ -10,7 +10,7 @@ metadata: period: year label: Iowa LIHEAP maximum benefit reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums, Award Calculation (maximum benefit $800), page 137 + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums, Award Calculation (maximum benefit $800) href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 2.6 (maximum benefit $800), page 9 + - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 2.6 (maximum benefit $800) href: https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/min_benefit.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/min_benefit.yaml index 18432fc5629..f32dc59ddb5 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/min_benefit.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/min_benefit.yaml @@ -7,7 +7,7 @@ metadata: period: year label: Iowa LIHEAP minimum benefit reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums, Award Calculation (minimum benefit $80), page 137 + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums, Award Calculation (minimum benefit $80) href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 2.6 (minimum benefit $80), page 9 + - title: Iowa LIHEAP FFY2026 Detailed Model Plan, Section 2.6 (minimum benefit $80) href: https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/poverty_points.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/poverty_points.yaml index 59b33a19a92..5c2fbc5dd9f 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/poverty_points.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/poverty_points.yaml @@ -36,7 +36,7 @@ metadata: period: year label: Iowa LIHEAP poverty level points reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums, Regular Assistance Payment Matrix, item 1 Poverty Level, page 137 + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Benefit Maximums, Regular Assistance Payment Matrix, item 1 Poverty Level href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/detached_dwelling.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/detached_dwelling.yaml index 07f39203254..1545b884f2b 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/detached_dwelling.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/detached_dwelling.yaml @@ -7,7 +7,7 @@ metadata: period: year label: Iowa LIHEAP detached dwelling points reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Detached Dwelling, page 137, and Definitions, Detached Household/Single Family Home (a single-unit dwelling), page 80 - href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + - title: "Iowa LIHEAP FY26 Policy and Procedures Manual, Definitions, Detached Household/Single Family Home (a single-unit dwelling), and Appendix I Payment Matrix, item 2 Targeting Factors, Detached Dwelling #page 80,137" + href: https://hhs.iowa.gov/media/17262/download?inline#page=80 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/disabled.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/disabled.yaml index 65306c8bcec..ca0bc8df5fd 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/disabled.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/disabled.yaml @@ -7,7 +7,7 @@ metadata: period: year label: Iowa LIHEAP disabled household points reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Disabled, page 137, and Definitions, Disabled (applicants self-declare a disability), page 80 - href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + - title: "Iowa LIHEAP FY26 Policy and Procedures Manual, Definitions, Disabled (applicants self-declare a disability), and Appendix I Payment Matrix, item 2 Targeting Factors, Disabled #page 80,137" + href: https://hhs.iowa.gov/media/17262/download?inline#page=80 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly.yaml index 2328571dcb1..89a5b4e79a4 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly.yaml @@ -7,7 +7,7 @@ metadata: period: year label: Iowa LIHEAP elderly household points reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Elderly (60 years of age and older), page 137 + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Elderly (60 years of age and older) href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly_age.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly_age.yaml index 4eacddb3505..4b569454a05 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly_age.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/elderly_age.yaml @@ -7,5 +7,5 @@ metadata: period: year label: Iowa LIHEAP elderly age reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Definitions, Elderly (60 years of age or older as of the application date), page 80, and Appendix I Payment Matrix, Elderly (60 years of age and older), page 137 + - title: "Iowa LIHEAP FY26 Policy and Procedures Manual, Definitions, Elderly (60 years of age or older as of the application date), and Appendix I Payment Matrix, Elderly (60 years of age and older) #page 80,137" href: https://hhs.iowa.gov/media/17262/download?inline#page=80 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/five_plex.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/five_plex.yaml index 8810d495a04..c1d7d95b655 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/five_plex.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/five_plex.yaml @@ -7,7 +7,7 @@ metadata: period: year label: Iowa LIHEAP five-plex or larger building points reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, 5-Plexes (or more than 5), page 137 + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, 5-Plexes (or more than 5) href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/fixed_income.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/fixed_income.yaml index b4b9728b1d1..02d551a2fbf 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/fixed_income.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/fixed_income.yaml @@ -7,7 +7,7 @@ metadata: period: year label: Iowa LIHEAP fixed income only points reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Fixed Income Only (no other sources of income), page 137, and Definitions, Fixed Income Household, page 81 - href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + - title: "Iowa LIHEAP FY26 Policy and Procedures Manual, Definitions, Fixed Income Household, and Appendix I Payment Matrix, item 2 Targeting Factors, Fixed Income Only (no other sources of income) #page 81,137" + href: https://hhs.iowa.gov/media/17262/download?inline#page=81 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/heat_in_rent.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/heat_in_rent.yaml index f76f86b2216..b39e22c4d2d 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/heat_in_rent.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/heat_in_rent.yaml @@ -7,7 +7,7 @@ metadata: period: year label: Iowa LIHEAP heating included in rent points reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Heating Included in Rent, page 137, and Section 5.41 Eligible Dwellings, page 20 - href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + - title: "Iowa LIHEAP FY26 Policy and Procedures Manual, Section 5.41 Eligible Dwellings and Appendix I Payment Matrix, item 2 Targeting Factors, Heating Included in Rent #page 20,137" + href: https://hhs.iowa.gov/media/17262/download?inline#page=20 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings.yaml index a254705b3e9..cc21436c155 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings.yaml @@ -7,7 +7,7 @@ metadata: period: year label: Iowa LIHEAP savings points reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Over $50,000 in Savings, page 137 + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Over $50,000 in Savings href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings_threshold.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings_threshold.yaml index 79ef2d1b0af..b63aaf938cd 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings_threshold.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/savings_threshold.yaml @@ -13,7 +13,7 @@ metadata: period: year label: Iowa LIHEAP savings threshold reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, Over $50,000 in Savings, page 137, Section 4.40 Automatically Eligible Households item 4, page 14, and Appendix C Savings Account and IAble Savings Plan rows, pages 112 and 102 - href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + - title: "Iowa LIHEAP FY26 Policy and Procedures Manual, Section 4.40 Automatically Eligible Households item 4, Appendix C IAble Savings Plan and Savings rows, and Appendix I Payment Matrix, Over $50,000 in Savings #page 14,102,112,137" + href: https://hhs.iowa.gov/media/17262/download?inline#page=14 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/subsidized_housing.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/subsidized_housing.yaml index 1ad1de5ecab..cd2dd0d98f7 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/subsidized_housing.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/subsidized_housing.yaml @@ -7,7 +7,7 @@ metadata: period: year label: Iowa LIHEAP subsidized housing points reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Subsidized Housing, page 137, and Definitions, Subsidized Housing, pages 84-85 - href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + - title: "Iowa LIHEAP FY26 Policy and Procedures Manual, Definitions, Subsidized Housing, and Appendix I Payment Matrix, item 2 Targeting Factors, Subsidized Housing #page 84-85,137" + href: https://hhs.iowa.gov/media/17262/download?inline#page=84 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child.yaml index 35a2c295b67..e35e1dfaad9 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child.yaml @@ -7,7 +7,7 @@ metadata: period: year label: Iowa LIHEAP young child household points reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Children (under 6 years of age in the household), page 137 + - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Appendix I Payment Matrix, item 2 Targeting Factors, Children (under 6 years of age in the household) href: https://hhs.iowa.gov/media/17262/download?inline#page=137 - - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I), page 1 + - title: Iowa FY2026 Benefit Matrix Heat-Crisis (LIHEAP Clearinghouse copy of Appendix I) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child_age.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child_age.yaml index 71f80f667cd..43ea542660c 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child_age.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/targeting/young_child_age.yaml @@ -7,5 +7,5 @@ metadata: period: year label: Iowa LIHEAP young child age limit reference: - - title: Iowa LIHEAP FY26 Policy and Procedures Manual, Definitions, Young Child (a person under six years of age), page 85, and Appendix I Payment Matrix, Children (under 6 years of age in the household), page 137 + - title: "Iowa LIHEAP FY26 Policy and Procedures Manual, Definitions, Young Child (a person under six years of age), and Appendix I Payment Matrix, Children (under 6 years of age in the household) #page 85,137" href: https://hhs.iowa.gov/media/17262/download?inline#page=85 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap.yaml index d359d4afa53..27bed1eff10 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap.yaml @@ -11,10 +11,12 @@ # https://hhs.iowa.gov/media/16690/download?inline#page=48 # FFY2026 plan section 2.6 p. 9 ($80 minimum, $800 maximum): # https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9 -# Modeling: 800 for PROPANE and FUEL_OIL (not KEROSENE, which the flat rule -# does not name); otherwise clip(40 x points, 80, 800). The largest total a -# non-deliverable household can reach is 8 + 5 + 5 = 18 points (720), so the -# 800 cap binds only through the flat amount. +# Modeling: 800 for the PROPANE and FUEL_OIL fuel categories, with KEROSENE +# following fuel oil by inference (Appendix I item 3 and the award rule both +# say "Fuel Oil", and section 12.20 p. 47 lists kerosene as a deliverable +# fuel); otherwise clip(40 x points, 80, 800). The largest total any other +# household can reach is 8 + 5 + 4 = 17 points (680), so the 800 cap binds +# only through the flat amount. - name: Case 1, retiree on Social Security in a detached gas-heated house receives 480. period: 2026 @@ -121,7 +123,7 @@ ia_liheap_points: 10 ia_liheap: 800 -- name: Case 5, the same household heating with kerosene receives the matrix 400. +- name: Case 5, the same household heating with kerosene receives the flat 800. period: 2026 input: people: @@ -139,10 +141,11 @@ members: [person1] state_code: IA output: - # Kerosene takes the fuel-oil 5 points but the flat 800 names only liquid - # propane and fuel oil: 4 + 1 + 5 = 10 points x 40 = 400. + # Kerosene follows fuel oil for both the 5 fuel points and the flat 800 + # (an inference; section 12.20 p. 47 lists kerosene as a deliverable fuel): + # 4 + 1 + 5 = 10 points x 40 = 400, raised to 800. ia_liheap_points: 10 - ia_liheap: 400 + ia_liheap: 800 - name: Case 6, retiree heating with fuel oil receives the flat 800. period: 2026 @@ -166,7 +169,7 @@ # 3; fuel oil 5; 12 points x 40 = 480, raised to 800. ia_liheap: 800 -- name: Case 7, the maximum 18 matrix points pay 720, below the 800 cap. +- name: Case 7, the maximum 18 matrix points with kerosene heat pay the flat 800. period: 2026 input: people: @@ -188,9 +191,12 @@ state_code: IA output: # 15,000 / 21,150 = 0.7092 -> 8; fixed income +1, elderly +1, disabled +1, - # young child +1, detached +1 = 5; kerosene 5; 18 points x 40 = 720. + # young child +1, detached +1 = 5; kerosene 5; 18 points x 40 = 720, + # raised to the flat 800 because kerosene follows fuel oil. Computed FIP is + # 0 (1,250 a month of Social Security exceeds the 361 two-person payment + # standard), so the household stays fixed income only. ia_liheap_points: 18 - ia_liheap: 720 + ia_liheap: 800 - name: Case 8, income over 200 percent of poverty receives nothing. period: 2026 @@ -358,3 +364,36 @@ # 1 point x 40 = 40 -> 80 minimum. ia_liheap_points: 1 ia_liheap: 80 + +- name: Case 15, grandparent on Social Security with a grandchild receiving child support gets no fixed-income point and receives 480. + period: 2026 + input: + people: + person1: + age: 63 + social_security: 18_000 + person2: + age: 8 + child_support_received: 3_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1, person2] + state_code: IA + output: + # Child support received is not counted (Appendix C p. 92): countable + # 18,000. 18,000 / 21,150 = 0.8511 -> 86 percent -> 6 poverty points. + # Child support "is not considered a fixed income source" (p. 92), so the + # fixed income point is lost: elderly +1, detached +1 = 2. Natural gas 4. + # 6 + 2 + 4 = 12 points x 40 = 480 (13 points and 520 with the point). + ia_liheap_countable_income: 18_000 + ia_liheap_fixed_income_only: false + ia_liheap_poverty_points: 6 + ia_liheap_targeting_points: 2 + ia_liheap_points: 12 + ia_liheap: 480 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.yaml index cc4b3522e9f..9d92818168a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.yaml @@ -11,8 +11,9 @@ # https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=5 # Modeling: the model's net self-employment and farm inputs are floored at zero # per source so a loss cannot offset other income (Kansas pattern). All income -# of members under 18 is excluded (user decision; section 6.00 p. 24 is in -# conflict and is recorded on the variable). +# of members under 18 is excluded, following section 6.20, Appendix C p. 102 +# and plan section 1.9 (section 6.00 p. 24 is in conflict and is recorded on +# the variable). - name: Case 1, adult wages are counted in full. period: 2026 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.yaml index b5c961af50e..8237d3b688d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.yaml @@ -6,8 +6,9 @@ # income is counted in full (section 7.30 p. 34): # https://hhs.iowa.gov/media/17262/download?inline#page=26 # Modeling: max(earned + unearned - child support paid, 0); all income of -# members under 18 is excluded (user decision; section 6.00 p. 24 is in -# conflict and is recorded on the earned-income variable). +# members under 18 is excluded, following section 6.20, Appendix C p. 102 and +# plan section 1.9 (section 6.00 p. 24 is in conflict and is recorded on the +# earned-income variable). - name: Case 1, two adults' wages are summed. period: 2026 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml index e28da65613f..4fea8a6bdee 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml @@ -2,12 +2,19 @@ # (REQ-018). Appendix I p. 137 of the FY26 LIHEAP Policy and Procedures Manual # awards +1 point; the Definitions (p. 81) limit fixed income to SSA benefits, # SSI, SSDI, Veterans' Assistance and pensions and say "Only mark 'fixed -# income' when that is the ONLY source(s)"; Appendix C notes alimony (p. 90) -# and earnings are not fixed income: +# income' when that is the ONLY source(s) of income"; Appendix C notes that +# alimony (p. 90), child support (p. 92) and FIP (p. 98) are not fixed income +# and labels Form RRB-1099-R "Annuities or Pensions by the Railroad Retirement +# Board" (p. 110): # https://hhs.iowa.gov/media/17262/download?inline#page=137 -# Modeling: true when the household's countable gross income (members aged 18 -# and over, before the child-support deduction) is positive and equals the sum -# of social_security, ssi, veterans_benefits and pension_income. +# Modeling: true when the household has a fixed source (social_security, ssi, +# pension_income, railroad_benefits, veterans_benefits) and no other recorded +# source. Recorded sources are every member's, at any age, including sources +# Iowa does not count: each earned source floored at zero, the countable +# unearned sources that are not fixed, child_support_received and FIP (read +# through tanf). A minor's Social Security is left out of the countable amount +# but still identifies a fixed source (Appendix C p. 114; section 4.40(3)(a) +# p. 14). Child support paid is a deduction, not an income source. - name: Case 1, Social Security alone is fixed income. period: 2026 @@ -26,7 +33,7 @@ members: [person1] state_code: IA output: - # gross 18,000 = fixed 18,000 > 0. + # fixed 18,000 > 0; no other source. ia_liheap_fixed_income_only: true - name: Case 2, Social Security plus part-time wages is not fixed income only. @@ -47,7 +54,7 @@ members: [person1] state_code: IA output: - # gross 21,000, fixed 18,000. + # fixed 18,000; the 3,000 of wages is another source. ia_liheap_fixed_income_only: false - name: Case 3, Social Security, a pension and VA benefits together are fixed income. @@ -69,7 +76,7 @@ members: [person1] state_code: IA output: - # gross 14,000 + 6,000 + 4,000 = 24,000 = fixed 24,000. + # fixed 14,000 + 6,000 + 4,000 = 24,000 > 0; no other source. ia_liheap_fixed_income_only: true - name: Case 4, SSI and Social Security together are fixed income. @@ -91,7 +98,7 @@ members: [person1] state_code: IA output: - # gross 3,000 + 9,168 = 12,168 = fixed 12,168. + # fixed 3,000 + 9,168 = 12,168 > 0; no other source. ia_liheap_fixed_income_only: true - name: Case 5, alimony is not fixed income. @@ -112,7 +119,7 @@ members: [person1] state_code: IA output: - # Appendix C p. 90: alimony is not fixed income; gross 24,000, fixed 18,000. + # Appendix C p. 90: alimony is not fixed income; fixed 18,000, other 6,000. ia_liheap_fixed_income_only: false - name: Case 6, a household with no income is not fixed income only. @@ -131,7 +138,7 @@ members: [person1] state_code: IA output: - # gross 0 is not positive. + # No fixed source (fixed 0). ia_liheap_fixed_income_only: false - name: Case 7, child support paid does not change the fixed-income test. @@ -152,7 +159,8 @@ members: [person1] state_code: IA output: - # The test compares gross sources before the deduction: 18,000 = 18,000. + # Child support paid is a deduction, not a source: fixed 18,000 > 0 and + # no other source. ia_liheap_fixed_income_only: true - name: Case 8, a child's wages are another source of income even though they are not countable. @@ -176,8 +184,8 @@ state_code: IA output: # The source test reads every member's recorded sources regardless of age - # (Definitions p. 81, "no other sources of income"): fixed 18,000 but all - # sources 18,000 + 4,000 = 22,000. The 16-year-old's wages stay out of the + # (Appendix I p. 137, "no other sources of income"): fixed 18,000, but the + # 16-year-old's 4,000 of wages is another source. The wages stay out of the # countable amount (18,000). ia_liheap_countable_income: 18_000 ia_liheap_fixed_income_only: false @@ -200,7 +208,7 @@ members: [person1] state_code: IA output: - # gross 18,200, fixed 18,000. + # fixed 18,000; the 200 of interest is another source. ia_liheap_fixed_income_only: false - name: Case 10, a retired couple on Social Security and a pension is fixed income only. @@ -223,7 +231,7 @@ members: [person1, person2] state_code: IA output: - # gross 18,000 + 9,000 = 27,000 = fixed 27,000. + # fixed 18,000 + 9,000 = 27,000 > 0; no other source. ia_liheap_fixed_income_only: true - name: Case 11, a child's Social Security survivor benefits as the only household source is fixed income only. @@ -248,7 +256,122 @@ # Appendix C p. 114 margin note: when the only source of household income # is Social Security for members under 18, record the source for the fixed # income criteria but the amount as 0.00 (also section 4.40(3)(a) p. 14). - # All-member fixed sources 9,000 = all-member sources 9,000 > 0 -> true; - # the countable amount excludes the child's 9,000 -> 0. + # Fixed 9,000 > 0 and no other recorded source -> true. Computed FIP is 0 + # (the child's 750 a month of Social Security exceeds the 361 two-person + # payment standard), so tanf adds no source. The countable amount excludes + # the child's 9,000 -> 0. ia_liheap_countable_income: 0 ia_liheap_fixed_income_only: true + +- name: Case 12, SSDI plus child support received is not fixed income only. + period: 2026 + input: + people: + person1: + age: 45 + # SSDI of 1,200 a month. + social_security: 14_400 + # 300 a month for the child. + child_support_received: 3_600 + person2: + age: 12 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1, person2] + state_code: IA + output: + # Appendix C p. 92: child support received is not counted, so the + # countable amount is the 14,400 of SSDI, but "Child support is not + # considered a fixed income source": fixed 14,400, other 3,600. + ia_liheap_countable_income: 14_400 + ia_liheap_fixed_income_only: false + +- name: Case 13, Social Security plus a grandchild's FIP grant is not fixed income only. + period: 2026 + input: + people: + person1: + age: 66 + social_security: 18_000 + person2: + age: 9 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_200 + # Child-only FIP grant at the one-person payment standard of 183 a + # month; ia_fip is a monthly variable, so the annual input is spread + # evenly over the twelve months. + ia_fip: 2_196 + households: + household: + members: [person1, person2] + state_code: IA + output: + # 183 x 12 = 2,196 of FIP, read through tanf. + tanf: 2_196 + # Appendix C p. 98: FIP is not counted (countable 18,000), and "FIP is not + # considered fixed income because the household composition is likely to + # change": fixed 18,000, other 2,196. + ia_liheap_countable_income: 18_000 + ia_liheap_fixed_income_only: false + +- name: Case 14, railroad retirement alone is fixed income only. + period: 2026 + input: + people: + person1: + # Age 62 is below the SSI aged threshold, so the model imputes no SSI. + age: 62 + railroad_benefits: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + households: + household: + members: [person1] + state_code: IA + output: + # Appendix C p. 109 defines pensions as payments "at regular intervals ... + # in consideration of past services, age" and p. 110 labels RRB-1099-R + # "Annuities or Pensions by the Railroad Retirement Board", so the annuity + # is a pension in the Definitions' fixed income list (p. 81): fixed 24,000 + # > 0 and no other source. + ia_liheap_countable_income: 24_000 + ia_liheap_fixed_income_only: true + +- name: Case 15, a railroad retiree and a spouse on Social Security are fixed income only. + period: 2026 + input: + people: + person1: + # Both members are under 65 and not disabled, so the model imputes no + # SSI. + age: 64 + railroad_benefits: 20_400 + person2: + age: 63 + social_security: 9_600 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_300 + households: + household: + members: [person1, person2] + state_code: IA + output: + # Railroad retirement is a pension (Appendix C pp. 109-110) and Social + # Security is fixed (Definitions p. 81): fixed 20,400 + 9,600 = 30,000 > 0 + # and no other source. + ia_liheap_countable_income: 30_000 + ia_liheap_fixed_income_only: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/integration.yaml index e43b7953052..42211c2e6b6 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/integration.yaml @@ -13,7 +13,8 @@ # limit = 2 x guideline: 31,300 (1) / 42,300 (2) / 53,300 (3) / 64,300 (4). # Poverty points by income / guideline: <= 0.75 -> 8, <= 1.00 -> 6, # <= 1.25 -> 5, <= 2.00 -> 4. Benefit = 40 x points clipped to [80, 800]; -# propane and fuel oil households receive the flat 800. +# propane and fuel oil households receive the flat 800, and kerosene follows +# fuel oil by inference. - name: Case 1, retiree on Social Security in a detached gas-heated house. period: 2026 @@ -261,7 +262,7 @@ ia_liheap_eligible: false ia_liheap: 0 -- name: Case 7, disabled grandparent raising a preschooler in a kerosene-heated house reaches the 18-point maximum. +- name: Case 7, disabled grandparent raising a preschooler in a kerosene-heated house reaches the 18-point maximum and receives the flat 800. period: 2026 input: people: @@ -288,9 +289,12 @@ # 15,000 Social Security; 1,250 a month exceeds the SSI federal benefit # rate, so no SSI is added. ia_liheap_countable_income: 15_000 + # Computed FIP is 0: 1,250 a month of Social Security exceeds the 361 + # two-person payment standard, so no other source is recorded. ia_liheap_fixed_income_only: true ia_liheap_eligible: true - # Kerosene takes the fuel-oil category by inference. + # Kerosene takes the fuel-oil category by inference (section 12.20 p. 47 + # lists kerosene as a deliverable fuel). ia_liheap_fuel_category: FUEL_OIL # 15,000 / 21,150 = 0.7092 -> 71 percent -> 0 to 75 band ia_liheap_poverty_points: 8 @@ -299,8 +303,8 @@ ia_liheap_fuel_points: 5 # 8 + 5 + 5, the largest total the matrix allows ia_liheap_points: 18 - # 40 x 18 = 720, below the 800 cap; kerosene does not take the flat 800 - ia_liheap: 720 + # 40 x 18 = 720, raised to the flat 800 because kerosene follows fuel oil + ia_liheap: 800 - name: Case 8, Section 8 tenant with heat in rent, large savings and an electric account has negative points and receives the 80 minimum. period: 2026 diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.py index 03a59845c7c..cfc90913085 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.py @@ -16,13 +16,14 @@ class ia_liheap_eligible(Variable): "the automatically eligible verification waiver are not modeled." ) reference = ( - # Section 5.10 Eligibility Criteria and 5.30 Who Can Apply (page 18), - # Section 5.50 Energy Burden (page 22), Section 7.30 (page 34) and - # Appendix D (pages 122-123). + # Section 5.10 Eligibility Criteria, 5.30 Who Can Apply, 5.50 Energy + # Burden and 7.30, and Appendix D. + # PDF pages 18, 22, 34, 122-123 "https://hhs.iowa.gov/media/17262/download?inline#page=18", # Sections 1.4-1.7 (no categorical eligibility) and 2.3 (no assets - # test), pages 5 and 8. - "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=8", + # test). + # PDF pages 5, 8 + "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=5", ) defined_for = StateCode.IA diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_fpg.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_fpg.py index 81621dfdc39..b92b60673fc 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_fpg.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_fpg.py @@ -16,7 +16,7 @@ class ia_liheap_fpg(Variable): "Zero when no member counts toward household size." ) reference = ( - # Appendix B, Poverty Guidelines (page 89). + # Appendix B, Poverty Guidelines. "https://hhs.iowa.gov/media/17262/download?inline#page=89", "https://hhs.iowa.gov/programs/programs-and-services/liheap", ) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_has_energy_burden.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_has_energy_burden.py index 89565db6c16..215b93a81c8 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_has_energy_burden.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_has_energy_burden.py @@ -17,9 +17,9 @@ class ia_liheap_has_energy_burden(Variable): "by anyone' case of Appendix D are not identified by existing inputs." ) reference = ( - # Section 5.41-5.42 (pages 20-21), Section 5.50 (page 22) and Appendix - # D, Energy Burden Table (pages 122-123). - "https://hhs.iowa.gov/media/17262/download?inline#page=122", + # Sections 5.41-5.42 and 5.50, and Appendix D, Energy Burden Table. + # PDF pages 20-22, 122-123 + "https://hhs.iowa.gov/media/17262/download?inline#page=20", # Section 2.3, subsidized housing and utilities-in-rent policies. "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=8", ) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.py index a9a3a49a2c3..389addc28df 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.py @@ -14,7 +14,8 @@ class ia_liheap_income_eligible(Variable): "qualifies." ) reference = ( - # Section 5.10 (page 18) and Appendix I (page 137). + # Section 5.10 and Appendix I. + # PDF pages 18, 137 "https://hhs.iowa.gov/media/17262/download?inline#page=18", ) defined_for = StateCode.IA diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_limit.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_limit.py index a37af5c070c..28d6c4dd056 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_limit.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_limit.py @@ -13,8 +13,9 @@ class ia_liheap_income_limit(Variable): "member in FY26." ) reference = ( - # Section 5.10 (page 18) and Appendix B (page 89). - "https://hhs.iowa.gov/media/17262/download?inline#page=89", + # Section 5.10 and Appendix B. + # PDF pages 18, 89 + "https://hhs.iowa.gov/media/17262/download?inline#page=18", # Section 2.1 note: the threshold is 200% FPG; 150.00% is a form # artifact. "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9", diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap.py index 32e8714208c..b740f0d64f9 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap.py @@ -11,7 +11,8 @@ class ia_liheap(Variable): "Annual Iowa Low Income Home Energy Assistance Program Regular " "Assistance heating benefit: the per-point amount times total payment " "matrix points, between the minimum and maximum benefit, or the flat " - "deliverable fuel amount for liquid propane and fuel oil households. " + "deliverable fuel amount for liquid propane and fuel oil (including " + "kerosene) households. " "The benefit does not depend on the heating bill. Verified for FY2026; " "earlier years use model parameter backfilling and are unverified " "historical estimates. Crisis assistance, cooling, heating equipment " @@ -19,9 +20,10 @@ class ia_liheap(Variable): ) defined_for = "ia_liheap_eligible" reference = ( - # Appendix I, Award Calculation (page 137), Section 11.00 Payments - # (page 42) and Section 5.41 (page 20). - "https://hhs.iowa.gov/media/17262/download?inline#page=137", + # Section 5.41, Section 11.00 Payments, Section 12.20 Deliverable Fuel + # Vendors and Appendix I, Award Calculation. + # PDF pages 20, 42, 47, 137 + "https://hhs.iowa.gov/media/17262/download?inline#page=20", "https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1", # Section 2.6, minimum $80 and maximum $800. "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9", @@ -32,18 +34,21 @@ class ia_liheap(Variable): def formula(spm_unit, period, parameters): p = parameters(period).gov.states.ia.hhs.liheap.payment points = spm_unit("ia_liheap_points", period) - heating_type = spm_unit("heating_type", period) - types = heating_type.possible_values + fuel = spm_unit("ia_liheap_fuel_category", period) + fuels = fuel.possible_values # Appendix I: "The minimum Regular Assistance benefit for liquid # propane and fuel oil LIHEAP customers is $800"; Section 11.00 raises # a lower matrix result "to meet the state-set benefit amount", and the # FY26 draft plan calls it "a flat amount ... that serves as both a - # minimum and maximum benefit for liquid propane and fuel oil". The - # rule names only those two fuels, so kerosene households receive the - # matrix amount. - deliverable_fuel = (heating_type == types.PROPANE) | ( - heating_type == types.FUEL_OIL - ) + # minimum and maximum benefit for liquid propane and fuel oil". + # Appendix I item 3 and the award rule use the same term, "Fuel Oil" + # (page 137; Section 11.00, page 42). The draft plan (page 48) explains + # the flat amount by deliverable fuel customers "not being eligible for + # winter shutoff moratorium protection", and Section 12.20 (page 47) + # lists kerosene as a deliverable fuel. Kerosene therefore follows fuel + # oil for both the fuel points and the flat amount; this is an + # inference, as the manual has no kerosene row. + deliverable_fuel = (fuel == fuels.PROPANE) | (fuel == fuels.FUEL_OIL) # Appendix I: "$40 per point"; "The minimum benefit an approved LIHEAP # customer may receive is $80, and the maximum is $800." Section 5.41: # the benefit "is determined by a matrix and does not depend on the diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_dwelling_type.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_dwelling_type.py index 47d8fbd75c6..bd006dee33b 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_dwelling_type.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_dwelling_type.py @@ -27,10 +27,10 @@ class ia_liheap_dwelling_type(Variable): "treated as DETACHED by inference. Defaults to DETACHED." ) reference = ( - # Appendix I matrix (page 137); Definitions, Attached Household (page - # 79), Detached Household/Single Family Home (page 80) and - # Mobile/Manufactured Home (page 82). - "https://hhs.iowa.gov/media/17262/download?inline#page=137", + # Definitions, Attached Household, Detached Household/Single Family + # Home and Mobile/Manufactured Home; Appendix I matrix. + # PDF pages 79-80, 82, 137 + "https://hhs.iowa.gov/media/17262/download?inline#page=79", # Section 2.5, dwelling type checked as a benefit variable. "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=9", ) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_fuel_category.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_fuel_category.py index 922d48bf764..aa594cc392a 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_fuel_category.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_fuel_category.py @@ -25,10 +25,10 @@ class ia_liheap_fuel_category(Variable): "published points." ) reference = ( - # Appendix I item 3, Fuel/Energy Type (page 137); Section 12.00 and - # 12.20 vendor fuel lists (pages 46-47); Definitions, Primary Heating - # Fuel Type (page 84). - "https://hhs.iowa.gov/media/17262/download?inline#page=137", + # Section 12.00 and 12.20 vendor fuel lists; Definitions, Primary + # Heating Fuel Type; Appendix I item 3, Fuel/Energy Type. + # PDF pages 46-47, 84, 137 + "https://hhs.iowa.gov/media/17262/download?inline#page=46", "https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1", ) defined_for = StateCode.IA @@ -42,9 +42,9 @@ def formula(spm_unit, period, parameters): # Appendix I prints points for "Fuel Oil" but none for kerosene. # Section 12.20 groups "LP, heating oil, wood, kerosene, and corn" as # deliverable fuels, and heating_expense already pairs kerosene with - # fuel oil, so kerosene takes the fuel oil points. This is an - # inference, not an express rule; the flat deliverable fuel benefit in - # ia_liheap still applies only to liquid propane and fuel oil. + # fuel oil, so kerosene takes the fuel oil category. This is an + # inference, not an express rule; the category drives both the fuel + # points and the flat deliverable fuel benefit in ia_liheap. fuel_oil = (heating_type == types.FUEL_OIL) | (heating_type == types.KEROSENE) # Appendix I: "Solid Fuel (Wood/Coal/Corn)". The model has no corn # heating type. diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_household_size.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_household_size.py index c0af11376d7..3109a7377ba 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_household_size.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap_household_size.py @@ -19,9 +19,9 @@ class ia_liheap_household_size(Variable): "members and students away from home are not identified." ) reference = ( - # Section 7.00 (page 31) and Section 7.30 (page 34); Definitions, - # Household/Customer (page 82). - "https://hhs.iowa.gov/media/17262/download?inline#page=34", + # Sections 7.00 and 7.30; Definitions, Household/Customer. + # PDF pages 31, 34, 82 + "https://hhs.iowa.gov/media/17262/download?inline#page=31", ) defined_for = StateCode.IA diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.py index 4672a13a664..b032334e1b5 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.py @@ -18,12 +18,13 @@ class ia_liheap_countable_earned_income(Variable): "represented. All income of members under 18 is excluded." ) reference = ( - # Section 6.00 (page 24), Section 6.20 (page 26), Appendix C Employment - # Income (page 97), Farm Income (page 98), Income Earned by HH Members - # under the age of 18 (page 102), Military Pay (page 108) and - # Self-Employment Income (page 113). - "https://hhs.iowa.gov/media/17262/download?inline#page=113", - # Sections 1.8-1.9, pages 5-6. + # Sections 6.00 and 6.20; Appendix C Employment Income, Farm Income, + # Income Earned by HH Members under the age of 18, Military Pay and + # Self-Employment Income. + # PDF pages 24, 26, 97-98, 102, 108, 113 + "https://hhs.iowa.gov/media/17262/download?inline#page=24", + # Sections 1.8-1.9. + # PDF pages 5-6 "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=5", ) defined_for = StateCode.IA @@ -45,7 +46,6 @@ def formula(person, period, parameters): # 102) says earnings of under-18 members who "work full-time" are "NOT # included as income" and the FFY2026 plan leaves "Earned income of a # child under the age of 18" unchecked. The model follows Section 6.20, - # Appendix C and the plan: all under-18 income is excluded (user - # decision of 2026-10-04); the Section 6.00 wording is a recorded - # conflict. + # Appendix C and plan Section 1.9: all under-18 income is excluded; the + # Section 6.00 wording is a recorded conflict. return where(age >= p.countable_min_age, earned, 0) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.py index ebd39aed16d..18744d55721 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.py @@ -15,9 +15,9 @@ class ia_liheap_countable_income(Variable): "verification period the household elects under Section 6.10." ) reference = ( - # Section 6.10 (page 24), Section 6.20 (page 26), Section 7.30 (page - # 34) and Appendix C Child Support notes (pages 92-93). - "https://hhs.iowa.gov/media/17262/download?inline#page=92", + # Sections 6.10, 6.20 and 7.30, and Appendix C Child Support notes. + # PDF pages 24, 26, 34, 92-93 + "https://hhs.iowa.gov/media/17262/download?inline#page=24", ) defined_for = StateCode.IA diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py index f7a365752be..5b309d8fc0a 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py @@ -14,24 +14,23 @@ class ia_liheap_countable_unearned_income(Variable): "compensation, strike benefits, disability and veterans' benefits, " "alimony, rental income, interest, dividends and gambling winnings. " "Approximations: Social Security is counted gross; Iowa counts the " - "amount paid net of Medicare premiums and SSA repayments, and netting " - "the Medicare Part B premium is deferred under coordinator ruling D6 " - "while SSA repayments have no input. All interest is counted although " + "amount paid net of Medicare premiums and SSA repayments, netting the " + "Medicare Part B premium is deferred, as in Kansas, and SSA repayments " + "have no input. All interest is counted although " "Iowa excludes interest on bank accounts; dividends are counted in full " "although Iowa ignores a combined annual total under $120. Social " "Security paid to members under 18 is recorded at $0.00 and is " "excluded here with all other under-18 income." ) reference = ( - # Section 6.10 (page 25), Section 6.20 (page 26) and Appendix C rows - # for Alimony (page 90), Disability Insurance (page 94), Dividends - # (page 96), Gambling (page 99), Interest (page 103), IRA (page 103), - # Pensions (page 109), Railroad Retirement (page 110), Rental Income - # (page 110), Retirement (page 111), Social Security Benefits (page - # 114), Strike Benefits (page 115), Unemployment (page 117), Veterans - # Benefits (pages 118-119) and Workers Compensation (page 121). - "https://hhs.iowa.gov/media/17262/download?inline#page=114", - # Section 1.9, page 6. + # Sections 6.10 and 6.20 and Appendix C rows for Alimony, Disability + # Insurance, Dividends, Gambling, Interest, IRA, Pensions, Railroad + # Retirement, Rental Income, Retirement, Social Security Benefits, + # Strike Benefits, Unemployment, Veterans Benefits and Workers + # Compensation. + # PDF pages 25-26, 90, 94-96, 99, 103, 109-111, 114-115, 117-119, 121 + "https://hhs.iowa.gov/media/17262/download?inline#page=25", + # Section 1.9. "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=6", ) defined_for = StateCode.IA diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py index 92190af0081..e2e7cefb81e 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py @@ -7,10 +7,13 @@ class ia_liheap_fixed_income_only(Variable): definition_period = YEAR label = "Iowa LIHEAP household has fixed income only" documentation = ( - "Whether every recorded income source of the household, across all " - "members regardless of age, is Social Security, SSI, veterans' benefits " - "or pensions. Definitions, Fixed Income Household: 'Only mark \"fixed " - "income\" when that is the ONLY source(s) of income.' Under-18 income " + "Whether the household has a fixed income source (Social Security, SSI, " + "pensions including railroad retirement, or veterans' benefits) and no " + "other recorded income source, across all members regardless of age. " + 'Definitions, Fixed Income Household: \'Only mark "fixed income" when ' + "that is the ONLY source(s) of income.' Recorded sources include the " + "countable earned and unearned sources and two that Iowa does not count " + "but calls not fixed: child support received and FIP. Under-18 income " "is excluded from the countable amount, but a minor's Social Security " "still identifies the household's income source: Appendix C records " "such payments at $0.00 'to capture source of income for AE fixed " @@ -19,28 +22,41 @@ class ia_liheap_fixed_income_only(Variable): "with no recorded income source is not fixed income only." ) reference = ( - # Definitions, Fixed Income Household (page 81); Section 4.40 item 3a - # (page 14); Appendix C Social Security Benefits margin note (page 114); - # Appendix I, Fixed Income Only (no other sources of income) (page 137); - # Appendix C notes that alimony, variable annuities and child support - # are not fixed income (pages 90-92). - "https://hhs.iowa.gov/media/17262/download?inline#page=81", + # Section 4.40 item 3a; Definitions, Fixed Income Household; Appendix C + # notes that alimony, variable annuities, child support and FIP are + # not fixed income, Pensions and Railroad Retirement rows and Social + # Security Benefits margin note; Appendix I, Fixed Income Only (no + # other sources of income). + # PDF pages 14, 81, 90-92, 98, 109-110, 114, 137 + "https://hhs.iowa.gov/media/17262/download?inline#page=14", ) defined_for = StateCode.IA def formula(spm_unit, period, parameters): - p = parameters(period).gov.states.ia.hhs.liheap.income + p = parameters(period).gov.states.ia.hhs.liheap.income.sources person = spm_unit.members - # Every recorded source of every member counts toward the test, with - # no age gate; earned sources are floored per source as in the - # countable amount. + # Definitions, Fixed Income Household (page 81): SSA, SSI, SSDI, + # veterans' benefits and pensions. Section 4.40(3)(a) (page 14) and + # Appendix C (page 114): a minor's Social Security still identifies a + # fixed source, so no age gate applies here. + fixed = add(spm_unit, period, p.fixed) + # Every other recorded source of every member defeats the factor: + # Appendix I reads "Fixed Income Only (no other sources of income)" + # (page 137) and the Definitions say "Only mark 'fixed income' when + # that is the ONLY source(s) of income" (page 81), which the formula + # follows for FY2026. The FY27 manual and Program Notice 27-01 + # (effective October 1, 2026) reword the factor to "no other countable + # sources of income"; that change is not modeled because only FY2026 + # is verified. Earned sources are floored per source as in the + # countable amount. Appendix C adds that child support (page 92) and + # FIP (page 98) are not fixed income although neither is counted. earned = 0 - for source in p.sources.earned: + for source in p.earned: earned = earned + max_(person(source, period), 0) - unearned = add(person, period, p.sources.unearned) - recorded = spm_unit.sum(earned + unearned) - # sources/fixed.yaml lists the fixed sources in the same relative order - # as sources/unearned.yaml, so the two sums are bit-identical when every - # other source is zero. - fixed = spm_unit.sum(add(person, period, p.sources.fixed)) - return (fixed > 0) & (fixed == recorded) + other_unearned = [source for source in p.unearned if source not in p.fixed] + non_fixed = ( + spm_unit.sum(earned) + + add(spm_unit, period, other_unearned) + + add(spm_unit, period, p.other_recorded) + ) + return (fixed > 0) & (non_fixed == 0) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_fuel_points.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_fuel_points.py index 4986551943d..c991f3c058a 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_fuel_points.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_fuel_points.py @@ -12,7 +12,7 @@ class ia_liheap_fuel_points(Variable): "5, solid fuel 2, other fuels 0." ) reference = ( - # Appendix I item 3, Fuel/Energy Type (page 137). + # Appendix I item 3, Fuel/Energy Type. "https://hhs.iowa.gov/media/17262/download?inline#page=137", "https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1", ) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_points.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_points.py index df8703f46ae..eb560a12c2c 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_points.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_points.py @@ -12,10 +12,10 @@ class ia_liheap_points(Variable): "applies." ) reference = ( - # Appendix I, Award Calculation (page 137) and Section 11.00 (page 42): - # "the amount of payment determined by the total number of matrix - # points". - "https://hhs.iowa.gov/media/17262/download?inline#page=137", + # Section 11.00, "the amount of payment determined by the total number + # of matrix points", and Appendix I, Award Calculation. + # PDF pages 42, 137 + "https://hhs.iowa.gov/media/17262/download?inline#page=42", "https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1", ) defined_for = StateCode.IA diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.py index ef44d257147..b0924b5af31 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.py @@ -16,8 +16,9 @@ class ia_liheap_poverty_points(Variable): "household with no counted member has no guideline and scores zero." ) reference = ( - # Appendix I item 1, Poverty Level (page 137) and Appendix B (page 89). - "https://hhs.iowa.gov/media/17262/download?inline#page=137", + # Appendix B and Appendix I item 1, Poverty Level. + # PDF pages 89, 137 + "https://hhs.iowa.gov/media/17262/download?inline#page=89", "https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1", ) defined_for = StateCode.IA diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_targeting_points.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_targeting_points.py index 70c78d75ee5..2d284319c01 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_targeting_points.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_targeting_points.py @@ -16,11 +16,11 @@ class ia_liheap_targeting_points(Variable): "is_disabled; savings are approximated by bank, stock and bond assets." ) reference = ( - # Appendix I item 2, Targeting Factors (page 137); Definitions, Disabled - # and Elderly (page 80), Fixed Income Household (page 81), Subsidized - # Housing (pages 84-85) and Young Child (page 85); Section 4.40 item 4 - # savings description (page 14). - "https://hhs.iowa.gov/media/17262/download?inline#page=137", + # Section 4.40 item 4 savings description; Definitions, Disabled, + # Elderly, Fixed Income Household, Subsidized Housing and Young Child; + # Appendix I item 2, Targeting Factors. + # PDF pages 14, 80-81, 84-85, 137 + "https://hhs.iowa.gov/media/17262/download?inline#page=14", "https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1", # Section 2.4: the matrix "awards additional benefit amounts for # households with members that are elderly, disabled, and/or have From 8490f2e5870a920832d52f314cc7caadcd1b91bf Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 20 Oct 2026 10:51:36 -0400 Subject: [PATCH 5/8] Read total_unemployment_compensation in Iowa LIHEAP income Main's code-health check rejects the reported-only unemployment_compensation input in income source lists; the total uses the reported amount when present and otherwise the modeled state unemployment insurance. Co-Authored-By: Claude Opus 5.5 --- .../gov/states/ia/hhs/liheap/income/sources/unearned.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml index 05e29a2c51c..00148e9db11 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml @@ -32,7 +32,7 @@ values: - railroad_benefits # Appendix C Unemployment: counted; the limited-duration annualization # rule of the Definitions (Annualized Income, page 79) is not modeled. - - unemployment_compensation + - total_unemployment_compensation - workers_compensation - strike_benefits # Appendix C Disability Insurance: long-term private disability counts, From 410a0a02419b587e7371cfda484ea7f848f13618 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 20 Oct 2026 11:18:01 -0400 Subject: [PATCH 6/8] Count military retirement income in Iowa LIHEAP Add main's military_retirement_income (military retirement pay and survivor benefit payments) to Iowa's unearned and fixed income lists: Appendix C counts pensions and retirement income, and the fixed income definition includes pensions. Co-Authored-By: Claude Opus 5.5 --- .../ia/hhs/liheap/income/sources/fixed.yaml | 2 + .../hhs/liheap/income/sources/unearned.yaml | 3 ++ .../ia_liheap_countable_unearned_income.yaml | 22 +++++++++ .../income/ia_liheap_fixed_income_only.yaml | 47 +++++++++++++++++++ 4 files changed, 74 insertions(+) diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/fixed.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/fixed.yaml index 035cb353dfd..4d7608679cb 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/fixed.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/fixed.yaml @@ -11,6 +11,8 @@ values: - social_security - ssi - pension_income + # Military retirement pay and survivor benefit payments are pensions. + - military_retirement_income # Appendix C defines Pensions as assistance "paid at regular intervals ... # in consideration of past services, age" and labels Form RRB-1099-R # "Annuities or Pensions by the Railroad Retirement Board", so railroad diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml index 00148e9db11..fdae0b95137 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml @@ -19,6 +19,9 @@ values: # ssi is a monthly variable; add() sums the twelve months. - ssi - pension_income + # Appendix C Pensions and Retirement Income rows: military retirement pay + # and survivor benefit payments are pension income. + - military_retirement_income # Appendix C IRA and Retirement rows: regular distributions count; # irregular withdrawals are not separable from the model inputs. # retirement_distributions already includes taxable_ira_distributions diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml index 39c990c8d1a..104934c1633 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml @@ -213,3 +213,25 @@ # Appendix C p. 110, Railroad Retirement: RRB-1099 box 5 and RRB-1099-R # box 7 are counted as income; 24,000. ia_liheap_countable_unearned_income: 24_000 + +- name: Case 10, military retirement pay is counted as a pension. + period: 2026 + input: + people: + person1: + # Age 48 is below the SSI aged threshold, so the model imputes no SSI. + age: 48 + military_retirement_pay: 26_400 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # Appendix C pp. 109 and 111, Pensions and Retirement Income: a 20-year + # military retiree's 2,200 a month pension counts; 26,400. + ia_liheap_countable_unearned_income: 26_400 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml index 4fea8a6bdee..92ffe50854c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml @@ -375,3 +375,50 @@ # and no other source. ia_liheap_countable_income: 30_000 ia_liheap_fixed_income_only: true + +- name: Case 16, military retirement pay alone is fixed income only. + period: 2026 + input: + people: + person1: + # Age 48 is below the SSI aged threshold, so the model imputes no SSI. + age: 48 + military_retirement_pay: 26_400 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # A military pension is one of the "pensions" in the Definitions' fixed + # income list (p. 81): fixed 26,400 > 0 and no other source. + ia_liheap_countable_income: 26_400 + ia_liheap_fixed_income_only: true + +- name: Case 17, military survivor benefit payments alone are fixed income only. + period: 2026 + input: + people: + person1: + # Age 60 is below the SSI aged threshold, so the model imputes no SSI. + age: 60 + military_retirement_pay_survivors: 14_400 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_400 + households: + household: + members: [person1] + state_code: IA + output: + # A Survivor Benefit Plan annuity of 1,200 a month is a pension paid in + # consideration of past service (Appendix C p. 109): fixed 14,400 > 0 and + # no other source. + ia_liheap_countable_income: 14_400 + ia_liheap_fixed_income_only: true From 5108878aac7b795215f9f2e649115c1386672ca5 Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 23 Oct 2026 19:57:10 -0400 Subject: [PATCH 7/8] Fix issues from review: Medicare premium, rental floor, deliverable fuel in rent - Count Social Security net of the Medicare Part B premium (plan item 1.9) - Floor rental income per source, including the fixed-income test - Pay the flat deliverable-fuel amount only when heat is not in the rent - Pin positive self-employment income; reword the UNSPECIFIED comment Co-Authored-By: Claude Opus 5.5 --- .../hhs/liheap/income/sources/unearned.yaml | 22 +++--- .../payment/deliverable_fuel_amount.yaml | 3 + .../eligibility/ia_liheap_eligible.yaml | 6 +- .../gov/states/ia/hhs/liheap/ia_liheap.yaml | 74 ++++++++++++++++++- .../ia_liheap_countable_earned_income.yaml | 22 ++++++ .../income/ia_liheap_countable_income.yaml | 27 +++++++ .../ia_liheap_countable_unearned_income.yaml | 71 ++++++++++++++++-- .../income/ia_liheap_fixed_income_only.yaml | 55 ++++++++++++-- .../gov/states/ia/hhs/liheap/integration.yaml | 4 +- .../liheap/eligibility/ia_liheap_eligible.py | 11 +-- .../gov/states/ia/hhs/liheap/ia_liheap.py | 21 +++++- .../ia_liheap_countable_unearned_income.py | 42 +++++++---- .../income/ia_liheap_fixed_income_only.py | 19 ++--- 13 files changed, 317 insertions(+), 60 deletions(-) diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml index fdae0b95137..a91642e2b5d 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml @@ -6,17 +6,18 @@ description: Iowa counts these unearned income sources toward gross household in # adoption assistance, inheritances and life insurance, AmeriCorps and VISTA, # reimbursements, rebates and tax refunds, in-kind income, short-term # disability, VA education and housing allowances, and savings balances. +# Each source is floored at zero per person in the countable unearned income +# formula, so a loss in one source (such as a rental loss) does not offset +# other income. values: 2025-10-01: - # Appendix C Social Security Benefits (SSA, SSI, SSDI): "Count the net - # amount as income (gross minus benefits repaid to SSA)" and the amount - # "paid by check or direct deposit"; the plan checks "Excluding MediCare - # deduction". The model counts social_security gross as a documented - # approximation: netting the Medicare Part B premium (the model has - # medicare_part_b_premium) is deferred, as in Kansas, and SSA repayments - # have no input. - - social_security - # ssi is a monthly variable; add() sums the twelve months. + # Social Security is counted in the countable unearned income formula, net + # of the Medicare Part B premium: Appendix C Social Security Benefits (SSA, + # SSI, SSDI) says "Count the net amount as income (gross minus benefits + # repaid to SSA)", Section 6.10 and Appendix C use the amount "paid by + # check or direct deposit", and plan Section 1.9 checks "Excluding MediCare + # deduction". SSA repayments and Part D premiums have no input. + # ssi is a monthly variable; the formula sums the twelve months. - ssi - pension_income # Appendix C Pensions and Retirement Income rows: military retirement pay @@ -46,6 +47,9 @@ values: # and are not separable from the model input. - veterans_benefits - alimony_income + # Appendix C Rental Income (page 110): "Income received from rental + # properties, such as farmland or dwellings." A rental loss is not income + # received, so it is floored at zero like the earned sources. - rental_income # Appendix C Interest (Bonds, Investments) is counted while Interest # Bearing Bank Accounts is not; the model input does not split them, so diff --git a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/deliverable_fuel_amount.yaml b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/deliverable_fuel_amount.yaml index a66861b24d3..30a9413f76c 100644 --- a/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/deliverable_fuel_amount.yaml +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/deliverable_fuel_amount.yaml @@ -9,6 +9,9 @@ description: Iowa pays this flat Regular Assistance benefit to households heatin # amount is $800." The three statements agree numerically for FY26 because the # general maximum is also $800. Kerosene follows fuel oil through # ia_liheap_fuel_category (an inference; the manual has no kerosene row). +# The model pays the flat amount only to households that buy the fuel +# themselves; a household whose heat is included in its rent takes the matrix +# amount (an inference from Appendix D, page 122; see ia_liheap). values: 2025-10-01: 800 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.yaml index 0653ec6a8d0..74d2cac2110 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.yaml @@ -5,9 +5,9 @@ # burden (section 5.50 p. 22 and Appendix D pp. 122-123, where electricity from # solar cells, wind, a generator or an extension cord is not a burden), and a # heating fuel: https://hhs.iowa.gov/media/17262/download?inline#page=18 -# Modeling: heating_type UNSPECIFIED pays nothing under the all-state -# convention (callers must send the fuel), NONE means the home has no heating, -# SOLAR is ineligible under Appendix D p. 123. +# Modeling: this implementation requires a known heating_type and treats +# UNSPECIFIED as ineligible, including when heat is included in the rent; NONE +# means the home has no heating; SOLAR is ineligible under Appendix D p. 123. - name: Case 1, single renter paying a natural gas vendor is eligible. period: 2026 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap.yaml index 27bed1eff10..5b77b0f293f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap.yaml @@ -14,9 +14,11 @@ # Modeling: 800 for the PROPANE and FUEL_OIL fuel categories, with KEROSENE # following fuel oil by inference (Appendix I item 3 and the award rule both # say "Fuel Oil", and section 12.20 p. 47 lists kerosene as a deliverable -# fuel); otherwise clip(40 x points, 80, 800). The largest total any other -# household can reach is 8 + 5 + 4 = 17 points (680), so the 800 cap binds -# only through the flat amount. +# fuel); otherwise clip(40 x points, 80, 800). A household whose heat is +# included in its rent does not buy the fuel and takes the matrix amount (an +# inference from the draft plan p. 48 and Appendix D p. 122). The largest +# total any other household can reach is 8 + 5 + 4 = 17 points (680), so the +# 800 cap binds only through the flat amount. - name: Case 1, retiree on Social Security in a detached gas-heated house receives 480. period: 2026 @@ -397,3 +399,69 @@ ia_liheap_targeting_points: 2 ia_liheap_points: 12 ia_liheap: 480 + +- name: Case 16, retiree whose Social Security is counted net of the Medicare Part B premium receives 440. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 66 + social_security: 33_000 + # 2,750 a month is above the Medicare Savings Program limit, so the + # full 2026 standard premium is paid: 202.90 x 12 = 2,434.80. + medicare_part_b_premium: 2_434.8 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1] + state_code: IA + output: + # Plan section 1.9 p. 6 counts SSA benefits "Excluding MediCare deduction" + # (Appendix C p. 114, the net amount): 33,000 - 2,434.80 = 30,565.20, at or + # below the 31,300 limit (gross 33,000 would exceed it and pay 0). + ia_liheap_countable_income: 30_565.2 + ia_liheap_income_eligible: true + ia_liheap_fixed_income_only: true + # 30,565.20 / 15,650 = 1.9531 -> 196 percent -> 126 to 200 band. + ia_liheap_poverty_points: 4 + # fixed income +1, elderly +1, detached +1 = 3. + ia_liheap_targeting_points: 3 + # 4 + 3 + natural gas 4 = 11 points x 40 = 440. + ia_liheap_points: 11 + ia_liheap: 440 + +- name: Case 17, tenant with heat in unsubsidized rent in a propane-heated building receives the matrix amount, not the flat 800. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + bottled_gas_expense: 0 + heat_expense_included_in_rent: true + ia_liheap_dwelling_type: ATTACHED_2_TO_4_UNITS + households: + household: + members: [person1] + state_code: IA + output: + # Unsubsidized heat in rent is an energy burden (Appendix D p. 122). + ia_liheap_has_energy_burden: true + ia_liheap_fuel_category: PROPANE + # The tenant does not buy the deliverable fuel, so the flat amount does not + # apply (an inference from the draft plan p. 48, section 11.00 p. 42 and + # Appendix D p. 122). 28,000 / 15,650 = 1.7891 -> 4; heat in rent -4, + # two to four units 0; propane 5; 4 - 4 + 5 = 5 points x 40 = 200. + ia_liheap_targeting_points: -4 + ia_liheap_points: 5 + ia_liheap: 200 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.yaml index 9d92818168a..87ea36076c6 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.yaml @@ -145,3 +145,25 @@ # 28,000 + 4,000 + 3,000 = 35,000 (Appendix C p. 108 military pay # excluding housing allowance; p. 113 self-employment and contract income). ia_liheap_countable_earned_income: 35_000 + +- name: Case 7, a self-employment profit is counted beside wages. + period: 2026 + input: + people: + person1: + age: 45 + employment_income: 26_000 + self_employment_income: 4_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # 26,000 + max(4,000, 0) = 30,000 (Appendix C p. 113, self-employment + # income). + ia_liheap_countable_earned_income: 30_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.yaml index 8237d3b688d..f716305d45b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.yaml @@ -196,3 +196,30 @@ output: # 30,000 + 12,000 - 2,400 = 39,600. ia_liheap_countable_income: 39_600 + +- name: Case 9, a rental loss cannot offset wages. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 34_000 + rental_income: -6_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + ia_liheap_dwelling_type: DETACHED + households: + household: + members: [person1] + state_code: IA + output: + # max(-6,000, 0) = 0 unearned (Appendix C p. 110); 34,000 + 0 = 34,000 > + # 31,300, so the household is over income and receives 0. Netting the loss + # would give 28,000 and 4 + 1 + 4 = 9 points, 360. + ia_liheap_countable_unearned_income: 0 + ia_liheap_countable_income: 34_000 + ia_liheap_income_eligible: false + ia_liheap: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml index 104934c1633..d4ee8ef2ff2 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml @@ -8,18 +8,28 @@ # (p. 96) and taxable gambling winnings (p. 99); it excludes child support # received (pp. 92-93) and capital gains (p. 92): # https://hhs.iowa.gov/media/17262/download?inline#page=114 -# Modeling: Social Security is counted gross (Iowa nets Medicare premiums); -# bank interest and the under-$120 dividend rule are approximations. ssi is a -# MONTH variable, so the twelve months are summed. +# Modeling: Social Security is counted net of the person's out-of-pocket +# Medicare Part B premium (medicare_part_b_premium), floored at zero; FFY2026 +# plan section 1.9 counts SSA benefits "Excluding MediCare deduction": +# https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=6 +# Every other source is floored at zero per person, so a rental loss offsets +# nothing (Appendix C p. 110, "Income received from rental properties"). Bank +# interest and the under-$120 dividend rule are approximations. ssi is a MONTH +# variable, so the twelve months are summed. - name: Case 1, Social Security and a pension are counted. period: 2026 + absolute_error_margin: 0.01 input: people: person1: age: 67 social_security: 18_000 pension_income: 6_000 + # Combined income of 2,000 a month is above the Medicare Savings + # Program limit, so the full 2026 standard premium is paid: + # 202.90 x 12 = 2,434.80. + medicare_part_b_premium: 2_434.8 spm_units: spm_unit: members: [person1] @@ -30,8 +40,8 @@ members: [person1] state_code: IA output: - # 18,000 + 6,000 = 24,000. - ia_liheap_countable_unearned_income: 24_000 + # Social Security net of the premium: 18,000 - 2,434.80 + 6,000 = 21,565.20. + ia_liheap_countable_unearned_income: 21_565.2 - name: Case 2, SSI is summed over the twelve months. period: 2026 @@ -235,3 +245,54 @@ # Appendix C pp. 109 and 111, Pensions and Retirement Income: a 20-year # military retiree's 2,200 a month pension counts; 26,400. ia_liheap_countable_unearned_income: 26_400 + +- name: Case 11, a Medicare premium larger than the Social Security benefit is floored at zero and does not reduce a pension. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 67 + # A small Social Security benefit of about 167 a month. + social_security: 2_000 + pension_income: 24_000 + # 26,000 a year is above the Medicare Savings Program limit, so the + # full 2026 standard premium is paid: 202.90 x 12 = 2,434.80. + medicare_part_b_premium: 2_434.8 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # max(2,000 - 2,434.80, 0) + 24,000 = 24,000 (an unfloored premium would + # give 23,565.20). + ia_liheap_countable_unearned_income: 24_000 + +- name: Case 12, a rental loss is floored at zero per source and does not offset a pension. + period: 2026 + input: + people: + person1: + # Age 62 is below the Medicare and SSI aged thresholds. + age: 62 + pension_income: 18_000 + rental_income: -4_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # Appendix C p. 110 counts "Income received from rental properties"; a + # loss is not income received: 18,000 + max(-4,000, 0) = 18,000 (a signed + # sum, or a floor on the unearned total, would give 14,000). + ia_liheap_countable_unearned_income: 18_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml index 92ffe50854c..7c14aef1849 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml @@ -10,11 +10,12 @@ # Modeling: true when the household has a fixed source (social_security, ssi, # pension_income, railroad_benefits, veterans_benefits) and no other recorded # source. Recorded sources are every member's, at any age, including sources -# Iowa does not count: each earned source floored at zero, the countable -# unearned sources that are not fixed, child_support_received and FIP (read -# through tanf). A minor's Social Security is left out of the countable amount -# but still identifies a fixed source (Appendix C p. 114; section 4.40(3)(a) -# p. 14). Child support paid is a deduction, not an income source. +# Iowa does not count: each earned source and each countable unearned source +# that is not fixed, floored at zero so a loss cannot cancel another source, +# child_support_received and FIP (read through tanf). A minor's Social +# Security is left out of the countable amount but still identifies a fixed +# source (Appendix C p. 114; section 4.40(3)(a) p. 14). Child support paid is +# a deduction, not an income source. - name: Case 1, Social Security alone is fixed income. period: 2026 @@ -422,3 +423,47 @@ # no other source. ia_liheap_countable_income: 14_400 ia_liheap_fixed_income_only: true + +- name: Case 18, Social Security with a rental loss and interest is not fixed income only. + period: 2026 + input: + people: + person1: + age: 67 + social_security: 18_000 + rental_income: -500 + interest_income: 500 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + households: + household: + members: [person1] + state_code: IA + output: + # fixed 18,000; other sources max(-500, 0) + 500 = 500 > 0, so false. A + # signed sum (-500 + 500 = 0) would let the loss cancel the interest. + ia_liheap_fixed_income_only: false + +- name: Case 19, Social Security plus a self-employment profit is not fixed income only. + period: 2026 + input: + people: + person1: + age: 67 + social_security: 18_000 + self_employment_income: 3_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + households: + household: + members: [person1] + state_code: IA + output: + # fixed 18,000; other sources max(3,000, 0) = 3,000 > 0, so false. + ia_liheap_fixed_income_only: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/integration.yaml index 42211c2e6b6..92e863c430b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/integration.yaml @@ -13,8 +13,8 @@ # limit = 2 x guideline: 31,300 (1) / 42,300 (2) / 53,300 (3) / 64,300 (4). # Poverty points by income / guideline: <= 0.75 -> 8, <= 1.00 -> 6, # <= 1.25 -> 5, <= 2.00 -> 4. Benefit = 40 x points clipped to [80, 800]; -# propane and fuel oil households receive the flat 800, and kerosene follows -# fuel oil by inference. +# propane and fuel oil households that buy their own fuel receive the flat +# 800, and kerosene follows fuel oil by inference. - name: Case 1, retiree on Social Security in a detached gas-heated house. period: 2026 diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.py index cfc90913085..7a0ae6d4d22 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.py @@ -35,11 +35,12 @@ def formula(spm_unit, period, parameters): energy_burden = spm_unit("ia_liheap_has_energy_burden", period) heating_type = spm_unit("heating_type", period) types = heating_type.possible_values - # UNSPECIFIED pays nothing under the all-state convention (callers must - # send the fuel; the fuel row of the matrix cannot be scored without - # it). NONE means the home has no heating. Appendix D: a household whose - # heat "is obtained from solar cells, wind power, an extension cord, or - # a generator" has no energy burden. + # This implementation requires a known heating_type and treats + # UNSPECIFIED as ineligible, including when heat is included in the + # rent, because the fuel row of the matrix cannot be scored without + # it. NONE means the home has no heating. Appendix D: a household + # whose heat "is obtained from solar cells, wind power, an extension + # cord, or a generator" has no energy burden. fuel_known = ( (heating_type != types.UNSPECIFIED) & (heating_type != types.NONE) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap.py index b740f0d64f9..677efca7734 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap.py @@ -12,7 +12,9 @@ class ia_liheap(Variable): "Assistance heating benefit: the per-point amount times total payment " "matrix points, between the minimum and maximum benefit, or the flat " "deliverable fuel amount for liquid propane and fuel oil (including " - "kerosene) households. " + "kerosene) households that buy the fuel themselves. A household whose " + "heat is included in its rent receives the matrix amount whatever the " + "building's fuel. " "The benefit does not depend on the heating bill. Verified for FY2026; " "earlier years use model parameter backfilling and are unverified " "historical estimates. Crisis assistance, cooling, heating equipment " @@ -21,8 +23,9 @@ class ia_liheap(Variable): defined_for = "ia_liheap_eligible" reference = ( # Section 5.41, Section 11.00 Payments, Section 12.20 Deliverable Fuel - # Vendors and Appendix I, Award Calculation. - # PDF pages 20, 42, 47, 137 + # Vendors, Appendix D Energy Burden Table and Appendix I, Award + # Calculation. + # PDF pages 20, 42, 47, 122, 137 "https://hhs.iowa.gov/media/17262/download?inline#page=20", "https://liheapch.acf.gov/docs/2026/benefits-matricies/IA_BenefitMatrix_Heat-Crisis_2026.pdf#page=1", # Section 2.6, minimum $80 and maximum $800. @@ -49,9 +52,19 @@ def formula(spm_unit, period, parameters): # oil for both the fuel points and the flat amount; this is an # inference, as the manual has no kerosene row. deliverable_fuel = (fuel == fuels.PROPANE) | (fuel == fuels.FUEL_OIL) + # The flat amount is limited to households that buy the deliverable + # fuel themselves; this is an inference. The draft plan (page 48) ties + # it to "deliverable fuel customers" and Section 11.00 (page 42) raises + # "the deliverable fuel benefit". Appendix D (page 122) pays a + # household whose heat is included in its rent through the electric + # vendor "because gas usage is indeterminate" or directly, so such a + # tenant does not buy the fuel and takes the matrix amount, including + # the heat-in-rent targeting factor. + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + buys_deliverable_fuel = deliverable_fuel & ~heat_in_rent # Appendix I: "$40 per point"; "The minimum benefit an approved LIHEAP # customer may receive is $80, and the maximum is $800." Section 5.41: # the benefit "is determined by a matrix and does not depend on the # actual amount owed by the household", so there is no bill cap. matrix_benefit = clip(p.amount_per_point * points, p.min_benefit, p.max_benefit) - return where(deliverable_fuel, p.deliverable_fuel_amount, matrix_benefit) + return where(buys_deliverable_fuel, p.deliverable_fuel_amount, matrix_benefit) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py index 5b309d8fc0a..cb11edaea1c 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py @@ -13,14 +13,14 @@ class ia_liheap_countable_unearned_income(Variable): "distributions, railroad retirement, unemployment and workers' " "compensation, strike benefits, disability and veterans' benefits, " "alimony, rental income, interest, dividends and gambling winnings. " - "Approximations: Social Security is counted gross; Iowa counts the " - "amount paid net of Medicare premiums and SSA repayments, netting the " - "Medicare Part B premium is deferred, as in Kansas, and SSA repayments " - "have no input. All interest is counted although " - "Iowa excludes interest on bank accounts; dividends are counted in full " - "although Iowa ignores a combined annual total under $120. Social " - "Security paid to members under 18 is recorded at $0.00 and is " - "excluded here with all other under-18 income." + "Each source is floored at zero, so a loss in one source (such as a " + "rental loss) does not offset other income. Social Security is counted " + "net of the out-of-pocket Medicare Part B premium; SSA repayments and " + "Part D premiums have no input. Approximations: all interest is counted " + "although Iowa excludes interest on bank accounts; dividends are " + "counted in full although Iowa ignores a combined annual total under " + "$120. Social Security paid to members under 18 is recorded at $0.00 " + "and is excluded here with all other under-18 income." ) reference = ( # Sections 6.10 and 6.20 and Appendix C rows for Alimony, Disability @@ -30,16 +30,32 @@ class ia_liheap_countable_unearned_income(Variable): # Compensation. # PDF pages 25-26, 90, 94-96, 99, 103, 109-111, 114-115, 117-119, 121 "https://hhs.iowa.gov/media/17262/download?inline#page=25", - # Section 1.9. - "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=6", + # Sections 1.8-1.9. + # PDF pages 5-6 + "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=5", ) defined_for = StateCode.IA def formula(person, period, parameters): p = parameters(period).gov.states.ia.hhs.liheap.income - # ssi is a monthly variable; add() sums the twelve months. - unearned = add(person, period, p.sources.unearned) + # Plan Section 1.8 (page 5) and Section 6.20 (page 26) count gross + # income, and Appendix C Rental Income (page 110) counts "Income + # received from rental properties". Each source is floored at zero, so + # a loss in one source cannot offset another. + unearned = 0 + for source in p.sources.unearned: + # ADD sums monthly sources such as ssi over the year. + unearned = unearned + max_(person(source, period, options=[ADD]), 0) + # Plan Section 1.9 (page 6) counts Social Security Administration + # benefits "Excluding MediCare deduction". Section 6.10 (page 25) and + # Appendix C, Social Security Benefits (page 114), count "the net + # amount" and use the amount "paid by check or direct deposit" as shown + # on the SSA-1099. medicare_part_b_premium is zero for a person who is + # not enrolled and excludes the share a Medicare Savings Program pays, + # which is not withheld from the check. + premium = person("medicare_part_b_premium", period) + social_security = max_(person("social_security", period) - premium, 0) age = person("age", period) # Appendix C, Social Security Benefits: "Only count for HH members 18 # and older"; payments to members under 18 are recorded at $0.00. - return where(age >= p.countable_min_age, unearned, 0) + return where(age >= p.countable_min_age, unearned + social_security, 0) diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py index e2e7cefb81e..860d3db5906 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py @@ -47,16 +47,13 @@ def formula(spm_unit, period, parameters): # follows for FY2026. The FY27 manual and Program Notice 27-01 # (effective October 1, 2026) reword the factor to "no other countable # sources of income"; that change is not modeled because only FY2026 - # is verified. Earned sources are floored per source as in the - # countable amount. Appendix C adds that child support (page 92) and - # FIP (page 98) are not fixed income although neither is counted. - earned = 0 - for source in p.earned: - earned = earned + max_(person(source, period), 0) + # is verified. Earned and unearned sources are floored per source as + # in the countable amounts, so a loss (such as a rental loss) cannot + # cancel another source. Appendix C adds that child support (page 92) + # and FIP (page 98) are not fixed income although neither is counted. other_unearned = [source for source in p.unearned if source not in p.fixed] - non_fixed = ( - spm_unit.sum(earned) - + add(spm_unit, period, other_unearned) - + add(spm_unit, period, p.other_recorded) - ) + other_income = 0 + for source in list(p.earned) + other_unearned: + other_income = other_income + max_(person(source, period, options=[ADD]), 0) + non_fixed = spm_unit.sum(other_income) + add(spm_unit, period, p.other_recorded) return (fixed > 0) & (non_fixed == 0) From 157a10db6d08b784dd8818f5f87203c660e30798 Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 26 Oct 2026 14:11:22 -0400 Subject: [PATCH 8/8] Document Part B-only premium netting; leave the coverage line to PR 10303 - Explain why Iowa subtracts no Part A premium (42 U.S.C. 426(a)) - Registry note: the flat amount goes to households that buy propane or fuel oil - Keep the LIHEAP coverage line as on main; PR 10303 adds IA there Co-Authored-By: Claude Opus 5.5 --- policyengine_us/programs.yaml | 4 ++-- .../liheap/income/ia_liheap_countable_unearned_income.py | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index e4831c72b18..e9917864499 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -987,7 +987,7 @@ programs: category: Benefits agency: HHS status: partial - coverage: AR, AL, DC, GA, Riverside County, MA, FL, IL, IA, IN, KS, KY, MD, MS, NC, NE, NJ, NY, OH, OK, OR, SD, RI, TX + coverage: AR, AL, DC, GA, Riverside County, MA, FL, IL, IN, KS, KY, MD, MS, NC, NE, NJ, NY, OH, OK, OR, SD, RI, TX state_implementations: - state: AL status: partial @@ -1067,7 +1067,7 @@ programs: variable: ia_liheap parameter_prefix: gov.states.ia.hhs.liheap verified_years: "2026" - notes: FY2026 Regular Assistance heating benefit from the points matrix, with a flat amount for propane and fuel oil. Crisis assistance, cooling and weatherization are out of scope. + notes: FY2026 Regular Assistance heating benefit from the points matrix, with a flat amount for households that buy propane or fuel oil. Crisis assistance, cooling and weatherization are out of scope. - state: IN status: partial name: Indiana EAP diff --git a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py index cb11edaea1c..43d844cb180 100644 --- a/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py @@ -16,7 +16,10 @@ class ia_liheap_countable_unearned_income(Variable): "Each source is floored at zero, so a loss in one source (such as a " "rental loss) does not offset other income. Social Security is counted " "net of the out-of-pocket Medicare Part B premium; SSA repayments and " - "Part D premiums have no input. Approximations: all interest is counted " + "Part D premiums have no input. No Part A premium is subtracted, " + "because a Social Security beneficiary aged 65 or older is entitled to " + "Part A without one under 42 U.S.C. 426(a), so none is withheld from " + "the check. Approximations: all interest is counted " "although Iowa excludes interest on bank accounts; dividends are " "counted in full although Iowa ignores a combined annual total under " "$120. Social Security paid to members under 18 is recorded at $0.00 " @@ -52,7 +55,9 @@ def formula(person, period, parameters): # amount" and use the amount "paid by check or direct deposit" as shown # on the SSA-1099. medicare_part_b_premium is zero for a person who is # not enrolled and excludes the share a Medicare Savings Program pays, - # which is not withheld from the check. + # which is not withheld from the check. Only Part B comes off: under + # 42 U.S.C. 426(a) a Social Security beneficiary aged 65 or older is + # entitled to Part A, so no Part A premium is withheld. premium = person("medicare_part_b_premium", period) social_security = max_(person("social_security", period) - premium, 0) age = person("age", period)