From 9da5488d458c1c514a9b9ce622a96af1cc9c6de6 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sat, 3 Oct 2026 19:49:19 -0400 Subject: [PATCH 1/7] Add AL regular heating LIHEAP --- changelog.d/al-liheap.added.md | 1 + .../al/adeca/liheap/eligibility/fpg_rate.yaml | 10 + .../liheap/eligibility/fpg_year_lag.yaml | 10 + .../liheap/income/earned_income_min_age.yaml | 10 + .../adeca/liheap/income/sources/earned.yaml | 17 + .../adeca/liheap/income/sources/unearned.yaml | 32 ++ .../payment/additional_person_amount.yaml | 10 + .../al/adeca/liheap/payment/base_amount.yaml | 17 + .../adeca/liheap/payment/iija_supplement.yaml | 12 + .../liheap/payment/income_band_reduction.yaml | 18 + .../maximum_payment_household_size.yaml | 10 + policyengine_us/programs.yaml | 10 +- .../gov/states/al/adeca/liheap/al_liheap.yaml | 123 ++++++ .../liheap/al_liheap_household_size.yaml | 84 +++++ .../eligibility/al_liheap_eligible.yaml | 266 +++++++++++++ .../eligibility/al_liheap_income_limit.yaml | 29 ++ .../income/al_liheap_countable_income.yaml | 357 ++++++++++++++++++ .../payment/al_liheap_base_payment.yaml | 72 ++++ .../liheap/payment/al_liheap_income_band.yaml | 31 ++ .../gov/states/al/adeca/liheap/al_liheap.py | 27 ++ .../adeca/liheap/al_liheap_household_size.py | 15 + .../liheap/eligibility/al_liheap_eligible.py | 31 ++ .../eligibility/al_liheap_income_limit.py | 31 ++ .../income/al_liheap_countable_income.py | 38 ++ .../liheap/payment/al_liheap_base_payment.py | 49 +++ .../liheap/payment/al_liheap_income_band.py | 23 ++ 26 files changed, 1332 insertions(+), 1 deletion(-) create mode 100644 changelog.d/al-liheap.added.md create mode 100644 policyengine_us/parameters/gov/states/al/adeca/liheap/eligibility/fpg_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/al/adeca/liheap/eligibility/fpg_year_lag.yaml create mode 100644 policyengine_us/parameters/gov/states/al/adeca/liheap/income/earned_income_min_age.yaml create mode 100644 policyengine_us/parameters/gov/states/al/adeca/liheap/income/sources/earned.yaml create mode 100644 policyengine_us/parameters/gov/states/al/adeca/liheap/income/sources/unearned.yaml create mode 100644 policyengine_us/parameters/gov/states/al/adeca/liheap/payment/additional_person_amount.yaml create mode 100644 policyengine_us/parameters/gov/states/al/adeca/liheap/payment/base_amount.yaml create mode 100644 policyengine_us/parameters/gov/states/al/adeca/liheap/payment/iija_supplement.yaml create mode 100644 policyengine_us/parameters/gov/states/al/adeca/liheap/payment/income_band_reduction.yaml create mode 100644 policyengine_us/parameters/gov/states/al/adeca/liheap/payment/maximum_payment_household_size.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/al_liheap.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/al_liheap_household_size.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/eligibility/al_liheap_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/eligibility/al_liheap_income_limit.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/income/al_liheap_countable_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/payment/al_liheap_base_payment.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/payment/al_liheap_income_band.yaml create mode 100644 policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap.py create mode 100644 policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap_household_size.py create mode 100644 policyengine_us/variables/gov/states/al/adeca/liheap/eligibility/al_liheap_eligible.py create mode 100644 policyengine_us/variables/gov/states/al/adeca/liheap/eligibility/al_liheap_income_limit.py create mode 100644 policyengine_us/variables/gov/states/al/adeca/liheap/income/al_liheap_countable_income.py create mode 100644 policyengine_us/variables/gov/states/al/adeca/liheap/payment/al_liheap_base_payment.py create mode 100644 policyengine_us/variables/gov/states/al/adeca/liheap/payment/al_liheap_income_band.py diff --git a/changelog.d/al-liheap.added.md b/changelog.d/al-liheap.added.md new file mode 100644 index 00000000000..9305378ab34 --- /dev/null +++ b/changelog.d/al-liheap.added.md @@ -0,0 +1 @@ +Add Alabama LIHEAP regular heating eligibility and base payment calculations. 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..21329bb64d6 --- /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, page 8 + 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..b8bfabbb83a --- /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, pages 4 and 9 (October-April heating season and 2025 guidelines) + 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..96519287cfe --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/income/earned_income_min_age.yaml @@ -0,0 +1,10 @@ +description: Alabama counts earned income starting at 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 and Procedures Manual, Income, pages 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..036f064ffad --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/income/sources/earned.yaml @@ -0,0 +1,17 @@ +description: Alabama counts these earned income sources for adult household members under the Low Income Home Energy Assistance Program. +# Existing net business inputs approximate net farm and nonfarm income without +# a separate LIHEAP expense deduction. No loss-offset rule is inferred. +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 and Procedures Manual, Income, pages 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..d9784614a97 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/income/sources/unearned.yaml @@ -0,0 +1,32 @@ +description: Alabama counts these unearned income sources for household members of all ages under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: + - unemployment_compensation + - strike_benefits + - social_security + - ssi + - pension_income + - survivor_benefits + - veterans_benefits + - disability_benefits + - workers_compensation + - al_tanf + - child_support_received + - alimony_income + - interest_income + - ordinary_dividend_income + - rental_income + - farm_rent_income + - estate_income + - railroad_benefits + - financial_assistance +# Net gambling winnings are calculated separately using existing winnings and +# losses inputs. Do not also include the gross winnings here. SSA is gross; +# the input does not deduct Medicare premiums. No other TANF aggregate is added. +metadata: + unit: list + period: year + label: Alabama LIHEAP unearned income sources + reference: + - title: Alabama FY2026 LIHEAP Policy and Procedures Manual, Income, pages 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..40838b204cc --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/additional_person_amount.yaml @@ -0,0 +1,10 @@ +description: Alabama increases the regular heating payment by this amount per additional household member 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 and Procedures Manual, Appendix D, page 57 (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..e55a88f1743 --- /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 and Procedures Manual, Appendix D, page 57 (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..c022eaa3b10 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/iija_supplement.yaml @@ -0,0 +1,12 @@ +description: Alabama provides this funded 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 +metadata: + unit: currency-USD + period: year + label: Alabama LIHEAP IIJA heating supplement + reference: + - title: Accepted FY2026 Alabama LIHEAP State Plan, Section 2.5 + href: https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-State-Plan.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 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..b2ba44e26f9 --- /dev/null +++ b/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/income_band_reduction.yaml @@ -0,0 +1,18 @@ +description: Alabama reduces the lowest-income heating payment by this amount 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 and Procedures Manual, Appendix D, page 57 (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..dbb88157901 --- /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, page 9 (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 756dc2c4988..2aa0dfdb64e 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -802,8 +802,16 @@ programs: category: Benefits agency: HHS status: partial - coverage: OR, DC, Riverside County, MA, IL, KS, MS, TX + coverage: AL, OR, DC, Riverside County, MA, IL, KS, MS, 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 grant and funded $100 IIJA supplement, with HUD allowance reduction. The discretionary $50 agency award and unavailable household facts are documented limitations. Historical backfills are unverified; FY2027 draft policy is not adopted. Regular heating only; crisis, cooling, weatherization and equipment assistance are excluded. - state: OR status: in_progress name: 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..049564bcc6a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/al_liheap.yaml @@ -0,0 +1,123 @@ +# FY2026 plan p10: funded IIJA supplement is $100 for each regular heating award. +# Manual pp22,57: subtract one monthly HUD allowance from the current award. +# 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/$450/$500/$540/$600 per month. +# One-person band-one electricity is $440; the funded regular total is $540. +- name: Case 3, monthly HUD allowances reduce the combined award and never make it negative. + 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_400, 6_000, 6_480, 7_200] + output: + al_liheap: [540, 440, 90, 40, 0, 0] + +- 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: [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] 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..e363c71d3cc --- /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 manual and current Appendix D; requirement bindings in the test manifest. + +# 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..e2088145033 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/eligibility/al_liheap_eligible.yaml @@ -0,0 +1,266 @@ +# Source: ADECA FY2026 manual and current Appendix D; requirement bindings in the test manifest. + +# Manual p21:1956per month inclusive; 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 + +# 1956.01monthly exceeds the published1956limit. +- 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 + +# Accepted plan categoricalNo; even a recipient must pass income eligibility. +- name: Case 8, SSI receipt does not bypass the income ceiling. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 30_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 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 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..cbaec7d2f0f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/eligibility/al_liheap_income_limit.yaml @@ -0,0 +1,29 @@ +# Source: ADECA FY2026 manual and current Appendix D; requirement bindings in the test manifest. + +# AppendixD/benefit chart, sizes1–15: each literal 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] + bottled_gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200, 1_200, 1_200, 1_200] + 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] + + +# 2025 model year uses2024FPG15060 and20440; monthly150% amounts1882.5 and2555 round to1883 and2555. Historical estimate, not verified2025 policy. +- 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] + bottled_gas_expense: [1_200, 1_200] + 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..0b2ed62641a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/income/al_liheap_countable_income.yaml @@ -0,0 +1,357 @@ +# Source: ADECA FY2026 manual and current Appendix D; requirement bindings in the test manifest. + +# 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: under18 earned income excluded, but annual SSI2400 and survivor1200 remain countable. +- 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: under18 earned income excluded, but annual SSI2400 and survivor1200 remain countable. +- 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 wages1200 plus annual TANF3600 =4800. YEAR test supplies annual amount to 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 + +# Contract v1.1 preserves existing signed net inputs, then floors the household total; the source does not expressly settle loss offsets. +- name: Case 7, negative combined existing income is floored at zero. + 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: 0 + +# 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. +# Wages23_400 + periodic estate1_200 =24_600 exceeds the annualized23_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 physical 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 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..54b96992162 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/payment/al_liheap_base_payment.yaml @@ -0,0 +1,72 @@ +# Source: ADECA FY2026 manual and current Appendix D; requirement bindings in the test manifest. + +# Current AppendixD/chart literal48cells in size,band,fuel order. This isolates matrix 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] + bottled_gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200] + 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] + gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200] + pre_subsidy_electricity_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200] + wood_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200] + 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] + bottled_gas_expense: [1_200, 1_200, 1_200] + al_liheap_household_size: [1, 1, 1] + al_liheap_income_band: [1, 2, 3] + wood_expense: [1_200, 1_200, 1_200] + coal_expense: [1_200, 1_200, 1_200] + fuel_oil_expense: [1_200, 1_200, 1_200] + output: + al_liheap_base_payment: [400, 370, 280] + + +# Chart note: more than4people receives size4amount; fixed base470 is not capped by a50annual heating bill. +- 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] + bottled_gas_expense: [1_200, 1_200, 1_200] + al_liheap_household_size: [4, 5, 15] + al_liheap_income_band: [1, 1, 1] + pre_subsidy_electricity_expense: [50, 50, 50] + 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..a53ed0ab474 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/adeca/liheap/payment/al_liheap_income_band.yaml @@ -0,0 +1,31 @@ +# Source: ADECA FY2026 manual and current Appendix D; requirement bindings in the test manifest. + +# Plan p9/chart: monthly0,652,652.01,1305,1305.01,1956 map to1,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] + bottled_gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200] + 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 boundaries: size2 881/1763; size3 1110/2221; size4 1340/2681; 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] + bottled_gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200] + 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..2495d2c233d --- /dev/null +++ b/policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap.py @@ -0,0 +1,27 @@ +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 + # Manual pages 22-23 cover the HUD adjustment and discretionary high need. + reference = "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=22" + + 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 + # FY2026 IIJA funds were awarded; assume program funds are available. + # The separate $50 high-need addition requires an agency decision + # unavailable in existing inputs, so demographic traits alone do not + # add it. FY2027 supplement policy is not verified by the draft plan. + # Fixed regular payments have no cap at the reported heating expense. + return eligible * max_(base + p.iija_supplement - allowance, 0) 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..9376a25b586 --- /dev/null +++ b/policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap_household_size.py @@ -0,0 +1,15 @@ +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 + reference = "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=22" + + # 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..a64f9faa83b --- /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 + # Pages 13 and 21-22: residency, income, immigration, and heating liability. + reference = "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=21" + + 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..a4e59ac87ce --- /dev/null +++ b/policyengine_us/variables/gov/states/al/adeca/liheap/income/al_liheap_countable_income.py @@ -0,0 +1,38 @@ +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 + # Pages 17-19 define income; page 22 retains nonqualified members' income. + 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 signed net business inputs need no further expense deduction. + # The manual does not expressly settle whether losses offset other income; + # preserve the signed inputs and floor the combined household total. + earned = add(person, period, p.sources.earned) + unearned = add(spm_unit, period, p.sources.unearned, options=[ADD]) + 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. + return max_( + spm_unit.sum(earned * adult + net_gambling) + unearned, + 0, + ) 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..c36df3102bf --- /dev/null +++ b/policyengine_us/variables/gov/states/al/adeca/liheap/payment/al_liheap_base_payment.py @@ -0,0 +1,49 @@ +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. Returning zero leaves those estimates incomplete; + # it does not establish legal ineligibility, including heat in rent. + 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, + ) From 22fbe6229a3a541bd9627f0102f1c210c33fcf3b Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 6 Oct 2026 16:00:23 -0400 Subject: [PATCH 2/7] Apply Alabama LIHEAP HUD allowance before the supplement --- changelog.d/al-liheap.added.md | 2 +- policyengine_us/programs.yaml | 2 +- .../gov/states/al/adeca/liheap/al_liheap.yaml | 16 ++++++++++------ .../gov/states/al/adeca/liheap/al_liheap.py | 12 ++++++++++-- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/changelog.d/al-liheap.added.md b/changelog.d/al-liheap.added.md index 9305378ab34..b8515e3f622 100644 --- a/changelog.d/al-liheap.added.md +++ b/changelog.d/al-liheap.added.md @@ -1 +1 @@ -Add Alabama LIHEAP regular heating eligibility and base payment calculations. +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/programs.yaml b/policyengine_us/programs.yaml index 2aa0dfdb64e..f17ae0c8607 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -811,7 +811,7 @@ programs: variable: al_liheap parameter_prefix: gov.states.al.adeca.liheap verified_years: "2026" - notes: FY2026 regular heating grant and funded $100 IIJA supplement, with HUD allowance reduction. The discretionary $50 agency award and unavailable household facts are documented limitations. Historical backfills are unverified; FY2027 draft policy is not adopted. Regular heating only; crisis, cooling, weatherization and equipment assistance are excluded. + notes: FY2026 regular heating grant with the monthly HUD allowance deducted before the funded $100 IIJA supplement. The supplement requires a positive remaining regular payment; this interprets the plan's receipt condition, which does not expressly address zero regular payments. Unlisted fuel payments remain unverified; a modeled zero is not a legal denial. The discretionary $50 agency award and unavailable household facts are documented limitations. Historical backfills are unverified; FY2027 draft policy is not adopted. Regular heating only; crisis, cooling, weatherization and equipment assistance are excluded. - state: OR status: in_progress name: 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 index 049564bcc6a..e8d6cd6666c 100644 --- 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 @@ -1,5 +1,7 @@ # FY2026 plan p10: funded IIJA supplement is $100 for each regular heating award. -# Manual pp22,57: subtract one monthly HUD allowance from the current award. +# 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. @@ -57,9 +59,10 @@ al_liheap_base_payment: 470 al_liheap: 570 -# Annual HUD allowances below represent $0/$100/$450/$500/$540/$600 per month. -# One-person band-one electricity is $440; the funded regular total is $540. -- name: Case 3, monthly HUD allowances reduce the combined award and never make it negative. +# 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] @@ -70,9 +73,10 @@ 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_400, 6_000, 6_480, 7_200] + hud_utility_allowance: [0, 1_200, 5_268, 5_280, 5_400, 7_200] output: - al_liheap: [540, 440, 90, 40, 0, 0] + al_liheap_base_payment: [440, 440, 440, 440, 440, 440] + al_liheap: [540, 440, 101, 0, 0, 0] - name: Case 4, the funded supplement cannot pay an income or heating or immigration ineligible household. period: 2026 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 index 2495d2c233d..55b11bae807 100644 --- a/policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap.py +++ b/policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap.py @@ -9,7 +9,10 @@ class al_liheap(Variable): label = "Alabama LIHEAP regular heating assistance" defined_for = StateCode.AL # Manual pages 22-23 cover the HUD adjustment and discretionary high need. - reference = "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=22" + reference = ( + "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=22", + "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-State-Plan.pdf#page=10", + ) def formula(spm_unit, period, parameters): p = parameters(period).gov.states.al.adeca.liheap.payment @@ -19,9 +22,14 @@ def formula(spm_unit, period, parameters): # 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) # FY2026 IIJA funds were awarded; assume program funds are available. + # 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. FY2027 supplement policy is not verified by the draft plan. # Fixed regular payments have no cap at the reported heating expense. - return eligible * max_(base + p.iija_supplement - allowance, 0) + return eligible * (regular_payment + supplement) From 3b7ca1dc9c50bcaa3025cbaa29e385c0ae834473 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 6 Oct 2026 18:13:30 -0400 Subject: [PATCH 3/7] Fix Alabama LIHEAP income counting from review - Count military retirement pay beside pension income (manual p18). - Floor each earned and unearned source at zero so a loss in one source does not offset another (manual p17, "all sources"). - Count TANF through the take-up-gated tanf variable instead of al_tanf. - Shorten the programs.yaml note. - Add tests for military pay, per-source loss floors and TANF take-up. Co-Authored-By: Claude Opus 5.5 --- .../adeca/liheap/income/sources/earned.yaml | 2 +- .../adeca/liheap/income/sources/unearned.yaml | 11 +- policyengine_us/programs.yaml | 2 +- .../gov/states/al/adeca/liheap/al_liheap.yaml | 42 ++++++ .../income/al_liheap_countable_income.yaml | 129 +++++++++++++++++- .../income/al_liheap_countable_income.py | 23 ++-- 6 files changed, 193 insertions(+), 16 deletions(-) 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 index 036f064ffad..593e23cda27 100644 --- 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 @@ -1,6 +1,6 @@ description: Alabama counts these earned income sources for adult household members under the Low Income Home Energy Assistance Program. # Existing net business inputs approximate net farm and nonfarm income without -# a separate LIHEAP expense deduction. No loss-offset rule is inferred. +# a separate LIHEAP expense deduction. The formula floors each source at zero. values: 2025-10-01: - employment_income 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 index d9784614a97..7726738df43 100644 --- 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 @@ -3,14 +3,19 @@ values: 2025-10-01: - unemployment_compensation - strike_benefits + # Social Security is counted gross; the input does not deduct Medicare + # premiums. - social_security - ssi + # 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 - - al_tanf - child_support_received - alimony_income - interest_income @@ -20,9 +25,9 @@ values: - 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. SSA is gross; -# the input does not deduct Medicare premiums. No other TANF aggregate is added. +# losses inputs. Do not also include the gross winnings here. metadata: unit: list period: year diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index f17ae0c8607..e3cd840b5bd 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -811,7 +811,7 @@ programs: variable: al_liheap parameter_prefix: gov.states.al.adeca.liheap verified_years: "2026" - notes: FY2026 regular heating grant with the monthly HUD allowance deducted before the funded $100 IIJA supplement. The supplement requires a positive remaining regular payment; this interprets the plan's receipt condition, which does not expressly address zero regular payments. Unlisted fuel payments remain unverified; a modeled zero is not a legal denial. The discretionary $50 agency award and unavailable household facts are documented limitations. Historical backfills are unverified; FY2027 draft policy is not adopted. Regular heating only; crisis, cooling, weatherization and equipment assistance are excluded. + 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: OR status: in_progress name: 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 index e8d6cd6666c..2e6c5cc18b5 100644 --- 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 @@ -125,3 +125,45 @@ 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 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 index 0b2ed62641a..33bb6507678 100644 --- 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 @@ -197,8 +197,9 @@ output: al_liheap_countable_income: 12_000 -# Contract v1.1 preserves existing signed net inputs, then floors the household total; the source does not expressly settle loss offsets. -- name: Case 7, negative combined existing income is floored at zero. +# 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: @@ -223,7 +224,7 @@ state_code: AL hud_utility_allowance: 0 output: - al_liheap_countable_income: 0 + 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. @@ -355,3 +356,125 @@ 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] 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 index a4e59ac87ce..311989cfce0 100644 --- 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 @@ -16,11 +16,16 @@ def formula(spm_unit, period, parameters): person = spm_unit.members adult = person("age", period) >= p.earned_income_min_age # Annual income approximates the preceding calendar month's income. - # Existing signed net business inputs need no further expense deduction. - # The manual does not expressly settle whether losses offset other income; - # preserve the signed inputs and floor the combined household total. - earned = add(person, period, p.sources.earned) - unearned = add(spm_unit, period, p.sources.unearned, options=[ADD]) + # 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, @@ -32,7 +37,9 @@ def formula(spm_unit, period, parameters): # principal or nonperiodic receipts. Financial assistance covers cash # support from family or friends; royalties, stipends, and some # severance payments remain unavailable as distinct inputs. - return max_( - spm_unit.sum(earned * adult + net_gambling) + unearned, - 0, + # 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 ) From 1676c456cc981460578aecd4b00001cf4c12b5de Mon Sep 17 00:00:00 2001 From: Ziming Date: Wed, 7 Oct 2026 22:56:52 -0400 Subject: [PATCH 4/7] Fix issues from review: count the Alabama SSP as income Add al_ssp to the countable unearned sources (FY2026 manual 5.5.1) and test it with an in-home-care SSI recipient. Co-Authored-By: Claude Opus 5.5 --- .../adeca/liheap/income/sources/unearned.yaml | 2 + .../income/al_liheap_countable_income.yaml | 52 +++++++++++++++++++ 2 files changed, 54 insertions(+) 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 index 7726738df43..dff4990e645 100644 --- 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 @@ -7,6 +7,8 @@ values: # 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. 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 index 33bb6507678..1ce65b71917 100644 --- 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 @@ -478,3 +478,55 @@ 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. +- 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 + ssi: 0 + 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 From dd5e165302972fddd4bbeccad16761707a99dd9a Mon Sep 17 00:00:00 2001 From: Ziming Date: Wed, 7 Oct 2026 23:24:30 -0400 Subject: [PATCH 5/7] Apply the reference page-citation convention One reference entry per source; single-page citations keep the page only in the href; multi-page titles end with a quoted #page list and the href opens the first cited page; variable references use '# PDF pages ...' comments above multi-page hrefs (CLAUDE.md, PolicyEngine/policyengine-us PR 10009). Reference lines only. Co-Authored-By: Claude Opus 5.5 --- .../gov/states/al/adeca/liheap/eligibility/fpg_rate.yaml | 2 +- .../gov/states/al/adeca/liheap/eligibility/fpg_year_lag.yaml | 2 +- .../states/al/adeca/liheap/income/earned_income_min_age.yaml | 2 +- .../gov/states/al/adeca/liheap/income/sources/earned.yaml | 2 +- .../gov/states/al/adeca/liheap/income/sources/unearned.yaml | 2 +- .../al/adeca/liheap/payment/additional_person_amount.yaml | 2 +- .../gov/states/al/adeca/liheap/payment/base_amount.yaml | 2 +- .../states/al/adeca/liheap/payment/income_band_reduction.yaml | 2 +- .../adeca/liheap/payment/maximum_payment_household_size.yaml | 2 +- .../variables/gov/states/al/adeca/liheap/al_liheap.py | 2 +- .../states/al/adeca/liheap/eligibility/al_liheap_eligible.py | 4 ++-- .../al/adeca/liheap/income/al_liheap_countable_income.py | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) 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 index 21329bb64d6..2f2d2065135 100644 --- 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 @@ -6,5 +6,5 @@ metadata: period: year label: Alabama LIHEAP federal poverty guideline rate reference: - - title: Alabama FY2026 LIHEAP State Plan, section 2.1, page 8 + - 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 index b8bfabbb83a..47485dcde73 100644 --- 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 @@ -6,5 +6,5 @@ metadata: period: year label: Alabama LIHEAP heating poverty guideline year lag reference: - - title: Alabama FY2026 LIHEAP State Plan, pages 4 and 9 (October-April heating season and 2025 guidelines) + - 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 index 96519287cfe..229559d5544 100644 --- 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 @@ -6,5 +6,5 @@ metadata: period: year label: Alabama LIHEAP earned income minimum age reference: - - title: Alabama FY2026 LIHEAP Policy and Procedures Manual, Income, pages 17-19 + - title: "Alabama FY2026 LIHEAP Policy and Procedures 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 index 593e23cda27..844e330218b 100644 --- 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 @@ -13,5 +13,5 @@ metadata: period: year label: Alabama LIHEAP earned income sources reference: - - title: Alabama FY2026 LIHEAP Policy and Procedures Manual, Income, pages 17-19 + - title: "Alabama FY2026 LIHEAP Policy and Procedures 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 index dff4990e645..82c41fa07da 100644 --- 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 @@ -35,5 +35,5 @@ metadata: period: year label: Alabama LIHEAP unearned income sources reference: - - title: Alabama FY2026 LIHEAP Policy and Procedures Manual, Income, pages 17-19 + - title: "Alabama FY2026 LIHEAP Policy and Procedures 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 index 40838b204cc..49b7b64e9b1 100644 --- 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 @@ -6,5 +6,5 @@ metadata: period: year label: Alabama LIHEAP additional person heating payment reference: - - title: Alabama FY2026 LIHEAP Policy and Procedures Manual, Appendix D, page 57 (current benefit chart) + - title: Alabama FY2026 LIHEAP Policy and Procedures 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 index e55a88f1743..cfc24c74a87 100644 --- 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 @@ -13,5 +13,5 @@ metadata: label: Alabama LIHEAP base heating payment by fuel propagate_metadata_to_children: true reference: - - title: Alabama FY2026 LIHEAP Policy and Procedures Manual, Appendix D, page 57 (current benefit chart) + - title: Alabama FY2026 LIHEAP Policy and Procedures 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/income_band_reduction.yaml b/policyengine_us/parameters/gov/states/al/adeca/liheap/payment/income_band_reduction.yaml index b2ba44e26f9..bec707f4bab 100644 --- 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 @@ -14,5 +14,5 @@ metadata: - range(1, 4) propagate_metadata_to_children: true reference: - - title: Alabama FY2026 LIHEAP Policy and Procedures Manual, Appendix D, page 57 (current benefit chart) + - title: Alabama FY2026 LIHEAP Policy and Procedures 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 index dbb88157901..b02c1ed57da 100644 --- 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 @@ -6,5 +6,5 @@ metadata: period: year label: Alabama LIHEAP maximum payment household size reference: - - title: Alabama FY2026 LIHEAP State Plan, section 2.5, page 9 (households over four receive the four-person payment) + - 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/variables/gov/states/al/adeca/liheap/al_liheap.py b/policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap.py index 55b11bae807..1556eb9262c 100644 --- a/policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap.py +++ b/policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap.py @@ -8,8 +8,8 @@ class al_liheap(Variable): unit = USD label = "Alabama LIHEAP regular heating assistance" defined_for = StateCode.AL - # Manual pages 22-23 cover the HUD adjustment and discretionary high need. reference = ( + # PDF pages 22-23 "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=22", "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-State-Plan.pdf#page=10", ) 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 index a64f9faa83b..4761ff36863 100644 --- 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 @@ -7,8 +7,8 @@ class al_liheap_eligible(Variable): definition_period = YEAR label = "Alabama LIHEAP eligibility" defined_for = StateCode.AL - # Pages 13 and 21-22: residency, income, immigration, and heating liability. - reference = "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=21" + # 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) 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 index 311989cfce0..fee93a40f59 100644 --- 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 @@ -8,7 +8,7 @@ class al_liheap_countable_income(Variable): unit = USD label = "Alabama LIHEAP annual countable household income" defined_for = StateCode.AL - # Pages 17-19 define income; page 22 retains nonqualified members' income. + # 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): From dc5fc558067190e35655c46b17187824d73dc758 Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 8 Oct 2026 00:24:15 -0400 Subject: [PATCH 6/7] Fix Alabama LIHEAP computed SSI integration test --- .../al/adeca/liheap/income/al_liheap_countable_income.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 index 1ce65b71917..2a48400c229 100644 --- 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 @@ -489,6 +489,9 @@ # 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 @@ -503,7 +506,6 @@ person2: age: 42 immigration_status: CITIZEN - ssi: 0 employment_income: 15_080 tax_units: tax_unit1: @@ -530,3 +532,4 @@ ssi: [11_928, 0] al_ssp: [720, 0] al_liheap_countable_income: 27_728 + al_liheap: 490 From af334f5714f74de3618580bc2f60d3626a6d8bef Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 15 Oct 2026 16:11:36 -0400 Subject: [PATCH 7/7] Address review round 1 on Alabama LIHEAP - S1: Merge blocker: the unearned list reads the raw unemployment_compensation, so main's guard fails and modeled Al - S2: Period 2027 still pays the $100 IIJA supplement, but the draft FY2027 plan drops it and the appropriation ran - S3: Four pieces of logic have no test that would fail if they broke, and 8 of the 20 unearned sources are never no - S4: Five parameter descriptions use verbs outside the house template Also applies the in-scope suggestions. Tests not run locally; CI validates. Co-Authored-By: Claude Opus 5.5 --- .../liheap/income/earned_income_min_age.yaml | 4 +- .../adeca/liheap/income/sources/earned.yaml | 4 +- .../adeca/liheap/income/sources/unearned.yaml | 8 +- .../payment/additional_person_amount.yaml | 4 +- .../al/adeca/liheap/payment/base_amount.yaml | 2 +- .../adeca/liheap/payment/iija_supplement.yaml | 13 +- .../liheap/payment/income_band_reduction.yaml | 4 +- .../gov/states/al/adeca/liheap/al_liheap.yaml | 54 ++++++- .../liheap/al_liheap_household_size.yaml | 2 +- .../eligibility/al_liheap_eligible.yaml | 57 +++++++- .../eligibility/al_liheap_income_limit.yaml | 10 +- .../income/al_liheap_countable_income.yaml | 134 +++++++++++++++++- .../payment/al_liheap_base_payment.yaml | 28 +--- .../liheap/payment/al_liheap_income_band.yaml | 9 +- .../gov/states/al/adeca/liheap/al_liheap.py | 12 +- .../adeca/liheap/al_liheap_household_size.py | 3 +- .../liheap/payment/al_liheap_base_payment.py | 6 +- 17 files changed, 282 insertions(+), 72 deletions(-) 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 index 229559d5544..4682551fb71 100644 --- 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 @@ -1,4 +1,4 @@ -description: Alabama counts earned income starting at this age under the Low Income Home Energy Assistance Program. +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: @@ -6,5 +6,5 @@ metadata: period: year label: Alabama LIHEAP earned income minimum age reference: - - title: "Alabama FY2026 LIHEAP Policy and Procedures Manual, Income #page 17-19" + - 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 index 844e330218b..62bbc66c41d 100644 --- 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 @@ -1,4 +1,4 @@ -description: Alabama counts these earned income sources for adult household members under the Low Income Home Energy Assistance Program. +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: @@ -13,5 +13,5 @@ metadata: period: year label: Alabama LIHEAP earned income sources reference: - - title: "Alabama FY2026 LIHEAP Policy and Procedures Manual, Income #page 17-19" + - 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 index 82c41fa07da..4ab8e63322e 100644 --- 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 @@ -1,7 +1,9 @@ -description: Alabama counts these unearned income sources for household members of all ages under the Low Income Home Energy Assistance Program. +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: - - unemployment_compensation + # 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. @@ -35,5 +37,5 @@ metadata: period: year label: Alabama LIHEAP unearned income sources reference: - - title: "Alabama FY2026 LIHEAP Policy and Procedures Manual, Income #page 17-19" + - 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 index 49b7b64e9b1..fb7ddf01173 100644 --- 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 @@ -1,4 +1,4 @@ -description: Alabama increases the regular heating payment by this amount per additional household member up to the payment household-size limit under the Low Income Home Energy Assistance Program. +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: @@ -6,5 +6,5 @@ metadata: period: year label: Alabama LIHEAP additional person heating payment reference: - - title: Alabama FY2026 LIHEAP Policy and Procedures Manual, Appendix D (current benefit chart) + - 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 index cfc24c74a87..b71aa51c967 100644 --- 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 @@ -13,5 +13,5 @@ metadata: label: Alabama LIHEAP base heating payment by fuel propagate_metadata_to_children: true reference: - - title: Alabama FY2026 LIHEAP Policy and Procedures Manual, Appendix D (current benefit chart) + - 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 index c022eaa3b10..63c025d5ff1 100644 --- 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 @@ -1,12 +1,19 @@ -description: Alabama provides this funded Infrastructure Investment and Jobs Act supplement with each regular heating assistance award under the Low Income Home Energy Assistance Program. +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: Accepted FY2026 Alabama LIHEAP State Plan, Section 2.5 - href: https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-State-Plan.pdf#page=10 + - 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 index bec707f4bab..4e608283ac0 100644 --- 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 @@ -1,4 +1,4 @@ -description: Alabama reduces the lowest-income heating payment by this amount for each income band under the Low Income Home Energy Assistance Program. +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 @@ -14,5 +14,5 @@ metadata: - range(1, 4) propagate_metadata_to_children: true reference: - - title: Alabama FY2026 LIHEAP Policy and Procedures Manual, Appendix D (current benefit chart) + - 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/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 index 2e6c5cc18b5..c3107ada620 100644 --- 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 @@ -1,4 +1,5 @@ -# FY2026 plan p10: funded IIJA supplement is $100 for each regular heating award. +# 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. @@ -78,6 +79,7 @@ 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: @@ -91,6 +93,7 @@ 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. @@ -167,3 +170,52 @@ 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 index e363c71d3cc..6c3c77c9642 100644 --- 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 @@ -1,4 +1,4 @@ -# Source: ADECA FY2026 manual and current Appendix D; requirement bindings in the test manifest. +# 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 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 index e2088145033..2c57636adf6 100644 --- 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 @@ -1,6 +1,6 @@ -# Source: ADECA FY2026 manual and current Appendix D; requirement bindings in the test manifest. +# Source: ADECA FY2026 LIHEAP Policy Manual and its Appendix D Payment Assistance Chart. -# Manual p21:1956per month inclusive; accepted plan has no asset test. +# 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: @@ -27,7 +27,7 @@ output: al_liheap_eligible: true -# 1956.01monthly exceeds the published1956limit. +# $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: @@ -188,19 +188,37 @@ output: al_liheap_eligible: true -# Accepted plan categoricalNo; even a recipient must pass income eligibility. +# 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: 30_000 + 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 @@ -208,9 +226,13 @@ 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. @@ -264,3 +286,28 @@ 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 index cbaec7d2f0f..40b7d744bbc 100644 --- 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 @@ -1,6 +1,6 @@ -# Source: ADECA FY2026 manual and current Appendix D; requirement bindings in the test manifest. +# Source: ADECA FY2026 LIHEAP Policy Manual and its Appendix D Payment Assistance Chart. -# AppendixD/benefit chart, sizes1–15: each literal published monthly limit is annualized once; this isolated unit test supplies size directly. +# 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 @@ -8,22 +8,20 @@ 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] - bottled_gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200, 1_200, 1_200, 1_200] 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] -# 2025 model year uses2024FPG15060 and20440; monthly150% amounts1882.5 and2555 round to1883 and2555. Historical estimate, not verified2025 policy. +# 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] - bottled_gas_expense: [1_200, 1_200] 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 index 2a48400c229..0a2afca1ca3 100644 --- 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 @@ -1,4 +1,4 @@ -# Source: ADECA FY2026 manual and current Appendix D; requirement bindings in the test manifest. +# 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. @@ -31,7 +31,7 @@ output: al_liheap_countable_income: 22_800 -# Manual pp17–19: under18 earned income excluded, but annual SSI2400 and survivor1200 remain countable. +# 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 @@ -67,7 +67,7 @@ output: al_liheap_countable_income: 15_600 -# Manual pp17–19: under18 earned income excluded, but annual SSI2400 and survivor1200 remain countable. +# 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 @@ -135,7 +135,7 @@ output: al_liheap_countable_income: 22_200 -# Manual p18: annual wages1200 plus annual TANF3600 =4800. YEAR test supplies annual amount to MONTH-defined al_tanf. +# 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 @@ -303,7 +303,7 @@ # 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. -# Wages23_400 + periodic estate1_200 =24_600 exceeds the annualized23_472 income ceiling. +# 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: @@ -328,7 +328,7 @@ al_liheap_countable_income: 24_600 al_liheap_eligible: false -# Manual physical p18 counts railroad retirement, financial support from family or friends, +# 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 @@ -533,3 +533,125 @@ 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 index 54b96992162..91a9355e94e 100644 --- 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 @@ -1,6 +1,6 @@ -# Source: ADECA FY2026 manual and current Appendix D; requirement bindings in the test manifest. +# Source: ADECA FY2026 LIHEAP Policy Manual and its Appendix D Payment Assistance Chart. -# Current AppendixD/chart literal48cells in size,band,fuel order. This isolates matrix lookup from eligibility and supplements. +# 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 @@ -14,26 +14,10 @@ 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] - bottled_gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200] 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] - gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200] - pre_subsidy_electricity_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200] - wood_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200] 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, @@ -47,26 +31,20 @@ input: state_code: [AL, AL, AL] heating_type: [WOOD, COAL, KEROSENE] - bottled_gas_expense: [1_200, 1_200, 1_200] al_liheap_household_size: [1, 1, 1] al_liheap_income_band: [1, 2, 3] - wood_expense: [1_200, 1_200, 1_200] - coal_expense: [1_200, 1_200, 1_200] - fuel_oil_expense: [1_200, 1_200, 1_200] output: al_liheap_base_payment: [400, 370, 280] -# Chart note: more than4people receives size4amount; fixed base470 is not capped by a50annual heating bill. +# 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] - bottled_gas_expense: [1_200, 1_200, 1_200] al_liheap_household_size: [4, 5, 15] al_liheap_income_band: [1, 1, 1] - pre_subsidy_electricity_expense: [50, 50, 50] 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 index a53ed0ab474..7a3314d7877 100644 --- 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 @@ -1,20 +1,19 @@ -# Source: ADECA FY2026 manual and current Appendix D; requirement bindings in the test manifest. +# Source: ADECA FY2026 LIHEAP Policy Manual and its Appendix D Payment Assistance Chart. -# Plan p9/chart: monthly0,652,652.01,1305,1305.01,1956 map to1,1,2,2,3,3; fractional dollars do not create gaps. +# 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] - bottled_gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200] 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 boundaries: size2 881/1763; size3 1110/2221; size4 1340/2681; each paired with the next cent. +# 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 @@ -22,8 +21,6 @@ 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] - bottled_gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200] 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] 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 index 1556eb9262c..a23c5a98977 100644 --- a/policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap.py +++ b/policyengine_us/variables/gov/states/al/adeca/liheap/al_liheap.py @@ -11,7 +11,7 @@ class al_liheap(Variable): reference = ( # PDF pages 22-23 "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=22", - "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-State-Plan.pdf#page=10", + "https://liheapch.acf.gov/docs/2026/state-plans/AL_Plan_2026.pdf#page=10", ) def formula(spm_unit, period, parameters): @@ -23,13 +23,17 @@ def formula(spm_unit, period, parameters): # 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) - # FY2026 IIJA funds were awarded; assume program funds are available. + # 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. FY2027 supplement policy is not verified by the draft plan. - # Fixed regular payments have no cap at the reported heating expense. + # 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 index 9376a25b586..1c6acb93f48 100644 --- 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 @@ -7,7 +7,8 @@ class al_liheap_household_size(Variable): definition_period = YEAR label = "Alabama LIHEAP household size" defined_for = StateCode.AL - reference = "https://adeca.alabama.gov/wp-content/uploads/FY-2026-LIHEAP-Manual-1.pdf#page=22" + # 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. 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 index c36df3102bf..44a969e971f 100644 --- 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 @@ -44,6 +44,8 @@ def formula(spm_unit, period, parameters): # 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. Returning zero leaves those estimates incomplete; - # it does not establish legal ineligibility, including heat in rent. + # 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)