diff --git a/changelog.d/al-liheap.added.md b/changelog.d/al-liheap.added.md new file mode 100644 index 00000000000..b8515e3f622 --- /dev/null +++ b/changelog.d/al-liheap.added.md @@ -0,0 +1 @@ +Add Alabama LIHEAP regular heating eligibility and payments, applying the HUD allowance before the supplement and requiring a positive remaining regular grant. diff --git a/policyengine_us/parameters/gov/states/al/adeca/liheap/eligibility/fpg_rate.yaml b/policyengine_us/parameters/gov/states/al/adeca/liheap/eligibility/fpg_rate.yaml new file mode 100644 index 00000000000..2f2d2065135 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/eligibility/fpg_rate.yaml @@ -0,0 +1,10 @@ +description: Alabama limits income to this multiple of the federal poverty guideline under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 1.5 +metadata: + unit: /1 + period: year + label: Alabama LIHEAP federal poverty guideline rate + reference: + - title: Alabama FY2026 LIHEAP State Plan, section 2.1 + href: https://liheapch.acf.gov/docs/2026/state-plans/AL_Plan_2026.pdf#page=8 diff --git a/policyengine_us/parameters/gov/states/al/adeca/liheap/eligibility/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/al/adeca/liheap/eligibility/fpg_year_lag.yaml new file mode 100644 index 00000000000..47485dcde73 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/eligibility/fpg_year_lag.yaml @@ -0,0 +1,10 @@ +description: Alabama uses federal poverty guidelines from this many years before the benefit year for regular heating assistance under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 1 +metadata: + unit: year + period: year + label: Alabama LIHEAP heating poverty guideline year lag + reference: + - title: "Alabama FY2026 LIHEAP State Plan (October-April heating season and 2025 guidelines) #page 4,9" + href: https://liheapch.acf.gov/docs/2026/state-plans/AL_Plan_2026.pdf#page=4 diff --git a/policyengine_us/parameters/gov/states/al/adeca/liheap/income/earned_income_min_age.yaml b/policyengine_us/parameters/gov/states/al/adeca/liheap/income/earned_income_min_age.yaml new file mode 100644 index 00000000000..4682551fb71 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/income/earned_income_min_age.yaml @@ -0,0 +1,10 @@ +description: Alabama excludes the earned income of household members below this age under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 18 +metadata: + unit: year + period: year + label: Alabama LIHEAP earned income minimum age + reference: + - title: "Alabama FY2026 LIHEAP Policy Manual, Income #page 17-19" + href: https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=17 diff --git a/policyengine_us/parameters/gov/states/al/adeca/liheap/income/sources/earned.yaml b/policyengine_us/parameters/gov/states/al/adeca/liheap/income/sources/earned.yaml new file mode 100644 index 00000000000..62bbc66c41d --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/income/sources/earned.yaml @@ -0,0 +1,17 @@ +description: Alabama uses these sources as earned income under the Low Income Home Energy Assistance Program. +# Existing net business inputs approximate net farm and nonfarm income without +# a separate LIHEAP expense deduction. The formula floors each source at zero. +values: + 2025-10-01: + - employment_income + - self_employment_income + - sstb_self_employment_income + - farm_operations_income + - partnership_s_corp_income +metadata: + unit: list + period: year + label: Alabama LIHEAP earned income sources + reference: + - title: "Alabama FY2026 LIHEAP Policy Manual, Income #page 17-19" + href: https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=17 diff --git a/policyengine_us/parameters/gov/states/al/adeca/liheap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/al/adeca/liheap/income/sources/unearned.yaml new file mode 100644 index 00000000000..4ab8e63322e --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/income/sources/unearned.yaml @@ -0,0 +1,41 @@ +description: Alabama uses these sources as unearned income for members of all ages under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: + # Reported unemployment compensation, or modeled Alabama UI (al_ui) when + # none is reported. + - total_unemployment_compensation + - strike_benefits + # Social Security is counted gross; the input does not deduct Medicare + # premiums. + - social_security + - ssi + # Page 18 excludes "Foster care benefits", read as board payments, not this SSP. + - al_ssp + # Military retirement pay is a separate input from the pension inputs that + # pension_income adds, so both are listed; a household reports the amount + # in one or the other. + - pension_income + - military_retirement_pay + - survivor_benefits + - veterans_benefits + - disability_benefits + - workers_compensation + - child_support_received + - alimony_income + - interest_income + - ordinary_dividend_income + - rental_income + - farm_rent_income + - estate_income + - railroad_benefits + - financial_assistance +# TANF is counted in the formula through the take-up-gated tanf aggregate. +# Net gambling winnings are calculated separately using existing winnings and +# losses inputs. Do not also include the gross winnings here. +metadata: + unit: list + period: year + label: Alabama LIHEAP unearned income sources + reference: + - title: "Alabama FY2026 LIHEAP Policy Manual, Income #page 17-19" + href: https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=17 diff --git a/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/additional_person_amount.yaml b/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/additional_person_amount.yaml new file mode 100644 index 00000000000..fb7ddf01173 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/additional_person_amount.yaml @@ -0,0 +1,10 @@ +description: Alabama provides this additional amount for each household member after the first, up to the payment household size limit, under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 10 +metadata: + unit: currency-USD + period: year + label: Alabama LIHEAP additional person heating payment + reference: + - title: Alabama FY2026 LIHEAP Policy Manual, Appendix D (current benefit chart) + href: https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=57 diff --git a/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/base_amount.yaml b/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/base_amount.yaml new file mode 100644 index 00000000000..b71aa51c967 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/base_amount.yaml @@ -0,0 +1,17 @@ +description: Alabama provides this base payment for a one-person household in the lowest income band, by heating fuel, under the Low Income Home Energy Assistance Program. +propane: + 2025-10-01: 500 +natural_gas: + 2025-10-01: 480 +electricity: + 2025-10-01: 440 +wood_coal_kerosene: + 2025-10-01: 400 +metadata: + unit: currency-USD + period: year + label: Alabama LIHEAP base heating payment by fuel + propagate_metadata_to_children: true + reference: + - title: Alabama FY2026 LIHEAP Policy Manual, Appendix D (current benefit chart) + href: https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=57 diff --git a/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/iija_supplement.yaml b/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/iija_supplement.yaml new file mode 100644 index 00000000000..63c025d5ff1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/iija_supplement.yaml @@ -0,0 +1,19 @@ +description: Alabama provides this Infrastructure Investment and Jobs Act supplement with each regular heating assistance award under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 100 + # The draft FY2027 plan drops the supplement, and the IIJA appropriation ran + # through September 30, 2026. + 2026-10-01: 0 +metadata: + unit: currency-USD + period: year + label: Alabama LIHEAP IIJA heating supplement + reference: + - title: Alabama FY2026 LIHEAP State Plan, section 2.5 + href: https://liheapch.acf.gov/docs/2026/state-plans/AL_Plan_2026.pdf#page=10 + - title: HHS TAGGS FY2026 Alabama LIHEAP Infrastructure Supplement award 2601ALLIEI + href: https://taggs.hhs.gov/Detail/AwardDetail?arg_AwardNum=2601ALLIEI&arg_ProgOfficeCode=31 + - title: "Alabama draft FY2027 LIHEAP State Plan, section 2.5 (no IIJA supplement) #page 9-10" + href: https://adeca.alabama.gov/wp-content/uploads/FY27-LIHEAP-State-Plan.pdf#page=9 + - title: Infrastructure Investment and Jobs Act, Pub. L. 117-58, Division J, Title VII, 135 Stat. 1412 + href: https://www.govinfo.gov/content/pkg/PLAW-117publ58/pdf/PLAW-117publ58.pdf#page=984 diff --git a/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/income_band_reduction.yaml b/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/income_band_reduction.yaml new file mode 100644 index 00000000000..4e608283ac0 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/income_band_reduction.yaml @@ -0,0 +1,18 @@ +description: Alabama sets this reduction from the lowest-band heating payment for each income band under the Low Income Home Energy Assistance Program. +# These differences reproduce every fuel and household-size cell in Appendix D. +1: + 2025-10-01: 0 +2: + 2025-10-01: 30 +3: + 2025-10-01: 120 +metadata: + unit: currency-USD + period: year + label: Alabama LIHEAP heating payment reduction by income band + breakdown: + - range(1, 4) + propagate_metadata_to_children: true + reference: + - title: Alabama FY2026 LIHEAP Policy Manual, Appendix D (current benefit chart) + href: https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=57 diff --git a/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/maximum_payment_household_size.yaml b/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/maximum_payment_household_size.yaml new file mode 100644 index 00000000000..b02c1ed57da --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/maximum_payment_household_size.yaml @@ -0,0 +1,10 @@ +description: Alabama uses no more than this many household members to determine the regular heating payment under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 4 +metadata: + unit: person + period: year + label: Alabama LIHEAP maximum payment household size + reference: + - title: Alabama FY2026 LIHEAP State Plan, section 2.5 (households over four receive the four-person payment) + href: https://liheapch.acf.gov/docs/2026/state-plans/AL_Plan_2026.pdf#page=9 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 5250ab3aa89..0c8f8fad6fa 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -808,8 +808,16 @@ programs: category: Benefits agency: HHS status: partial - coverage: DC, Riverside County, MA, IL, IN, KS, KY, MD, MS, NC, NE, NJ, NY, OR, TX + coverage: AL, DC, Riverside County, MA, IL, IN, KS, KY, MD, MS, NC, NE, NJ, NY, OR, TX state_implementations: + - state: AL + status: partial + name: Alabama LIHEAP + full_name: Alabama Low Income Home Energy Assistance Program + variable: al_liheap + parameter_prefix: gov.states.al.adeca.liheap + verified_years: "2026" + notes: FY2026 regular heating with the HUD allowance deduction and the funded IIJA supplement. Crisis, cooling, weatherization and equipment assistance are out of scope. - state: DC status: complete name: DC LIHEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/al_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/al_liheap.yaml new file mode 100644 index 00000000000..c3107ada620 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/al_liheap.yaml @@ -0,0 +1,221 @@ +# FY2026 plan p10: the funded IIJA supplement is $100 for each regular heating award. +# The draft FY2027 plan drops it, so it is $0 from October 1, 2026. +# Manual pp22,57: subtract one monthly HUD allowance from the regular award. +# Interpret the supplement as requiring a positive regular payment afterward; +# the plan does not expressly address a regular payment reduced to zero. +# The discretionary high-need award requires an unobserved agency determination. + +- name: Case 1, the funded supplement is added without capping the award at a small bill. + period: 2026 + input: + state_code: AL + age: 40 + immigration_status: CITIZEN + employment_income: 6_000 + ssi: 0 + al_tanf: 0 + heating_type: PROPANE + heating_expense: 50 + hud_utility_allowance: 0 + output: + al_liheap_base_payment: 500 + al_liheap: 600 + +# Four-person electricity, $1,000 monthly income: Appendix D gives $470. +# The funded $100 is per household; age/disability do not award an automatic $50. +- name: Case 2, a family with several vulnerable members receives one funded supplement. + period: 2026 + input: + people: + grandparent: + age: 65 + immigration_status: CITIZEN + is_disabled: true + ssi: 0 + parent: + age: 40 + immigration_status: CITIZEN + employment_income: 12_000 + ssi: 0 + child1: + age: 12 + immigration_status: CITIZEN + ssi: 0 + child2: + age: 8 + immigration_status: CITIZEN + ssi: 0 + spm_units: + family: + members: [grandparent, parent, child1, child2] + al_tanf: 0 + heating_type: ELECTRICITY + heating_expense: 1_200 + households: + household: + members: [grandparent, parent, child1, child2] + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_base_payment: 470 + al_liheap: 570 + +# Annual HUD allowances below represent $0/$100/$439/$440/$450/$600 per month. +# The $440 electric base nets to $440/$340/$1/$0/$0/$0. Only the first three +# households receive the full $100 supplement under the accepted interpretation. +- name: Case 3, the supplement requires a positive regular payment after the HUD allowance. + period: 2026 + input: + state_code: [AL, AL, AL, AL, AL, AL] + age: [40, 40, 40, 40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN, CITIZEN, CITIZEN, CITIZEN] + employment_income: [6_000, 6_000, 6_000, 6_000, 6_000, 6_000] + ssi: [0, 0, 0, 0, 0, 0] + al_tanf: [0, 0, 0, 0, 0, 0] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY] + heating_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200] + hud_utility_allowance: [0, 1_200, 5_268, 5_280, 5_400, 7_200] + output: + al_liheap_base_payment: [440, 440, 440, 440, 440, 440] + al_liheap: [540, 440, 101, 0, 0, 0] + +# Unit 2 supplies a heating bill, so only the NONE heating type makes it ineligible. +- name: Case 4, the funded supplement cannot pay an income or heating or immigration ineligible household. + period: 2026 + input: + state_code: [AL, AL, AL] + age: [40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, UNDOCUMENTED] + employment_income: [23472.12, 6_000, 6_000] + ssi: [0, 0, 0] + al_tanf: [0, 0, 0] + heating_type: [PROPANE, NONE, PROPANE] + heating_expense: [1_200, 1_200, 1_200] + hud_utility_allowance: [0, 0, 0] + output: + al_liheap_eligible: [false, false, false] + al_liheap: [0, 0, 0] + +# Size one, $1,000 monthly income gives the $450 gas base in Appendix D. +- name: Case 5, nonsubsidized heat in rent receives the funded award without a separate bill. + period: 2026 + input: + state_code: [AL, AL] + age: [40, 40] + immigration_status: [CITIZEN, CITIZEN] + employment_income: [12_000, 12_000] + ssi: [0, 0] + al_tanf: [0, 0] + heating_type: [NATURAL_GAS, NATURAL_GAS] + heating_expense: [0, 0] + heat_expense_included_in_rent: [true, true] + receives_housing_assistance: [false, true] + hud_utility_allowance: [0, 0] + output: + al_liheap: [550, 0] + +- name: Case 6, the regular Alabama award remains restricted to Alabama in a mixed state population. + period: 2026 + input: + state_code: [AL, GA, FL] + age: [40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN] + employment_income: [6_000, 6_000, 6_000] + ssi: [0, 0, 0] + al_tanf: [0, 0, 0] + heating_type: [PROPANE, PROPANE, PROPANE] + heating_expense: [1_200, 1_200, 1_200] + hud_utility_allowance: [0, 0, 0] + output: + al_liheap: [600, 0, 0] + +# Manual p18 counts "Government employee pensions (including military retirement pay)". +# Military retirement 24_000 > size-one limit 23_472 (1_956 x 12), so no regular award +# or supplement. Without the military pay, income 0 would have paid electricity 440 + 100 = 540. +- name: Case 7, military retirement pay above the income limit means no award. + period: 2026 + input: + state_code: AL + age: 62 + immigration_status: CITIZEN + military_retirement_pay: 24_000 + ssi: 0 + al_tanf: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + hud_utility_allowance: 0 + output: + al_liheap_countable_income: 24_000 + al_liheap_income_limit: 23_472 + al_liheap_eligible: false + al_liheap: 0 + +# Manual p17 counts cash receipts from all sources, so the rental loss is floored at zero: +# max(24_000, 0) wages + max(-3_000, 0) rent = 24_000 > 23_472 limit, so no award. +# Netting the loss would give 21_000 (1_750 per month, band 3) and electricity 440 - 120 + 100 = 420. +- name: Case 8, a rental loss does not bring wages under the income limit. + period: 2026 + input: + state_code: AL + age: 40 + immigration_status: CITIZEN + employment_income: 24_000 + rental_income: -3_000 + ssi: 0 + al_tanf: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + hud_utility_allowance: 0 + output: + al_liheap_countable_income: 24_000 + al_liheap_eligible: false + al_liheap: 0 + +# Both units have $6,000 of wages, $500 a month, within the $1,956 size-one limit, and +# neither heating type is NONE, so both are eligible. Unit 1 pays a $1,200 fuel-oil bill; +# unit 2 has heat in unsubsidized rent with an unknown fuel (manual 6.1.3, p22). +# Manual 3.1 (p9) names electricity, natural gas, propane, wood, coal and kerosene, and +# Appendix D (p57) has no fuel-oil or unspecified column, so the chart amount is $0. +# The supplement requires a positive regular payment, so both awards are $0. +# See PolicyEngine/policyengine-us issue 9754 for the unspecified-fuel default. +- name: Case 9, eligible households with a fuel outside the chart receive no payment. + period: 2026 + input: + state_code: [AL, AL] + age: [40, 40] + immigration_status: [CITIZEN, CITIZEN] + employment_income: [6_000, 6_000] + ssi: [0, 0] + al_tanf: [0, 0] + heating_type: [FUEL_OIL, UNSPECIFIED] + fuel_oil_expense: [1_200, 0] + heat_expense_included_in_rent: [false, true] + receives_housing_assistance: [false, false] + hud_utility_allowance: [0, 0] + output: + al_liheap_eligible: [true, true] + al_liheap_base_payment: [0, 0] + al_liheap: [0, 0] + +# Period 2027 reads the values in force on January 1, 2027. With a one-year lag, the +# 2026 guideline is $15,960 for one person (draft FY2027 plan p9): 15,960 x 1.5 / 12 +# = 1,995, or 23,940 a year. Band 1 runs to floor(1,995 / 3 + 0.5) = 665, the plan's +# "$0 - $665"; wages of 6,000 / 12 = 500 fall in band 1. Electricity: 440 - 0 = 440. +# The IIJA supplement is $0 from October 1, 2026, so the award is 440, not 540. +- name: Case 10, the IIJA supplement ends after FY2026. + period: 2027 + input: + state_code: AL + age: 40 + immigration_status: CITIZEN + employment_income: 6_000 + ssi: 0 + al_tanf: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + hud_utility_allowance: 0 + output: + al_liheap_income_limit: 23_940 + al_liheap_income_band: 1 + al_liheap_base_payment: 440 + al_liheap: 440 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/al_liheap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/al_liheap_household_size.yaml new file mode 100644 index 00000000000..6c3c77c9642 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/al_liheap_household_size.yaml @@ -0,0 +1,84 @@ +# Source: ADECA FY2026 LIHEAP Policy Manual and its Appendix D Payment Assistance Chart. + +# Manual pp13,22: qualified size excludes nonqualified members; no SNAP five-year bar is applied. +- name: Case 1, citizens permanent residents and asylees count while nonqualified members do + not. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + person2: + age: 39 + immigration_status: LEGAL_PERMANENT_RESIDENT + ssi: 0 + person3: + age: 19 + immigration_status: ASYLEE + ssi: 0 + person4: + age: 17 + immigration_status: DACA + ssi: 0 + person5: + age: 8 + immigration_status: UNDOCUMENTED + ssi: 0 + spm_units: + spm_unit: + members: + - person1 + - person2 + - person3 + - person4 + - person5 + heating_type: PROPANE + al_tanf: 0 + bottled_gas_expense: 1_200 + households: + household: + members: + - person1 + - person2 + - person3 + - person4 + - person5 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_household_size: 3 + +# Manual p22: all nonqualified members are excluded from benefit household size. +- name: Case 2, a household without any qualified member has size zero. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + ssi: 0 + person2: + age: 8 + immigration_status: DACA + ssi: 0 + spm_units: + spm_unit: + members: + - person1 + - person2 + heating_type: PROPANE + al_tanf: 0 + bottled_gas_expense: 1_200 + households: + household: + members: + - person1 + - person2 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_household_size: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/eligibility/al_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/eligibility/al_liheap_eligible.yaml new file mode 100644 index 00000000000..2c57636adf6 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/eligibility/al_liheap_eligible.yaml @@ -0,0 +1,313 @@ +# Source: ADECA FY2026 LIHEAP Policy Manual and its Appendix D Payment Assistance Chart. + +# Manual p21: the $1,956 monthly limit is inclusive; the accepted plan has no asset test. +- name: Case 1, exact rounded monthly income ceiling is eligible without an asset test. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 23_472 + spm_units: + spm_unit: + members: + - person1 + heating_type: PROPANE + al_tanf: 0 + bottled_gas_expense: 1_200 + spm_unit_assets: 1_000_000 + households: + household: + members: + - person1 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_eligible: true + +# $1,956.01 a month exceeds the published $1,956 limit. +- name: Case 2, one cent per month above the income ceiling is ineligible. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 23472.12 + spm_units: + spm_unit: + members: + - person1 + heating_type: PROPANE + al_tanf: 0 + bottled_gas_expense: 1_200 + households: + household: + members: + - person1 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_eligible: false + +# Manual pp13,21: a heating responsibility is necessary. +- name: Case 3, no separate bill and no heat in rent means no heating responsibility. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 12_000 + spm_units: + spm_unit: + members: + - person1 + heating_type: PROPANE + al_tanf: 0 + bottled_gas_expense: 0 + households: + household: + members: + - person1 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_eligible: false + +# Canonical NONE means no heating; a nonheating gas bill does not change it. +- name: Case 4, explicit no heating fails despite an unrelated fuel bill. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 12_000 + spm_units: + spm_unit: + members: + - person1 + heating_type: NONE + al_tanf: 0 + gas_expense: 1_200 + households: + household: + members: + - person1 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_eligible: false + +# Manual p22: verified nonsubsidized heat-in-rent arrangement is eligible. +- name: Case 5, nonsubsidized heat included in rent qualifies with no separate bill. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 12_000 + spm_units: + spm_unit: + members: + - person1 + heating_type: NATURAL_GAS + al_tanf: 0 + gas_expense: 0 + heat_expense_included_in_rent: true + households: + household: + members: + - person1 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_eligible: true + +# Manual p21: subsidized energy-in-rent household needs out-of-pocket costs. +- name: Case 6, subsidized heat included in rent without excess charge is ineligible. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 12_000 + spm_units: + spm_unit: + members: + - person1 + heating_type: NATURAL_GAS + al_tanf: 0 + gas_expense: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + households: + household: + members: + - person1 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_eligible: false + +# Manual pp21–22: positive primary heating charge approximates a verified public-housing excess charge. +- name: Case 7, reported primary heating excess charge restores public housing eligibility. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 12_000 + spm_units: + spm_unit: + members: + - person1 + heating_type: ELECTRICITY + al_tanf: 0 + pre_subsidy_electricity_expense: 120 + heat_expense_included_in_rent: true + households: + household: + members: + - person1 + state_code: AL + hud_utility_allowance: 0 + is_in_public_housing: true + output: + al_liheap_eligible: true + +# The accepted plan answers No to categorical eligibility (item 1.4, p5), so SSI receipt +# does not waive the income test. The full 2026 SSI federal rate is $994 a month +# (90 FR 49047): 994 x 12 = 11,928. Two adult children each work full time at the $7.25 +# federal minimum wage (29 U.S.C. 206(a)(1)(C); Alabama has none): 2,080 x 7.25 = 15,080. +# al_ssp is 0 under the default NONE care setting. Income 11,928 + 15,080 + 15,080 = 42,088. +# Size-3 limit: (15,650 + 2 x 5,500) x 1.5 / 12 = 3,331.25, rounded to 3,331, x 12 = 39,972. +# 42,088 > 39,972, so the household is ineligible; without the SSI, 30,160 would qualify. +- name: Case 8, SSI receipt does not bypass the income ceiling. + period: 2026 + input: + people: + person1: + age: 70 + immigration_status: CITIZEN + ssi: 11_928 + person2: + age: 42 + immigration_status: CITIZEN + ssi: 0 + employment_income: 15_080 + person3: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 15_080 + spm_units: + spm_unit: + members: + - person1 + - person2 + - person3 + heating_type: PROPANE + al_tanf: 0 + bottled_gas_expense: 1_200 + households: + household: + members: + - person1 + - person2 + - person3 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_countable_income: 42_088 + al_liheap_income_limit: 39_972 + al_liheap_eligible: false + +# Manual pp13,22: at least one citizen or qualified household member is necessary. +- name: Case 9, no qualified person receives no regular heating benefit. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + ssi: 0 + employment_income: 12_000 + spm_units: + spm_unit: + members: + - person1 + heating_type: PROPANE + al_tanf: 0 + bottled_gas_expense: 1_200 + households: + household: + members: + - person1 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_eligible: false + +# Manual p13 requires Alabama residence; mixed-state lookups must remain safe. +- name: Case 10, out of state households do not qualify for Alabama assistance. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 12_000 + spm_units: + spm_unit: + members: + - person1 + heating_type: PROPANE + al_tanf: 0 + bottled_gas_expense: 1_200 + households: + household: + members: + - person1 + state_code: GA + hud_utility_allowance: 0 + output: + al_liheap_eligible: false + +# Manual 6.1.1 (p21): "Subsidized housing tenants whose home energy costs are included in +# their rent and do not pay anything out-of-pocket are not eligible for assistance." +# Unit 1 lives in public housing with heat in rent and a $0 bill, so it is not responsible +# for heat. Unit 2 has heat in unsubsidized rent, so it qualifies (6.1.3, p22): +# 12,000 / 12 = 1,000 falls in the size-one band 2 ($653-$1,305), and electricity +# 440 - 30 = 410, plus the $100 IIJA supplement, is 510. +- name: Case 11, public housing with heat in rent and no out-of-pocket cost is ineligible. + period: 2026 + input: + state_code: [AL, AL] + age: [40, 40] + immigration_status: [CITIZEN, CITIZEN] + employment_income: [12_000, 12_000] + ssi: [0, 0] + al_tanf: [0, 0] + heating_type: [ELECTRICITY, ELECTRICITY] + pre_subsidy_electricity_expense: [0, 0] + heat_expense_included_in_rent: [true, true] + receives_housing_assistance: [false, false] + is_in_public_housing: [true, false] + hud_utility_allowance: [0, 0] + output: + al_liheap_eligible: [false, true] + al_liheap: [0, 510] diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/eligibility/al_liheap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/eligibility/al_liheap_income_limit.yaml new file mode 100644 index 00000000000..40b7d744bbc --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/eligibility/al_liheap_income_limit.yaml @@ -0,0 +1,27 @@ +# Source: ADECA FY2026 LIHEAP Policy Manual and its Appendix D Payment Assistance Chart. + +# Appendix D, sizes 1-15: each published monthly limit is annualized once; this isolated unit test supplies size directly. +- name: Case 1, all fifteen published monthly limits are reproduced annually. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL] + heating_type: [PROPANE, PROPANE, PROPANE, PROPANE, PROPANE, PROPANE, PROPANE, PROPANE, PROPANE, + PROPANE, PROPANE, PROPANE, PROPANE, PROPANE, PROPANE] + al_liheap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + output: + al_liheap_income_limit: [23_472, 31_728, 39_972, 48_228, 56_472, 64_728, 72_972, 81_228, 89_472, + 97_728, 105_972, 114_228, 122_472, 130_728, 138_972] + + +# The 2025 model year uses the 2024 guidelines of $15,060 and $20,440: 150% a month is $1,882.50 and $2,555, +# rounded to $1,883 and $2,555. Historical estimate; 2025 is not a verified year. +- name: Case 2, backfilled prior year still uses its own lagged federal guideline. + period: 2025 + absolute_error_margin: 0.01 + input: + state_code: [AL, AL] + heating_type: [PROPANE, PROPANE] + al_liheap_household_size: [1, 2] + output: + al_liheap_income_limit: [22_596, 30_660] diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/income/al_liheap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/income/al_liheap_countable_income.yaml new file mode 100644 index 00000000000..0a2afca1ca3 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/income/al_liheap_countable_income.yaml @@ -0,0 +1,657 @@ +# Source: ADECA FY2026 LIHEAP Policy Manual and its Appendix D Payment Assistance Chart. + +# Manual pp17–18: 12000 + 6000 + 600 + 2400 + 1800 = 22800; no additional business deduction. +- name: Case 1, adult wages and existing net business sources are counted. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 12_000 + self_employment_income: 6_000 + sstb_self_employment_income: 600 + farm_operations_income: 2_400 + partnership_s_corp_income: 1_800 + spm_units: + spm_unit: + members: + - person1 + heating_type: PROPANE + al_tanf: 0 + bottled_gas_expense: 1_200 + households: + household: + members: + - person1 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_countable_income: 22_800 + +# Manual pp17-19: earned income under 18 is excluded, but annual SSI of 2,400 and survivor benefits of 1,200 count: 12,000 + 2,400 + 1,200 = 15,600. +- name: Case 2, age 17 earned income follows the under eighteen boundary. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 12_000 + person2: + age: 17 + immigration_status: CITIZEN + ssi: 2_400 + employment_income: 6_000 + self_employment_income: 2_400 + survivor_benefits: 1_200 + spm_units: + spm_unit: + members: + - person1 + - person2 + heating_type: PROPANE + al_tanf: 0 + bottled_gas_expense: 1_200 + households: + household: + members: + - person1 + - person2 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_countable_income: 15_600 + +# Manual pp17-19: earned income counts from age 18: 12,000 + 6,000 + 2,400 + 2,400 + 1,200 = 24,000. +- name: Case 3, age 18 earned income follows the under eighteen boundary. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 12_000 + person2: + age: 18 + immigration_status: CITIZEN + ssi: 2_400 + employment_income: 6_000 + self_employment_income: 2_400 + survivor_benefits: 1_200 + spm_units: + spm_unit: + members: + - person1 + - person2 + heating_type: PROPANE + al_tanf: 0 + bottled_gas_expense: 1_200 + households: + household: + members: + - person1 + - person2 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_countable_income: 24_000 + +# Manual p18: all six distinct sources count; farm_rent_income and survivor_benefits are independent inputs. +- name: Case 4, independent farm rent and survivor receipts are included. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 12_000 + rental_income: 2_400 + farm_rent_income: 1_200 + survivor_benefits: 600 + pension_income: 4_800 + disability_benefits: 1_200 + spm_units: + spm_unit: + members: + - person1 + heating_type: PROPANE + al_tanf: 0 + bottled_gas_expense: 1_200 + households: + household: + members: + - person1 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_countable_income: 22_200 + +# Manual p18: annual wages of 1,200 plus annual TANF of 3,600 = 4,800. The YEAR test supplies an annual amount to the MONTH-defined al_tanf. +- name: Case 5, Alabama TANF receipts are counted once and annualized once. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 1_200 + person2: + age: 8 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: + - person1 + - person2 + heating_type: PROPANE + al_tanf: 3_600 + bottled_gas_expense: 1_200 + households: + household: + members: + - person1 + - person2 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_countable_income: 4_800 + +# Manual pp18–19 excludes SNAP, capital gains and utility allowances; allowance deduction is a benefit rule. +- name: Case 6, capital gains SNAP and HUD allowances do not become income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 12_000 + capital_gains: 30_000 + spm_units: + spm_unit: + members: + - person1 + heating_type: PROPANE + al_tanf: 0 + bottled_gas_expense: 1_200 + snap: 6_000 + households: + household: + members: + - person1 + state_code: AL + hud_utility_allowance: 1_200 + output: + al_liheap_countable_income: 12_000 + +# Manual p17 counts "total monthly cash receipts ... from all sources", so each source is floored at zero. +# max(6_000, 0) wages + max(-18_000, 0) self-employment = 6_000 + 0 = 6_000. +- name: Case 7, a business loss does not offset wages. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 6_000 + self_employment_income: -18_000 + spm_units: + spm_unit: + members: + - person1 + heating_type: PROPANE + al_tanf: 0 + bottled_gas_expense: 1_200 + households: + household: + members: + - person1 + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_countable_income: 6_000 + +# Manual p18 counts net gambling winnings; gambling losses cannot create negative gambling income. +- name: Case 8, gambling receipts are net of losses without a negative gambling offset. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + employment_income: 12_000 + gambling_winnings: 5_000 + gambling_losses: 2_000 + ssi: 0 + person2: + age: 39 + immigration_status: CITIZEN + gambling_winnings: 1_000 + gambling_losses: 3_000 + ssi: 0 + spm_units: + spm_unit: + members: + - person1 + - person2 + heating_type: PROPANE + bottled_gas_expense: 1_200 + al_tanf: 0 + households: + household: + members: + - person1 + - person2 + state_code: AL + output: + al_liheap_countable_income: 15_000 + + + +# Manual p22 mixed-status example: the nonqualified parent's $500 monthly income counts in full, +# but only the two citizen children determine size. The current propane matrix gives $510. +- name: Case 9, nonqualified parent income counts in full for two qualified children. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + self_employment_income: 6_000 + ssi: 0 + person2: + age: 8 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 6 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: PROPANE + bottled_gas_expense: 1_200 + al_tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: AL + output: + al_liheap_household_size: 2 + al_liheap_countable_income: 6_000 + al_liheap_income_limit: 31_728 + al_liheap_income_band: 1 + al_liheap_base_payment: 510 + +# Manual p18 counts periodic estate or trust receipts. Existing estate_income is disjoint +# from separately reported interest, dividends and capital gains; this is not a lump inheritance. +# Wages 23,400 + periodic estate income 1,200 = 24,600 exceeds the annualized 23,472 income ceiling. +- name: Case 10, periodic estate income raises income above the eligibility limit. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + employment_income: 23_400 + estate_income: 1_200 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + bottled_gas_expense: 1_200 + al_tanf: 0 + households: + household: + members: [person1] + state_code: AL + output: + al_liheap_countable_income: 24_600 + al_liheap_eligible: false + +# Manual p18 counts railroad retirement, financial support from family or friends, +# and dividends. 21,000 + 1,200 + 1,200 + 600 = 24,000 exceeds the 23,472 annual ceiling. +- name: Case 11, railroad benefits regular family support and dividends raise income above the limit. + period: 2026 + input: + people: + person1: + age: 65 + immigration_status: CITIZEN + employment_income: 21_000 + railroad_benefits: 1_200 + financial_assistance: 1_200 + ordinary_dividend_income: 600 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + bottled_gas_expense: 1_200 + al_tanf: 0 + households: + household: + members: [person1] + state_code: AL + output: + al_liheap_countable_income: 24_000 + al_liheap_income_limit: 23_472 + al_liheap_eligible: false + +# Manual p18 counts "Government employee pensions (including military retirement pay)". +# Military retirement pay 6_000 + civilian pension 1_200 = 7_200; 7_200 / 12 = 600 <= 652 +# size-one band 1 top, so electricity 440 - 0 + IIJA 100 = 540. +- name: Case 12, military retirement pay is counted alongside a civilian pension. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 58 + immigration_status: CITIZEN + ssi: 0 + military_retirement_pay: 6_000 + taxable_pension_income: 1_200 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + al_tanf: 0 + households: + household: + members: [person1] + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_countable_income: 7_200 + al_liheap_income_band: 1 + al_liheap: 540 + +# Manual p17 counts cash receipts from all sources, so each person's source is floored at zero. +# person1: wages 12_000 + rent 6_000 = 18_000. person2: max(-4_000, 0) business +# + max(-6_000, 0) rent + farm rent 1_200 = 1_200. Total 19_200; 19_200 / 12 = 1_600 is above +# the size-two band 1 top 881 and at most the band 2 top 1_763. Propane 500 - 30 + 10 + 100 = 580. +- name: Case 13, one member's losses offset neither another source nor another member's income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 45 + immigration_status: CITIZEN + ssi: 0 + employment_income: 12_000 + rental_income: 6_000 + person2: + age: 44 + immigration_status: CITIZEN + ssi: 0 + self_employment_income: -4_000 + rental_income: -6_000 + farm_rent_income: 1_200 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: PROPANE + bottled_gas_expense: 1_200 + al_tanf: 0 + households: + household: + members: [person1, person2] + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_countable_income: 19_200 + al_liheap_income_limit: 31_728 + al_liheap_income_band: 2 + al_liheap: 580 + +# Both families have the same 3_600 annual (300 per month) Alabama TANF entitlement. +# The first takes up TANF: wages 1_200 + tanf 3_600 = 4_800. +# The second does not: tanf is 0, so countable income is wages 1_200 only. +- name: Case 14, TANF is counted only when the family takes it up. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + immigration_status: CITIZEN + ssi: 0 + employment_income: 1_200 + person2: + age: 5 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 30 + immigration_status: CITIZEN + ssi: 0 + employment_income: 1_200 + person4: + age: 5 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit1: + members: [person1, person2] + heating_type: PROPANE + bottled_gas_expense: 1_200 + al_tanf: 3_600 + takes_up_tanf_if_eligible: true + spm_unit2: + members: [person3, person4] + heating_type: PROPANE + bottled_gas_expense: 1_200 + al_tanf: 3_600 + takes_up_tanf_if_eligible: false + households: + household1: + members: [person1, person2] + state_code: AL + hud_utility_allowance: 0 + household2: + members: [person3, person4] + state_code: AL + hud_utility_allowance: 0 + output: + al_tanf: [3_600, 3_600] + tanf: [3_600, 0] + al_liheap_countable_income: [4_800, 1_200] + +# Manual p17 counts cash receipts from all sources; the pp18-19 exclusions do not list +# the Alabama State Supplementary Payment, so it counts alongside SSI. +# person1 (70) receives SSI in their own household, computed rather than input: +# 2026 individual FBR 994 x 12 = 11_928. Independent Homelife Care Level A in a +# private home adds the al_ssp supplement: 60 x 12 = 720. +# person2, the adult child, works full time at the 7.25 federal minimum wage +# (29 U.S.C. 206(a)(1)(C); Alabama has no state minimum wage law per +# https://www.dol.gov/agencies/whd/minimum-wage/state): 7.25 x 2_080 hours = 15_080. +# Separate tax and marital units keep the child's wages out of SSI spousal deeming. +# Countable income: SSI 11_928 + al_ssp 720 + wages 15_080 = 27_728. +# Leave SSI unset for both people so the model computes receipt. +# FY2026 Appendix D gives 390 for two people, propane and the third income band; +# with no HUD allowance, the plan's 100 IIJA supplement makes the final award 490. +- name: Case 15, Alabama SSP of an SSI recipient living with a working adult child is counted. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: CITIZEN + al_ssp_care_setting: PRIVATE_HOME_IHC + al_receives_elderly_disabled_medicaid_waiver: true + al_meets_snf_criteria: true + person2: + age: 42 + immigration_status: CITIZEN + employment_income: 15_080 + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2] + marital_units: + marital_unit1: + members: [person1] + marital_unit2: + members: [person2] + spm_units: + spm_unit: + members: [person1, person2] + heating_type: PROPANE + bottled_gas_expense: 1_200 + al_tanf: 0 + households: + household: + members: [person1, person2] + state_code: AL + hud_utility_allowance: 0 + output: + ssi: [11_928, 0] + al_ssp: [720, 0] + al_liheap_countable_income: 27_728 + al_liheap: 490 + +# Plan item 1.9 (p6) checks "Including MediCare deduction", so Social Security counts gross. +# The 2026 standard Part B premium is 202.90 x 12 = 2,434.80, but countable income stays +# 20,000, not 20,000 - 2,434.80 = 17,565.20. 20,000 / 12 = 1,666.67 is above the size-one +# band 2 top of 1,305 and at most the 1,956 limit, so band 3. Natural gas: 480 - 120 = 360, +# plus the $100 IIJA supplement, is 460. +- name: Case 16, Social Security counts gross of the Medicare Part B premium. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: CITIZEN + ssi: 0 + social_security_retirement: 20_000 + medicare_part_b_premium: 2_434.8 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_200 + al_tanf: 0 + households: + household: + members: [person1] + state_code: AL + hud_utility_allowance: 0 + output: + al_liheap_countable_income: 20_000 + al_liheap_income_band: 3 + al_liheap: 460 + +# Manual 5.5.1 (pp17-18) counts "Unemployment compensation". With none reported, the +# modeled Alabama UI counts. Base period: full-time work at the $7.25 federal minimum wage +# (29 U.S.C. 206(a)(1)(C); Alabama has none) is 520 x 7.25 = 3,770 a quarter, 15,080 a year. +# Weekly benefit (Code of Ala. 25-4-72(b)): (3,770 + 3,770) / 52 = 145, under the $275 +# maximum. The January 2026 rate of 2.7% gives 14 weeks (25-4-74); the maximum benefit is +# min(14 x 145 = 2,030, 0.25 x 15,080 = 3,770) = 2,030. After the one waiting week, 15 weeks +# unemployed pay 14 x 145 = 2,030. Wages for the other 37 weeks: 37 x 40 x 7.25 = 10,730. +# Countable income: 10,730 + 2,030 = 12,760, or 1,063.33 a month, in the size-one band 2 +# ($653-$1,305). Electricity: 440 - 30 = 410, plus the $100 IIJA supplement, is 510. +- name: Case 17, modeled Alabama unemployment insurance counts as unearned income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 10_730 + al_ui_base_period_wages: 15_080 + al_ui_high_quarter_wages: 3_770 + al_ui_second_high_quarter_wages: 3_770 + al_ui_quarters_with_wages: 4 + weeks_unemployed: 15 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + al_tanf: 0 + households: + household: + members: [person1] + state_code: AL + hud_utility_allowance: 0 + output: + al_ui: 2_030 + # No reported unemployment compensation, so the total falls back to modeled al_ui. + total_unemployment_compensation: 2_030 + al_liheap_countable_income: 12_760 + al_liheap_income_band: 2 + al_liheap: 510 + +# Manual pp17-19: each listed unearned source is set to 100 times its position in the +# list, so 100 x (1 + 2 + ... + 20) = 100 x 210 = 21,000, with no earnings or gambling. +# A nonzero reported amount overrides modeled UI. ssi and al_ssp are MONTH-defined, so +# their annual inputs are split across months and summed back. +- name: Case 18, every listed unearned source counts. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + unemployment_compensation: 100 + strike_benefits: 200 + social_security: 300 + ssi: 400 + al_ssp: 500 + pension_income: 600 + military_retirement_pay: 700 + survivor_benefits: 800 + veterans_benefits: 900 + disability_benefits: 1_000 + workers_compensation: 1_100 + child_support_received: 1_200 + alimony_income: 1_300 + interest_income: 1_400 + ordinary_dividend_income: 1_500 + rental_income: 1_600 + farm_rent_income: 1_700 + estate_income: 1_800 + railroad_benefits: 1_900 + financial_assistance: 2_000 + spm_units: + spm_unit: + members: [person1] + heating_type: PROPANE + bottled_gas_expense: 1_200 + al_tanf: 0 + households: + household: + members: [person1] + state_code: AL + hud_utility_allowance: 0 + output: + total_unemployment_compensation: 100 + al_liheap_countable_income: 21_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/payment/al_liheap_base_payment.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/payment/al_liheap_base_payment.yaml new file mode 100644 index 00000000000..91a9355e94e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/payment/al_liheap_base_payment.yaml @@ -0,0 +1,50 @@ +# Source: ADECA FY2026 LIHEAP Policy Manual and its Appendix D Payment Assistance Chart. + +# Current Appendix D: the 48 printed cells in size, band, fuel order. This isolates the chart lookup from eligibility and supplements. +- name: Case 1, all forty eight published size band and fuel amounts are retained. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, + AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, + AL, AL, AL] + heating_type: [PROPANE, NATURAL_GAS, ELECTRICITY, WOOD, PROPANE, NATURAL_GAS, ELECTRICITY, WOOD, + PROPANE, NATURAL_GAS, ELECTRICITY, WOOD, PROPANE, NATURAL_GAS, ELECTRICITY, WOOD, PROPANE, NATURAL_GAS, + ELECTRICITY, WOOD, PROPANE, NATURAL_GAS, ELECTRICITY, WOOD, PROPANE, NATURAL_GAS, ELECTRICITY, + WOOD, PROPANE, NATURAL_GAS, ELECTRICITY, WOOD, PROPANE, NATURAL_GAS, ELECTRICITY, WOOD, PROPANE, + NATURAL_GAS, ELECTRICITY, WOOD, PROPANE, NATURAL_GAS, ELECTRICITY, WOOD, PROPANE, NATURAL_GAS, + ELECTRICITY, WOOD] + al_liheap_household_size: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4] + al_liheap_income_band: [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3] + output: + al_liheap_base_payment: [500, 480, 440, 400, 470, 450, 410, 370, 380, 360, 320, 280, 510, 490, 450, + 410, 480, 460, 420, 380, 390, 370, 330, 290, 520, 500, 460, 420, 490, 470, 430, 390, 400, 380, + 340, 300, 530, 510, 470, 430, 500, 480, 440, 400, 410, 390, 350, 310] + + +# The printed Wood/Coal/Kerosene column applies to all three distinct canonical enum values. +- name: Case 2, wood coal and kerosene share the printed fuel column. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [AL, AL, AL] + heating_type: [WOOD, COAL, KEROSENE] + al_liheap_household_size: [1, 1, 1] + al_liheap_income_band: [1, 2, 3] + output: + al_liheap_base_payment: [400, 370, 280] + + +# Chart note: households of more than 4 people receive the size-4 amount and keep their own income band. +- name: Case 3, large households retain their income bands but cap the payment size increment. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [AL, AL, AL] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY] + al_liheap_household_size: [4, 5, 15] + al_liheap_income_band: [1, 1, 1] + output: + al_liheap_base_payment: [470, 470, 470] diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/payment/al_liheap_income_band.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/payment/al_liheap_income_band.yaml new file mode 100644 index 00000000000..7a3314d7877 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/payment/al_liheap_income_band.yaml @@ -0,0 +1,28 @@ +# Source: ADECA FY2026 LIHEAP Policy Manual and its Appendix D Payment Assistance Chart. + +# Plan p9 and chart: monthly 0, 652, 652.01, 1,305, 1,305.01 and 1,956 map to bands 1, 1, 2, 2, 3, 3; fractional dollars do not create gaps. +- name: Case 1, one person band boundaries include exact tops and adjacent cents. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [AL, AL, AL, AL, AL, AL] + heating_type: [PROPANE, PROPANE, PROPANE, PROPANE, PROPANE, PROPANE] + al_liheap_household_size: [1, 1, 1, 1, 1, 1] + al_liheap_countable_income: [0, 7_824, 7824.12, 15_660, 15660.12, 23_472] + output: + al_liheap_income_band: [1, 1, 2, 2, 3, 3] + + +# Chart monthly band tops: size 2 881/1,763; size 3 1,110/2,221; size 4 1,340/2,681; each paired with the next cent. +- name: Case 2, published household specific rounded band boundaries are retained. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL] + heating_type: [PROPANE, PROPANE, PROPANE, PROPANE, PROPANE, PROPANE, PROPANE, PROPANE, PROPANE, + PROPANE, PROPANE, PROPANE] + al_liheap_household_size: [2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] + al_liheap_countable_income: [10_572, 10572.12, 21_156, 21156.12, 13_320, 13320.12, 26_652, 26652.12, + 16_080, 16080.12, 32_172, 32172.12] + output: + al_liheap_income_band: [1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3] diff --git a/policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap.py b/policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap.py new file mode 100644 index 00000000000..a23c5a98977 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap.py @@ -0,0 +1,39 @@ +from policyengine_us.model_api import * + + +class al_liheap(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Alabama LIHEAP regular heating assistance" + defined_for = StateCode.AL + reference = ( + # PDF pages 22-23 + "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=22", + "https://liheapch.acf.gov/docs/2026/state-plans/AL_Plan_2026.pdf#page=10", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.al.adeca.liheap.payment + eligible = spm_unit("al_liheap_eligible", period) + base = spm_unit("al_liheap_base_payment", period) + # The monthly HUD allowance reduces the award, not countable income. + # AL local allowance schedules are not encoded in the shared input; + # an observed annual allowance can be supplied directly. + allowance = spm_unit.household("hud_utility_allowance", period) / MONTHS_IN_YEAR + regular_payment = max_(base - allowance, 0) + # The plan pays the $100 IIJA supplement "if funding is available". + # The FY2026 award (2601ALLIEI, $1,359,705, roughly 13,600 supplements + # across heating and cooling) can run out during the season; the model + # assumes it does not, so population totals overstate funded awards. + # The draft FY2027 plan drops the supplement, which ends after FY2026. + # Interpret the plan's "receives a Heating benefit" as requiring a + # positive regular payment after the HUD adjustment. The plan does + # not expressly address a regular payment reduced to zero. + supplement = where(regular_payment > 0, p.iija_supplement, 0) + # The separate $50 high-need addition requires an agency decision + # unavailable in existing inputs, so demographic traits alone do not + # add it. Fixed regular payments have no cap at the reported heating + # expense. + return eligible * (regular_payment + supplement) diff --git a/policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap_household_size.py b/policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap_household_size.py new file mode 100644 index 00000000000..1c6acb93f48 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap_household_size.py @@ -0,0 +1,16 @@ +from policyengine_us.model_api import * + + +class al_liheap_household_size(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Alabama LIHEAP household size" + defined_for = StateCode.AL + # PDF pages 7, 13, 22 + reference = "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=7" + + # Nonqualified members' income counts, but they do not increase size. + # The SPM unit approximates the economic unit buying energy together. + # Existing inputs do not identify temporary absences or custody documents. + adds = ["is_citizen_or_legal_immigrant"] diff --git a/policyengine_us/variables/gov/states/al/adeca/liheap/eligibility/al_liheap_eligible.py b/policyengine_us/variables/gov/states/al/adeca/liheap/eligibility/al_liheap_eligible.py new file mode 100644 index 00000000000..4761ff36863 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/adeca/liheap/eligibility/al_liheap_eligible.py @@ -0,0 +1,31 @@ +from policyengine_us.model_api import * + + +class al_liheap_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Alabama LIHEAP eligibility" + defined_for = StateCode.AL + # PDF pages 13, 21-22 + reference = "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=13" + + def formula(spm_unit, period, parameters): + size = spm_unit("al_liheap_household_size", period) + income = spm_unit("al_liheap_countable_income", period) + limit = spm_unit("al_liheap_income_limit", period) + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + subsidized = spm_unit("receives_housing_assistance", period) | ( + spm_unit.household("is_in_public_housing", period) + ) + pays_for_heat = spm_unit("heating_expense", period) > 0 + # Heat included in nonsubsidized rent can qualify without a separate + # bill. The rent flag stands for the agency's verified arrangement. + # In subsidized housing, a positive heating bill approximates verified + # excess charges; the input does not distinguish the excess component. + responsible = pays_for_heat | (heat_in_rent & ~subsidized) + heating_type = spm_unit("heating_type", period) + fuel = heating_type.possible_values + return ( + (size > 0) & (income <= limit) & responsible & (heating_type != fuel.NONE) + ) diff --git a/policyengine_us/variables/gov/states/al/adeca/liheap/eligibility/al_liheap_income_limit.py b/policyengine_us/variables/gov/states/al/adeca/liheap/eligibility/al_liheap_income_limit.py new file mode 100644 index 00000000000..344ec073c87 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/adeca/liheap/eligibility/al_liheap_income_limit.py @@ -0,0 +1,31 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class al_liheap_income_limit(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Alabama LIHEAP annualized income limit" + defined_for = StateCode.AL + reference = ( + "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=57", + "https://liheapch.acf.gov/docs/2026/state-plans/AL_Plan_2026.pdf#page=9", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.al.adeca.liheap.eligibility + size = spm_unit("al_liheap_household_size", period) + state_group = spm_unit.household("state_group_str", period) + guideline = fpg( + max_(size, 1), + state_group, + period, + parameters, + year_lag=p.fpg_year_lag, + ) + monthly_limit = guideline * p.fpg_rate / MONTHS_IN_YEAR + # Nearest-dollar, half-up rounding reproduces all 15 printed limits. + # The FY2026 heating season ends before the May 2026 chart update. + return np.floor(monthly_limit + 0.5) * MONTHS_IN_YEAR diff --git a/policyengine_us/variables/gov/states/al/adeca/liheap/income/al_liheap_countable_income.py b/policyengine_us/variables/gov/states/al/adeca/liheap/income/al_liheap_countable_income.py new file mode 100644 index 00000000000..fee93a40f59 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/adeca/liheap/income/al_liheap_countable_income.py @@ -0,0 +1,45 @@ +from policyengine_us.model_api import * + + +class al_liheap_countable_income(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Alabama LIHEAP annual countable household income" + defined_for = StateCode.AL + # PDF pages 17-19, 22 + reference = "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=17" + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.al.adeca.liheap.income + person = spm_unit.members + adult = person("age", period) >= p.earned_income_min_age + # Annual income approximates the preceding calendar month's income. + # Existing net business inputs need no further expense deduction. + # Manual 5.5.1 counts "total monthly cash receipts before taxes from + # all sources", so each source is floored at zero and a loss in one + # source cannot offset another. + earned = 0 + for source in p.sources.earned: + earned = earned + max_(person(source, period), 0) + unearned = 0 + for source in p.sources.unearned: + unearned = unearned + max_(person(source, period), 0) + net_gambling = max_( + person("gambling_winnings", period) - person("gambling_losses", period), + 0, + ) + # Earned income under age 18 is excluded; unearned income is included + # at every age. Immigration status does not remove anyone's income. + # Estate income captures existing net estate/trust income separately + # from interest and dividends; it cannot classify distributions of + # principal or nonperiodic receipts. Financial assistance covers cash + # support from family or friends; royalties, stipends, and some + # severance payments remain unavailable as distinct inputs. + # The annual tanf aggregate includes al_tanf and applies take-up; + # al_tanf alone is the entitlement and would count benefits a + # nonrecipient could get. + return spm_unit.sum(earned * adult + unearned + net_gambling) + spm_unit( + "tanf", period + ) diff --git a/policyengine_us/variables/gov/states/al/adeca/liheap/payment/al_liheap_base_payment.py b/policyengine_us/variables/gov/states/al/adeca/liheap/payment/al_liheap_base_payment.py new file mode 100644 index 00000000000..44a969e971f --- /dev/null +++ b/policyengine_us/variables/gov/states/al/adeca/liheap/payment/al_liheap_base_payment.py @@ -0,0 +1,51 @@ +from policyengine_us.model_api import * + + +class al_liheap_base_payment(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Alabama LIHEAP base heating payment" + defined_for = StateCode.AL + reference = "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=57" + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.al.adeca.liheap.payment + heating_type = spm_unit("heating_type", period) + fuel = heating_type.possible_values + base = select( + [ + heating_type == fuel.PROPANE, + heating_type == fuel.NATURAL_GAS, + heating_type == fuel.ELECTRICITY, + (heating_type == fuel.WOOD) + | (heating_type == fuel.COAL) + | (heating_type == fuel.KEROSENE), + ], + [ + p.base_amount.propane, + p.base_amount.natural_gas, + p.base_amount.electricity, + p.base_amount.wood_coal_kerosene, + ], + default=0, + ) + size = spm_unit("al_liheap_household_size", period) + additional_people = clip(size - 1, 0, p.maximum_payment_household_size - 1) + # Cross-state rows have band zero before defined_for masks the result; + # keep the eager lookup within the published three bands. + band = clip(spm_unit("al_liheap_income_band", period), 1, 3) + amount = ( + base + - p.income_band_reduction[band] + + additional_people * p.additional_person_amount + ) + # This compact representation reproduces Appendix D's entire matrix. + # The manual's worked example on page 22 uses an older payment amount. + # No published category was identified for fuel oil, solar, other, or + # unspecified heat (manual 3.1 names kerosene, not fuel oil). Returning + # zero leaves those estimates incomplete; it does not establish legal + # ineligibility, including heat in rent. Issue 9754 tracks the + # unspecified-fuel default. + return where(base > 0, amount, 0) diff --git a/policyengine_us/variables/gov/states/al/adeca/liheap/payment/al_liheap_income_band.py b/policyengine_us/variables/gov/states/al/adeca/liheap/payment/al_liheap_income_band.py new file mode 100644 index 00000000000..184c0a34af6 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/adeca/liheap/payment/al_liheap_income_band.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * + + +class al_liheap_income_band(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Alabama LIHEAP monthly income band" + defined_for = StateCode.AL + reference = "https://liheapch.acf.gov/docs/2026/state-plans/AL_Plan_2026.pdf#page=9" + + def formula(spm_unit, period, parameters): + monthly_limit = spm_unit("al_liheap_income_limit", period) / MONTHS_IN_YEAR + monthly_income = spm_unit("al_liheap_countable_income", period) / MONTHS_IN_YEAR + first_upper = np.floor(monthly_limit / 3 + 0.5) + second_upper = 2 * first_upper + 1 + # Published upper bounds are inclusive. Fractional dollars immediately + # above an upper bound enter the next band without a one-dollar gap. + return select( + [monthly_income <= first_upper, monthly_income <= second_upper], + [1, 2], + default=3, + )