diff --git a/changelog.d/az-liheap-wave1c.added.md b/changelog.d/az-liheap-wave1c.added.md new file mode 100644 index 00000000000..bcbfcaef0e6 --- /dev/null +++ b/changelog.d/az-liheap-wave1c.added.md @@ -0,0 +1 @@ +Add Arizona LIHEAP Standard benefit (az_liheap): FY2026 regular benefit from the points worksheet with the greater of 60% state median income or 150% federal poverty guideline income test and TANF or SNAP categorical eligibility. diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/eligibility/categorical_programs.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/eligibility/categorical_programs.yaml new file mode 100644 index 00000000000..44c5fc2ee21 --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/eligibility/categorical_programs.yaml @@ -0,0 +1,21 @@ +description: Arizona treats a household as categorically eligible when a member receives one of these benefits under the Low Income Home Energy Assistance Program. +# Section VI.A.6.b.i: "If one (1) Household member's current enrollment in Cash +# Assistance or Nutrition Assistance is verified, the Household will be +# considered categorically eligible." A positive modeled benefit stands for +# current enrollment; snap and tanf are take-up gated, and tanf includes +# Arizona Cash Assistance (az_tanf). The FY2026 plan (Section 1.4) answers No +# for SSI and for means-tested veterans programs. +values: + 2025-08-25: + - snap + - tanf + +metadata: + unit: list + period: year + label: Arizona LIHEAP categorical eligibility programs + reference: + - title: "Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Categorical Eligibility definition and Section VI.A.6.a-b #page 3-4,7-8" + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=3 + - title: Arizona LIHEAP FY2026 State Plan, Sections 1.4 and 1.4a (TANF and SNAP yes; SSI and means-tested veterans programs no) + href: https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/eligibility/fpg_rate.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/eligibility/fpg_rate.yaml new file mode 100644 index 00000000000..09375227c23 --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/eligibility/fpg_rate.yaml @@ -0,0 +1,13 @@ +description: Arizona limits countable household income to this multiple of the federal poverty guidelines, when that arm exceeds the state median income arm, under the Low Income Home Energy Assistance Program. +values: + 2025-08-25: 1.5 + +metadata: + unit: /1 + period: year + label: Arizona LIHEAP federal poverty guideline rate + reference: + - title: Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Section VI.A.6.c.i (gross income at or below the greater of 60% of the SMI or 150% of the FPL) + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=8 + - title: Arizona LIHEAP FY2026 State Plan, Section 2.1 (household sizes 10 and over at 150.00% of the HHS Poverty Guidelines) + href: https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/eligibility/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/eligibility/fpg_year_lag.yaml new file mode 100644 index 00000000000..a1c451ebe0a --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/eligibility/fpg_year_lag.yaml @@ -0,0 +1,22 @@ +description: Arizona uses federal poverty guidelines from this many years before the modeled program year for the income limit under the Low Income Home Energy Assistance Program. +# Verified for FFY2026: revision 13's Appendix A household sizes 10 to 12 and +# its $687 additional-person increment reproduce with the 2025 guideline +# (15,650 plus 5,500 per additional person), not the 2026 guideline. Revision +# 16A's Appendix A prints the FFY2027 limits, whose sizes 10 to 12 and $710 +# increment reproduce with the 2026 guideline, the same one-year lag. This is a +# relative year selection, not a fixed guideline year. Earlier Arizona policy +# is unverified. +values: + 2025-08-25: 1 + +metadata: + unit: year + period: year + label: Arizona LIHEAP federal poverty guideline year lag + reference: + - title: Arizona DES LIHEAP Policy, revision 13 (effective August 25, 2025), Appendix A Income Eligibility by Household Size (FFY2026 limits, archived copy) + href: https://web.archive.org/web/20251030203752/https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=37 + - title: Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Appendix A LIHEAP Income Eligibility by Household Size (FFY2027 limits) + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=39 + - title: LIHEAP Clearinghouse, FFY2026 poverty guidelines by household size (2025 HHS guideline) + href: https://liheapch.acf.gov/profiles/povertytables/FY2026/popstate.htm diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/eligibility/max_table_size.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/eligibility/max_table_size.yaml new file mode 100644 index 00000000000..106db4cae44 --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/eligibility/max_table_size.yaml @@ -0,0 +1,11 @@ +description: Arizona tabulates monthly income limits through this household size, with an increment for each additional person, under the Low Income Home Energy Assistance Program. +values: + 2025-08-25: 12 + +metadata: + unit: person + period: year + label: Arizona LIHEAP maximum tabulated household size + reference: + - title: Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Appendix A LIHEAP Income Eligibility by Household Size (sizes 1 to 12 and "EACH ADDL OVER 12 +$710" for FFY2027) + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=39 diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/eligibility/smi_rate.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/eligibility/smi_rate.yaml new file mode 100644 index 00000000000..c356939faf1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/eligibility/smi_rate.yaml @@ -0,0 +1,15 @@ +description: Arizona limits countable household income to this share of the state median income, when that arm exceeds the federal poverty guideline arm, under the Low Income Home Energy Assistance Program. +values: + 2025-08-25: 0.6 + +metadata: + unit: /1 + period: year + label: Arizona LIHEAP state median income rate + reference: + - title: Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Section VI.A.6.c.i (gross income at or below the greater of 60% of the SMI or 150% of the FPL) + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=8 + - title: Arizona LIHEAP FY2026 State Plan, Section 2.1 (household sizes 1 to 9 at 60.00% of State Median Income) + href: https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=9 + - title: LIHEAP Clearinghouse, Arizona 60% of State Median Income, FFY2026 (four-person amount $64,789) + href: https://liheapch.acf.gov/profiles/povertytables/FY2026/azsmi.htm diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/income/adult_age.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/income/adult_age.yaml new file mode 100644 index 00000000000..881aa92a750 --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/income/adult_age.yaml @@ -0,0 +1,11 @@ +description: Arizona counts the income of household members at or above this age under the Low Income Home Energy Assistance Program. +values: + 2025-08-25: 18 + +metadata: + unit: year + period: year + label: Arizona LIHEAP adult age + reference: + - title: "Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Section VI.A.6.d.ii (income from all household members aged 18 and older) and Section VII.A.1.c.ii #page 8,13" + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=8 diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/income/child_unearned_income_counted.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/income/child_unearned_income_counted.yaml new file mode 100644 index 00000000000..407e4dc7508 --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/income/child_unearned_income_counted.yaml @@ -0,0 +1,24 @@ +description: Arizona counts the unearned income of household members under the adult age when this is true under the Low Income Home Energy Assistance Program. +# Revision 13, Section VI.A.6.e.ii.E: "Income from Household members under the +# age of 18 is not considered Countable Income." Revision 15 (effective March +# 23, 2026) rewrote the sentence to "Earned income from Household members under +# the age of 18 is not considered Countable Income", and revision 16A keeps +# that wording. Earned income of members under 18 is excluded under every +# revision. +# NOTE: Counting a child's unearned income (SSI, survivor benefits, child +# support) from March 23, 2026 is an inference from that narrowed exclusion. +# The revisions do not say so directly, and Section VI.A.6.d.ii still says +# that the income of members aged 18 and older is household income. +values: + 2025-08-25: false + 2026-03-23: true + +metadata: + unit: bool + period: year + label: Arizona LIHEAP counts unearned income of members under 18 + reference: + - title: Arizona DES LIHEAP Policy, revision 13 (effective August 25, 2025), Section VI.A.6.e.ii.E (archived copy) + href: https://web.archive.org/web/20251030203752/https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=9 + - title: Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Section VI.A.6.e.ii.E (the revision 15 wording of March 23, 2026) + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/income/sources/earned.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/income/sources/earned.yaml new file mode 100644 index 00000000000..0f6f65ebaba --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/income/sources/earned.yaml @@ -0,0 +1,27 @@ +description: Arizona uses these earned income sources to approximate countable household earnings under the Low Income Home Energy Assistance Program. +# Section VI.A.6.e.ii.A counts "Earned income from employment, including +# self-employment (includes wages, salaries, tips, etc.)"; the plan's Section +# 1.9 also checks contract income, commissions, WIA employment income and work +# study, which existing inputs report as wages. Section VII.A.1.c.ii.B counts +# "Income from self-employment or sole-proprietorship" and Section +# VII.A.1.c.iii.B.1 accepts a "Schedule C (Profit and Loss Statement)"; neither +# says whether a loss offsets other income, so the existing net self-employment +# inputs are used without a further expense deduction and each is floored at +# zero in the formula. +values: + 2025-08-25: + - employment_income + - self_employment_income + - sstb_self_employment_income + - partnership_s_corp_income + - farm_operations_income + +metadata: + unit: list + period: year + label: Arizona LIHEAP countable earned income sources + reference: + - title: "Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Section VI.A.6.e.ii.A, Section VII.A.1.c.ii.A-B and Section VII.A.1.c.iii.B.1 #page 8,13-14" + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=8 + - title: "Arizona LIHEAP FY2026 State Plan, Section 1.9 (wages, self-employment income, contract income, commissions, WIA employment income and work study counted) #page 6-7" + href: https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/income/sources/unearned.yaml new file mode 100644 index 00000000000..fd82e590676 --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/income/sources/unearned.yaml @@ -0,0 +1,60 @@ +description: Arizona uses these person-level unearned income sources to determine gross household income under the Low Income Home Energy Assistance Program. +# Section VI.A.6.e.ii.B-D counts "Unearned income, including Social Security +# and TANF benefits, alimony, child support, gambling profits, and retirement +# income", "Income from Unemployment Insurance" and "Consistent income received +# from other sources"; the plan's Section 1.9 checks the full list. Each source +# is floored at zero in the formula, so a rental loss does not offset other +# income. Gambling profits (winnings less losses) are added in the formula. +# Cash Assistance (tanf) is a household benefit and is added in +# az_liheap_countable_income, which also subtracts the household's Medicare +# Part A and Part B premiums. +values: + 2025-08-25: + - total_unemployment_compensation + - strike_benefits + - social_security + - ssi + - pension_income + # Section VI.A.6.e.ii.B "retirement income": military retirement pay and + # survivor benefit payments. + - military_retirement_income + # retirement_distributions already sums taxable and tax-exempt IRA, 401(k), + # SEP, 403(b) and Keogh distributions. + - retirement_distributions + # Railroad Retirement annuities are "retirement income" (Section + # VI.A.6.e.ii.B, page 9) and "retirement benefits" (Section + # VII.A.1.c.ii.D, page 14); plan Section 1.9 checks "Retirement / pension + # benefits" (page 6). + - railroad_benefits + - veterans_benefits + - alimony_income + - child_support_received + # Plan Section 1.9 checks "Rental income" (page 6); farm rent is counted + # with it. + - rental_income + - farm_rent_income + - interest_income + - ordinary_dividend_income + - general_assistance + # Employment disability insurance: plan Section 1.9 checks "Insurance + # payments made directly to the insured" (page 6), and Gross Income is + # "The total Household income for all Household members before taxes and + # other deductions" (page 4). + - disability_benefits + # Plan Section 1.9 neither lists nor excludes workers' compensation; the + # Gross Income definition (page 4) and Section VI.A.6.e.ii.D "Consistent + # income received from other sources" (page 9) support counting it. + - workers_compensation + # Cash gifts over $50, jury duty, legal settlements, other insurance + # payments to the insured, mortgage or sales-contract payments, royalties + # and foster or adoption care payments have no model input. + +metadata: + unit: list + period: year + label: Arizona LIHEAP countable unearned income sources + reference: + - title: "Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Gross Income definition, Section VI.A.6.e.ii.B-D and Section VII.A.1.c.ii.C-F #page 4,9,14" + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=4 + - title: "Arizona LIHEAP FY2026 State Plan, Section 1.9 (unemployment insurance, strike pay, SSA benefits excluding the Medicare deduction, SSI, retirement and pension, general assistance, TANF, rental income, alimony, child support, interest, dividends or royalties, insurance payments made directly to the insured and VA benefits counted) #page 6-7" + href: https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/payment/amount.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/payment/amount.yaml new file mode 100644 index 00000000000..e57ba6b0164 --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/payment/amount.yaml @@ -0,0 +1,41 @@ +description: Arizona pays this annual Standard benefit by total benefit matrix points under the Low Income Home Energy Assistance Program. +# Worksheet Section 4: "1 - 4 $160, 5 - 8 $320, 9 - 12 $480, 13 or over $640". +# Thresholds are the band starts, read with the default lower-inclusive calc +# on integer points. The first threshold is 0 rather than the worksheet's 1: +# an income-eligible household always scores at least one income point, and +# the plan (Section 2.6) states a $160 minimum benefit, so a categorically +# eligible household above 100% of the income limit with no burden or need +# points receives the $160 minimum rather than nothing. +brackets: + - threshold: + 2024-10-12: 0 + amount: + 2024-10-12: 160 + - threshold: + 2024-10-12: 5 + amount: + 2024-10-12: 320 + - threshold: + 2024-10-12: 9 + amount: + 2024-10-12: 480 + - threshold: + 2024-10-12: 13 + amount: + 2024-10-12: 640 + +metadata: + type: single_amount + threshold_unit: /1 + amount_unit: currency-USD + period: year + label: Arizona LIHEAP Standard benefit by points + reference: + - title: Arizona LIHEAP Eligibility Worksheet, $640 Limit (effective October 12, 2024), Section 4 Payment Matrix + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1 + - title: Arizona DES, Low Income Home Energy Assistance Program page (benefit by points table) + href: https://des.az.gov/liheap + - title: Arizona LIHEAP FY2026 State Plan, Section 2.6 (minimum benefit $160, maximum benefit $640) + href: https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=10 + - title: "Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Section VII.B.1.a (up to $640 for a standard payment), Section VII.B.4.b and Appendix B Section 4 Benefit Calculation #page 31,35,41" + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=31 diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/points/child_age_limit.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/points/child_age_limit.yaml new file mode 100644 index 00000000000..5e05dc74841 --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/points/child_age_limit.yaml @@ -0,0 +1,17 @@ +description: Arizona awards an energy need point when a household member is younger than this age under the Low Income Home Energy Assistance Program. +# The worksheet row is "Child age 6 or younger" and the policy says "Aged six +# (6) and under", so the point applies below age 7. +values: + 2024-10-12: 7 + +metadata: + unit: year + period: year + label: Arizona LIHEAP young child age limit + reference: + - title: Arizona LIHEAP Eligibility Worksheet, $640 Limit (effective October 12, 2024), Section 3 Energy Need (Child age 6 or younger, 1 point) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1 + - title: 'Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Section VI.B.1.d.i (household members "Aged six (6) and under") and Appendix B Section 3 (Child age 6 or younger, 1 point) #page 10,41' + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=10 + - title: Arizona LIHEAP FY2026 State Plan, Section 2.4 (additional points for children 6 years old and under) + href: https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=10 diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/points/elderly_age.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/points/elderly_age.yaml new file mode 100644 index 00000000000..b6930ef6db5 --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/points/elderly_age.yaml @@ -0,0 +1,15 @@ +description: Arizona awards an energy need point when a household member is at or above this age under the Low Income Home Energy Assistance Program. +values: + 2025-08-25: 60 + +metadata: + unit: year + period: year + label: Arizona LIHEAP elderly age + reference: + - title: Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Section VI.B.1.d.ii (household members "Aged 60 and over") + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=10 + - title: Arizona LIHEAP FY2026 State Plan, Section 2.4 (additional points for the elderly, 60+ years old) + href: https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=10 + - title: Arizona LIHEAP Eligibility Worksheet, $640 Limit (effective October 12, 2024), Section 3 Energy Need (Elderly, 1 point) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/points/energy_burden.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/points/energy_burden.yaml new file mode 100644 index 00000000000..54939f380ab --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/points/energy_burden.yaml @@ -0,0 +1,50 @@ +description: Arizona awards these benefit matrix points by the ratio of energy cost to gross income under the Low Income Home Energy Assistance Program. +# Worksheet rows "5% or less 0 points, 6% - 10% 1 point, 11% - 15% 2 points, +# 16% - 20% 3 points, 21% - 25% 4 points, 26% - 30% 5 points, 31% or over 6 +# points". "5% or less" is band-top wording and the DES page places its 17.5% +# example in the 16% to 20% band, so the thresholds are the band tops and +# callers use calc(burden, right=True). The -inf first threshold keeps a zero +# burden in the first band. +brackets: + - threshold: + 2024-10-12: -.inf + amount: + 2024-10-12: 0 + - threshold: + 2024-10-12: 0.05 + amount: + 2024-10-12: 1 + - threshold: + 2024-10-12: 0.1 + amount: + 2024-10-12: 2 + - threshold: + 2024-10-12: 0.15 + amount: + 2024-10-12: 3 + - threshold: + 2024-10-12: 0.2 + amount: + 2024-10-12: 4 + - threshold: + 2024-10-12: 0.25 + amount: + 2024-10-12: 5 + - threshold: + 2024-10-12: 0.3 + amount: + 2024-10-12: 6 + +metadata: + type: single_amount + threshold_unit: /1 + amount_unit: /1 + period: year + label: Arizona LIHEAP energy burden points + reference: + - title: Arizona LIHEAP Eligibility Worksheet, $640 Limit (effective October 12, 2024), Section 2 Energy Burden + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1 + - title: Arizona DES, Low Income Home Energy Assistance Program page (energy burden formula and the 350 / 2,000 = 17.5% example) + href: https://des.az.gov/liheap + - title: Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Appendix B Section 2 Energy Burden + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=40 diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/points/income_ratio.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/points/income_ratio.yaml new file mode 100644 index 00000000000..fe57e24eb91 --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/points/income_ratio.yaml @@ -0,0 +1,47 @@ +description: Arizona awards these benefit matrix points by the ratio of gross income to the income limit for the household size under the Low Income Home Energy Assistance Program. +# Worksheet rows "0% - 20% 5 points, 21% - 40% 4 points, 41% - 60% 3 points, +# 61% - 80% 2 points, 81% - 100% 1 point". The DES page computes the ratio as +# "Total Monthly Gross Income Divided by the Monthly Gross Income Limit" and +# places its 44% example in the 41% to 60% band, so the thresholds are the +# band tops and callers use calc(ratio, right=True). The -inf first threshold +# keeps a zero ratio in the first band. No row is printed above 100%: only a +# categorically eligible household can reach it, and it scores 0 points. +brackets: + - threshold: + 2024-10-12: -.inf + amount: + 2024-10-12: 5 + - threshold: + 2024-10-12: 0.2 + amount: + 2024-10-12: 4 + - threshold: + 2024-10-12: 0.4 + amount: + 2024-10-12: 3 + - threshold: + 2024-10-12: 0.6 + amount: + 2024-10-12: 2 + - threshold: + 2024-10-12: 0.8 + amount: + 2024-10-12: 1 + - threshold: + 2024-10-12: 1 + amount: + 2024-10-12: 0 + +metadata: + type: single_amount + threshold_unit: /1 + amount_unit: /1 + period: year + label: Arizona LIHEAP income points + reference: + - title: Arizona LIHEAP Eligibility Worksheet, $640 Limit (effective October 12, 2024), Section 1 Income Eligibility + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1 + - title: Arizona DES, Low Income Home Energy Assistance Program page (income points formula and the 2,000 / 4,535 = 44% example) + href: https://des.az.gov/liheap + - title: Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Appendix B Section 1 Income Eligibility + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=40 diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/points/portable_fuel.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/points/portable_fuel.yaml new file mode 100644 index 00000000000..e5d4f31dae8 --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/points/portable_fuel.yaml @@ -0,0 +1,15 @@ +description: Arizona adds this many benefit matrix points for households that use portable fuel under the Low Income Home Energy Assistance Program. +values: + 2024-10-12: 2 + +metadata: + unit: /1 + period: year + label: Arizona LIHEAP portable fuel points + reference: + - title: Arizona LIHEAP Eligibility Worksheet, $640 Limit (effective October 12, 2024), Section 2 note (add 2 points for portable fuels) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1 + - title: Arizona DES, Low Income Home Energy Assistance Program page ("2 points are added for households that utilize deliverable/portal fuel") + href: https://des.az.gov/liheap + - title: "Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Portable Fuel definition (propane, wood, coal, fuel for gas generators and pellets) and Appendix B Section 2 (Deliverable/Portable Fuel, 2 points) #page 5,40" + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/az/des/liheap/points/vulnerable_member.yaml b/policyengine_us/parameters/gov/states/az/des/liheap/points/vulnerable_member.yaml new file mode 100644 index 00000000000..92ea754da11 --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/des/liheap/points/vulnerable_member.yaml @@ -0,0 +1,17 @@ +description: Arizona awards this many benefit matrix points for each energy need category present in the household under the Low Income Home Energy Assistance Program. +# Worksheet Section 3: Elderly 1 point, Disabled 1 point, Veteran 1 point, +# Child age 6 or younger 1 point, "(Only one point per category allowed)". +values: + 2024-10-12: 1 + +metadata: + unit: /1 + period: year + label: Arizona LIHEAP energy need points per category + reference: + - title: Arizona LIHEAP Eligibility Worksheet, $640 Limit (effective October 12, 2024), Section 3 Energy Need + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1 + - title: Arizona LIHEAP FY2026 State Plan, Section 2.4 ("A household may receive one point for each category, if eligible") + href: https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=10 + - title: "Arizona DES LIHEAP Policy, revision 16A (effective July 1, 2026), Section VI.B.1.d (benefit factors) and Appendix B Section 3 Energy Need (one point per category) #page 10,41" + href: https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=10 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 86c8759b02d..29836b75729 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -1004,6 +1004,14 @@ programs: variable: ar_liheap parameter_prefix: gov.states.ar.ee.aeo.liheap notes: FY2025-FY2026 regular heating matrices and income limits are checked; income rules follow the FFY 2025 manual (no FY2026 manual was found), and other years are unverified. Crisis, cooling, weatherization and equipment assistance are out of scope. + - state: AZ + status: partial + name: Arizona LIHEAP + full_name: Arizona Low Income Home Energy Assistance Program + variable: az_liheap + parameter_prefix: gov.states.az.des.liheap + verified_years: "2026" + notes: FY2026 Standard benefit covering heating and cooling bills from the point-based benefit matrix. Crisis assistance, water for evaporative cooling and weatherization are out of scope. - state: GA status: partial name: Georgia LIHEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/az_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/az_liheap.yaml new file mode 100644 index 00000000000..d3eba53b8e1 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/az_liheap.yaml @@ -0,0 +1,388 @@ +# Arizona Standard LIHEAP benefit: total points (income + energy burden + +# energy need) map to $160 for 1-4 points, $320 for 5-8, $480 for 9-12 and +# $640 for 13 or more (LIHEAP Eligibility Worksheet, $640 Limit, eff. +# 2024-10-12, section 4, +# https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1; +# FY2026 plan 2.6 minimum $160 and maximum $640, +# https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=10; +# DES program page payment table, https://des.az.gov/liheap). The benefit is +# not capped at the bill: any remainder is credited to the Priority 1 +# account (DES LIHEAP Policy rev. 16A, pp. 34-35 VII.B.3.b.iii, +# https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=34). +# A categorically eligible household with zero points receives the plan's +# $160 minimum. Annual limits: size 1 33,684; size 2 44,052; size 3 54,420. + +- name: Case 1, single minimum-wage earner with a modest electric bill totals 2 points and receives 160. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_576 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Income 30,576 / 33,684 = 0.908 -> 1; burden 1,800 / 30,576 = 0.059 -> 1; + # need 0; total 2 -> 160. + az_liheap_points: 2 + az_liheap: 160 + +- name: Case 2, four points is the top of the 160 band. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_576 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 3_600 + gas_expense: 1_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Income 1; burden (3,600 + 1,400) / 30,576 = 0.164 -> 3; need 0; + # total 4 -> 160. + az_liheap_points: 4 + az_liheap: 160 + +- name: Case 3, five points is the bottom of the 320 band. + period: 2026 + input: + people: + person1: + age: 40 + is_veteran: true + employment_income: 30_576 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 3_600 + gas_expense: 1_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Income 1; burden 3; veteran 1; total 5 -> 320. + az_liheap_points: 5 + az_liheap: 320 + +- name: Case 4, eight points is the top of the 320 band. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 12_000 + person2: + age: 37 + employment_income: 9_000 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 4_000 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # Income 21,000 / 54,420 = 0.386 -> 4; burden 4,000 / 21,000 = 0.190 -> 3; + # child aged 4 -> 1; total 8 -> 320. + az_liheap_points: 8 + az_liheap: 320 + +- name: Case 5, nine points is the bottom of the 480 band. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 12_000 + person2: + age: 37 + employment_income: 9_000 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 4_000 + gas_expense: 600 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # Income 4; burden (4,000 + 600) / 21,000 = 0.219 -> 4; child 1; + # total 9 -> 480. + az_liheap_points: 9 + az_liheap: 480 + +- name: Case 6, twelve points is the top of the 480 band. + period: 2026 + input: + people: + person1: + age: 62 + employment_income: 10_000 + ssi: 0 + person2: + age: 45 + is_disabled: true + ssi: 0 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # Income 10,000 / 54,420 = 0.184 -> 5; burden 2,400 / 10,000 = 0.24 -> 4; + # elderly 1 + disabled 1 + child 1 = 3; total 12 -> 480. + az_liheap_points: 12 + az_liheap: 480 + +- name: Case 7, thirteen points is the bottom of the 640 band. + period: 2026 + input: + people: + person1: + age: 62 + employment_income: 10_000 + ssi: 0 + person2: + age: 45 + is_disabled: true + ssi: 0 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_700 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # Income 5; burden 2,700 / 10,000 = 0.27 -> 5; need 3; total 13 -> 640. + az_liheap_points: 13 + az_liheap: 640 + +- name: Case 8, the maximum of seventeen points receives 640. + period: 2026 + input: + people: + person1: + age: 66 + is_disabled: true + veterans_benefits: 8_400 + ssi: 0 + person2: + age: 3 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: PROPANE + pre_subsidy_electricity_expense: 1_200 + bottled_gas_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2] + state_code: AZ + output: + # Income 8,400 / 44,052 = 0.191 -> 5; burden (1,200 + 1,800) / 8,400 = + # 0.357 -> 6 + 2 portable; elderly + disabled + veteran + child = 4; + # total 17 -> 640. + az_liheap_points: 17 + az_liheap: 640 + +- name: Case 9, a SNAP household above the limit with zero points receives the 160 minimum. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 40_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 1_200 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Income 40,000 / 33,684 = 1.19 -> 0; burden 1,800 / 40,000 = 0.045 -> 0; + # need 0; total 0. Plan 2.6: minimum benefit 160. + az_liheap_eligible: true + az_liheap_points: 0 + az_liheap: 160 + +- name: Case 10, the benefit is not capped at a small bill. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 300 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Income 0 -> 5; burden 300 against zero income -> 6; need 0; total 11 + # -> 480, more than the 300 bill (policy pp. 34-35: remainder credited). + az_liheap_points: 11 + az_liheap: 480 + +- name: Case 11, an unspecified heating type pays nothing. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_576 + spm_units: + spm_unit: + members: [person1] + heating_type: UNSPECIFIED + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_eligible: false + az_liheap: 0 + +- name: Case 12, income over the limit without categorical eligibility pays nothing. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 40_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # 40,000 > 33,684 and no SNAP or Cash Assistance. + az_liheap_eligible: false + az_liheap: 0 + +- name: Case 13, the same household in New Mexico pays nothing. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_576 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: NM + output: + az_liheap: 0 + +- name: Case 14, a railroad retiree's annuity puts a single person over the limit and pays nothing. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + railroad_benefits: 40_000 + # Income of 3,333 a month is above the Medicare Savings Program + # limits, so the retiree pays the 2026 standard premium of 202.90 x 12. + medicare_part_b_premium: 2_434.80 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Railroad Retirement is "retirement income" (policy p. 9 VI.A.6.e.ii.B), + # less the Part B premium (p. 14 note): 40,000 - 2,434.80 = 37,565.20. + az_liheap_countable_income: 37_565.20 + # Appendix A size 1: 2,807 x 12 = 33,684. + az_liheap_income_limit: 33_684 + # 37,565.20 > 33,684. + az_liheap_income_eligible: false + # No SNAP or Cash Assistance. + az_liheap_categorically_eligible: false + az_liheap_eligible: false + az_liheap: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/az_liheap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/az_liheap_household_size.yaml new file mode 100644 index 00000000000..0bc1bcee266 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/az_liheap_household_size.yaml @@ -0,0 +1,167 @@ +# Arizona LIHEAP household size. Only Eligible Household Members (U.S. citizens +# and Qualified Noncitizens under 8 U.S.C. 1641(b)) count toward the household +# size; in a Mixed-Status Household the income of every member aged 18 or +# older still counts (DES LIHEAP Policy rev. 16A eff. 2026-07-01, pp. 4-5 +# definitions and p. 8 VI.A.6.d, +# https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=8). +# The variable is defined only for Arizona households. + +- name: Case 1, single citizen adult counts as one. + period: 2026 + input: + people: + person1: + age: 40 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_household_size: 1 + +- name: Case 2, two citizen adults and two citizen children count four. + period: 2026 + input: + people: + person1: + age: 38 + person2: + age: 36 + person3: + age: 10 + person4: + age: 6 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 2_000 + gas_expense: 600 + households: + household: + members: [person1, person2, person3, person4] + state_code: AZ + output: + az_liheap_household_size: 4 + +- name: Case 3, mixed-status household counts citizens only but all adult income. + period: 2026 + input: + people: + person1: + age: 34 + immigration_status: CITIZEN + employment_income: 26_000 + person2: + age: 36 + immigration_status: UNDOCUMENTED + employment_income: 22_000 + person3: + age: 9 + person4: + age: 5 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + tanf: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_700 + households: + household: + members: [person1, person2, person3, person4] + state_code: AZ + output: + # Policy p. 8 VI.A.6.d.i: 4 members - 1 undocumented adult = 3. + az_liheap_household_size: 3 + # Policy p. 8 VI.A.6.d.ii: 26,000 + 22,000 = 48,000, both adults' wages. + az_liheap_countable_income: 48_000 + +- name: Case 4, lawful permanent resident is a qualified noncitizen and counts. + period: 2026 + input: + people: + person1: + age: 45 + immigration_status: CITIZEN + person2: + age: 44 + immigration_status: LEGAL_PERMANENT_RESIDENT + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_900 + households: + household: + members: [person1, person2] + state_code: AZ + output: + az_liheap_household_size: 2 + +- name: Case 5, two undocumented adults count zero. + period: 2026 + input: + people: + person1: + age: 30 + immigration_status: UNDOCUMENTED + person2: + age: 28 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_500 + households: + household: + members: [person1, person2] + state_code: AZ + output: + az_liheap_household_size: 0 + +- name: Case 6, DACA member is not a qualified noncitizen and is excluded. + period: 2026 + input: + people: + person1: + age: 50 + immigration_status: CITIZEN + person2: + age: 22 + immigration_status: DACA + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + households: + household: + members: [person1, person2] + state_code: AZ + output: + # Policy p. 5: DACA is not on the 8 U.S.C. 1641(b) qualified list. + az_liheap_household_size: 1 + +- name: Case 7, the same citizen in New Mexico has no Arizona household size. + period: 2026 + input: + people: + person1: + age: 40 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + households: + household: + members: [person1] + state_code: NM + output: + az_liheap_household_size: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/eligibility/az_liheap_categorically_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/eligibility/az_liheap_categorically_eligible.yaml new file mode 100644 index 00000000000..65e72aad5be --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/eligibility/az_liheap_categorically_eligible.yaml @@ -0,0 +1,120 @@ +# Arizona LIHEAP categorical eligibility: a household is categorically +# eligible when one member's current enrollment in DES Cash Assistance (TANF) +# or Nutrition Assistance (SNAP) is verified; SSI and veterans programs do not +# confer it (DES LIHEAP Policy rev. 16A eff. 2026-07-01, pp. 7-8 VI.A.6.b, +# https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=7; +# FY2026 plan 1.4 and 1.4a, +# https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=5). +# snap and az_tanf are monthly variables; in a yearly test their inputs are +# annual totals (snap 2,400 = 200 a month; az_tanf 4,164 = 347 a month). + +- name: Case 1, SNAP receipt makes a household above the income limit categorically eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 36_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 2_400 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_categorically_eligible: true + +- name: Case 2, Cash Assistance receipt confers categorical eligibility. + period: 2026 + input: + people: + person1: + age: 28 + employment_income: 0 + person2: + age: 2 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_900 + snap: 0 + az_tanf: 4_164 + households: + household: + members: [person1, person2] + state_code: AZ + output: + az_liheap_categorically_eligible: true + +- name: Case 3, a household receiving neither program is not categorically eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_576 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_categorically_eligible: false + +- name: Case 4, SSI receipt alone does not confer categorical eligibility. + period: 2026 + input: + people: + person1: + age: 50 + is_disabled: true + ssi: 11_928 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_600 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Plan 1.4a: SSI is not a categorical program. + az_liheap_categorically_eligible: false + +- name: Case 5, VA benefits alone do not confer categorical eligibility. + period: 2026 + input: + people: + person1: + age: 58 + veterans_benefits: 20_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 1_500 + gas_expense: 600 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Plan 1.4a: means-tested veterans programs are not categorical. + az_liheap_categorically_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/eligibility/az_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/eligibility/az_liheap_eligible.yaml new file mode 100644 index 00000000000..438e2705b4f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/eligibility/az_liheap_eligible.yaml @@ -0,0 +1,245 @@ +# Arizona LIHEAP eligibility: an Arizona household with at least one eligible +# member that meets categorical or income eligibility and needs help with an +# energy bill (DES LIHEAP Policy rev. 16A eff. 2026-07-01, pp. 6-8 VI.A, +# https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=6). +# The Standard benefit covers every home energy bill (electricity, gas and +# portable fuel, p. 4 "Energy Utility"), so a home with no heating system but +# an electricity bill qualifies, and renters with energy in rent are eligible +# (p. 33 VII.B.3.a.ii.B). An unspecified heating type pays nothing (project +# rule). Size-1 annual limit 2,807 x 12 = 33,684. + +- name: Case 1, single minimum-wage earner with an electricity bill is eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_576 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # 30,576 <= 33,684; energy cost 1,800 > 0. + az_liheap_eligible: true + +- name: Case 2, an unspecified heating type is not eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_576 + spm_units: + spm_unit: + members: [person1] + heating_type: UNSPECIFIED + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_eligible: false + +- name: Case 3, a home without heating but with an electricity bill is eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_576 + spm_units: + spm_unit: + members: [person1] + heating_type: NONE + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # The Standard benefit is for any Energy Utility bill, not heating alone. + az_liheap_eligible: true + +- name: Case 4, energy included in rent with no separate bill is eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_576 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heat_expense_included_in_rent: true + pre_subsidy_electricity_expense: 0 + gas_expense: 0 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Policy p. 5: undesignated energy payments in rent form a household; + # p. 33: payment goes to the applicant when utilities are in rent. + az_liheap_energy_cost: 0 + az_liheap_eligible: true + +- name: Case 5, no energy bill and no energy in rent is not eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_576 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heat_expense_included_in_rent: false + pre_subsidy_electricity_expense: 0 + gas_expense: 0 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Policy p. 6 VI.A.1: the applicant must require energy assistance. + az_liheap_eligible: false + +- name: Case 6, income over the limit without categorical eligibility is not eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 40_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # 40,000 > 33,684. + az_liheap_income_eligible: false + az_liheap_categorically_eligible: false + az_liheap_eligible: false + +- name: Case 7, income over the limit with SNAP receipt is eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 40_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 2_400 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Policy p. 7 VI.A.6.a: categorical or income eligibility. + az_liheap_income_eligible: false + az_liheap_categorically_eligible: true + az_liheap_eligible: true + +- name: Case 8, a household with no eligible member is not eligible. + period: 2026 + input: + people: + person1: + age: 30 + immigration_status: UNDOCUMENTED + employment_income: 20_000 + person2: + age: 28 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_500 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2] + state_code: AZ + output: + # Policy p. 7 VI.A.3: at least one member must be a citizen or + # qualified noncitizen. + az_liheap_household_size: 0 + az_liheap_eligible: false + +- name: Case 9, the same household in New Mexico is not eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_576 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: NM + output: + az_liheap_eligible: false + +- name: Case 10, a propane bill alone satisfies the energy-expense requirement. + period: 2026 + input: + people: + person1: + age: 52 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + pre_subsidy_electricity_expense: 0 + bottled_gas_expense: 1_500 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Policy p. 5: portable fuel (propane) is an Energy Utility. + az_liheap_energy_cost: 1_500 + az_liheap_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/eligibility/az_liheap_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/eligibility/az_liheap_income_eligible.yaml new file mode 100644 index 00000000000..b4514cdb7ee --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/eligibility/az_liheap_income_eligible.yaml @@ -0,0 +1,401 @@ +# Arizona LIHEAP income eligibility: when categorical eligibility does not +# apply, gross income must be "at or below" the Appendix A limit for the +# eligible household size (DES LIHEAP Policy rev. 16A eff. 2026-07-01, p. 8 +# VI.A.6.c.i, https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=8; +# FFY2026 Appendix A in rev. 13 eff. 2025-08-25, p. 37, +# https://web.archive.org/web/20251030203752/https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=37). +# Annual limits are twelve times the monthly limits: size 1 2,807 x 12 = +# 33,684; size 2 3,671 x 12 = 44,052; size 5 6,262 x 12 = 75,144; size 6 +# 7,126 x 12 = 85,512; size 10 8,143 x 12 = 97,716; size 13 (9,518 + 687) x +# 12 = 122,460. The 30-day income snapshot is annualized by the policy's own +# arithmetic (p. 14 VII.A.1.c.v), so annual income compares with 12 x the +# monthly limit. + +- name: Case 1, single adult earning exactly the size-1 limit is eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 33_684 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_countable_income: 33_684 + az_liheap_income_limit: 33_684 + # 33,684 <= 33,684. + az_liheap_income_eligible: true + +- name: Case 2, single adult one dollar over the size-1 limit is ineligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 33_685 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + households: + household: + members: [person1] + state_code: AZ + output: + # 33,685 > 33,684. + az_liheap_income_eligible: false + +- name: Case 3, five-person family at exactly the size-5 limit is eligible. + period: 2026 + input: + people: + person1: + age: 38 + employment_income: 44_000 + person2: + age: 36 + employment_income: 31_144 + person3: + age: 12 + person4: + age: 8 + person5: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5] + tanf: 0 + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 2_400 + gas_expense: 700 + households: + household: + members: [person1, person2, person3, person4, person5] + state_code: AZ + output: + # 44,000 + 31,144 = 75,144 <= 6,262 x 12 = 75,144. + az_liheap_countable_income: 75_144 + az_liheap_income_eligible: true + +- name: Case 4, five-person family one dollar over the size-5 limit is ineligible. + period: 2026 + input: + people: + person1: + age: 38 + employment_income: 44_000 + person2: + age: 36 + employment_income: 31_145 + person3: + age: 12 + person4: + age: 8 + person5: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5] + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 2_400 + gas_expense: 700 + households: + household: + members: [person1, person2, person3, person4, person5] + state_code: AZ + output: + # 44,000 + 31,145 = 75,145 > 75,144. + az_liheap_income_eligible: false + +- name: Case 5, six-person family at exactly the size-6 limit is eligible. + period: 2026 + input: + people: + person1: + age: 41 + employment_income: 50_000 + person2: + age: 39 + employment_income: 35_512 + person3: + age: 15 + person4: + age: 12 + person5: + age: 9 + person6: + age: 6 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6] + tanf: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 3_000 + households: + household: + members: [person1, person2, person3, person4, person5, person6] + state_code: AZ + output: + # 50,000 + 35,512 = 85,512 <= 7,126 x 12 = 85,512. + az_liheap_countable_income: 85_512 + az_liheap_income_limit: 85_512 + az_liheap_income_eligible: true + +- name: Case 6, ten-person household at exactly the size-10 limit is eligible. + period: 2026 + input: + people: + person1: + age: 42 + employment_income: 55_000 + person2: + age: 40 + employment_income: 42_716 + person3: + age: 66 + # No income of their own, so a Medicare Savings Program pays the + # Part B premium and nothing is subtracted. + medicare_part_b_premium: 0 + ssi: 0 + person4: + age: 17 + person5: + age: 15 + person6: + age: 13 + person7: + age: 11 + person8: + age: 9 + person9: + age: 7 + person10: + age: 5 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10] + tanf: 0 + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 3_600 + gas_expense: 900 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10] + state_code: AZ + output: + az_liheap_household_size: 10 + # 55,000 + 42,716 = 97,716 <= 8,143 x 12 = 97,716 (the 150% FPG arm). + az_liheap_countable_income: 97_716 + az_liheap_income_limit: 97_716 + az_liheap_income_eligible: true + +- name: Case 7, ten-person household one dollar over the size-10 limit is ineligible. + period: 2026 + input: + people: + person1: + age: 42 + employment_income: 55_000 + person2: + age: 40 + employment_income: 42_717 + person3: + age: 66 + # No income of their own, so a Medicare Savings Program pays the + # Part B premium and nothing is subtracted. + medicare_part_b_premium: 0 + ssi: 0 + person4: + age: 17 + person5: + age: 15 + person6: + age: 13 + person7: + age: 11 + person8: + age: 9 + person9: + age: 7 + person10: + age: 5 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10] + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 3_600 + gas_expense: 900 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10] + state_code: AZ + output: + # 55,000 + 42,717 = 97,717 > 97,716. + az_liheap_income_eligible: false + +- name: Case 8, thirteen-person household at exactly the size-13 limit is eligible. + period: 2026 + input: + people: + person1: + age: 45 + employment_income: 70_000 + person2: + age: 43 + employment_income: 52_460 + person3: + age: 70 + # No income of their own, so a Medicare Savings Program pays the + # Part B premiums of both grandparents and nothing is subtracted. + medicare_part_b_premium: 0 + ssi: 0 + person4: + age: 68 + medicare_part_b_premium: 0 + ssi: 0 + person5: + age: 17 + person6: + age: 16 + person7: + age: 14 + person8: + age: 12 + person9: + age: 10 + person10: + age: 8 + person11: + age: 6 + person12: + age: 4 + person13: + age: 2 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13] + tanf: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 4_800 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13] + state_code: AZ + output: + az_liheap_household_size: 13 + # 70,000 + 52,460 = 122,460 <= (9,518 + 687) x 12 = 122,460. + az_liheap_countable_income: 122_460 + az_liheap_income_limit: 122_460 + az_liheap_income_eligible: true + +- name: Case 9, thirteen-person household one dollar over the size-13 limit is ineligible. + period: 2026 + input: + people: + person1: + age: 45 + employment_income: 70_000 + person2: + age: 43 + employment_income: 52_461 + person3: + age: 70 + # No income of their own, so a Medicare Savings Program pays the + # Part B premiums of both grandparents and nothing is subtracted. + medicare_part_b_premium: 0 + ssi: 0 + person4: + age: 68 + medicare_part_b_premium: 0 + ssi: 0 + person5: + age: 17 + person6: + age: 16 + person7: + age: 14 + person8: + age: 12 + person9: + age: 10 + person10: + age: 8 + person11: + age: 6 + person12: + age: 4 + person13: + age: 2 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 4_800 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13] + state_code: AZ + output: + # 70,000 + 52,461 = 122,461 > 122,460. + az_liheap_income_eligible: false + +- name: Case 10, a household with no income is income eligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + households: + household: + members: [person1] + state_code: AZ + output: + # 0 <= 33,684. + az_liheap_income_eligible: true + +- name: Case 11, mixed-status household is measured against the eligible-member size with all adult income. + period: 2026 + input: + people: + person1: + age: 34 + immigration_status: CITIZEN + employment_income: 24_000 + person2: + age: 36 + immigration_status: UNDOCUMENTED + employment_income: 24_000 + person3: + age: 5 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # Policy p. 8 VI.A.6.d: size 2 (citizen parent and child), limit + # 3,671 x 12 = 44,052; income 24,000 + 24,000 = 48,000 from both adults. + az_liheap_household_size: 2 + az_liheap_countable_income: 48_000 + az_liheap_income_limit: 44_052 + # 48,000 > 44,052 (it would pass the size-3 limit of 54,420). + az_liheap_income_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/eligibility/az_liheap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/eligibility/az_liheap_income_limit.yaml new file mode 100644 index 00000000000..ba13426436e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/eligibility/az_liheap_income_limit.yaml @@ -0,0 +1,95 @@ +# Arizona LIHEAP annual income limit: twelve times the Appendix A monthly +# gross income limit, which is the greater of 60% of the state median income +# and 150% of the federal poverty level (DES LIHEAP Policy rev. 16A eff. +# 2026-07-01, p. 8 VI.A.6.c.i, https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=8; +# FFY2026 Appendix A in rev. 13 eff. 2025-08-25, p. 37, +# https://web.archive.org/web/20251030203752/https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=37). +# FFY2026 Appendix A monthly limits: 2,807; 3,671; 4,535; 5,399; 6,262; 7,126; +# 7,288; 7,450; 7,612; 8,143; 8,831; 9,518; +687 for each person over 12. +# Reproduction from the federal guidelines (period 2026): +# SMI arm: four-person Arizona SMI 107,983 (FFY2026); floor(0.6 x 107,983) = +# 64,789; size share 0.52 + 0.16 per person two to six + 0.03 per person +# above six (45 CFR 96.85); floor(floor(share x 64,789) / 12). +# FPG arm: 2025 guideline 15,650 + 5,500 per additional person (one-year lag); +# floor(1.5 x FPG(size) / 12), table capped at 12 with +# floor(1.5 x 5,500 / 12) = floor(687.5) = 687 per person above 12. + +- name: Case 1, Appendix A monthly limits times twelve for sizes 1 to 14. + period: 2026 + input: + state_code: [AZ, AZ, AZ, AZ, AZ, AZ, AZ, AZ, AZ, AZ, AZ, AZ, AZ, AZ] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY] + pre_subsidy_electricity_expense: [1_800, 1_800, 1_800, 1_800, 1_800, 1_800, 1_800, 1_800, 1_800, 1_800, 1_800, 1_800, 1_800, 1_800] + az_liheap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] + output: + # size 1: floor(0.52 x 64,789) = 33,690; floor(33,690 / 12) = 2,807; + # FPG floor(1.5 x 15,650 / 12) = 1,956; max 2,807 x 12 = 33,684. + # size 2: floor(0.68 x 64,789) = 44,056 -> 3,671; FPG 2,643; 3,671 x 12 = 44,052. + # size 3: floor(0.84 x 64,789) = 54,422 -> 4,535; FPG 3,331; 4,535 x 12 = 54,420. + # size 4: 64,789 -> 5,399; FPG 4,018; 5,399 x 12 = 64,788. + # size 5: floor(1.16 x 64,789) = 75,155 -> 6,262; FPG 4,706; 6,262 x 12 = 75,144. + # size 6: floor(1.32 x 64,789) = 85,521 -> 7,126; FPG 5,393; 7,126 x 12 = 85,512. + # size 7: floor(1.35 x 64,789) = 87,465 -> 7,288; FPG 6,081; 7,288 x 12 = 87,456. + # size 8: floor(1.38 x 64,789) = 89,408 -> 7,450; FPG 6,768; 7,450 x 12 = 89,400. + # size 9: floor(1.41 x 64,789) = 91,352 -> 7,612; FPG 7,456; 7,612 x 12 = 91,344. + # size 10: SMI floor(1.44 x 64,789) = 93,296 -> 7,774; FPG floor(1.5 x + # 65,150 / 12) = floor(8,143.75) = 8,143; max 8,143 x 12 = 97,716. + # size 11: SMI 7,936; FPG floor(1.5 x 70,650 / 12) = 8,831; x 12 = 105,972. + # size 12: SMI 8,098; FPG floor(1.5 x 76,150 / 12) = 9,518; x 12 = 114,216. + # size 13: SMI 8,260; FPG 9,518 + 687 = 10,205; x 12 = 122,460. + # size 14: SMI 8,422; FPG 9,518 + 2 x 687 = 10,892; x 12 = 130,704. + az_liheap_income_limit: [33_684, 44_052, 54_420, 64_788, 75_144, 85_512, 87_456, 89_400, 91_344, 97_716, 105_972, 114_216, 122_460, 130_704] + +- name: Case 2, a household with no eligible member has no income limit. + period: 2026 + input: + people: + person1: + age: 30 + immigration_status: UNDOCUMENTED + person2: + age: 28 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_500 + households: + household: + members: [person1, person2] + state_code: AZ + output: + az_liheap_household_size: 0 + az_liheap_income_limit: 0 + +- name: Case 3, a five-person family's limit follows its computed household size. + period: 2026 + input: + people: + person1: + age: 38 + employment_income: 44_000 + person2: + age: 36 + employment_income: 31_000 + person3: + age: 12 + person4: + age: 8 + person5: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5] + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 2_400 + gas_expense: 700 + households: + household: + members: [person1, person2, person3, person4, person5] + state_code: AZ + output: + az_liheap_household_size: 5 + # Appendix A size 5: 6,262 x 12 = 75,144. + az_liheap_income_limit: 75_144 diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/income/az_liheap_countable_earned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/income/az_liheap_countable_earned_income.yaml new file mode 100644 index 00000000000..89b1d84ed88 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/income/az_liheap_countable_earned_income.yaml @@ -0,0 +1,152 @@ +# Arizona LIHEAP countable earned income per person. Gross wages and +# self-employment income of every member aged 18 or older count (DES LIHEAP +# Policy rev. 16A eff. 2026-07-01, p. 8 VI.A.6.d.ii and p. 13 VII.A.1.c.ii, +# https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=8). +# Earned income of members under 18 never counts: rev. 13 p. 9 E excludes all +# under-18 income and rev. 15 (eff. 2026-03-23) p. 9 E excludes their earned +# income. The FY2026 plan leaves "earned income of a child under 18" unchecked +# (https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=7). +# Self-employment is documented with a Schedule C "Profit and Loss Statement" +# (policy p. 14 VII.A.1.c.iii.B.1); the model floors each net source at zero +# (project convention). +# Arizona's 2025 minimum wage is $14.70 an hour: 14.70 x 2,080 = 30,576. + +- name: Case 1, full-time minimum-wage earnings are counted in full. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_576 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_countable_earned_income: 30_576 + +- name: Case 2, a 17-year-old's wages are excluded. + period: 2026 + input: + people: + person1: + age: 42 + employment_income: 30_576 + person2: + age: 17 + employment_income: 6_000 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_100 + households: + household: + members: [person1, person2] + state_code: AZ + output: + # Policy p. 9 E: the 17-year-old's 6,000 is not countable income. + az_liheap_countable_earned_income: [30_576, 0] + az_liheap_countable_income: 30_576 + +- name: Case 3, an 18-year-old's wages are counted. + period: 2026 + input: + people: + person1: + age: 42 + employment_income: 30_576 + person2: + age: 18 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_100 + households: + household: + members: [person1, person2] + state_code: AZ + output: + # Policy p. 8 VI.A.6.d.ii: members aged 18 and older; 30,576 + 12,000. + az_liheap_countable_earned_income: [30_576, 12_000] + az_liheap_countable_income: 42_576 + +- name: Case 4, a self-employment loss is floored at zero and does not offset wages. + period: 2026 + input: + people: + person1: + age: 45 + employment_income: 20_000 + self_employment_income: -4_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 1_400 + gas_expense: 500 + households: + household: + members: [person1] + state_code: AZ + output: + # max(20,000, 0) + max(-4,000, 0) = 20,000. + az_liheap_countable_earned_income: 20_000 + +- name: Case 5, every earned source is summed for an adult. + period: 2026 + input: + people: + person1: + age: 51 + employment_income: 18_000 + self_employment_income: 6_000 + farm_operations_income: 2_000 + partnership_s_corp_income: 3_000 + sstb_self_employment_income: 1_000 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + pre_subsidy_electricity_expense: 1_300 + bottled_gas_expense: 1_500 + households: + household: + members: [person1] + state_code: AZ + output: + # 18,000 + 6,000 + 2,000 + 3,000 + 1,000 = 30,000 (plan 1.9: wages and + # self-employment income checked). + az_liheap_countable_earned_income: 30_000 + +- name: Case 6, a 16-year-old's wages stay excluded under revision 15 in 2027. + period: 2027 + input: + people: + person1: + age: 39 + employment_income: 28_000 + person2: + age: 16 + employment_income: 4_500 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_000 + households: + household: + members: [person1, person2] + state_code: AZ + output: + # Rev. 15 p. 9 E: "Earned income from Household members under the age of + # 18 is not considered Countable Income." + az_liheap_countable_earned_income: [28_000, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/income/az_liheap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/income/az_liheap_countable_income.yaml new file mode 100644 index 00000000000..7e5886444a7 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/income/az_liheap_countable_income.yaml @@ -0,0 +1,192 @@ +# Arizona LIHEAP countable income of the household: countable earned plus +# unearned income of every member, plus the unit's DES Cash Assistance (TANF) +# benefit (DES LIHEAP Policy rev. 16A eff. 2026-07-01, p. 9 VI.A.6.e.ii.B +# "Unearned income, including Social Security and TANF benefits", +# https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=9; +# FY2026 plan 1.9 TANF checked, +# https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=6). +# az_tanf is a monthly variable; in a yearly test its input is the annual +# total. Arizona's 2026 payment standard for a parent and one child is 347 a +# month: 347 x 12 = 4,164. +# Members' out-of-pocket Medicare Part B premiums are subtracted from the +# household total, floored at zero (policy p. 14 VII.A.1.c.ii.D note: +# "Expenses related to Medicare (such as premiums) should be subtracted +# when determining gross income"). 2026 Part B standard premium 202.90 a +# month: 202.90 x 12 = 2,434.80. + +- name: Case 1, TANF received by the unit is added to the parent's wages. + period: 2026 + input: + people: + person1: + age: 28 + employment_income: 9_000 + person2: + age: 2 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_900 + az_tanf: 4_164 + snap: 0 + households: + household: + members: [person1, person2] + state_code: AZ + output: + # 9,000 wages + 4,164 Cash Assistance = 13,164. + az_liheap_countable_income: 13_164 + +- name: Case 2, earned and unearned income of two adults are summed and a child's income is excluded. + period: 2026 + input: + people: + person1: + age: 44 + employment_income: 26_000 + unemployment_compensation: 2_400 + person2: + age: 67 + social_security: 18_000 + # Their own 1,500 a month is within the Medicare Savings Program + # limits, so the program pays the Part B premium and nothing is + # subtracted. + medicare_part_b_premium: 0 + ssi: 0 + person3: + age: 15 + employment_income: 3_000 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 2_000 + gas_expense: 700 + az_tanf: 0 + snap: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # 26,000 + 2,400 + 18,000 = 46,400; the 15-year-old's 3,000 is excluded + # (rev. 13 p. 9 E). + az_liheap_countable_income: 46_400 + +- name: Case 3, subtracting the Medicare Part B premium brings a retiree under the income limit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 67 + social_security: 34_000 + # Income of 2,833 a month is above the Medicare Savings Program + # limits, so the retiree pays the 2,434.80 standard premium. + medicare_part_b_premium: 2_434.80 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Social Security counts gross at the person level. + az_liheap_countable_unearned_income: 34_000 + # 34,000 - 2,434.80 = 31,565.20. + az_liheap_countable_income: 31_565.20 + # 31,565.20 <= 33,684; gross income of 34,000 would be over it. + az_liheap_income_eligible: true + # Income 31,565.20 / 33,684 = 0.937 -> 1; burden 1,800 / 31,565.20 = + # 0.057 -> 1; elderly 1; total 3 -> 160. + az_liheap_points: 3 + az_liheap: 160 + +- name: Case 4, the Medicare Part B premium is subtracted from a pension with no Social Security. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 67 + pension_income: 24_000 + # Income of 2,000 a month is above the Medicare Savings Program + # limits, so the retiree pays the 2,434.80 standard premium. + medicare_part_b_premium: 2_434.80 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_600 + tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_countable_unearned_income: 24_000 + # 24,000 - 2,434.80 = 21,565.20: the premium comes off household income + # whatever the source it is paid from. + az_liheap_countable_income: 21_565.20 + +- name: Case 5, a retiree's Medicare Part A premium is subtracted along with Part B. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 67 + pension_income: 24_000 + # 30 to 39 quarters of coverage: the 2026 reduced Part A premium + # 311 x 12 = 3,732, paid by the member. + medicare_part_a_premium: 3_732 + medicare_part_b_premium: 2_434.80 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_600 + tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_countable_unearned_income: 24_000 + # "Expenses related to Medicare (such as premiums)": 24,000 - 3,732 - + # 2,434.80 = 17,833.20. + az_liheap_countable_income: 17_833.20 + +- name: Case 6, gambling losses above winnings count as zero and do not reduce wages. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 20_000 + gambling_winnings: 1_000 + gambling_losses: 3_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_700 + tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # max(1,000 - 3,000, 0) = 0 gambling profits. + az_liheap_countable_unearned_income: 0 + # Wages of 20,000 are not reduced by the 2,000 net gambling loss. + az_liheap_countable_income: 20_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/income/az_liheap_countable_unearned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/income/az_liheap_countable_unearned_income.yaml new file mode 100644 index 00000000000..9d741f15ad4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/income/az_liheap_countable_unearned_income.yaml @@ -0,0 +1,371 @@ +# Arizona LIHEAP countable unearned income per person. Gross unearned income +# of every member aged 18 or older counts: SSA, SSI, retirement (including +# Railroad Retirement), unemployment insurance, alimony, child support, rental +# and farm rental income, gambling profits, interest, dividends, general +# assistance, VA benefits, employment disability insurance and workers' +# compensation (DES LIHEAP Policy rev. 16A eff. 2026-07-01, pp. 8-9 VI.A.6.e, +# https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=8; +# FY2026 plan 1.9, +# https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=6). +# Members under 18: rev. 13 p. 9 E counts none of their income; rev. 15 +# (eff. 2026-03-23) p. 9 E excludes only their earned income, so a child's +# unearned income counts from period 2027 (parameters are read at the start +# of the period). Each source is floored at zero per person, so a rental or +# farm rental loss does not offset other income. Social Security counts +# gross here: the Medicare Part B premium (policy p. 14 VII.A.1.c.ii.D note) +# comes off household income in az_liheap_countable_income. +# 2026 Part B standard premium 202.90 a month: 202.90 x 12 = 2,434.80. +# 2026 SSI federal benefit rate 994 a month: 994 x 12 = 11,928. + +- name: Case 1, a retiree's Social Security and pension income are counted gross of the Medicare premium. + period: 2026 + input: + people: + person1: + age: 70 + social_security: 23_712 + # Income of 2,476 a month is above the Medicare Savings Program + # limits, so the retiree pays the 2,434.80 standard premium. + medicare_part_b_premium: 2_434.80 + pension_income: 6_000 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + households: + household: + members: [person1] + state_code: AZ + output: + # 23,712 + 6,000 = 29,712 (plan 1.9: SSA and retirement/pension + # checked); the premium is not deducted at the person level. + az_liheap_countable_unearned_income: 29_712 + +- name: Case 2, unemployment, child support, alimony and rent are counted. + period: 2026 + input: + people: + person1: + age: 45 + unemployment_compensation: 9_600 + child_support_received: 4_800 + alimony_income: 3_000 + rental_income: 6_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 1_500 + gas_expense: 600 + households: + household: + members: [person1] + state_code: AZ + output: + # 9,600 + 4,800 + 3,000 + 6,000 = 23,400. + az_liheap_countable_unearned_income: 23_400 + +- name: Case 3, SSI, general assistance and VA benefits of a disabled adult are counted. + period: 2026 + input: + people: + person1: + age: 50 + is_disabled: true + ssi: 11_928 + general_assistance: 1_200 + veterans_benefits: 4_800 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_600 + households: + household: + members: [person1] + state_code: AZ + output: + # 11,928 + 1,200 + 4,800 = 17,928 (plan 1.9: SSI, general assistance and + # VA benefits checked). + az_liheap_countable_unearned_income: 17_928 + +- name: Case 4, interest, dividends, strike pay and an IRA distribution each count once. + period: 2026 + input: + people: + person1: + age: 40 + taxable_interest_income: 1_200 + ordinary_dividend_income: 800 + strike_benefits: 2_000 + taxable_ira_distributions: 3_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_700 + households: + household: + members: [person1] + state_code: AZ + output: + # 1,200 + 800 + 2,000 + 3,000 = 7,000. The IRA distribution reaches the + # total once, through retirement_distributions. + az_liheap_countable_unearned_income: 7_000 + +- name: Case 5, a child's SSI and child support are not counted in 2026 under revision 13. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 30_000 + person2: + age: 10 + is_disabled: true + ssi: 11_928 + child_support_received: 4_800 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_200 + households: + household: + members: [person1, person2] + state_code: AZ + output: + # Rev. 13 p. 9 E: "Income from Household members under the age of 18 is + # not considered Countable Income." + az_liheap_countable_unearned_income: [0, 0] + az_liheap_countable_income: 30_000 + +- name: Case 6, the same child's SSI and child support are counted in 2027 under revision 15. + period: 2027 + input: + people: + person1: + age: 35 + employment_income: 30_000 + person2: + age: 10 + is_disabled: true + ssi: 11_928 + child_support_received: 4_800 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_200 + households: + household: + members: [person1, person2] + state_code: AZ + output: + # Rev. 15 p. 9 E excludes only earned income of members under 18: + # 11,928 + 4,800 = 16,728 counts. + az_liheap_countable_unearned_income: [0, 16_728] + # 30,000 + 16,728 = 46,728. + az_liheap_countable_income: 46_728 + +- name: Case 7, a Railroad Retirement annuity is counted as retirement income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 68 + railroad_benefits: 30_000 + # Income of 2,500 a month is above the Medicare Savings Program + # limits, so the retiree pays the 2,434.80 standard premium. + medicare_part_b_premium: 2_434.80 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_500 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Policy p. 9 VI.A.6.e.ii.B "retirement income", p. 14 "retirement + # benefits"; plan 1.9 "Retirement / pension benefits" checked: 30,000. + az_liheap_countable_unearned_income: 30_000 + # Household income less the Part B premium: 30,000 - 2,434.80. + az_liheap_countable_income: 27_565.20 + +- name: Case 8, long-term disability insurance paid to the insured worker is counted. + period: 2026 + input: + people: + person1: + age: 52 + disability_benefits: 27_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 1_400 + gas_expense: 500 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Plan 1.9 "Insurance payments made directly to the insured" checked; + # policy p. 4 Gross Income is all income before taxes and deductions. + # 60% of a 45,000 salary = 27,000 counts. + az_liheap_countable_unearned_income: 27_000 + az_liheap_countable_income: 27_000 + +- name: Case 9, workers' compensation of an injured worker is counted. + period: 2026 + input: + people: + person1: + age: 38 + workers_compensation: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_700 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Policy p. 4 Gross Income and p. 9 VI.A.6.e.ii.D "Consistent income + # received from other sources"; two-thirds of 36,000 prior wages = 24,000 + # counts. + az_liheap_countable_unearned_income: 24_000 + az_liheap_countable_income: 24_000 + +- name: Case 10, military retirement pay is counted as retirement income. + period: 2026 + input: + people: + person1: + age: 48 + military_retirement_pay: 26_400 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Policy p. 9 VI.A.6.e.ii.B counts "retirement income"; a 20-year + # military retiree's 2,200 a month pension counts in full. + az_liheap_countable_unearned_income: 26_400 + az_liheap_countable_income: 26_400 + +- name: Case 11, military survivor benefit payments are counted as retirement income. + period: 2026 + input: + people: + person1: + age: 55 + military_retirement_pay_survivors: 14_400 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_500 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Survivor Benefit Plan annuity of 1,200 a month is retirement income + # under VI.A.6.e.ii.B. + az_liheap_countable_unearned_income: 14_400 + az_liheap_countable_income: 14_400 + +- name: Case 12, a rental loss does not offset wages. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 36_000 + rental_income: -6_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # max(-6,000, 0) = 0: a loss is not income (policy p. 4 Gross Income). + az_liheap_countable_unearned_income: 0 + az_liheap_countable_income: 36_000 + # 36,000 > 33,684; netting the loss (30,000) would have paid 160. + az_liheap_income_eligible: false + az_liheap_eligible: false + az_liheap: 0 + +- name: Case 13, farm rent and gambling winnings are counted as unearned income. + period: 2026 + input: + people: + person1: + age: 58 + employment_income: 22_000 + farm_rent_income: 4_800 + gambling_winnings: 1_200 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_700 + tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Plan 1.9 "Rental income" checked; policy VI.A.6.e.ii.B "gambling + # profits": 4,800 + 1,200 = 6,000. + az_liheap_countable_unearned_income: 6_000 + # 22,000 + 6,000 = 28,000. + az_liheap_countable_income: 28_000 + +- name: Case 14, gambling losses reduce gambling winnings to the profit. + period: 2026 + input: + people: + person1: + age: 40 + gambling_winnings: 3_000 + gambling_losses: 1_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_700 + tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Policy VI.A.6.e.ii.B "gambling profits": 3,000 - 1,000 = 2,000. + az_liheap_countable_unearned_income: 2_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/integration.yaml new file mode 100644 index 00000000000..a554e5c5e30 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/integration.yaml @@ -0,0 +1,285 @@ +# Arizona Standard LIHEAP benefit, end to end. Sources: DES LIHEAP Policy +# rev. 16A eff. 2026-07-01 (household and immigration pp. 4-8, categorical +# eligibility pp. 7-8, countable income pp. 8-9 with the rev. 15 p. 9 E +# exclusion of under-18 earned income only, benefit factors p. 10, FY27 +# Appendix A p. 39), https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=8; +# rev. 13 eff. 2025-08-25 (FFY2026 Appendix A p. 37), +# https://web.archive.org/web/20251030203752/https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=37; +# LIHEAP Eligibility Worksheet, $640 Limit, eff. 2024-10-12 (point tables and +# payment matrix), +# https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1; +# FY2026 plan (categorical programs 1.4, income 1.8-1.9, $160 minimum and +# $640 maximum 2.6), https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=10; +# DES program page (formulas and examples), https://des.az.gov/liheap. +# FFY2026 annual limits (Appendix A monthly x 12): size 1 2,807 x 12 = +# 33,684; size 2 3,671 x 12 = 44,052; size 3 4,535 x 12 = 54,420. +# Points: income ratio 0-20% 5, -40% 4, -60% 3, -80% 2, -100% 1; burden 5% or +# less 0, -10% 1, -15% 2, -20% 3, -25% 4, -30% 5, over 30% 6, +2 portable +# fuel; need 1 each for elderly 60+, disabled, veteran, child 6 or under. +# Payment: 1-4 points 160, 5-8 320, 9-12 480, 13+ 640; 0 points pays the +# plan's 160 minimum. + +- name: Case 1, two working parents and a preschooler in Phoenix with electric heat receive 320. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 14_000 + person2: + age: 37 + employment_income: 10_000 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + az_liheap_household_size: 3 + # 14,000 + 10,000 = 24,000 (2,000 a month). + az_liheap_countable_income: 24_000 + # Appendix A size 3: 4,535 x 12. + az_liheap_income_limit: 54_420 + # 24,000 <= 54,420. + az_liheap_income_eligible: true + az_liheap_categorically_eligible: false + az_liheap_energy_cost: 2_400 + az_liheap_eligible: true + # 24,000 / 54,420 = 0.441 (2,000 / 4,535 = 44%): 41%-60% band. + az_liheap_income_points: 3 + # 2,400 / 24,000 = 0.10 exactly: top of the 6%-10% band, 1 point. + az_liheap_energy_burden_points: 1 + # Child aged 4. + az_liheap_energy_need_points: 1 + # 3 + 1 + 1 = 5: the 5-8 band. + az_liheap_points: 5 + az_liheap: 320 + +- name: Case 2, a retiree on SNAP with propane heat, under the limit after the Medicare premium, receives 320. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + social_security: 36_000 + # Income of 3,000 a month is above the Medicare Savings Program + # limits, so the retiree pays the 2026 standard premium of 202.90 x 12. + medicare_part_b_premium: 2_434.80 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + pre_subsidy_electricity_expense: 1_200 + bottled_gas_expense: 1_500 + snap: 2_400 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_household_size: 1 + # Gross income less the Part B premium (policy p. 14 note): + # 36,000 - 2,434.80 = 33,565.20. + az_liheap_countable_income: 33_565.20 + # Appendix A size 1: 2,807 x 12. + az_liheap_income_limit: 33_684 + # 33,565.20 <= 33,684. + az_liheap_income_eligible: true + # SNAP receipt (200 a month) also confers categorical eligibility. + az_liheap_categorically_eligible: true + # 1,200 + 1,500. + az_liheap_energy_cost: 2_700 + az_liheap_eligible: true + # 33,565.20 / 33,684 = 0.996: 81%-100% band. + az_liheap_income_points: 1 + # 2,700 / 33,565.20 = 0.080: 6%-10% band, 1 point + 2 portable fuel. + az_liheap_energy_burden_points: 3 + # Aged 60 and over. + az_liheap_energy_need_points: 1 + # 1 + 3 + 1 = 5: the 5-8 band. + az_liheap_points: 5 + az_liheap: 320 + +- name: Case 3, a mixed-status family of four is sized at three and receives 160. + period: 2026 + input: + people: + person1: + age: 34 + immigration_status: CITIZEN + employment_income: 26_000 + person2: + age: 36 + immigration_status: UNDOCUMENTED + employment_income: 22_000 + person3: + age: 9 + person4: + age: 5 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 2_700 + gas_expense: 500 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3, person4] + state_code: AZ + output: + # Policy p. 8 VI.A.6.d.i: the undocumented parent is not counted. + az_liheap_household_size: 3 + # Policy p. 8 VI.A.6.d.ii: 26,000 + 22,000, both adults' wages. + az_liheap_countable_income: 48_000 + az_liheap_income_limit: 54_420 + # 48,000 <= 54,420. + az_liheap_income_eligible: true + # 2,700 + 500. + az_liheap_energy_cost: 3_200 + az_liheap_eligible: true + # 48,000 / 54,420 = 0.882: 81%-100% band. + az_liheap_income_points: 1 + # 3,200 / 48,000 = 0.067: 6%-10% band; natural gas, no bonus. + az_liheap_energy_burden_points: 1 + # Child aged 5. + az_liheap_energy_need_points: 1 + # 1 + 1 + 1 = 3. + az_liheap_points: 3 + az_liheap: 160 + +- name: Case 4, a Cash Assistance family with energy included in rent receives 320. + period: 2026 + input: + people: + person1: + age: 28 + employment_income: 0 + person2: + age: 2 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + heat_expense_included_in_rent: true + pre_subsidy_electricity_expense: 0 + gas_expense: 0 + snap: 0 + tanf: 4_164 + households: + household: + members: [person1, person2] + state_code: AZ + output: + az_liheap_household_size: 2 + # Cash Assistance 347 x 12 = 4,164 counts as income (policy p. 9 B). + az_liheap_countable_income: 4_164 + # Appendix A size 2: 3,671 x 12. + az_liheap_income_limit: 44_052 + az_liheap_income_eligible: true + # Current Cash Assistance enrollment (policy p. 7 VI.A.6.b). + az_liheap_categorically_eligible: true + az_liheap_energy_cost: 0 + # Energy in rent satisfies the energy-expense requirement. + az_liheap_eligible: true + # 4,164 / 44,052 = 0.095: 0%-20% band. + az_liheap_income_points: 5 + # No separate energy bill: 0 burden points. + az_liheap_energy_burden_points: 0 + # Child aged 2. + az_liheap_energy_need_points: 1 + # 5 + 0 + 1 = 6: the 5-8 band. + az_liheap_points: 6 + az_liheap: 320 + +- name: Case 5, an otherwise eligible household with an unspecified heating type receives nothing. + period: 2026 + input: + people: + person1: + age: 45 + employment_income: 28_000 + spm_units: + spm_unit: + members: [person1] + heating_type: UNSPECIFIED + pre_subsidy_electricity_expense: 2_000 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_household_size: 1 + az_liheap_countable_income: 28_000 + az_liheap_income_limit: 33_684 + # 28,000 <= 33,684. + az_liheap_income_eligible: true + az_liheap_energy_cost: 2_000 + # Project rule: an unspecified heating type is not eligible. + az_liheap_eligible: false + # 28,000 / 33,684 = 0.831: 81%-100% band. + az_liheap_income_points: 1 + # 2,000 / 28,000 = 0.071: 6%-10% band. + az_liheap_energy_burden_points: 1 + az_liheap_points: 2 + az_liheap: 0 + +- name: Case 6, in 2027 a child's child support counts under revision 15 and the FFY2027 limit applies. + period: 2027 + input: + people: + person1: + age: 30 + employment_income: 18_000 + person2: + age: 8 + child_support_received: 3_600 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_000 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2] + state_code: AZ + output: + az_liheap_household_size: 2 + # Rev. 15 p. 9 E excludes only earned income of members under 18. + az_liheap_countable_unearned_income: [0, 3_600] + # 18,000 + 3,600. + az_liheap_countable_income: 21_600 + # FY27 limit: Arizona SMI 112,946; floor(0.6 x 112,946) = 67,767; + # floor(0.68 x 67,767) = 46,081; floor(46,081 / 12) = 3,840; FPG arm + # floor(1.5 x (15,960 + 5,680) / 12) = 2,705; 3,840 x 12 = 46,080. The + # model reproduces the published FY27 size-2 limit of 3,840 a month (DES + # LIHEAP Policy revision 16A, Appendix A, p. 39). Verified coverage stays + # FY2026; 2027 is not claimed. + az_liheap_income_limit: 46_080 + az_liheap_income_eligible: true + az_liheap_eligible: true + # 21,600 / 46,080 = 0.469: 41%-60% band. + az_liheap_income_points: 3 + # 2,000 / 21,600 = 0.093: 6%-10% band. + az_liheap_energy_burden_points: 1 + # Child aged 8: no need point. + az_liheap_energy_need_points: 0 + # 3 + 1 + 0 = 4: the 1-4 band. + az_liheap_points: 4 + az_liheap: 160 diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/points/az_liheap_energy_burden_points.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/points/az_liheap_energy_burden_points.yaml new file mode 100644 index 00000000000..5f295603a52 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/points/az_liheap_energy_burden_points.yaml @@ -0,0 +1,436 @@ +# Arizona LIHEAP energy burden points: total monthly energy cost (electric, +# gas and portable fuel) divided by monthly gross income; 5% or less 0 points, +# 6%-10% 1, 11%-15% 2, 16%-20% 3, 21%-25% 4, 26%-30% 5, 31% or over 6, plus 2 +# points for portable fuels (LIHEAP Eligibility Worksheet, $640 Limit, eff. +# 2024-10-12, section 2, +# https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1; +# DES program page example "350 / 2,000 = 17.5%, three points", +# https://des.az.gov/liheap). Portable Fuel is propane, wood, coal, generator +# fuel and pellets (DES LIHEAP Policy rev. 16A, p. 5, +# https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=5), +# modeled as heating_type PROPANE, WOOD or COAL. Annual bills over annual +# income give the same ratio as the monthly figures; a band top belongs to its +# band (exactly 5% is 0 points). Zero income with a bill is the top band; no +# bill is 0 points. Single adult earning 24,000 (2,000 a month) throughout. + +- name: Case 1, the program-page example of a 350 bill on 2,000 income gives 3 points. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 4_200 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # 4,200 / 24,000 = 0.175 (350 / 2,000 = 17.5%): 16%-20% band. + az_liheap_energy_burden_points: 3 + +- name: Case 2, a bill at exactly 5 percent of income scores 0 points. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # 1,200 / 24,000 = 0.05 exactly: "5% or less". + az_liheap_energy_burden_points: 0 + +- name: Case 3, one dollar above 5 percent of income scores 1 point. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_201 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # 1,201 / 24,000 = 0.05004 > 0.05: 6%-10% band. + az_liheap_energy_burden_points: 1 + +- name: Case 4, a bill at exactly 10 percent of income keeps 1 point. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # 2,400 / 24,000 = 0.10 exactly: top of the 6%-10% band. + az_liheap_energy_burden_points: 1 + +- name: Case 5, a bill at exactly 15 percent of income keeps 2 points. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 3_600 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # 3,600 / 24,000 = 0.15 exactly: top of the 11%-15% band. + az_liheap_energy_burden_points: 2 + +- name: Case 6, a bill at exactly 20 percent of income keeps 3 points. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 4_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # 4,800 / 24,000 = 0.20 exactly: top of the 16%-20% band. + az_liheap_energy_burden_points: 3 + +- name: Case 7, a bill at exactly 25 percent of income keeps 4 points. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 6_000 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # 6,000 / 24,000 = 0.25 exactly: top of the 21%-25% band. + az_liheap_energy_burden_points: 4 + +- name: Case 8, a bill at exactly 30 percent of income keeps 5 points. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 7_200 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # 7,200 / 24,000 = 0.30 exactly: top of the 26%-30% band. + az_liheap_energy_burden_points: 5 + +- name: Case 9, a bill at 31 percent of income scores 6 points. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 7_440 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # 7,440 / 24,000 = 0.31: "31% or over". + az_liheap_energy_burden_points: 6 + +- name: Case 10, a household with no income and a bill scores the top band. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # 1,800 against zero income: the burden is unbounded, 31% or over. + az_liheap_energy_burden_points: 6 + +- name: Case 11, no energy bill scores 0 points even with low income. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 12_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heat_expense_included_in_rent: true + pre_subsidy_electricity_expense: 0 + gas_expense: 0 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # 0 / 12,000 = 0: "5% or less". + az_liheap_energy_burden_points: 0 + +- name: Case 12, propane heat adds 2 portable-fuel points. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + pre_subsidy_electricity_expense: 1_200 + bottled_gas_expense: 1_500 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # (1,200 + 1,500) / 24,000 = 0.1125: 11%-15% band 2 points + 2 portable. + az_liheap_energy_cost: 2_700 + az_liheap_energy_burden_points: 4 + +- name: Case 13, wood heat adds 2 portable-fuel points. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: WOOD + pre_subsidy_electricity_expense: 1_200 + wood_expense: 900 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # (1,200 + 900) / 24,000 = 0.0875: 6%-10% band 1 point + 2 portable. + az_liheap_energy_burden_points: 3 + +- name: Case 14, coal heat adds 2 portable-fuel points. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: COAL + pre_subsidy_electricity_expense: 1_200 + coal_expense: 800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # (1,200 + 800) / 24,000 = 0.0833: 6%-10% band 1 point + 2 portable. + az_liheap_energy_burden_points: 3 + +- name: Case 15, natural gas heat earns no portable-fuel points. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 1_200 + gas_expense: 600 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # (1,200 + 600) / 24,000 = 0.075: 6%-10% band, 1 point, no bonus. + az_liheap_energy_burden_points: 1 + +- name: Case 16, fuel oil is not an Arizona portable fuel and earns no bonus. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: FUEL_OIL + pre_subsidy_electricity_expense: 1_200 + fuel_oil_expense: 1_500 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # (1,200 + 1,500) / 24,000 = 0.1125: 2 points; policy p. 5 lists propane, + # wood, coal, generator fuel and pellets only. + az_liheap_energy_burden_points: 2 + +- name: Case 17, all seven energy bills are summed into the burden. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 24_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + gas_expense: 600 + bottled_gas_expense: 300 + wood_expense: 200 + coal_expense: 100 + fuel_oil_expense: 150 + other_heating_fuel_expense: 450 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # 1,200 + 600 + 300 + 200 + 100 + 150 + 450 = 3,000. + az_liheap_energy_cost: 3_000 + # 3,000 / 24,000 = 0.125: 11%-15% band; electric heat, no bonus. + az_liheap_energy_burden_points: 2 + +- name: Case 18, no energy bill with no income scores 0 burden points. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + heat_expense_included_in_rent: true + pre_subsidy_electricity_expense: 0 + gas_expense: 0 + snap: 0 + tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_countable_income: 0 + az_liheap_energy_cost: 0 + # Energy in rent with no bill is no burden even though 0 / 0 is + # undefined; without the zero-cost guard it would take the 6-point band. + az_liheap_energy_burden_points: 0 + # Income 0 / 33,684 = 0 -> 5; burden 0; need 0; total 5 -> 320 (11 points + # and 480 without the guard). + az_liheap_points: 5 + az_liheap: 320 diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/points/az_liheap_energy_need_points.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/points/az_liheap_energy_need_points.yaml new file mode 100644 index 00000000000..20f1a2a6261 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/points/az_liheap_energy_need_points.yaml @@ -0,0 +1,382 @@ +# Arizona LIHEAP energy need points: one point each, and only one point per +# category, when any member is elderly (60 and over), disabled, a veteran, or +# a child aged 6 or younger (LIHEAP Eligibility Worksheet, $640 Limit, eff. +# 2024-10-12, section 3, +# https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1; +# DES LIHEAP Policy rev. 16A, p. 10 VI.B.1.d "Aged six (6) and under; Aged 60 +# and over; A veteran; A person with a disability", +# https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=10). +# Disability (policy p. 4): an ADA impairment, or categorically a recipient of +# Title II or XVI benefits "or other State-administered disability benefits", +# read as disability benefits: SSDI, or SSI paid on the basis of blindness or +# disability (aged-only SSI does not qualify). 2026 SSI rate 994 x 12 = +# 11,928. Base household: a 40-year-old earning 30,000 with electric heat. + +- name: Case 1, a working-age adult with no need factor has 0 points. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_energy_need_points: 0 + +- name: Case 2, a member aged 60 is elderly and adds 1 point. + period: 2026 + input: + people: + person1: + age: 60 + employment_income: 30_000 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_energy_need_points: 1 + +- name: Case 3, a member aged 59 is not elderly. + period: 2026 + input: + people: + person1: + age: 59 + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_energy_need_points: 0 + +- name: Case 4, a disabled member adds 1 point. + period: 2026 + input: + people: + person1: + age: 40 + is_disabled: true + employment_income: 30_000 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_energy_need_points: 1 + +- name: Case 5, an SSI recipient paid on the basis of disability is categorically disabled and adds 1 point. + period: 2026 + input: + people: + person1: + age: 40 + is_disabled: false + is_ssi_disabled: true + ssi: 11_928 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_600 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Policy p. 4: a recipient of Title XVI disability benefits meets the + # disability definition: elderly 0 + disabled 1 = 1. + az_liheap_energy_need_points: 1 + +- name: Case 6, an SSDI recipient is categorically disabled and adds 1 point. + period: 2026 + input: + people: + person1: + age: 48 + is_disabled: false + social_security_disability: 18_960 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_600 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Policy p. 4: a Title II recipient meets the disability definition. + az_liheap_energy_need_points: 1 + +- name: Case 7, a veteran adds 1 point. + period: 2026 + input: + people: + person1: + age: 40 + is_veteran: true + employment_income: 30_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_energy_need_points: 1 + +- name: Case 8, VA benefit receipt identifies a veteran and adds 1 point. + period: 2026 + input: + people: + person1: + age: 45 + veterans_benefits: 9_600 + employment_income: 20_000 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + az_liheap_energy_need_points: 1 + +- name: Case 9, a child aged 6 adds 1 point. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + person2: + age: 6 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_900 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2] + state_code: AZ + output: + # "Aged six (6) and under". + az_liheap_energy_need_points: 1 + +- name: Case 10, a child aged 7 does not add a point. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 30_000 + person2: + age: 7 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_900 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2] + state_code: AZ + output: + az_liheap_energy_need_points: 0 + +- name: Case 11, all four categories together add 4 points. + period: 2026 + input: + people: + person1: + age: 70 + social_security: 20_000 + ssi: 0 + person2: + age: 45 + is_disabled: true + employment_income: 15_000 + ssi: 0 + person3: + age: 50 + is_veteran: true + employment_income: 18_000 + person4: + age: 5 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 2_400 + gas_expense: 700 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3, person4] + state_code: AZ + output: + # Elderly 1 + disabled 1 + veteran 1 + child aged 6 or under 1. + az_liheap_energy_need_points: 4 + +- name: Case 12, two elderly members and two young children count one point per category. + period: 2026 + input: + people: + person1: + age: 68 + social_security: 18_000 + ssi: 0 + person2: + age: 66 + social_security: 12_000 + ssi: 0 + person3: + age: 4 + person4: + age: 2 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_600 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3, person4] + state_code: AZ + output: + # "Only one point per category allowed": elderly 1 + child 1 = 2. + az_liheap_energy_need_points: 2 + +- name: Case 13, one member who is elderly, disabled and a veteran earns 3 category points. + period: 2026 + input: + people: + person1: + age: 72 + is_disabled: true + is_veteran: true + social_security: 16_000 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_700 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Points attach to categories, not persons: elderly + disabled + veteran. + az_liheap_energy_need_points: 3 + +- name: Case 14, an aged SSI recipient without a disability gets the elderly point only. + period: 2026 + input: + people: + person1: + age: 70 + is_disabled: false + is_blind: false + ssi: 11_928 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_600 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # Aged 70 >= 60: elderly 1. SSI paid on the basis of age alone is not a + # disability benefit under policy p. 4, and there is no impairment or + # SSDI: disabled 0. Total 1 + 0 = 1. + az_liheap_energy_need_points: 1 + +- name: Case 15, a blind SSI recipient aged 40 is categorically disabled and adds 1 point. + period: 2026 + input: + people: + person1: + age: 40 + is_disabled: false + is_blind: true + ssi: 11_928 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_600 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1] + state_code: AZ + output: + # SSI paid on the basis of blindness is a Title XVI disability benefit + # (policy p. 4): disabled 1; aged 40, so elderly 0. Total 0 + 1 = 1. + az_liheap_energy_need_points: 1 diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/points/az_liheap_income_points.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/points/az_liheap_income_points.yaml new file mode 100644 index 00000000000..e566f4c199d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/az/des/liheap/points/az_liheap_income_points.yaml @@ -0,0 +1,334 @@ +# Arizona LIHEAP income points: monthly gross income divided by the monthly +# income limit for the eligible household size; 0%-20% 5 points, 21%-40% 4, +# 41%-60% 3, 61%-80% 2, 81%-100% 1, above 100% none (LIHEAP Eligibility +# Worksheet, $640 Limit, eff. 2024-10-12, section 1, +# https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1; +# DES program page formula and example "2,000 / 4,535 = 44%, three points", +# https://des.az.gov/liheap). Annual income over the annual limit gives the +# same ratio as the monthly figures. The ratio is continuous and a band top +# belongs to its band (exactly 20% is 5 points). Household of three (two +# adults and a child aged 4): limit 4,535 x 12 = 54,420. + +- name: Case 1, the program-page example of 2,000 a month against the 4,535 limit gives 3 points. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 14_000 + person2: + age: 37 + employment_income: 10_000 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # 24,000 / 54,420 = 0.441 (2,000 / 4,535 = 44%): 41%-60% band. + az_liheap_income_points: 3 + +- name: Case 2, a household with no income has 5 points. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 0 + person2: + age: 37 + employment_income: 0 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # 0 / 54,420 = 0: 0%-20% band. + az_liheap_income_points: 5 + +- name: Case 3, income at exactly 20 percent of the limit keeps 5 points. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 10_884 + person2: + age: 37 + employment_income: 0 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # 10,884 / 54,420 = 0.20 exactly: top of the 0%-20% band. + az_liheap_income_points: 5 + +- name: Case 4, one dollar above 20 percent of the limit drops to 4 points. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 10_885 + person2: + age: 37 + employment_income: 0 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # 10,885 / 54,420 = 0.20002 > 0.20: 21%-40% band. + az_liheap_income_points: 4 + +- name: Case 5, income at exactly 40 percent of the limit keeps 4 points. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 21_768 + person2: + age: 37 + employment_income: 0 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # 21,768 / 54,420 = 0.40 exactly. + az_liheap_income_points: 4 + +- name: Case 6, one dollar above 40 percent of the limit drops to 3 points. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 21_769 + person2: + age: 37 + employment_income: 0 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # 21,769 / 54,420 = 0.40002 > 0.40. + az_liheap_income_points: 3 + +- name: Case 7, income at exactly 60 percent of the limit keeps 3 points. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 20_000 + person2: + age: 37 + employment_income: 12_652 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # 20,000 + 12,652 = 32,652; 32,652 / 54,420 = 0.60 exactly. + az_liheap_income_points: 3 + +- name: Case 8, one dollar above 60 percent of the limit drops to 2 points. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 20_000 + person2: + age: 37 + employment_income: 12_653 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # 32,653 / 54,420 = 0.60002 > 0.60. + az_liheap_income_points: 2 + +- name: Case 9, income at exactly 80 percent of the limit keeps 2 points. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 26_000 + person2: + age: 37 + employment_income: 17_536 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # 26,000 + 17,536 = 43,536; 43,536 / 54,420 = 0.80 exactly. + az_liheap_income_points: 2 + +- name: Case 10, one dollar above 80 percent of the limit drops to 1 point. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 26_000 + person2: + age: 37 + employment_income: 17_537 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # 43,537 / 54,420 = 0.80002 > 0.80. + az_liheap_income_points: 1 + +- name: Case 11, income at exactly the limit keeps 1 point. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 30_000 + person2: + age: 37 + employment_income: 24_420 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + snap: 0 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # 30,000 + 24,420 = 54,420; 54,420 / 54,420 = 1.00: 81%-100% band. + az_liheap_income_points: 1 + +- name: Case 12, a SNAP household above the limit has no income points. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 34_000 + person2: + age: 37 + employment_income: 26_000 + person3: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + snap: 2_400 + az_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AZ + output: + # 60,000 / 54,420 = 1.10 > 1.00: no worksheet row. + az_liheap_income_points: 0 diff --git a/policyengine_us/variables/gov/states/az/des/liheap/az_liheap.py b/policyengine_us/variables/gov/states/az/des/liheap/az_liheap.py new file mode 100644 index 00000000000..282b9c3586e --- /dev/null +++ b/policyengine_us/variables/gov/states/az/des/liheap/az_liheap.py @@ -0,0 +1,40 @@ +from policyengine_us.model_api import * + + +class az_liheap(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Arizona LIHEAP" + documentation = ( + "Annual Arizona Standard LIHEAP benefit, paid once per 12-month " + "eligibility period from the benefit matrix points: $160 for up to 4 " + "points, $320 for 5 to 8, $480 for 9 to 12 and $640 for 13 or more. " + "It covers heating and cooling energy bills alike and is not capped at " + "the bill. Verified for FY2026; earlier years use model parameter " + "backfilling and are unverified historical estimates. Crisis " + "assistance, water for evaporative cooling, weatherization and the " + "Senior Utility Assistance Program recertification maximum are not " + "modeled." + ) + defined_for = "az_liheap_eligible" + reference = ( + "https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1", + # PDF pages 4, 10 + "https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=4", + # PDF pages 10, 31, 34-35, 41 + "https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=10", + "https://des.az.gov/liheap", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.az.des.liheap.payment + points = spm_unit("az_liheap_points", period) + # Section VII.B.3.b.iii.D.2: if all vendor accounts "have been paid in + # full by their LIHEAP benefit and there is a remaining benefit, this + # benefit will be allocated to the highest priority" account, so the + # payment is the matrix amount, not the bill. The plan's heating + # (16.80%) and cooling (38.12%) components are funding shares of this + # single Standard benefit, not separate household payments. + return p.amount.calc(points) diff --git a/policyengine_us/variables/gov/states/az/des/liheap/az_liheap_energy_cost.py b/policyengine_us/variables/gov/states/az/des/liheap/az_liheap_energy_cost.py new file mode 100644 index 00000000000..69687c93e1e --- /dev/null +++ b/policyengine_us/variables/gov/states/az/des/liheap/az_liheap_energy_cost.py @@ -0,0 +1,36 @@ +from policyengine_us.model_api import * + + +class az_liheap_energy_cost(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Arizona LIHEAP annual energy cost" + documentation = "Annual electricity, gas and portable fuel bills entering the energy burden, the worksheet's 'Electric + Gas + Portable Fuel = Total Monthly Cost' taken over the year. Water for evaporative cooling, sewer, trash and phone bills are excluded." + reference = ( + "https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1", + # PDF pages 4-5 + "https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=4", + ) + defined_for = StateCode.AZ + + # "Energy Utility: A service or Portable Fuel that provides a source of + # heating or cooling for a Household. Eligible Energy Utility service + # providers include vendors of electricity or gas; Portable Fuel for + # purposes of heating or cooling" (page 4); "Portable Fuel: Energy that is + # derived from propane, wood, coal, fuel for gas generators, and/or pellets + # and that is used for heating or cooling a residence" (page 5). Fuel oil + # and other heating fuel bills are purchased home energy and are counted + # with them. The electric bill is read before subsidies, as heating_expense + # does, so the burden is gross of discount programs and no subsidy chain + # feeds back into LIHEAP. + adds = [ + "pre_subsidy_electricity_expense", + "gas_expense", + "bottled_gas_expense", + "wood_expense", + "coal_expense", + "fuel_oil_expense", + "other_heating_fuel_expense", + ] diff --git a/policyengine_us/variables/gov/states/az/des/liheap/az_liheap_household_size.py b/policyengine_us/variables/gov/states/az/des/liheap/az_liheap_household_size.py new file mode 100644 index 00000000000..3e765cb7b4b --- /dev/null +++ b/policyengine_us/variables/gov/states/az/des/liheap/az_liheap_household_size.py @@ -0,0 +1,22 @@ +from policyengine_us.model_api import * + + +class az_liheap_household_size(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Arizona LIHEAP household size" + documentation = "Number of household members who are U.S. citizens or qualified noncitizens. Other members are not counted in household size, although the income of every adult member counts toward household income." + reference = ( + # PDF pages 4-5, 8 + "https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=4", + ) + defined_for = StateCode.AZ + + # Section VI.A.6.d: "In Mixed-Status Households: i. Only Eligible Household + # Members will be considered as part of the Household size; and ii. Income + # from all Household members aged 18 and older - regardless of eligibility - + # is considered Household income." An Eligible Household Member is "a U.S. + # citizen or Qualified Noncitizen" (page 4). The model's qualified statuses + # omit COFA citizens, trafficking victims and battered noncitizens. + adds = ["is_citizen_or_legal_immigrant"] diff --git a/policyengine_us/variables/gov/states/az/des/liheap/eligibility/az_liheap_categorically_eligible.py b/policyengine_us/variables/gov/states/az/des/liheap/eligibility/az_liheap_categorically_eligible.py new file mode 100644 index 00000000000..c9ac6f12515 --- /dev/null +++ b/policyengine_us/variables/gov/states/az/des/liheap/eligibility/az_liheap_categorically_eligible.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class az_liheap_categorically_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Arizona LIHEAP categorically eligible" + documentation = "A household member receives DES Cash Assistance (TANF) or Nutrition Assistance (SNAP), which satisfies the income requirement; all other eligibility criteria still apply." + reference = ( + # PDF pages 3-4, 7 + "https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=3", + "https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=5", + ) + defined_for = StateCode.AZ + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.az.des.liheap.eligibility + # Section VI.A.6.b.i: "If one (1) Household member's current enrollment + # in Cash Assistance or Nutrition Assistance is verified, the Household + # will be considered categorically eligible." A positive modeled + # benefit in the year stands for current enrollment; add() sums the + # MONTH-defined snap over the year, and the annual tanf includes + # Arizona Cash Assistance. Both are take-up gated. SSI and veterans + # programs do not confer categorical eligibility (plan Section 1.4). + return add(spm_unit, period, p.categorical_programs) > 0 diff --git a/policyengine_us/variables/gov/states/az/des/liheap/eligibility/az_liheap_eligible.py b/policyengine_us/variables/gov/states/az/des/liheap/eligibility/az_liheap_eligible.py new file mode 100644 index 00000000000..79182279951 --- /dev/null +++ b/policyengine_us/variables/gov/states/az/des/liheap/eligibility/az_liheap_eligible.py @@ -0,0 +1,50 @@ +from policyengine_us.model_api import * + + +class az_liheap_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Eligible for Arizona LIHEAP" + documentation = ( + "Arizona household with at least one citizen or qualified noncitizen " + "member that is categorically or income eligible, has a counted energy " + "bill or energy included in its rent, and reports its heating fuel. The " + "subsidized-housing utility allowance test, the Tribal LIHEAP " + "first-apply rule and the bar on costs already paid by another program " + "have no inputs and are not modeled." + ) + reference = ( + # PDF pages 4-8, 33 + "https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=4", + "https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=9", + ) + defined_for = StateCode.AZ + + def formula(spm_unit, period, parameters): + # Section VI.A.3: "An Applicant, or a Household member, must be an + # Eligible Household Member"; the size counts only such members. + has_eligible_member = spm_unit("az_liheap_household_size", period) > 0 + # Section VI.A.6.a: "A Household must either meet Categorical + # Eligibility or Income Eligibility requirements." + categorically_eligible = spm_unit("az_liheap_categorically_eligible", period) + income_eligible = spm_unit("az_liheap_income_eligible", period) + # Section VI.A.1: "An Applicant must require assistance with payment of + # energy-related expenses." The Household definition (page 5) includes + # members "who make undesignated payments for Energy Utilities in the + # form of rent", and Section VII.B.3.a.ii.B pays the applicant directly + # when "Energy Utilities are included within the cost of their rent". + has_energy_cost = spm_unit("az_liheap_energy_cost", period) > 0 + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + # heating_type is a required input and UNSPECIFIED pays nothing. NONE + # stays eligible: the Standard benefit pays any heating or cooling + # Energy Utility bill, not the heating fuel alone, so a home without + # heating that pays for electricity still requires assistance. + heating_type = spm_unit("heating_type", period) + known_fuel = heating_type != heating_type.possible_values.UNSPECIFIED + return ( + has_eligible_member + & (categorically_eligible | income_eligible) + & (has_energy_cost | heat_in_rent) + & known_fuel + ) diff --git a/policyengine_us/variables/gov/states/az/des/liheap/eligibility/az_liheap_income_eligible.py b/policyengine_us/variables/gov/states/az/des/liheap/eligibility/az_liheap_income_eligible.py new file mode 100644 index 00000000000..cda9c9f6718 --- /dev/null +++ b/policyengine_us/variables/gov/states/az/des/liheap/eligibility/az_liheap_income_eligible.py @@ -0,0 +1,22 @@ +from policyengine_us.model_api import * + + +class az_liheap_income_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Arizona LIHEAP income eligible" + documentation = "Countable household income does not exceed the Appendix A income limit for the eligible household size." + reference = ( + # PDF pages 8, 39 + "https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=8", + "https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=9", + ) + defined_for = StateCode.AZ + + def formula(spm_unit, period, parameters): + income = spm_unit("az_liheap_countable_income", period) + limit = spm_unit("az_liheap_income_limit", period) + # Section VI.A.6.c.i: income "must be at or below" the limit, so a + # household exactly at the limit qualifies. + return income <= limit diff --git a/policyengine_us/variables/gov/states/az/des/liheap/eligibility/az_liheap_income_limit.py b/policyengine_us/variables/gov/states/az/des/liheap/eligibility/az_liheap_income_limit.py new file mode 100644 index 00000000000..b2a8bf34149 --- /dev/null +++ b/policyengine_us/variables/gov/states/az/des/liheap/eligibility/az_liheap_income_limit.py @@ -0,0 +1,67 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.hhs_smi import liheap_smi_limit +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class az_liheap_income_limit(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Arizona LIHEAP annual income limit" + documentation = ( + "Twelve times the Appendix A monthly gross income limit for the " + "eligible household size: the greater of 60% of the state median income " + "and 150% of the federal poverty guideline, each floored to whole " + "monthly dollars." + ) + reference = ( + # PDF pages 8, 39 + "https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=8", + "https://web.archive.org/web/20251030203752/https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=37", + "https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=9", + "https://liheapch.acf.gov/profiles/povertytables/FY2026/azsmi.htm", + "https://liheapch.acf.gov/profiles/povertytables/FY2026/popstate.htm", + ) + defined_for = StateCode.AZ + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.az.des.liheap.eligibility + size = spm_unit("az_liheap_household_size", period) + state = spm_unit.household("state_code_str", period) + state_group = spm_unit.household("state_group_str", period) + # Section VI.A.6.c.i: "Gross Income must be at or below the greater of: + # A) 60% of the SMI; or B) 150% of the FPL." Appendix A prints the + # monthly result for sizes 1 to 12 plus an amount per additional person + # ($687 for FFY2026 in revision 13, $710 for FFY2027 in revision 16A). + # Its SMI arm reproduces only with the shared HHS floor order, the + # four-person 60% amount first (64,789 from 107,983 for FFY2026) and + # then the size-adjusted annual amount, followed by a floor of the + # monthly amount; this is a reconciled numerical pattern, not an + # explicit rounding rule. + smi_annual = liheap_smi_limit(size, state, p.smi_rate, period, parameters) + smi_monthly = np.floor(smi_annual / MONTHS_IN_YEAR) + # The FPL arm uses the guideline in force when the season's limits were + # set (the 2025 guideline for FFY2026) and floors the monthly amount. + # Appendix A's "EACH ADDL OVER 12" amount is the floored monthly value + # of 150% of the additional-person guideline, applied beyond the table. + capped_size = clip(size, 1, p.max_table_size) + guideline = fpg( + capped_size, state_group, period, parameters, year_lag=p.fpg_year_lag + ) + additional_guideline = ( + fpg( + capped_size + 1, + state_group, + period, + parameters, + year_lag=p.fpg_year_lag, + ) + - guideline + ) + fpg_monthly = np.floor(p.fpg_rate * guideline / MONTHS_IN_YEAR) + increment = np.floor(p.fpg_rate * additional_guideline / MONTHS_IN_YEAR) + additional_people = max_(size - p.max_table_size, 0) + fpg_monthly = fpg_monthly + additional_people * increment + monthly_limit = max_(smi_monthly, fpg_monthly) + return where(size > 0, monthly_limit * MONTHS_IN_YEAR, 0) diff --git a/policyengine_us/variables/gov/states/az/des/liheap/income/az_liheap_countable_earned_income.py b/policyengine_us/variables/gov/states/az/des/liheap/income/az_liheap_countable_earned_income.py new file mode 100644 index 00000000000..6c41fdb222e --- /dev/null +++ b/policyengine_us/variables/gov/states/az/des/liheap/income/az_liheap_countable_earned_income.py @@ -0,0 +1,40 @@ +from policyengine_us.model_api import * + + +class az_liheap_countable_earned_income(Variable): + value_type = float + entity = Person + definition_period = YEAR + label = "Arizona LIHEAP countable earned income" + documentation = ( + "Gross wages and reported net self-employment income of household " + "members aged 18 and older. Each source is floored at zero, so a " + "business loss does not offset other earnings. Earned income of members " + "under 18 never counts." + ) + unit = USD + reference = ( + # PDF pages 8-9, 13-14 + "https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=8", + # PDF pages 6-7 + "https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=6", + ) + defined_for = StateCode.AZ + + def formula(person, period, parameters): + p = parameters(period).gov.states.az.des.liheap.income + # Section VII.A.1.c.ii.B counts "Income from self-employment or + # sole-proprietorship" and Section VII.A.1.c.iii.B.1 accepts a + # "Schedule C (Profit and Loss Statement)", without saying whether a + # loss offsets other income, so each net source is floored at zero + # (project convention). + earned = 0 + for source in p.sources.earned: + earned = earned + max_(person(source, period), 0) + # Section VI.A.6.d.ii counts "Income from all Household members aged 18 + # and older"; Section VI.A.6.e.ii.E excludes income of members under 18 + # in revision 13 and their earned income from revision 15, and the plan's + # Section 1.9 leaves "Earned income of a child under the age of 18" + # unchecked, so under-18 earnings never count. + age = person("age", period) + return where(age >= p.adult_age, earned, 0) diff --git a/policyengine_us/variables/gov/states/az/des/liheap/income/az_liheap_countable_income.py b/policyengine_us/variables/gov/states/az/des/liheap/income/az_liheap_countable_income.py new file mode 100644 index 00000000000..a9e775fe076 --- /dev/null +++ b/policyengine_us/variables/gov/states/az/des/liheap/income/az_liheap_countable_income.py @@ -0,0 +1,65 @@ +from policyengine_us.model_api import * + + +class az_liheap_countable_income(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + label = "Arizona LIHEAP countable income" + documentation = ( + "Annual gross household income counted under Arizona LIHEAP, including " + "the income of members who are not citizens or qualified noncitizens, " + "less each member's out-of-pocket Medicare Part A and Part B premiums " + "and floored at zero. Policy VII.A.1.c.ii.D " + "(CCSD-LIHEAP-Policy.pdf#page=14): " + "'Expenses related to Medicare (such as premiums) should be subtracted " + "when determining gross income'; Part D has no premium variable and is " + "not deducted. Annual amounts stand for twelve times the policy's " + "30-day income snapshot." + ) + unit = USD + reference = ( + # PDF pages 4, 8-9, 14, 32 + "https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=4", + # PDF pages 5-7 + "https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=5", + ) + defined_for = StateCode.AZ + + def formula(spm_unit, period, parameters): + # Section VII.A.1.c.v annualizes the 30-day snapshot as "([Average + # Gross Income per Pay Period X Income Frequency] / 12)", so annual + # income over twelve is the policy's own arithmetic. Cash Assistance + # is a household benefit with no person-level attribution, so it is + # summed here rather than in the person-level unearned source list: + # Section VI.A.6.e.ii.B counts "Social Security and TANF benefits", and + # Section VII.B.1.c.ii adds "the Cash Assistance benefit" to the + # reported income of households categorically eligible through it. The + # take-up-gated tanf includes Arizona Cash Assistance, so a family that + # does not take it up has none counted. The policy's narrower base for + # categorically eligible households (the enrolled member's reported + # income) is not modeled; the whole household's countable income is + # used. + gross_income = add( + spm_unit, + period, + [ + "az_liheap_countable_earned_income", + "az_liheap_countable_unearned_income", + "tanf", + ], + ) + # Section VII.A.1.c.ii.D note (CCSD-LIHEAP-Policy.pdf#page=14): + # "Expenses related to Medicare (such as premiums) should be + # subtracted when determining gross income." Plan Section 1.9 counts + # SSA benefits "Excluding MediCare deduction". The Part A and Part B + # premium variables are each member's out-of-pocket premiums, zero for + # a member who is not enrolled, whose premium a Medicare Savings + # Program pays, or (for Part A) who has premium-free Part A; no Part D + # premium variable exists. + medicare_premiums = add( + spm_unit, + period, + ["medicare_part_a_premium", "medicare_part_b_premium"], + ) + return max_(gross_income - medicare_premiums, 0) diff --git a/policyengine_us/variables/gov/states/az/des/liheap/income/az_liheap_countable_unearned_income.py b/policyengine_us/variables/gov/states/az/des/liheap/income/az_liheap_countable_unearned_income.py new file mode 100644 index 00000000000..7716e5f7fca --- /dev/null +++ b/policyengine_us/variables/gov/states/az/des/liheap/income/az_liheap_countable_unearned_income.py @@ -0,0 +1,61 @@ +from policyengine_us.model_api import * + + +class az_liheap_countable_unearned_income(Variable): + value_type = float + entity = Person + definition_period = YEAR + label = "Arizona LIHEAP countable unearned income" + documentation = ( + "Gross unearned income of a household member: counted in full for " + "members aged 18 and older, and for younger members only from policy " + "revision 15 (March 23, 2026). Each source is floored at zero, so a " + "rental loss does not offset other income. Gambling counts as profits: " + "winnings less gambling losses, floored at zero. Social Security " + "counts gross here; az_liheap_countable_income adds Cash Assistance, a " + "household benefit, and subtracts the household's Medicare Part A and " + "Part B premiums under the Policy VII.A.1.c.ii.D note." + ) + unit = USD + reference = ( + # PDF pages 4, 8-9, 14 + "https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=4", + "https://web.archive.org/web/20251030203752/https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=9", + # PDF pages 6-7 + "https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=6", + ) + defined_for = StateCode.AZ + + def formula(person, period, parameters): + p = parameters(period).gov.states.az.des.liheap.income + # Gross Income is "The total Household income for all Household + # members before taxes and other deductions" (page 4), and a loss is + # not income, so each source is floored at zero and a rental loss + # cannot offset another source. ADD sums the MONTH-defined ssi over + # the year. + unearned = 0 + for source in p.sources.unearned: + unearned = unearned + max_(person(source, period, options=[ADD]), 0) + # Section VI.A.6.e.ii.B counts "gambling profits" (page 9), so + # gambling losses offset winnings but not other income. Plan Section + # 1.9 leaves one-time lump sums such as "winnings from lotteries" + # unchecked (page 6); the inputs do not separate one-time winnings, so + # all profits are counted. + gambling_profits = max_( + person("gambling_winnings", period) - person("gambling_losses", period), + 0, + ) + # Social Security counts gross here. The Section VII.A.1.c.ii.D note + # (CCSD-LIHEAP-Policy.pdf#page=14), "Expenses related to Medicare (such + # as premiums) should be subtracted when determining gross income", is + # applied to household gross income in az_liheap_countable_income. + age = person("age", period) + # Revision 13, Section VI.A.6.e.ii.E: "Income from Household members + # under the age of 18 is not considered Countable Income." Revision 15 + # narrows this to "Earned income from Household members under the age + # of 18", as revision 16A still reads. Counting a child's unearned + # income from March 23, 2026 is an inference from that narrowing; + # Section VI.A.6.d.ii still counts the income of members aged 18 and + # older. + counted = (age >= p.adult_age) | p.child_unearned_income_counted + return where(counted, unearned + gambling_profits, 0) diff --git a/policyengine_us/variables/gov/states/az/des/liheap/points/az_liheap_energy_burden_points.py b/policyengine_us/variables/gov/states/az/des/liheap/points/az_liheap_energy_burden_points.py new file mode 100644 index 00000000000..c2068d6fc4a --- /dev/null +++ b/policyengine_us/variables/gov/states/az/des/liheap/points/az_liheap_energy_burden_points.py @@ -0,0 +1,56 @@ +from policyengine_us.model_api import * + + +class az_liheap_energy_burden_points(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Arizona LIHEAP energy burden points" + documentation = ( + "Benefit matrix points from energy cost as a share of countable income " + "(5% or less gives 0 points, then one point per five-percent band up to " + "6 points at 31% or over), plus 2 points for portable fuel heating " + "(propane, wood or coal). A positive cost with no income takes the top " + "band; no cost gives no burden points." + ) + reference = ( + "https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1", + "https://des.az.gov/liheap", + # PDF pages 5, 10, 40 + "https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=5", + ) + defined_for = StateCode.AZ + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.az.des.liheap.points + cost = spm_unit("az_liheap_energy_cost", period) + income = spm_unit("az_liheap_countable_income", period) + # Worksheet: "Total Monthly Cost รท 30 Days Gross Income = % Energy + # Burden"; annual over annual gives the same ratio. The worksheet does + # not address zero income: a positive cost with no income is an + # unbounded burden and takes the top band, and no cost is no burden. + # The quotient is formed in float64 so that an exact band top such as + # 2,400 / 24,000 = 0.10 stays in its band. + burden = np.divide( + cost.astype(np.float64), + income, + out=np.full(cost.shape, np.inf), + where=income > 0, + ) + burden = where(cost > 0, burden, 0) + # "5% or less" is 0 points and the DES example places 17.5% in the + # "16% - 20%" band, so right=True with the -inf first threshold. + burden_points = p.energy_burden.calc(burden, right=True) + # Worksheet: "Note: Add 2 points for portable fuels". "Portable Fuel: + # Energy that is derived from propane, wood, coal, fuel for gas + # generators, and/or pellets" (page 5); generator fuel and pellets have + # no heating type of their own, and fuel oil and kerosene are not + # portable fuels under this definition. + heating_type = spm_unit("heating_type", period) + types = heating_type.possible_values + portable_fuel = ( + (heating_type == types.PROPANE) + | (heating_type == types.WOOD) + | (heating_type == types.COAL) + ) + return burden_points + where(portable_fuel, p.portable_fuel, 0) diff --git a/policyengine_us/variables/gov/states/az/des/liheap/points/az_liheap_energy_need_points.py b/policyengine_us/variables/gov/states/az/des/liheap/points/az_liheap_energy_need_points.py new file mode 100644 index 00000000000..9a46c32fe26 --- /dev/null +++ b/policyengine_us/variables/gov/states/az/des/liheap/points/az_liheap_energy_need_points.py @@ -0,0 +1,58 @@ +from policyengine_us.model_api import * + + +class az_liheap_energy_need_points(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Arizona LIHEAP energy need points" + documentation = ( + "One benefit matrix point for each category present in the household, " + "at most one per category: a member aged 60 or over, a member with a " + "disability (including recipients of SSI paid on the basis of blindness " + "or disability and of Social Security disability benefits), a veteran, " + "and a child aged 6 or under." + ) + reference = ( + "https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1", + # PDF pages 4, 10, 41 + "https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=4", + "https://liheapch.acf.gov/docs/2026/state-plans/AZ_Plan_2026.pdf#page=10", + ) + defined_for = StateCode.AZ + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.az.des.liheap.points + person = spm_unit.members + age = person("age", period) + # Section VI.B.1.d: whether any household members are "Aged six (6) and + # under; Aged 60 and over; A veteran; or A person with a disability." + # Worksheet: "(Only one point per category allowed)". + elderly = spm_unit.any(age >= p.elderly_age) + young_child = spm_unit.any(age < p.child_age_limit) + veteran = spm_unit.any(person("is_veteran", period)) + # "Disability: A physical or mental impairment that substantially limits + # at least one major life activity ... For the purposes of this Policy, + # a recipient of benefits under Title II or XVI of the Social Security + # Act, or other State-administered disability benefits, is considered + # to categorically meet this definition" (page 4). "Other ... + # disability benefits" reads the Title II and XVI benefits as disability + # benefits, so Social Security retirement or survivor benefits and SSI + # paid only on the basis of age do not qualify; Title XVI counts when + # the SSI recipient is blind or SSI-disabled. SSI is MONTH-defined, so + # add() sums it over the year. + receives_ssi = add(person, period, ["ssi"]) > 0 + blind = person("is_blind", period) + ssi_disabled = person("is_ssi_disabled", period) + disabled = spm_unit.any( + person("is_disabled", period) + | (receives_ssi & (blind | ssi_disabled)) + | (person("social_security_disability", period) > 0) + ) + point = p.vulnerable_member + return ( + where(elderly, point, 0) + + where(disabled, point, 0) + + where(veteran, point, 0) + + where(young_child, point, 0) + ) diff --git a/policyengine_us/variables/gov/states/az/des/liheap/points/az_liheap_income_points.py b/policyengine_us/variables/gov/states/az/des/liheap/points/az_liheap_income_points.py new file mode 100644 index 00000000000..e51a34f5ffb --- /dev/null +++ b/policyengine_us/variables/gov/states/az/des/liheap/points/az_liheap_income_points.py @@ -0,0 +1,41 @@ +from policyengine_us.model_api import * + + +class az_liheap_income_points(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Arizona LIHEAP income points" + documentation = ( + "Benefit matrix points from countable income as a share of the income " + "limit for the household size: 0% to 20% gives 5 points, 21% to 40% 4, " + "41% to 60% 3, 61% to 80% 2, 81% to 100% 1, and above 100% (reachable " + "only through categorical eligibility) 0." + ) + reference = ( + "https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1", + "https://des.az.gov/liheap", + # PDF pages 10, 40 + "https://des.az.gov/sites/default/files/dl/CCSD-LIHEAP-Policy.pdf#page=10", + ) + defined_for = StateCode.AZ + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.az.des.liheap.points + income = spm_unit("az_liheap_countable_income", period) + limit = spm_unit("az_liheap_income_limit", period) + # DES: "Total Monthly Gross Income Divided by the Monthly Gross Income + # Limit for the Household Size = % Income"; annual over annual gives the + # same ratio. A zero limit (no eligible member) is treated as a ratio + # above every band, scoring 0 points. The quotient is formed in float64 + # so that an exact band top such as 10,884 / 54,420 = 0.20 stays in its + # band instead of rounding above the threshold. + ratio = np.divide( + income.astype(np.float64), + limit, + out=np.full(income.shape, np.inf), + where=limit > 0, + ) + # Band tops: "0% - 20%" is 5 points and the DES example places 44% in + # the 41% to 60% band, so right=True with the -inf first threshold. + return p.income_ratio.calc(ratio, right=True) diff --git a/policyengine_us/variables/gov/states/az/des/liheap/points/az_liheap_points.py b/policyengine_us/variables/gov/states/az/des/liheap/points/az_liheap_points.py new file mode 100644 index 00000000000..c236e59b030 --- /dev/null +++ b/policyengine_us/variables/gov/states/az/des/liheap/points/az_liheap_points.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class az_liheap_points(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Arizona LIHEAP total benefit matrix points" + documentation = "Worksheet total of income points, energy burden points (including portable fuel points) and energy need points; at most 17." + reference = ( + "https://liheapch.acf.gov/docs/2026/benefits-matricies/AZ_BenefitMatrix_Heating_2026.pdf#page=1", + "https://des.az.gov/liheap", + ) + defined_for = StateCode.AZ + + # Worksheet Section 4: "Total All Points: (a+b+c+d+e+f)". + adds = [ + "az_liheap_income_points", + "az_liheap_energy_burden_points", + "az_liheap_energy_need_points", + ]