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..04a55b03bce --- /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) #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) + 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..b2c563e08d7 --- /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) + 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..c22f5b68b8a --- /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.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 new file mode 100644 index 00000000000..e2ef1b39169 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/earned.yaml @@ -0,0 +1,34 @@ +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 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 + # 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 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 new file mode 100644 index 00000000000..4d7608679cb --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/fixed.yaml @@ -0,0 +1,29 @@ +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. +values: + 2025-10-01: + - 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 + # retirement annuities are pensions in the Definitions' fixed income list. + - railroad_benefits + - 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, 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 new file mode 100644 index 00000000000..a91642e2b5d --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/income/sources/unearned.yaml @@ -0,0 +1,74 @@ +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. +# 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: + # 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 + # 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 + # (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". + # 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. + - total_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 + # 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 + # 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, 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 new file mode 100644 index 00000000000..071b27e3af2 --- /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) + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - 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 new file mode 100644 index 00000000000..30a9413f76c --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/hhs/liheap/payment/deliverable_fuel_amount.yaml @@ -0,0 +1,26 @@ +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. 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 + +metadata: + unit: currency-USD + period: year + label: Iowa LIHEAP deliverable fuel benefit + reference: + - 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 new file mode 100644 index 00000000000..b615edba18a --- /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 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - 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: + 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..137e81e17a6 --- /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) + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - 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 new file mode 100644 index 00000000000..f32dc59ddb5 --- /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) + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - 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 new file mode 100644 index 00000000000..5c2fbc5dd9f --- /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 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - 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 new file mode 100644 index 00000000000..1545b884f2b --- /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, 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 new file mode 100644 index 00000000000..ca0bc8df5fd --- /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, 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 new file mode 100644 index 00000000000..89a5b4e79a4 --- /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) + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - 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 new file mode 100644 index 00000000000..4b569454a05 --- /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), 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 new file mode 100644 index 00000000000..c1d7d95b655 --- /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) + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - 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 new file mode 100644 index 00000000000..02d551a2fbf --- /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, 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 new file mode 100644 index 00000000000..b39e22c4d2d --- /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, 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 new file mode 100644 index 00000000000..cc21436c155 --- /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 + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - 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 new file mode 100644 index 00000000000..b63aaf938cd --- /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, 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 new file mode 100644 index 00000000000..cd2dd0d98f7 --- /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, 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 new file mode 100644 index 00000000000..e35e1dfaad9 --- /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) + href: https://hhs.iowa.gov/media/17262/download?inline#page=137 + - 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 new file mode 100644 index 00000000000..43ea542660c --- /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), 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/programs.yaml b/policyengine_us/programs.yaml index 86c8759b02d..e9917864499 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -1060,6 +1060,14 @@ programs: full_name: Illinois Low Income Home Energy Assistance Program variable: il_liheap parameter_prefix: gov.states.il.dceo.liheap + - 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 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/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..74d2cac2110 --- /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: 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 + 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..0b2f530cb0b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.yaml @@ -0,0 +1,146 @@ +# 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 + +- name: Case 6, railroad retiree with 35,000 alone is over the one-person limit. + period: 2026 + input: + people: + person1: + # Age 62 is below the SSI aged threshold, so the model imputes no SSI. + age: 62 + 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/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..5b77b0f293f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/ia_liheap.yaml @@ -0,0 +1,467 @@ +# 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 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). 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 + 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 flat 800. + 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 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: 800 + +- 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 with kerosene heat pay the flat 800. + 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, + # 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: 800 + +- 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 + +- 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 + +- 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/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..87ea36076c6 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.yaml @@ -0,0 +1,169 @@ +# 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, 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 + 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 + +- 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 new file mode 100644 index 00000000000..f716305d45b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_income.yaml @@ -0,0 +1,225 @@ +# 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, 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 + 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 + +- 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 new file mode 100644 index 00000000000..d4ee8ef2ff2 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.yaml @@ -0,0 +1,298 @@ +# 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 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] + heating_type: NATURAL_GAS + gas_expense: 1_100 + households: + household: + members: [person1] + state_code: IA + output: + # 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 + 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 + +- name: Case 9, railroad retirement is counted. + 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. 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 + +- 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 new file mode 100644 index 00000000000..7c14aef1849 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.yaml @@ -0,0 +1,469 @@ +# 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) 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 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 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 + 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: + # 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. + 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: + # 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. + 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: + # 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. + 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: + # 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. + 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; 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. + 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: + # No fixed source (fixed 0). + 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: + # 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. + 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 source test reads every member's recorded sources regardless of age + # (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 + +- 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: + # 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. + 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: + # 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. + 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). + # 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 + +- 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 + +- 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 new file mode 100644 index 00000000000..92e863c430b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/integration.yaml @@ -0,0 +1,344 @@ +# 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 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 + 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 and receives the flat 800. + 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 + # 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 (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 + # 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, 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 + 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..67cf0903202 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.yaml @@ -0,0 +1,255 @@ +# 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 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 + +- 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..7a0ae6d4d22 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_eligible.py @@ -0,0 +1,49 @@ +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, 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). + # PDF pages 5, 8 + "https://liheapch.acf.gov/docs/2026/state-plans/IA_Plan_2026.pdf#page=5", + ) + 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 + # 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) + & (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..b92b60673fc --- /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. + "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..215b93a81c8 --- /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 = ( + # 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", + ) + 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..389addc28df --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_eligible.py @@ -0,0 +1,26 @@ +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 and Appendix I. + # PDF pages 18, 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..28d6c4dd056 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/eligibility/ia_liheap_income_limit.py @@ -0,0 +1,28 @@ +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 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", + "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..677efca7734 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/ia_liheap.py @@ -0,0 +1,70 @@ +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 (including " + "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 " + "repair and weatherization are out of scope." + ) + defined_for = "ia_liheap_eligible" + reference = ( + # Section 5.41, Section 11.00 Payments, Section 12.20 Deliverable Fuel + # 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. + "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) + 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". + # 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) + # 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(buys_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..bd006dee33b --- /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 = ( + # 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 new file mode 100644 index 00000000000..aa594cc392a --- /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 = ( + # 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 + + 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 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. + 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..3109a7377ba --- /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 = ( + # 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 + + 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..b032334e1b5 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_earned_income.py @@ -0,0 +1,51 @@ +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 " + "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 = ( + # 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 + + 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: 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 + # 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 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 new file mode 100644 index 00000000000..18744d55721 --- /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 = ( + # 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 + + 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..43d844cb180 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_countable_unearned_income.py @@ -0,0 +1,66 @@ +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, railroad retirement, unemployment and workers' " + "compensation, strike benefits, disability and veterans' benefits, " + "alimony, rental income, interest, dividends and gambling winnings. " + "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. 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 " + "and is excluded here with all other under-18 income." + ) + reference = ( + # 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", + # 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 + # 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. 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) + # 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 + 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 new file mode 100644 index 00000000000..860d3db5906 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/income/ia_liheap_fixed_income_only.py @@ -0,0 +1,59 @@ +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 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 " + "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 = ( + # 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.sources + person = spm_unit.members + # 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 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] + 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) 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..c991f3c058a --- /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. + "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..eb560a12c2c --- /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 = ( + # 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 + + 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..b0924b5af31 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/hhs/liheap/payment/ia_liheap_poverty_points.py @@ -0,0 +1,44 @@ +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). A " + "household with no counted member has no guideline and scores zero." + ) + reference = ( + # 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 + + 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. + 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=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. + # 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) 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..2d284319c01 --- /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 = ( + # 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 + # 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