From 353aef396aff0a1bcf55a6dfecd9af61f640b643 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sat, 3 Oct 2026 19:49:20 -0400 Subject: [PATCH 01/10] Add GA regular heating LIHEAP --- changelog.d/ga-liheap.added.md | 1 + .../ga/dfcs/liheap/eligibility/smi_rate.yaml | 10 + .../ga/dfcs/liheap/income/minimum_age.yaml | 10 + .../income/monthly_interest_disregard.yaml | 10 + .../dfcs/liheap/income/sources/household.yaml | 11 + .../ga/dfcs/liheap/income/sources/person.yaml | 35 +++ .../ga/dfcs/liheap/payment/base_amount.yaml | 19 ++ .../liheap/payment/lower_income_limit.yaml | 45 +++ .../liheap/payment/lower_income_uplift.yaml | 10 + .../liheap/payment/maximum_table_size.yaml | 10 + policyengine_us/programs.yaml | 10 +- .../eligibility/ga_liheap_eligible.yaml | 105 +++++++ .../eligibility/ga_liheap_income_limit.yaml | 29 ++ .../gov/states/ga/dfcs/liheap/ga_liheap.yaml | 280 ++++++++++++++++++ .../dfcs/liheap/ga_liheap_household_size.yaml | 69 +++++ .../dfcs/liheap/income/ga_liheap_income.yaml | 236 +++++++++++++++ .../payment/ga_liheap_matrix_amount.yaml | 75 +++++ .../liheap/eligibility/ga_liheap_eligible.py | 37 +++ .../eligibility/ga_liheap_income_limit.py | 27 ++ .../gov/states/ga/dfcs/liheap/ga_liheap.py | 17 ++ .../dfcs/liheap/ga_liheap_household_size.py | 16 + .../ga/dfcs/liheap/income/ga_liheap_income.py | 44 +++ .../liheap/payment/ga_liheap_matrix_amount.py | 43 +++ 23 files changed, 1148 insertions(+), 1 deletion(-) create mode 100644 changelog.d/ga-liheap.added.md create mode 100644 policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/smi_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/minimum_age.yaml create mode 100644 policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml create mode 100644 policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml create mode 100644 policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml create mode 100644 policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/base_amount.yaml create mode 100644 policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_limit.yaml create mode 100644 policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_uplift.yaml create mode 100644 policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/maximum_table_size.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap_household_size.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml create mode 100644 policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py create mode 100644 policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.py create mode 100644 policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap.py create mode 100644 policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py create mode 100644 policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py create mode 100644 policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py diff --git a/changelog.d/ga-liheap.added.md b/changelog.d/ga-liheap.added.md new file mode 100644 index 00000000000..f31013ef95a --- /dev/null +++ b/changelog.d/ga-liheap.added.md @@ -0,0 +1 @@ +Add Georgia LIHEAP household, income, ordinary eligibility, and regular heating benefits, with documented categorical and payment-table coverage limits. diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/smi_rate.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/smi_rate.yaml new file mode 100644 index 00000000000..0474e764be4 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/smi_rate.yaml @@ -0,0 +1,10 @@ +description: Georgia limits annual income to this fraction of state median income under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 0.6 +metadata: + unit: /1 + period: year + label: Georgia LIHEAP state median income rate + reference: + - title: Georgia FY2026 LIHEAP State Plan, section 2.1, page 8 + href: https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=8 diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/minimum_age.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/minimum_age.yaml new file mode 100644 index 00000000000..468baa09b29 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/minimum_age.yaml @@ -0,0 +1,10 @@ +description: Georgia counts household members' income starting at this age under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 18 +metadata: + unit: year + period: year + label: Georgia LIHEAP countable income minimum age + reference: + - title: Georgia LIHEAP Policy Manual, January 2022, section 1200.2, page 62 + href: https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=62 diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml new file mode 100644 index 00000000000..8ea3d187c7d --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml @@ -0,0 +1,10 @@ +description: Georgia disregards this amount of monthly interest income under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 25 +metadata: + unit: currency-USD + period: month + label: Georgia LIHEAP monthly interest disregard + reference: + - title: Georgia LIHEAP Policy Manual, January 2022, section 1200.4, pages 66 and 69 (first interest amount disregarded) + href: https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=66 diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml new file mode 100644 index 00000000000..3f13a65e5da --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml @@ -0,0 +1,11 @@ +description: Georgia counts these household benefits once under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: + - ga_tanf +metadata: + unit: list + period: year + label: Georgia LIHEAP household income sources + reference: + - title: Georgia FY2026 LIHEAP State Plan, section 1.9, page 6 (TANF counted) + href: https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml new file mode 100644 index 00000000000..45a1f6f3874 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml @@ -0,0 +1,35 @@ +description: Georgia counts these income sources of adult household members under the Low Income Home Energy Assistance Program. +# Interest is calculated separately after its disregard. State TANF is counted +# once at SPM-unit level. Signed net business inputs have no extra deduction. +values: + 2025-10-01: + - employment_income + - self_employment_income + - sstb_self_employment_income + - farm_operations_income + - partnership_s_corp_income + - unemployment_compensation + - social_security + - ssi + - pension_income + - survivor_benefits + - veterans_benefits + - disability_benefits + - workers_compensation + - strike_benefits + - general_assistance + - rental_income + - farm_rent_income + - ordinary_dividend_income + - capital_gains + - alimony_income + - child_support_received +metadata: + unit: list + period: year + label: Georgia LIHEAP adult person income sources + reference: + - title: Georgia LIHEAP Policy Manual, January 2022, sections 1200.2-1200.4, pages 62-69 + href: https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=62 + - title: Georgia FY2026 LIHEAP State Plan, section 1.9, pages 5-6 (current included and excluded sources) + href: https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/base_amount.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/base_amount.yaml new file mode 100644 index 00000000000..815969b9e0d --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/base_amount.yaml @@ -0,0 +1,19 @@ +description: Georgia provides this Level II heating payment by primary fuel under the Low Income Home Energy Assistance Program. +electricity: + 2025-10-01: 400 +natural_gas: + 2025-10-01: 560 +fuel_oil: + 2025-10-01: 710 +propane: + 2025-10-01: 580 +wood: + 2025-10-01: 400 +metadata: + unit: currency-USD + period: year + label: Georgia LIHEAP Level II heating payment by fuel + propagate_metadata_to_children: true + reference: + - title: Georgia Benefit Matrix, FY2026 income guidelines, step 2 (heating amounts) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/GA_BenefitMatrix_Heat-Cool_2026.pdf diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_limit.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_limit.yaml new file mode 100644 index 00000000000..b4cb7419af4 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_limit.yaml @@ -0,0 +1,45 @@ +description: Georgia limits annual income to this amount by household size for Level I heating payments under the Low Income Home Energy Assistance Program. +# Preserve the published size-four amount. The source does not define these +# cutoffs as a fixed percentage of current federal poverty or median income. +1: + 2025-10-01: 13_589 +2: + 2025-10-01: 17_770 +3: + 2025-10-01: 21_952 +4: + 2025-10-01: 25_469 +5: + 2025-10-01: 30_315 +6: + 2025-10-01: 34_496 +7: + 2025-10-01: 35_280 +8: + 2025-10-01: 36_064 +9: + 2025-10-01: 36_848 +10: + 2025-10-01: 37_632 +11: + 2025-10-01: 38_416 +12: + 2025-10-01: 39_200 +13: + 2025-10-01: 39_984 +14: + 2025-10-01: 40_768 +15: + 2025-10-01: 41_552 +16: + 2025-10-01: 42_336 +metadata: + unit: currency-USD + period: year + label: Georgia LIHEAP Level I annual income limit by household size + breakdown: + - range(1, 17) + propagate_metadata_to_children: true + reference: + - title: Georgia Benefit Matrix, FY2026 income guidelines, step 1 (inclusive Level I upper limits) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/GA_BenefitMatrix_Heat-Cool_2026.pdf diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_uplift.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_uplift.yaml new file mode 100644 index 00000000000..29aa5c064de --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_uplift.yaml @@ -0,0 +1,10 @@ +description: Georgia increases the heating payment by this amount for households in income Level I under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 100 +metadata: + unit: currency-USD + period: year + label: Georgia LIHEAP lower income heating payment increase + reference: + - title: Georgia Benefit Matrix, FY2026 income guidelines, step 2 (difference between Level I and Level II heating amounts) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/GA_BenefitMatrix_Heat-Cool_2026.pdf diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/maximum_table_size.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/maximum_table_size.yaml new file mode 100644 index 00000000000..0765c12055c --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/maximum_table_size.yaml @@ -0,0 +1,10 @@ +description: Georgia publishes Level I income thresholds through this household size under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 16 +metadata: + unit: person + period: year + label: Georgia LIHEAP maximum published benefit table size + reference: + - title: Georgia Benefit Matrix, FY2026 income guidelines, step 1 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/GA_BenefitMatrix_Heat-Cool_2026.pdf diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 756dc2c4988..6f66feb7ce2 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: GA, OR, DC, Riverside County, MA, IL, KS, MS, TX state_implementations: + - state: GA + status: partial + name: Georgia LIHEAP + full_name: Georgia Low Income Home Energy Assistance Program + variable: ga_liheap + parameter_prefix: gov.states.ga.dfcs.liheap + verified_years: "2026" + notes: FY2026 ordinary regular heating eligibility and fuel payments. Actual categorical recipient membership, assistance-only resources and utility credit balances cannot be identified from existing inputs; those limitations are documented. Historical backfills and FY2027 draft changes are unverified. 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/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml new file mode 100644 index 00000000000..21fab097998 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml @@ -0,0 +1,105 @@ +# Georgia ordinary heating route only. Categorical recipient facts, utility credits, +# unpriced fuels and size-above-16 coverage remain documented limitations. + +# FY2026 matrix p1 sets the size1 ordinary ceiling at34549; manual p50 requires income at or below the limit. Real adult wages are aggregated, not overridden countable income. +- name: Case 1, the ordinary one person income ceiling is inclusive. + period: 2026 + input: + state_code: [GA, GA] + age: [40, 40] + immigration_status: [CITIZEN, CITIZEN] + employment_income: [34_549, 34549.01] + ssi: [0, 0] + ga_tanf: [0, 0] + heating_type: [ELECTRICITY, ELECTRICITY] + pre_subsidy_electricity_expense: [1_200, 1_200] + output: + ga_liheap_eligible: [true, false] + +# Manual pp51-52 excludes undocumented members from size; without a qualified adult or child there is no eligible member, even with a positive bill. +- name: Case 2, a household with no qualified member fails ordinary eligibility. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + employment_income: 0 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + ga_tanf: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + households: + household: + members: [person1] + state_code: GA + output: + ga_liheap_eligible: false + +# Manual pp4,57 permits a verified energy burden in subsidized and other rental housing. Positive primary bills establish burden; rent inclusion alone and no explicit obligation do not. +- name: Case 3, heat in rent needs a verified burden with or without housing subsidy. + period: 2026 + input: + state_code: [GA, GA, GA, GA] + age: [40, 40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN, CITIZEN] + employment_income: [12_000, 12_000, 12_000, 12_000] + ssi: [0, 0, 0, 0] + ga_tanf: [0, 0, 0, 0] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_200, 0, 1_200, 0] + heat_expense_included_in_rent: [true, true, true, true] + receives_housing_assistance: [false, false, true, true] + is_in_public_housing: [false, false, true, true] + output: + ga_liheap_eligible: [true, false, true, false] + +# Manual p3 requires responsibility for home heating costs; v1.2 reuses an explicit existing obligation flag. NONE still means no heating; a gas household with an explicit obligation can qualify with no reported bill amount. +- name: Case 4, explicit no heating fails even when an obligation flag is supplied. + period: 2026 + input: + state_code: [GA, GA] + age: [40, 40] + immigration_status: [CITIZEN, CITIZEN] + employment_income: [12_000, 12_000] + ssi: [0, 0] + ga_tanf: [0, 0] + heating_type: [NONE, NATURAL_GAS] + gas_expense: [1_200, 0] + has_heating_expense: [true, true] + output: + ga_liheap_eligible: [false, true] + +# Manual pp3,50 requires heating responsibility or verified burden. With no explicit obligation, gas-primary households use their gas bill: an electric bill alone does not identify gas heating expense. +- name: Case 5, an unrelated utility bill does not establish the selected primary fuel burden. + period: 2026 + input: + state_code: [GA, GA] + age: [40, 40] + immigration_status: [CITIZEN, CITIZEN] + employment_income: [12_000, 12_000] + ssi: [0, 0] + ga_tanf: [0, 0] + heating_type: [NATURAL_GAS, NATURAL_GAS] + gas_expense: [0, 1] + pre_subsidy_electricity_expense: [1_200, 0] + output: + ga_liheap_eligible: [false, true] + +# Manual pp3,50 addresses Georgia resident households; the same real wage and positive primary bill in Alabama must not receive Georgia eligibility. +- name: Case 6, ordinary eligibility is limited to Georgia in mixed state arrays. + period: 2026 + input: + state_code: [GA, AL] + age: [40, 40] + immigration_status: [CITIZEN, CITIZEN] + employment_income: [12_000, 12_000] + ssi: [0, 0] + ga_tanf: [0, 0] + heating_type: [ELECTRICITY, ELECTRICITY] + pre_subsidy_electricity_expense: [1_200, 1_200] + output: + ga_liheap_eligible: [true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.yaml new file mode 100644 index 00000000000..097e48f919c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.yaml @@ -0,0 +1,29 @@ +# Source-derived Georgia independent components; no final eligibility or total award asserted. + +# FY2026 original matrix literal annual limits; all16 are checked independently of the model formula or parameters. +- name: Case 1, all sixteen published annual SMI limits match the reconciled flooring order. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY] + 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] + ga_liheap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + output: + ga_liheap_income_limit: [34_549, 45_179, 55_810, 66_441, 77_071, 87_702, 89_695, 91_688, 93_681, + 95_675, 97_668, 99_661, 101_654, 103_647, 105_641, 107_634] + +# Independent state-defined calculation returns the GA one-person limit only for Georgia; safe batch behavior. +- name: Case 2, mixed state calculation masks the Georgia income limit outside Georgia. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [GA, AL] + heating_type: [ELECTRICITY, ELECTRICITY] + pre_subsidy_electricity_expense: [1_200, 1_200] + ga_liheap_household_size: [1, 1] + output: + ga_liheap_income_limit: [34_549, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml new file mode 100644 index 00000000000..6fd699f8d25 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml @@ -0,0 +1,280 @@ +# Georgia ordinary heating route only. Categorical recipient facts, utility credits, +# unpriced fuels and size-above-16 coverage remain documented limitations. + +# FY2026 matrix p1: size1 income12000 is LevelI and electric pays500. Manual pp4,50 establishes heating burden, not an expense cap; the25 bill must not reduce the scheduled award. +- name: Case 1, a small positive primary bill receives the full fixed heating award. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + employment_income: 12_000 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + ga_tanf: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 25 + households: + household: + members: [person1] + state_code: GA + output: + ga_liheap_household_size: 1 + ga_liheap_income: 12_000 + ga_liheap_income_limit: 34_549 + heating_expense: 25 + ga_liheap_matrix_amount: 500 + ga_liheap_eligible: true + ga_liheap: 500 + +# Manual pp51-52,62: parent income counts fully, parent is excluded from size, and all under18 income is excluded. FY2026 matrix p1 gives the size1 LevelI gas award660. +- name: Case 2, a citizen child qualifies while the nonqualified parent income counts fully. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + employment_income: 12_000 + ssi: 0 + person2: + age: 10 + immigration_status: CITIZEN + employment_income: 6_000 + survivor_benefits: 1_200 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + ga_tanf: 0 + heating_type: NATURAL_GAS + gas_expense: 1_200 + households: + household: + members: [person1, person2] + state_code: GA + output: + ga_liheap_household_size: 1 + ga_liheap_income: 12_000 + ga_liheap_income_limit: 34_549 + ga_liheap_matrix_amount: 660 + ga_liheap_eligible: true + ga_liheap: 660 + +# Manual pp51-52,62,67 counts the full30000 wages plus5000 farm-property rent against qualified size1.35000 exceeds the FY2026 matrix ceiling34549, so ordinary eligibility gates the final grant to zero. +- name: Case 3, full nonqualified adult wages and farm rent can exceed the qualified child ceiling. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + employment_income: 30_000 + farm_rent_income: 5_000 + ssi: 0 + person2: + age: 10 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + ga_tanf: 0 + heating_type: NATURAL_GAS + gas_expense: 1_200 + households: + household: + members: [person1, person2] + state_code: GA + output: + ga_liheap_household_size: 1 + ga_liheap_income: 35_000 + ga_liheap_income_limit: 34_549 + ga_liheap_eligible: false + ga_liheap: 0 + +# FY2026 matrix p1 explicitly gives size4 LevelI ceiling25469, despite its unusual pattern. Two actual four-person families with wages25469 and25469.01 remain below66441 but receive gas660 and560 respectively. +- name: Case 4, real four person families use the printed lower band boundary before the award. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 25_469 + person2: + age: 39 + immigration_status: CITIZEN + ssi: 0 + employment_income: 0 + person3: + age: 10 + immigration_status: CITIZEN + ssi: 0 + employment_income: 0 + person4: + age: 8 + immigration_status: CITIZEN + ssi: 0 + employment_income: 0 + person5: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 25469.01 + person6: + age: 39 + immigration_status: CITIZEN + ssi: 0 + employment_income: 0 + person7: + age: 10 + immigration_status: CITIZEN + ssi: 0 + employment_income: 0 + person8: + age: 8 + immigration_status: CITIZEN + ssi: 0 + employment_income: 0 + tax_units: + tax_unit1: + members: [person1, person2, person3, person4] + tax_unit2: + members: [person5, person6, person7, person8] + spm_units: + spm_unit1: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ga_tanf: 0 + spm_unit2: + members: [person5, person6, person7, person8] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ga_tanf: 0 + households: + household1: + members: [person1, person2, person3, person4] + state_code: GA + household2: + members: [person5, person6, person7, person8] + state_code: GA + output: + ga_liheap_household_size: [4, 4] + ga_liheap_income_limit: [66_441, 66_441] + ga_liheap_eligible: [true, true] + ga_liheap_matrix_amount: [660, 560] + ga_liheap: [660, 560] + +# FY2026 matrix p1: real single-adult wages20000 are above13589 and below34549. LevelII oil710, propane580 and wood400 pass through ordinary eligibility; the Alabama household receives no Georgia award. Existing unit cases already cover the full ten-cell matrix. +- name: Case 5, ordinary upper band awards use the primary fuel and exclude another state. + period: 2026 + input: + state_code: [GA, GA, GA, AL] + age: [40, 40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN, CITIZEN] + employment_income: [20_000, 20_000, 20_000, 20_000] + ssi: [0, 0, 0, 0] + ga_tanf: [0, 0, 0, 0] + heating_type: [FUEL_OIL, PROPANE, WOOD, FUEL_OIL] + fuel_oil_expense: [1_200, 0, 0, 1_200] + bottled_gas_expense: [0, 1_200, 0, 0] + wood_expense: [0, 0, 1_200, 0] + output: + ga_liheap_eligible: [true, true, true, false] + ga_liheap: [710, 580, 400, 0] + +# Manual pp4,57 permits verified energy burden in subsidized heat-in-rent housing. The shared Boolean defaults false for heat in rent, but the reported primary bill100 supplies burden; FY2026 LevelI propane pays680 uncapped. +- name: Case 6, a subsidized renter with verified primary expense can receive the ordinary grant. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + employment_income: 12_000 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + ga_tanf: 0 + heating_type: PROPANE + bottled_gas_expense: 100 + heat_expense_included_in_rent: true + receives_housing_assistance: true + households: + household: + members: [person1] + state_code: GA + is_in_public_housing: true + output: + ga_liheap_household_size: 1 + ga_liheap_income: 12_000 + heating_expense: 100 + has_heating_expense: false + ga_liheap_matrix_amount: 680 + ga_liheap_eligible: true + ga_liheap: 680 + +# Contract retains existing signed net-business inputs and floors combined household income0; no additional business-expense policy is introduced. FY2026 matrix p1 fixes LevelI wood at500, so a negative pre-floor total cannot increase the grant. +- name: Case 7, existing net business losses do not inflate the fixed heating award. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + employment_income: 6_000 + self_employment_income: -18_000 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + ga_tanf: 0 + heating_type: WOOD + wood_expense: 100 + households: + household: + members: [person1] + state_code: GA + output: + ga_liheap_income: 0 + ga_liheap_household_size: 1 + ga_liheap_matrix_amount: 500 + ga_liheap_eligible: true + ga_liheap: 500 + +# Manual p3 requires responsibility for energy costs or verified burden; v1.2 honors the existing explicit has_heating_expense fact even when no paid bill amount is reported. FY2026 matrix p1 gives LevelI oil810 with no spending cap. +- name: Case 8, an explicit heating obligation with a zero reported bill receives the scheduled award. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + employment_income: 12_000 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + ga_tanf: 0 + heating_type: FUEL_OIL + fuel_oil_expense: 0 + has_heating_expense: true + households: + household: + members: [person1] + state_code: GA + output: + ga_liheap_household_size: 1 + ga_liheap_income: 12_000 + heating_expense: 0 + has_heating_expense: true + ga_liheap_matrix_amount: 810 + ga_liheap_eligible: true + ga_liheap: 810 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap_household_size.yaml new file mode 100644 index 00000000000..9cb2d08c61b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap_household_size.yaml @@ -0,0 +1,69 @@ +# Source-derived Georgia independent components; no final eligibility or total award asserted. + +# Manual pp51–52 counts citizen/qualified people including minors; DACA and undocumented members do not count in size. +- name: Case 1, qualified adults and minors count without SNAP waiting bars. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: LEGAL_PERMANENT_RESIDENT + ssi: 0 + person2: + age: 39 + immigration_status: ASYLEE + ssi: 0 + person3: + age: 16 + immigration_status: CITIZEN + ssi: 0 + person4: + age: 19 + 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: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + ga_tanf: 0 + households: + household: + members: [person1, person2, person3, person4, person5] + state_code: GA + hud_utility_allowance: 0 + output: + ga_liheap_household_size: 3 + +# No final eligibility result is asserted; this isolates qualified-member size. +- name: Case 2, a wholly nonqualified household 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: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + ga_tanf: 0 + households: + household: + members: [person1, person2] + state_code: GA + hud_utility_allowance: 0 + output: + ga_liheap_household_size: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml new file mode 100644 index 00000000000..c6f392349b6 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml @@ -0,0 +1,236 @@ +# Source-derived Georgia independent components; no final eligibility or total award asserted. + +# Manual pp65–68: wages and business17400 + ordinary/farmrent2400 + survivors600 + pension1200 + SSA2400 + SSI1200 + gains600 =25800; no duplicated component aggregates. +- name: Case 1, supported independent adult income sources are counted once. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 1_200 + employment_income: 12_000 + self_employment_income: 3_000 + farm_operations_income: 1_200 + sstb_self_employment_income: 600 + partnership_s_corp_income: 600 + rental_income: 1_200 + farm_rent_income: 1_200 + survivor_benefits: 600 + pension_income: 1_200 + social_security: 2_400 + capital_gains: 600 + spm_units: + spm_unit: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + ga_tanf: 0 + households: + household: + members: [person1] + state_code: GA + hud_utility_allowance: 0 + output: + ga_liheap_income: 25_800 + +# Manual p62 excludes ALL under18 income. At18,6000wages+2400SSI+1200SSA+600dividends+300netinterest adds10500 to parent12000. +- name: Case 2, all age 17 income follows the under eighteen exclusion 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 + social_security: 1_200 + ordinary_dividend_income: 600 + interest_income: 600 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + ga_tanf: 0 + households: + household: + members: [person1, person2] + state_code: GA + hud_utility_allowance: 0 + output: + ga_liheap_income: 12_000 + +# Manual p62 excludes ALL under18 income. At18,6000wages+2400SSI+1200SSA+600dividends+300netinterest adds10500 to parent12000. +- name: Case 3, all age 18 income follows the under eighteen exclusion 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 + social_security: 1_200 + ordinary_dividend_income: 600 + interest_income: 600 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + ga_tanf: 0 + households: + household: + members: [person1, person2] + state_code: GA + hud_utility_allowance: 0 + output: + ga_liheap_income: 22_500 + +# Manual pp66,69 and contractv1 household interpretation: combined adultinterest400 minus annual300, plus600dividends =700; no per-person multiplication. +- name: Case 4, interest disregard is shared once across adults while dividends fully count. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + interest_income: 200 + ordinary_dividend_income: 600 + person2: + age: 39 + immigration_status: CITIZEN + ssi: 0 + interest_income: 200 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + ga_tanf: 0 + households: + household: + members: [person1, person2] + state_code: GA + hud_utility_allowance: 0 + output: + ga_liheap_income: 700 + +# Monthly25 allowance annualizes to300; exact threshold is excluded and first dollar above it counts. +- name: Case 5, annual household interest allowance has an exact three hundred dollar boundary. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [GA, GA, GA] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY] + pre_subsidy_electricity_expense: [1_200, 1_200, 1_200] + age: [40, 40, 40] + ssi: [0, 0, 0] + ga_tanf: [0, 0, 0] + interest_income: [299, 300, 301] + output: + ga_liheap_income: [0, 0, 1] + +# Manual p67 TANF grant counts once at SPM level: caregiver wages1200 + annual ga_tanf3600 =4800; minorSSI2400 excluded. +- name: Case 6, caregiver TANF cash counts once despite excluding a minor SSI payment. + 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: 2_400 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + ga_tanf: 3_600 + households: + household: + members: [person1, person2] + state_code: GA + hud_utility_allowance: 0 + output: + ga_liheap_income: 4_800 + +# Contractv1 preserves signed existing net-business inputs and floors the combined household income0; no additional business-expense treatment. +- name: Case 7, existing signed net losses cannot make the household total negative. + 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: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + ga_tanf: 0 + households: + household: + members: [person1] + state_code: GA + hud_utility_allowance: 0 + output: + ga_liheap_income: 0 + +# Manual pp51–52,62: parent income counts in full, citizen child counts in size but all child income is excluded. Matrix1person electric LevelI=500; final eligibility is not asserted. +- name: Case 8, nonqualified adult income counts while the citizen child determines size. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: UNDOCUMENTED + ssi: 0 + employment_income: 12_000 + person2: + age: 8 + immigration_status: CITIZEN + ssi: 2_400 + social_security: 3_600 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + ga_tanf: 0 + households: + household: + members: [person1, person2] + state_code: GA + hud_utility_allowance: 0 + output: + ga_liheap_household_size: 1 + ga_liheap_income: 12_000 + ga_liheap_income_limit: 34_549 + ga_liheap_matrix_amount: 500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml new file mode 100644 index 00000000000..6bbd10da1e0 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml @@ -0,0 +1,75 @@ +# Source-derived Georgia independent components; no final eligibility or total award asserted. + +# Original matrix lower-income cutoff and first cent above for each size1–16; preserve anomalous size4=25469 exactly. +- name: Case 1, all sixteen published lower level upper bounds remain inclusive. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, + GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, + NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, + NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, + NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, + NATURAL_GAS] + 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] + 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] + ga_liheap_household_size: [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13, 14, 14, 15, 15, 16, 16] + ga_liheap_income: [13_589, 13589.01, 17_770, 17770.01, 21_952, 21952.01, 25_469, 25469.01, 30_315, + 30315.01, 34_496, 34496.01, 35_280, 35280.01, 36_064, 36064.01, 36_848, 36848.01, 37_632, 37632.01, + 38_416, 38416.01, 39_200, 39200.01, 39_984, 39984.01, 40_768, 40768.01, 41_552, 41552.01, 42_336, + 42336.01] + output: + ga_liheap_matrix_amount: [660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, + 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560] + +# Original matrix heating column only: LevelI500/660/810/680/500; LevelII400/560/710/580/400. Cooling column is not an oracle. +- name: Case 2, all five priced fuels match both heating benefit levels including zero income. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [GA, GA, GA, GA, GA, GA, GA, GA, GA, GA] + heating_type: [ELECTRICITY, NATURAL_GAS, FUEL_OIL, PROPANE, WOOD, ELECTRICITY, NATURAL_GAS, FUEL_OIL, + PROPANE, WOOD] + pre_subsidy_electricity_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, + 1_200] + gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200] + fuel_oil_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200] + bottled_gas_expense: [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] + ga_liheap_household_size: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ga_liheap_income: [0, 0, 0, 0, 0, 13589.01, 13589.01, 13589.01, 13589.01, 13589.01] + output: + ga_liheap_matrix_amount: [500, 660, 810, 680, 500, 400, 560, 710, 580, 400] + +# Ungated regular heating matrix gives660 for natural gas; no actual-expense cap is stated. +- name: Case 3, a small heating bill does not cap the fixed matrix amount. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: GA + heating_type: NATURAL_GAS + pre_subsidy_electricity_expense: 1_200 + gas_expense: 50 + ga_liheap_household_size: 1 + ga_liheap_income: 0 + output: + ga_liheap_matrix_amount: 660 + +# NONE affirmatively means no heating. No assertions here establish legal ineligibility for unpriced fuel or out-of-table size. +- name: Case 4, explicit no heating has no supported matrix amount. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: GA + heating_type: NONE + pre_subsidy_electricity_expense: 1_200 + ga_liheap_household_size: 1 + ga_liheap_income: 0 + output: + ga_liheap_matrix_amount: 0 diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py new file mode 100644 index 00000000000..18cb1ebbcd0 --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py @@ -0,0 +1,37 @@ +from policyengine_us.model_api import * + + +class ga_liheap_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Georgia LIHEAP ordinary eligibility" + defined_for = StateCode.GA + # State plan pages 5, 8, 9; manual pages 3, 50, 56, 57, 75. + reference = ( + "https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=5", + "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=3", + ) + + def formula(spm_unit, period, parameters): + size = spm_unit("ga_liheap_household_size", period) + income = spm_unit("ga_liheap_income", period) + limit = spm_unit("ga_liheap_income_limit", period) + heating_type = spm_unit("heating_type", period) + heating_expense = spm_unit("heating_expense", period) + # Explicit heating responsibility can qualify with no current bill. + # A positive primary bill also establishes burden with heat in rent, + # including subsidized housing. The rent flag alone is insufficient. + energy_burden = spm_unit("has_heating_expense", period) | (heating_expense > 0) + # Pure-assistance categorical eligibility requires actual receipt by + # every member and assistance-only income/resources. Aggregate TANF/SNAP + # inputs cannot establish those facts, so this uses the ordinary test. + # Account credits of $1,000+ and administrative household exclusions + # also need unavailable inputs; the selected heating account is assumed + # to satisfy those rules. Annual expense is not an account-credit proxy. + return ( + (size > 0) + & (income <= limit) + & energy_burden + & (heating_type != heating_type.possible_values.NONE) + ) diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.py new file mode 100644 index 00000000000..fd65d704f61 --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.hhs_smi import smi + + +class ga_liheap_income_limit(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Georgia LIHEAP annual income limit" + defined_for = StateCode.GA + reference = ( + "https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=8", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/GA_BenefitMatrix_Heat-Cool_2026.pdf", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ga.dfcs.liheap.eligibility + size = spm_unit("ga_liheap_household_size", period) + state = spm_unit.household("state_code_str", period) + four_person_smi = parameters(period).gov.hhs.smi.amount[state] + size_share = smi(size, state, period, parameters) / four_person_smi + # Flooring 60% of the four-person SMI before applying the federal size + # factor reproduces all 16 FY2026 published limits. This is a numerical + # reconciliation; the sources do not expressly specify the floor order. + four_person_limit = np.floor(four_person_smi * p.smi_rate) + return where(size > 0, np.floor(four_person_limit * size_share), 0) diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap.py new file mode 100644 index 00000000000..3b9f608070a --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap.py @@ -0,0 +1,17 @@ +from policyengine_us.model_api import * + + +class ga_liheap(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Georgia LIHEAP ordinary heating benefit" + defined_for = "ga_liheap_eligible" + reference = ( + "https://liheapch.acf.gov/docs/2026/benefits-matricies/GA_BenefitMatrix_Heat-Cool_2026.pdf", + "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=4", + ) + # The ordinary grant is a fixed schedule amount with no bill cap. Unpriced + # fuels and the matrix's size-above-16 approximation remain unverified. + adds = ["ga_liheap_matrix_amount"] diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py new file mode 100644 index 00000000000..bae87678c05 --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py @@ -0,0 +1,16 @@ +from policyengine_us.model_api import * + + +class ga_liheap_household_size(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "Georgia LIHEAP household size" + defined_for = StateCode.GA + # Pages 51-52 exclude nonqualified members from size; page 62 counts income. + reference = "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=51" + + # The SPM unit approximates the economic unit purchasing energy together. + # Existing inputs do not identify all roomers, boarders, foster-child + # elections, or excluded group-quarter arrangements. + adds = ["is_citizen_or_legal_immigrant"] diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py new file mode 100644 index 00000000000..9bd6b139546 --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py @@ -0,0 +1,44 @@ +from policyengine_us.model_api import * + + +class ga_liheap_income(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Georgia LIHEAP annual countable household income" + defined_for = StateCode.GA + reference = ( + # Manual pages 62-69; current plan pages 5-6 govern listed exclusions. + "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=62", + "https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=5", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ga.dfcs.liheap.income + person = spm_unit.members + adult = person("age", period) >= p.minimum_age + # Annual inputs approximate income in the application period. Georgia + # excludes all income of under-18s, not only their earnings, and counts + # otherwise countable income of nonqualified adults in full. + person_income = add(person, period, p.sources.person, options=[ADD]) + adult_interest = spm_unit.sum(person("interest_income", period) * adult) + # The manual excludes the first $25 of monthly interest without saying + # this allowance repeats per person or account. Apply it once to the + # household's adult interest; dividends remain fully countable. + interest = max_( + adult_interest - p.monthly_interest_disregard * MONTHS_IN_YEAR, + 0, + ) + # State TANF is included once at SPM level as a caregiver grant rather + # than projecting the household award onto every member. + household_income = add(spm_unit, period, p.sources.household, options=[ADD]) + # Keep existing signed net business inputs without another deduction; + # floor the combined total. Rental income is assumed nonnegative. + # Roomer/boarder expense allowances, Medicare premiums, mortgage-sale + # contracts, stipends, and recurring support lack the scoped handling. + # Current plan exclusions govern cash gifts and lump-sum receipts. + return max_( + spm_unit.sum(person_income * adult) + interest + household_income, + 0, + ) diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py new file mode 100644 index 00000000000..26dab672570 --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py @@ -0,0 +1,43 @@ +from policyengine_us.model_api import * + + +class ga_liheap_matrix_amount(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Georgia LIHEAP heating matrix amount" + defined_for = StateCode.GA + reference = "https://liheapch.acf.gov/docs/2026/benefits-matricies/GA_BenefitMatrix_Heat-Cool_2026.pdf" + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ga.dfcs.liheap.payment + size = spm_unit("ga_liheap_household_size", period) + income = spm_unit("ga_liheap_income", period) + # The published lower-income thresholds end at size 16. Capping this + # lookup is provisional for larger units, not verified policy coverage. + lookup_size = clip(size, 1, p.maximum_table_size) + lower_income = income <= p.lower_income_limit[lookup_size] + heating_type = spm_unit("heating_type", period) + fuel = heating_type.possible_values + base = select( + [ + heating_type == fuel.ELECTRICITY, + heating_type == fuel.NATURAL_GAS, + heating_type == fuel.FUEL_OIL, + heating_type == fuel.PROPANE, + heating_type == fuel.WOOD, + ], + [ + p.base_amount.electricity, + p.base_amount.natural_gas, + p.base_amount.fuel_oil, + p.base_amount.propane, + p.base_amount.wood, + ], + default=0, + ) + # This is an ungated schedule amount with no cap at the reported bill. + # Unpriced kerosene, coal, solar, other, and unspecified fuels return an + # incomplete zero estimate; this is not a legal ineligibility finding. + return where(base > 0, base + lower_income * p.lower_income_uplift, 0) From 35a47c84442444ba82593175691a3ead97dda77a Mon Sep 17 00:00:00 2001 From: Ziming Date: Sat, 3 Oct 2026 21:51:38 -0400 Subject: [PATCH 02/10] Fix Georgia LIHEAP bundled-utility rental payments --- .../income/monthly_interest_disregard.yaml | 2 +- .../eligibility/ga_liheap_eligible.yaml | 26 +++++++- .../gov/states/ga/dfcs/liheap/ga_liheap.yaml | 63 +++++++++++++++++++ .../payment/ga_liheap_matrix_amount.yaml | 19 ++++++ .../liheap/eligibility/_ga_liheap_housing.py | 17 +++++ .../liheap/eligibility/ga_liheap_eligible.py | 11 +++- .../liheap/payment/ga_liheap_matrix_amount.py | 36 ++++++++--- 7 files changed, 160 insertions(+), 14 deletions(-) create mode 100644 policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/_ga_liheap_housing.py diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml index 8ea3d187c7d..e37c144d984 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml @@ -6,5 +6,5 @@ metadata: period: month label: Georgia LIHEAP monthly interest disregard reference: - - title: Georgia LIHEAP Policy Manual, January 2022, section 1200.4, pages 66 and 69 (first interest amount disregarded) + - title: Georgia LIHEAP Policy Manual, January 2022, section 1200.7, pages 66 and 69 (first interest amount disregarded) href: https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=66 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml index 21fab097998..ea4c7ee57fb 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml @@ -39,7 +39,7 @@ output: ga_liheap_eligible: false -# Manual pp4,57 permits a verified energy burden in subsidized and other rental housing. Positive primary bills establish burden; rent inclusion alone and no explicit obligation do not. +# Manual pp4,57 permits a verified energy burden in subsidized and other rental housing. These tenants pay their own utilities by default; positive primary bills establish burden, while the heat-in-rent flag alone does not. - name: Case 3, heat in rent needs a verified burden with or without housing subsidy. period: 2026 input: @@ -103,3 +103,27 @@ pre_subsidy_electricity_expense: [1_200, 1_200] output: ga_liheap_eligible: [true, false] + +# Manual p89 recognizes all utilities included in unsubsidized rent as an energy burden. +# Each column changes one requirement from the first qualifying rental household: +# separately paid utilities, no rent, housing assistance, public housing, no qualified +# member, excess income, no heating, or heat excluded from rent. Ordinary gates remain. +- name: Case 7, bundled utilities in unsubsidized rent qualify subject to ordinary eligibility. + period: 2026 + input: + state_code: [GA, GA, GA, GA, GA, GA, GA, GA, GA] + age: [40, 40, 40, 40, 40, 40, 40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN, CITIZEN, CITIZEN, UNDOCUMENTED, CITIZEN, CITIZEN, CITIZEN] + employment_income: [12_000, 12_000, 12_000, 12_000, 12_000, 12_000, 34549.01, 12_000, 12_000] + ssi: [0, 0, 0, 0, 0, 0, 0, 0, 0] + ga_tanf: [0, 0, 0, 0, 0, 0, 0, 0, 0] + rent: [12_000, 12_000, 0, 12_000, 12_000, 12_000, 12_000, 12_000, 12_000] + tenant_pays_utilities: [false, true, false, false, false, false, false, false, false] + receives_housing_assistance: [false, false, false, true, false, false, false, false, false] + is_in_public_housing: [false, false, false, false, true, false, false, false, false] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NONE, NATURAL_GAS] + gas_expense: [0, 0, 0, 0, 0, 0, 0, 0, 0] + pre_subsidy_electricity_expense: [0, 0, 0, 0, 0, 0, 0, 0, 0] + heat_expense_included_in_rent: [true, true, true, true, true, true, true, true, false] + output: + ga_liheap_eligible: [true, false, false, false, false, false, false, false, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml index 6fd699f8d25..7e2368109c8 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml @@ -278,3 +278,66 @@ ga_liheap_matrix_amount: 810 ga_liheap_eligible: true ga_liheap: 810 + +# Manual p89 recognizes an energy burden when all utilities are included in +# unsubsidized rent. FY2026 plan p30 requires the lowest award within the income +# level for payment to the applicant:500 for12000 income and400 for15000 income. +# Both are below the ordinary size1 ceiling34549 and have no separately paid bill. +- name: Case 9, unsubsidized renters with all utilities bundled receive each income level minimum. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + employment_income: 12_000 + rent: 12_000 + ssi: 0 + person2: + age: 40 + immigration_status: CITIZEN + employment_income: 15_000 + rent: 12_000 + ssi: 0 + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2] + spm_units: + spm_unit1: + members: [person1] + ga_tanf: 0 + heating_type: NATURAL_GAS + heat_expense_included_in_rent: true + receives_housing_assistance: false + gas_expense: 0 + pre_subsidy_electricity_expense: 0 + spm_unit2: + members: [person2] + ga_tanf: 0 + heating_type: NATURAL_GAS + heat_expense_included_in_rent: true + receives_housing_assistance: false + gas_expense: 0 + pre_subsidy_electricity_expense: 0 + households: + household1: + members: [person1] + state_code: GA + is_in_public_housing: false + tenant_pays_utilities: false + household2: + members: [person2] + state_code: GA + is_in_public_housing: false + tenant_pays_utilities: false + output: + ga_liheap_household_size: [1, 1] + ga_liheap_income: [12_000, 15_000] + ga_liheap_income_limit: [34_549, 34_549] + heating_expense: [0, 0] + has_heating_expense: [false, false] + ga_liheap_matrix_amount: [500, 400] + ga_liheap_eligible: [true, true] + ga_liheap: [500, 400] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml index 6bbd10da1e0..3323c2a6683 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml @@ -73,3 +73,22 @@ ga_liheap_income: 0 output: ga_liheap_matrix_amount: 0 + +# FY2026 plan p30 assigns direct applicant payments the lowest award within the +# income level. The matrix gives500 through13589 and400 above it for size1. +# A separately paid utility retains the ordinary gas award660; NONE remains zero. +- name: Case 5, direct rental payments use the income level minimum and preserve no heating. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [GA, GA, GA, GA] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NONE] + ga_liheap_household_size: [1, 1, 1, 1] + ga_liheap_income: [13_589, 13589.01, 12_000, 12_000] + rent: [12_000, 12_000, 12_000, 12_000] + heat_expense_included_in_rent: [true, true, true, true] + tenant_pays_utilities: [false, false, true, false] + receives_housing_assistance: [false, false, false, false] + is_in_public_housing: [false, false, false, false] + output: + ga_liheap_matrix_amount: [500, 400, 660, 0] diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/_ga_liheap_housing.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/_ga_liheap_housing.py new file mode 100644 index 00000000000..4c3e68b231d --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/_ga_liheap_housing.py @@ -0,0 +1,17 @@ +from policyengine_us.model_api import add + + +def ga_liheap_has_direct_payment_route(spm_unit, period): + """Identify unsubsidized rent that includes all utilities. + + Georgia's manual energy-burden table (physical page 89) permits payment + directly to these renters. The rental agreement is assumed verified. + https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=89 + """ + return ( + spm_unit("heat_expense_included_in_rent", period) + & ~spm_unit.household("tenant_pays_utilities", period) + & (add(spm_unit, period, ["rent"]) > 0) + & ~spm_unit("receives_housing_assistance", period) + & ~spm_unit.household("is_in_public_housing", period) + ) diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py index 18cb1ebbcd0..008955b7070 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.ga.dfcs.liheap.eligibility._ga_liheap_housing import ( + ga_liheap_has_direct_payment_route, +) class ga_liheap_eligible(Variable): @@ -7,7 +10,7 @@ class ga_liheap_eligible(Variable): definition_period = YEAR label = "Georgia LIHEAP ordinary eligibility" defined_for = StateCode.GA - # State plan pages 5, 8, 9; manual pages 3, 50, 56, 57, 75. + # State plan pages 5, 8, 9, 30; manual pages 3, 50, 56, 57, 75, 89. reference = ( "https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=5", "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=3", @@ -22,7 +25,11 @@ def formula(spm_unit, period, parameters): # Explicit heating responsibility can qualify with no current bill. # A positive primary bill also establishes burden with heat in rent, # including subsidized housing. The rent flag alone is insufficient. - energy_burden = spm_unit("has_heating_expense", period) | (heating_expense > 0) + energy_burden = ( + spm_unit("has_heating_expense", period) + | (heating_expense > 0) + | ga_liheap_has_direct_payment_route(spm_unit, period) + ) # Pure-assistance categorical eligibility requires actual receipt by # every member and assistance-only income/resources. Aggregate TANF/SNAP # inputs cannot establish those facts, so this uses the ordinary test. diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py index 26dab672570..cc1680657d1 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.ga.dfcs.liheap.eligibility._ga_liheap_housing import ( + ga_liheap_has_direct_payment_route, +) class ga_liheap_matrix_amount(Variable): @@ -8,7 +11,11 @@ class ga_liheap_matrix_amount(Variable): unit = USD label = "Georgia LIHEAP heating matrix amount" defined_for = StateCode.GA - reference = "https://liheapch.acf.gov/docs/2026/benefits-matricies/GA_BenefitMatrix_Heat-Cool_2026.pdf" + reference = ( + "https://liheapch.acf.gov/docs/2026/benefits-matricies/GA_BenefitMatrix_Heat-Cool_2026.pdf", + "https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=30", + "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=89", + ) def formula(spm_unit, period, parameters): p = parameters(period).gov.states.ga.dfcs.liheap.payment @@ -20,6 +27,13 @@ def formula(spm_unit, period, parameters): lower_income = income <= p.lower_income_limit[lookup_size] heating_type = spm_unit("heating_type", period) fuel = heating_type.possible_values + base_amounts = [ + p.base_amount.electricity, + p.base_amount.natural_gas, + p.base_amount.fuel_oil, + p.base_amount.propane, + p.base_amount.wood, + ] base = select( [ heating_type == fuel.ELECTRICITY, @@ -28,16 +42,18 @@ def formula(spm_unit, period, parameters): heating_type == fuel.PROPANE, heating_type == fuel.WOOD, ], - [ - p.base_amount.electricity, - p.base_amount.natural_gas, - p.base_amount.fuel_oil, - p.base_amount.propane, - p.base_amount.wood, - ], + base_amounts, default=0, ) + # Applicant payments use the lowest award in the income level. + direct_payment = ga_liheap_has_direct_payment_route(spm_unit, period) + base = where( + direct_payment & (heating_type != fuel.NONE), + np.minimum.reduce(base_amounts), + base, + ) # This is an ungated schedule amount with no cap at the reported bill. - # Unpriced kerosene, coal, solar, other, and unspecified fuels return an - # incomplete zero estimate; this is not a legal ineligibility finding. + # Ordinary vendor payments for unpriced kerosene, coal, solar, other, + # and unspecified fuels return an incomplete zero estimate; this is + # not a legal ineligibility finding. return where(base > 0, base + lower_income * p.lower_income_uplift, 0) From f6f2dadc5e58fbd3bdf86eae8e10f4bf7035a889 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sun, 4 Oct 2026 20:17:39 -0400 Subject: [PATCH 03/10] Clarify GA LIHEAP coverage limitations --- policyengine_us/programs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 6f66feb7ce2..50ffca7f055 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -811,7 +811,7 @@ programs: variable: ga_liheap parameter_prefix: gov.states.ga.dfcs.liheap verified_years: "2026" - notes: FY2026 ordinary regular heating eligibility and fuel payments. Actual categorical recipient membership, assistance-only resources and utility credit balances cannot be identified from existing inputs; those limitations are documented. Historical backfills and FY2027 draft changes are unverified. Regular heating only; crisis, cooling, weatherization and equipment assistance are excluded. + notes: FY2026 ordinary regular heating eligibility and published fuel payments. Unlisted fuel payments and the Level I cutoff above household size 16 remain unverified; zero for an unlisted fuel is not a legal denial. Actual categorical recipient membership, assistance-only resources and utility credit balances use documented existing-input approximations. Historical backfills and FY2027 draft changes are unverified. Regular heating only; crisis, cooling, weatherization and equipment assistance are excluded. - state: OR status: in_progress name: LIHEAP From 2f108f7a1960cdf6367b5e284025c8df71d88a06 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 6 Oct 2026 17:26:20 -0400 Subject: [PATCH 04/10] Fix issues from review: FY2025 cutoffs, heat-in-rent electric route, income - Add the published FY2025 Level I income cutoffs for sizes 1-16. - Make renters whose heat is in rent and who are billed for electric eligible (manual p89 row 3; p4 for subsidized renters with the bill in their name), paid the matrix amount for the main heating fuel. - Count Social Security net of the Medicare Part B premium; count military and railroad retirement; floor each income source at zero; read take-up-gated tanf instead of ga_tanf. - Shorten the registry note; set verified_years to 2025-2026. - Add period-2025, heat-in-rent, income-source and branch cases; tidy test comments. Co-Authored-By: Claude Opus 5.5 --- .../dfcs/liheap/income/sources/household.yaml | 4 +- .../ga/dfcs/liheap/income/sources/person.yaml | 18 +- .../liheap/payment/lower_income_limit.yaml | 18 + policyengine_us/programs.yaml | 6 +- .../eligibility/ga_liheap_eligible.yaml | 54 ++- .../eligibility/ga_liheap_income_limit.yaml | 22 +- .../gov/states/ga/dfcs/liheap/ga_liheap.yaml | 316 +++++++++++++++++- .../dfcs/liheap/ga_liheap_household_size.yaml | 3 +- .../dfcs/liheap/income/ga_liheap_income.yaml | 171 +++++++++- .../payment/ga_liheap_matrix_amount.yaml | 51 ++- .../liheap/eligibility/_ga_liheap_housing.py | 24 ++ .../liheap/eligibility/ga_liheap_eligible.py | 9 +- .../ga/dfcs/liheap/income/ga_liheap_income.py | 37 +- .../liheap/payment/ga_liheap_matrix_amount.py | 6 +- 14 files changed, 674 insertions(+), 65 deletions(-) diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml index 3f13a65e5da..edc8807616d 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml @@ -1,7 +1,9 @@ description: Georgia counts these household benefits once under the Low Income Home Energy Assistance Program. values: 2025-10-01: - - ga_tanf + # The tanf aggregate includes ga_tanf and applies take-up; ga_tanf alone is + # the entitlement and would count benefits a nonrecipient could get. + - tanf metadata: unit: list period: year diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml index 45a1f6f3874..07f2fe9c65a 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml @@ -1,6 +1,8 @@ description: Georgia counts these income sources of adult household members under the Low Income Home Energy Assistance Program. -# Interest is calculated separately after its disregard. State TANF is counted -# once at SPM-unit level. Signed net business inputs have no extra deduction. +# Interest is calculated separately after its disregard. TANF is counted once +# at SPM-unit level. Each source is floored at zero in the formula, so a loss +# in one source cannot offset another; net business inputs have no extra +# deduction. values: 2025-10-01: - employment_income @@ -9,9 +11,17 @@ values: - farm_operations_income - partnership_s_corp_income - unemployment_compensation - - social_security + # Social Security is counted in the formula, net of the Medicare Part B + # premium. - ssi - pension_income + # 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. + - military_retirement_pay + # Manual application item 12 groups Railroad Retirement with Social + # Security; only Social Security is netted of the Medicare premium. + - railroad_benefits - survivor_benefits - veterans_benefits - disability_benefits @@ -29,7 +39,7 @@ metadata: period: year label: Georgia LIHEAP adult person income sources reference: - - title: Georgia LIHEAP Policy Manual, January 2022, sections 1200.2-1200.4, pages 62-69 + - title: Georgia LIHEAP Policy Manual, January 2022, sections 1200.1-1200.7, pages 62-69, and application item 12, page 83 href: https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=62 - title: Georgia FY2026 LIHEAP State Plan, section 1.9, pages 5-6 (current included and excluded sources) href: https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_limit.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_limit.yaml index b4cb7419af4..391374081a6 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_limit.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_limit.yaml @@ -2,36 +2,52 @@ description: Georgia limits annual income to this amount by household size for L # Preserve the published size-four amount. The source does not define these # cutoffs as a fixed percentage of current federal poverty or median income. 1: + 2024-10-01: 12_995 2025-10-01: 13_589 2: + 2024-10-01: 16_993 2025-10-01: 17_770 3: + 2024-10-01: 20_992 2025-10-01: 21_952 4: + 2024-10-01: 24_355 2025-10-01: 25_469 5: + 2024-10-01: 28_989 2025-10-01: 30_315 6: + 2024-10-01: 32_987 2025-10-01: 34_496 7: + 2024-10-01: 33_737 2025-10-01: 35_280 8: + 2024-10-01: 34_487 2025-10-01: 36_064 9: + 2024-10-01: 35_236 2025-10-01: 36_848 10: + 2024-10-01: 35_986 2025-10-01: 37_632 11: + 2024-10-01: 36_736 2025-10-01: 38_416 12: + 2024-10-01: 37_486 2025-10-01: 39_200 13: + 2024-10-01: 38_235 2025-10-01: 39_984 14: + 2024-10-01: 38_985 2025-10-01: 40_768 15: + 2024-10-01: 39_735 2025-10-01: 41_552 16: + 2024-10-01: 40_485 2025-10-01: 42_336 metadata: unit: currency-USD @@ -41,5 +57,7 @@ metadata: - range(1, 17) propagate_metadata_to_children: true reference: + - title: Georgia Benefit Matrix, FY2025 income guidelines, step 1 (inclusive Level I upper limits) + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/GA_BenefitMatrix_2025.pdf - title: Georgia Benefit Matrix, FY2026 income guidelines, step 1 (inclusive Level I upper limits) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/GA_BenefitMatrix_Heat-Cool_2026.pdf diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 50ffca7f055..b7b9273d06c 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -802,7 +802,7 @@ programs: category: Benefits agency: HHS status: partial - coverage: GA, OR, DC, Riverside County, MA, IL, KS, MS, TX + coverage: OR, DC, GA, Riverside County, MA, IL, KS, MS, TX state_implementations: - state: GA status: partial @@ -810,8 +810,8 @@ programs: full_name: Georgia Low Income Home Energy Assistance Program variable: ga_liheap parameter_prefix: gov.states.ga.dfcs.liheap - verified_years: "2026" - notes: FY2026 ordinary regular heating eligibility and published fuel payments. Unlisted fuel payments and the Level I cutoff above household size 16 remain unverified; zero for an unlisted fuel is not a legal denial. Actual categorical recipient membership, assistance-only resources and utility credit balances use documented existing-input approximations. Historical backfills and FY2027 draft changes are unverified. Regular heating only; crisis, cooling, weatherization and equipment assistance are excluded. + verified_years: "2025-2026" + notes: FY2025-FY2026 regular heating only, with the published fuel matrix and the bundled-utility rental payment. 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/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml index ea4c7ee57fb..3965493457e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml @@ -1,7 +1,9 @@ # Georgia ordinary heating route only. Categorical recipient facts, utility credits, # unpriced fuels and size-above-16 coverage remain documented limitations. -# FY2026 matrix p1 sets the size1 ordinary ceiling at34549; manual p50 requires income at or below the limit. Real adult wages are aggregated, not overridden countable income. +# FY2026 matrix page 1 sets the size 1 ordinary ceiling at $34,549; manual page 50 +# requires income at or below the limit. Adult wages are aggregated rather than +# overriding countable income. - name: Case 1, the ordinary one person income ceiling is inclusive. period: 2026 input: @@ -16,7 +18,8 @@ output: ga_liheap_eligible: [true, false] -# Manual pp51-52 excludes undocumented members from size; without a qualified adult or child there is no eligible member, even with a positive bill. +# Manual pages 51-52 exclude undocumented members from size; without a qualified adult +# or child there is no eligible member, even with a positive bill. - name: Case 2, a household with no qualified member fails ordinary eligibility. period: 2026 input: @@ -39,7 +42,9 @@ output: ga_liheap_eligible: false -# Manual pp4,57 permits a verified energy burden in subsidized and other rental housing. These tenants pay their own utilities by default; positive primary bills establish burden, while the heat-in-rent flag alone does not. +# Manual pages 4 and 57 permit a verified energy burden in subsidized and other rental +# housing. These tenants pay their own utilities by default; positive primary bills +# establish burden, while the heat-in-rent flag alone does not. - name: Case 3, heat in rent needs a verified burden with or without housing subsidy. period: 2026 input: @@ -57,7 +62,9 @@ output: ga_liheap_eligible: [true, false, true, false] -# Manual p3 requires responsibility for home heating costs; v1.2 reuses an explicit existing obligation flag. NONE still means no heating; a gas household with an explicit obligation can qualify with no reported bill amount. +# Manual page 3 requires responsibility for home heating costs; the model reuses the +# existing explicit has_heating_expense input. NONE still means no heating; a natural +# gas household with an explicit obligation can qualify with no reported bill amount. - name: Case 4, explicit no heating fails even when an obligation flag is supplied. period: 2026 input: @@ -73,7 +80,9 @@ output: ga_liheap_eligible: [false, true] -# Manual pp3,50 requires heating responsibility or verified burden. With no explicit obligation, gas-primary households use their gas bill: an electric bill alone does not identify gas heating expense. +# Manual pages 3 and 50 require heating responsibility or a verified burden. With no +# explicit obligation, natural gas households use their gas bill: an electric bill alone +# does not establish a natural gas heating expense. - name: Case 5, an unrelated utility bill does not establish the selected primary fuel burden. period: 2026 input: @@ -89,7 +98,8 @@ output: ga_liheap_eligible: [false, true] -# Manual pp3,50 addresses Georgia resident households; the same real wage and positive primary bill in Alabama must not receive Georgia eligibility. +# Manual pages 3 and 50 address Georgia resident households; the same wages and +# positive primary bill in Alabama do not receive Georgia eligibility. - name: Case 6, ordinary eligibility is limited to Georgia in mixed state arrays. period: 2026 input: @@ -104,7 +114,7 @@ output: ga_liheap_eligible: [true, false] -# Manual p89 recognizes all utilities included in unsubsidized rent as an energy burden. +# Manual page 89 recognizes all utilities included in unsubsidized rent as an energy burden. # Each column changes one requirement from the first qualifying rental household: # separately paid utilities, no rent, housing assistance, public housing, no qualified # member, excess income, no heating, or heat excluded from rent. Ordinary gates remain. @@ -127,3 +137,33 @@ heat_expense_included_in_rent: [true, true, true, true, true, true, true, true, false] output: ga_liheap_eligible: [true, false, false, false, false, false, false, false, false] + +# Manual page 89, third row, and page 4: a renter with heat in rent who pays rent and +# is billed directly for electric has an energy burden, whether or not the housing is +# subsidized. Each household is one adult with $12,000 of wages (under the $34,549 +# limit) and heat in rent. Unsubsidized rent is $9,600 ($800 a month); the subsidized +# tenant share is $3,000 ($250 a month). +# Households 1-4 have a $1,200 electric bill: unsubsidized with natural gas, +# unsubsidized with UNSPECIFIED fuel, housing assistance, and public housing. All are +# eligible. +# Household 5: housing assistance with no electric bill: no energy burden. +# Household 6: an electric bill but no rent paid: not a renter, so no energy burden. +- name: Case 8, heat in rent renters billed for electric have an energy burden with or without a subsidy. + period: 2026 + input: + state_code: [GA, GA, GA, GA, GA, GA] + age: [40, 40, 40, 40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN, CITIZEN, CITIZEN, CITIZEN] + employment_income: [12_000, 12_000, 12_000, 12_000, 12_000, 12_000] + ssi: [0, 0, 0, 0, 0, 0] + ga_tanf: [0, 0, 0, 0, 0, 0] + rent: [9_600, 9_600, 3_000, 3_000, 3_000, 0] + heating_type: [NATURAL_GAS, UNSPECIFIED, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + heat_expense_included_in_rent: [true, true, true, true, true, true] + tenant_pays_utilities: [true, true, true, true, true, true] + receives_housing_assistance: [false, false, true, false, true, false] + is_in_public_housing: [false, false, false, true, false, false] + gas_expense: [0, 0, 0, 0, 0, 0] + pre_subsidy_electricity_expense: [1_200, 1_200, 1_200, 1_200, 0, 1_200] + output: + ga_liheap_eligible: [true, true, true, true, false, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.yaml index 097e48f919c..524ecaf70a8 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.yaml @@ -1,6 +1,7 @@ # Source-derived Georgia independent components; no final eligibility or total award asserted. -# FY2026 original matrix literal annual limits; all16 are checked independently of the model formula or parameters. +# FY2026 matrix step 1 Level II tops for all 16 sizes, taken from the published table +# rather than from the model formula or parameters. - name: Case 1, all sixteen published annual SMI limits match the reconciled flooring order. period: 2026 absolute_error_margin: 0.01 @@ -16,7 +17,8 @@ ga_liheap_income_limit: [34_549, 45_179, 55_810, 66_441, 77_071, 87_702, 89_695, 91_688, 93_681, 95_675, 97_668, 99_661, 101_654, 103_647, 105_641, 107_634] -# Independent state-defined calculation returns the GA one-person limit only for Georgia; safe batch behavior. +# The Georgia one-person limit is returned only for Georgia, so mixed-state batches +# stay safe. - name: Case 2, mixed state calculation masks the Georgia income limit outside Georgia. period: 2026 absolute_error_margin: 0.01 @@ -27,3 +29,19 @@ ga_liheap_household_size: [1, 1] output: ga_liheap_income_limit: [34_549, 0] + +# FY2025 matrix step 1 Level II tops for sizes 1, 4 and 16: $33,038, $63,535 and +# $102,926. In 2025 the FY2025 four-person SMI of $105,893 applies: +# floor($105,893 x 0.6) = $63,535; size 1: floor($63,535 x 0.52 = $33,038.20) = $33,038; +# size 4: $63,535 x 1.00 = $63,535; size 16: floor($63,535 x 1.62 = $102,926.70) +# = $102,926. +- name: Case 3, the FY2025 annual limits apply in 2025. + period: 2025 + absolute_error_margin: 0.01 + input: + state_code: [GA, GA, GA] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY] + pre_subsidy_electricity_expense: [1_200, 1_200, 1_200] + ga_liheap_household_size: [1, 4, 16] + output: + ga_liheap_income_limit: [33_038, 63_535, 102_926] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml index 7e2368109c8..062aa6fb901 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml @@ -1,7 +1,9 @@ # Georgia ordinary heating route only. Categorical recipient facts, utility credits, # unpriced fuels and size-above-16 coverage remain documented limitations. -# FY2026 matrix p1: size1 income12000 is LevelI and electric pays500. Manual pp4,50 establishes heating burden, not an expense cap; the25 bill must not reduce the scheduled award. +# FY2026 matrix page 1: a size 1 household with $12,000 income is in Level I, and +# electric heat pays $500. Manual pages 4 and 50 establish a heating burden, not an +# expense cap, so the $25 bill does not reduce the scheduled award. - name: Case 1, a small positive primary bill receives the full fixed heating award. period: 2026 input: @@ -30,7 +32,9 @@ ga_liheap_eligible: true ga_liheap: 500 -# Manual pp51-52,62: parent income counts fully, parent is excluded from size, and all under18 income is excluded. FY2026 matrix p1 gives the size1 LevelI gas award660. +# Manual pages 51-52 and 62: the parent's income counts in full, the parent is +# excluded from size, and all income of members under 18 is excluded. FY2026 matrix +# page 1 gives the size 1 Level I natural gas award of $660. - name: Case 2, a citizen child qualifies while the nonqualified parent income counts fully. period: 2026 input: @@ -64,7 +68,9 @@ ga_liheap_eligible: true ga_liheap: 660 -# Manual pp51-52,62,67 counts the full30000 wages plus5000 farm-property rent against qualified size1.35000 exceeds the FY2026 matrix ceiling34549, so ordinary eligibility gates the final grant to zero. +# Manual pages 51-52, 62 and 67: the full $30,000 of wages plus $5,000 of farm rent +# count against qualified size 1. $35,000 exceeds the FY2026 matrix ceiling of +# $34,549, so ordinary eligibility sets the final grant to zero. - name: Case 3, full nonqualified adult wages and farm rent can exceed the qualified child ceiling. period: 2026 input: @@ -96,7 +102,9 @@ ga_liheap_eligible: false ga_liheap: 0 -# FY2026 matrix p1 explicitly gives size4 LevelI ceiling25469, despite its unusual pattern. Two actual four-person families with wages25469 and25469.01 remain below66441 but receive gas660 and560 respectively. +# FY2026 matrix page 1 prints the size 4 Level I ceiling as $25,469, despite its +# unusual pattern. Two four-person families with wages of $25,469 and $25,469.01 are +# both below the $66,441 limit but receive natural gas awards of $660 and $560. - name: Case 4, real four person families use the printed lower band boundary before the award. period: 2026 input: @@ -171,7 +179,10 @@ ga_liheap_matrix_amount: [660, 560] ga_liheap: [660, 560] -# FY2026 matrix p1: real single-adult wages20000 are above13589 and below34549. LevelII oil710, propane580 and wood400 pass through ordinary eligibility; the Alabama household receives no Georgia award. Existing unit cases already cover the full ten-cell matrix. +# FY2026 matrix page 1: single-adult wages of $20,000 are above $13,589 and below +# $34,549. Level II oil ($710), propane ($580) and wood ($400) pass ordinary +# eligibility; the Alabama household receives no Georgia award. Unit cases cover the +# full ten-cell matrix. - name: Case 5, ordinary upper band awards use the primary fuel and exclude another state. period: 2026 input: @@ -189,7 +200,9 @@ ga_liheap_eligible: [true, true, true, false] ga_liheap: [710, 580, 400, 0] -# Manual pp4,57 permits verified energy burden in subsidized heat-in-rent housing. The shared Boolean defaults false for heat in rent, but the reported primary bill100 supplies burden; FY2026 LevelI propane pays680 uncapped. +# Manual pages 4 and 57 permit a verified energy burden in subsidized heat-in-rent +# housing. has_heating_expense is false when heat is in rent, but the reported $100 +# primary bill supplies the burden; FY2026 Level I propane pays $680 with no cap. - name: Case 6, a subsidized renter with verified primary expense can receive the ordinary grant. period: 2026 input: @@ -221,8 +234,11 @@ ga_liheap_eligible: true ga_liheap: 680 -# Contract retains existing signed net-business inputs and floors combined household income0; no additional business-expense policy is introduced. FY2026 matrix p1 fixes LevelI wood at500, so a negative pre-floor total cannot increase the grant. -- name: Case 7, existing net business losses do not inflate the fixed heating award. +# Manual sections 1200.6-1200.7 (pages 64-66) net self-employment within the +# business, so each source is floored at zero and the $18,000 self-employment loss +# does not offset the $6,000 of wages: countable income is $6,000. That is at or +# below the $13,589 Level I cutoff, so FY2026 Level I wood pays $500. +- name: Case 7, a self-employment loss does not offset wages or inflate the fixed heating award. period: 2026 input: people: @@ -243,13 +259,15 @@ members: [person1] state_code: GA output: - ga_liheap_income: 0 + ga_liheap_income: 6_000 ga_liheap_household_size: 1 ga_liheap_matrix_amount: 500 ga_liheap_eligible: true ga_liheap: 500 -# Manual p3 requires responsibility for energy costs or verified burden; v1.2 honors the existing explicit has_heating_expense fact even when no paid bill amount is reported. FY2026 matrix p1 gives LevelI oil810 with no spending cap. +# Manual page 3 requires responsibility for energy costs or a verified burden; the +# model honors an explicit has_heating_expense input even when no paid bill amount is +# reported. FY2026 matrix page 1 gives Level I oil $810 with no spending cap. - name: Case 8, an explicit heating obligation with a zero reported bill receives the scheduled award. period: 2026 input: @@ -279,10 +297,10 @@ ga_liheap_eligible: true ga_liheap: 810 -# Manual p89 recognizes an energy burden when all utilities are included in -# unsubsidized rent. FY2026 plan p30 requires the lowest award within the income -# level for payment to the applicant:500 for12000 income and400 for15000 income. -# Both are below the ordinary size1 ceiling34549 and have no separately paid bill. +# Manual page 89 recognizes an energy burden when all utilities are included in +# unsubsidized rent. FY2026 plan page 30 pays an applicant the lowest award within the +# income level: $500 for $12,000 of income and $400 for $15,000 of income. Both are +# below the size 1 ceiling of $34,549 and have no separately paid bill. - name: Case 9, unsubsidized renters with all utilities bundled receive each income level minimum. period: 2026 input: @@ -341,3 +359,273 @@ ga_liheap_matrix_amount: [500, 400] ga_liheap_eligible: [true, true] ga_liheap: [500, 400] + +# FY2025 matrix step 1: size 1 Level I is $0 - $12,995 and Level II is +# $12,996 - $33,038. Deliberate boundary case: $12,995 is the Level I top and $13,000 +# is just above it (the FY2026 cutoff of $13,589 would put both in Level I). +# Limit: floor($105,893 x 0.6) = $63,535; floor($63,535 x 0.52 = $33,038.20) = $33,038. +# Step 2 electric heat pays $500 in Level I and $400 in Level II. +- name: Case 10, the FY2025 Level I cutoff applies in 2025. + period: 2025 + input: + state_code: [GA, GA] + age: [40, 40] + immigration_status: [CITIZEN, CITIZEN] + employment_income: [12_995, 13_000] + ssi: [0, 0] + ga_tanf: [0, 0] + heating_type: [ELECTRICITY, ELECTRICITY] + pre_subsidy_electricity_expense: [1_200, 1_200] + output: + ga_liheap_household_size: [1, 1] + ga_liheap_income: [12_995, 13_000] + ga_liheap_income_limit: [33_038, 33_038] + heating_expense: [1_200, 1_200] + ga_liheap_matrix_amount: [500, 400] + ga_liheap_eligible: [true, true] + ga_liheap: [500, 400] + +# FY2025 and FY2026 plans, section 1.9 (page 6), count Social Security "Excluding +# MediCare deduction", and manual section 1200.7 (page 69) excludes the premiums. +# The 2026 Part B premium is pinned as an input: $202.90 x 12 = $2,434.80. +# Countable income: $36,000 - $2,434.80 = $33,565.20. That is at or below the size 1 +# limit of $34,549 (gross Social Security would exceed it) and above the $13,589 +# Level I cutoff, so FY2026 Level II electric pays $400. +- name: Case 11, a retiree's Social Security counts net of the Medicare Part B premium. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: CITIZEN + social_security_retirement: 36_000 + medicare_part_b_premium: 2_434.8 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + ga_tanf: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + households: + household: + members: [person1] + state_code: GA + output: + ga_liheap_household_size: 1 + ga_liheap_income: 33_565.2 + ga_liheap_income_limit: 34_549 + heating_expense: 1_800 + ga_liheap_matrix_amount: 400 + ga_liheap_eligible: true + ga_liheap: 400 + +# FY2026 plan page 6 counts retirement and pension benefits; manual page 66 counts a +# pension as unearned income, and page 83 groups Railroad Retirement with Social +# Security. Household 1: $36,000 of military retirement pay. Household 2: $36,000 of +# railroad retirement, counted gross; only Social Security is netted of the pinned +# $2,434.80 premium. Both incomes of $36,000 exceed the size 1 limit of $34,549, so +# both are ineligible. The ungated matrix amounts are Level II natural gas $560 and +# Level II electric $400. +- name: Case 12, military retirement pay and railroad retirement count as income. + period: 2026 + input: + state_code: [GA, GA] + age: [50, 70] + immigration_status: [CITIZEN, CITIZEN] + military_retirement_pay: [36_000, 0] + railroad_benefits: [0, 36_000] + medicare_part_b_premium: [0, 2_434.8] + ssi: [0, 0] + ga_tanf: [0, 0] + heating_type: [NATURAL_GAS, ELECTRICITY] + gas_expense: [1_200, 0] + pre_subsidy_electricity_expense: [0, 1_500] + output: + ga_liheap_household_size: [1, 1] + ga_liheap_income: [36_000, 36_000] + ga_liheap_income_limit: [34_549, 34_549] + ga_liheap_matrix_amount: [560, 400] + ga_liheap_eligible: [false, false] + ga_liheap: [0, 0] + +# Manual page 65 counts capital gains, not losses, and pages 64-66 net business +# income within each business. Each source is floored at zero before the sources are +# added. Household 1: max($40,000, 0) + max(-$10,000, 0) = $40,000. Household 2: +# max($36,000, 0) + max(-$5,000, 0) = $36,000. Both exceed the size 1 limit of +# $34,549; netting the losses ($30,000 and $31,000) would have paid $400 each. +- name: Case 13, a capital loss or a rental loss does not offset wages. + period: 2026 + input: + state_code: [GA, GA] + age: [40, 40] + immigration_status: [CITIZEN, CITIZEN] + employment_income: [40_000, 36_000] + long_term_capital_gains: [-10_000, 0] + rental_income: [0, -5_000] + ssi: [0, 0] + ga_tanf: [0, 0] + heating_type: [ELECTRICITY, ELECTRICITY] + pre_subsidy_electricity_expense: [1_500, 1_500] + output: + ga_liheap_income: [40_000, 36_000] + ga_liheap_income_limit: [34_549, 34_549] + ga_liheap_matrix_amount: [400, 400] + ga_liheap_eligible: [false, false] + ga_liheap: [0, 0] + +# Manual page 89 gives an energy burden to an unsubsidized renter whose rent includes +# all utilities, and FY2026 plan page 30 pays the applicant the lowest award in the +# income level. The heat-in-rent route does not need the fuel, so an UNSPECIFIED fuel +# still pays: lowest Level II award min($400, $560, $710, $580, $400) = $400, plus +# $100 in Level I. Household 1: $12,000 is at or below $13,589, so $500. +# Household 2: $24,000 is above $13,589, so $400. Household 3 pays its own utilities +# with heat outside rent: UNSPECIFIED has no primary fuel bill (heating_expense is $0), +# so there is no energy burden and no award. +- name: Case 14, bundled utilities in rent pay the income level minimum when the fuel is unspecified. + period: 2026 + input: + state_code: [GA, GA, GA] + age: [40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN] + employment_income: [12_000, 24_000, 12_000] + ssi: [0, 0, 0] + ga_tanf: [0, 0, 0] + rent: [12_000, 12_000, 12_000] + heating_type: [UNSPECIFIED, UNSPECIFIED, UNSPECIFIED] + heat_expense_included_in_rent: [true, true, false] + tenant_pays_utilities: [false, false, true] + receives_housing_assistance: [false, false, false] + is_in_public_housing: [false, false, false] + pre_subsidy_electricity_expense: [0, 0, 1_200] + output: + ga_liheap_income: [12_000, 24_000, 12_000] + heating_expense: [0, 0, 0] + has_heating_expense: [false, false, false] + ga_liheap_matrix_amount: [500, 400, 0] + ga_liheap_eligible: [true, true, false] + ga_liheap: [500, 400, 0] + +# The FY2026 matrix publishes Level I cutoffs through size 16, so a 17-person +# household uses the size 16 cutoff of $42,336. Adult wages: $22,000 + $18,000 = +# $40,000; the 13 children have no income. The eligibility limit uses the federal +# size factor 0.52 + 5 x 0.16 + 11 x 0.03 = 1.65: +# floor($66,441 x 1.65 = $109,627.65) = $109,627. $40,000 is at or below $42,336, so +# Level I natural gas pays $660. +- name: Case 15, a household above the published table size receives the size sixteen award. + period: 2026 + input: + people: + person1: {age: 45, immigration_status: CITIZEN, employment_income: 22_000, ssi: 0} + person2: {age: 43, immigration_status: CITIZEN, employment_income: 18_000, ssi: 0} + person3: {age: 41, immigration_status: CITIZEN, ssi: 0} + person4: {age: 39, immigration_status: CITIZEN, ssi: 0} + person5: {age: 17, immigration_status: CITIZEN, ssi: 0} + person6: {age: 16, immigration_status: CITIZEN, ssi: 0} + person7: {age: 15, immigration_status: CITIZEN, ssi: 0} + person8: {age: 14, immigration_status: CITIZEN, ssi: 0} + person9: {age: 13, immigration_status: CITIZEN, ssi: 0} + person10: {age: 12, immigration_status: CITIZEN, ssi: 0} + person11: {age: 11, immigration_status: CITIZEN, ssi: 0} + person12: {age: 10, immigration_status: CITIZEN, ssi: 0} + person13: {age: 9, immigration_status: CITIZEN, ssi: 0} + person14: {age: 7, immigration_status: CITIZEN, ssi: 0} + person15: {age: 5, immigration_status: CITIZEN, ssi: 0} + person16: {age: 3, immigration_status: CITIZEN, ssi: 0} + person17: {age: 1, immigration_status: CITIZEN, ssi: 0} + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8, + person9, person10, person11, person12, person13, person14, person15, person16, + person17] + ga_tanf: 0 + heating_type: NATURAL_GAS + gas_expense: 1_800 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8, + person9, person10, person11, person12, person13, person14, person15, person16, + person17] + state_code: GA + output: + ga_liheap_household_size: 17 + ga_liheap_income: 40_000 + ga_liheap_income_limit: 109_627 + heating_expense: 1_800 + ga_liheap_matrix_amount: 660 + ga_liheap_eligible: true + ga_liheap: 660 + +# Manual page 89, third row: an unsubsidized renter with heat in rent who is billed +# directly for electric has an energy burden, paid to the electric vendor. Page 4 also +# makes subsidized renters eligible when the utility bill is in their name. The award +# is the FY2026 matrix step 2 amount for the main heating fuel; it is not the +# lowest award. Each household is one adult with heat in rent and a $1,200 a year +# ($100 a month) electric bill. Unsubsidized rent is $9,600 ($800 a month); the +# subsidized tenant share is $3,000 ($250 a month). The size 1 limit is $34,549 and +# the Level I cutoff is $13,589. +# Household 1: natural gas, $12,000 wages, Level I: $560 + $100 = $660. +# Household 2: natural gas, $24,000 wages, Level II: $560. +# Household 3: electric, $24,000 wages, Level II: $400. +# Household 4: UNSPECIFIED fuel, $12,000 wages: eligible, but the matrix has no amount +# for an unspecified fuel, so $0. +# Household 5: subsidized, natural gas, $12,000 wages, with the electric bill: $660. +# Household 6: subsidized, natural gas, $12,000 wages, no bill: no energy burden, so +# the $660 matrix amount is not paid. +- name: Case 16, heat in rent renters billed for electric receive the main heating fuel award. + period: 2026 + input: + state_code: [GA, GA, GA, GA, GA, GA] + age: [40, 40, 40, 40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN, CITIZEN, CITIZEN, CITIZEN] + employment_income: [12_000, 24_000, 24_000, 12_000, 12_000, 12_000] + ssi: [0, 0, 0, 0, 0, 0] + ga_tanf: [0, 0, 0, 0, 0, 0] + rent: [9_600, 9_600, 9_600, 9_600, 3_000, 3_000] + heating_type: [NATURAL_GAS, NATURAL_GAS, ELECTRICITY, UNSPECIFIED, NATURAL_GAS, NATURAL_GAS] + heat_expense_included_in_rent: [true, true, true, true, true, true] + tenant_pays_utilities: [true, true, true, true, true, true] + receives_housing_assistance: [false, false, false, false, true, true] + is_in_public_housing: [false, false, false, false, false, false] + gas_expense: [0, 0, 0, 0, 0, 0] + pre_subsidy_electricity_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 0] + output: + ga_liheap_household_size: [1, 1, 1, 1, 1, 1] + ga_liheap_income: [12_000, 24_000, 24_000, 12_000, 12_000, 12_000] + ga_liheap_income_limit: [34_549, 34_549, 34_549, 34_549, 34_549, 34_549] + heating_expense: [0, 0, 1_200, 0, 0, 0] + has_heating_expense: [false, false, false, false, false, false] + ga_liheap_eligible: [true, true, true, true, true, false] + ga_liheap_matrix_amount: [660, 560, 400, 0, 660, 660] + ga_liheap: [660, 560, 400, 0, 660, 0] + +# Manual page 89 and FY2026 plan page 30: when unsubsidized rent includes all +# utilities, the tenant pays no utility vendor and gets the lowest award in the +# income level, even if an electric bill is also reported. Ordinary natural gas would +# pay $660 or $560; the lowest award is min($400, $560, $710, $580, $400) = $400, +# plus $100 in Level I. Rent is $9,600 ($800 a month). +# Household 1: $12,000 wages, Level I: $500. Household 2: $24,000 wages, Level II: $400. +- name: Case 17, rent including all utilities keeps the lowest award when an electric bill is reported. + period: 2026 + input: + state_code: [GA, GA] + age: [40, 40] + immigration_status: [CITIZEN, CITIZEN] + employment_income: [12_000, 24_000] + ssi: [0, 0] + ga_tanf: [0, 0] + rent: [9_600, 9_600] + heating_type: [NATURAL_GAS, NATURAL_GAS] + heat_expense_included_in_rent: [true, true] + tenant_pays_utilities: [false, false] + receives_housing_assistance: [false, false] + is_in_public_housing: [false, false] + gas_expense: [0, 0] + pre_subsidy_electricity_expense: [1_200, 1_200] + output: + ga_liheap_income: [12_000, 24_000] + heating_expense: [0, 0] + ga_liheap_eligible: [true, true] + ga_liheap_matrix_amount: [500, 400] + ga_liheap: [500, 400] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap_household_size.yaml index 9cb2d08c61b..410c036b1d5 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap_household_size.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap_household_size.yaml @@ -1,6 +1,7 @@ # Source-derived Georgia independent components; no final eligibility or total award asserted. -# Manual pp51–52 counts citizen/qualified people including minors; DACA and undocumented members do not count in size. +# Manual pages 51-52 count citizens and qualified noncitizens, including minors; DACA +# and undocumented members do not count in size. - name: Case 1, qualified adults and minors count without SNAP waiting bars. period: 2026 absolute_error_margin: 0.01 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml index c6f392349b6..b541e74a15b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml @@ -1,6 +1,9 @@ # Source-derived Georgia independent components; no final eligibility or total award asserted. -# Manual pp65–68: wages and business17400 + ordinary/farmrent2400 + survivors600 + pension1200 + SSA2400 + SSI1200 + gains600 =25800; no duplicated component aggregates. +# Manual pages 65-68: wages and business income $17,400 + ordinary and farm rent $2,400 +# + survivors $600 + pension $1,200 + Social Security $2,400 + SSI $1,200 + gains $600 +# = $25,800; no component aggregate is counted twice. The person is under 65 and not on +# Medicare, so the Part B premium is pinned at $0 and Social Security counts in full. - name: Case 1, supported independent adult income sources are counted once. period: 2026 absolute_error_margin: 0.01 @@ -20,6 +23,7 @@ survivor_benefits: 600 pension_income: 1_200 social_security: 2_400 + medicare_part_b_premium: 0 capital_gains: 600 spm_units: spm_unit: @@ -35,7 +39,9 @@ output: ga_liheap_income: 25_800 -# Manual p62 excludes ALL under18 income. At18,6000wages+2400SSI+1200SSA+600dividends+300netinterest adds10500 to parent12000. +# Manual page 62 excludes all income of members under 18. At 18, $6,000 wages + $2,400 +# SSI + $1,200 Social Security + $600 dividends + $300 net interest would add $10,500 +# to the parent's $12,000. The Part B premium is pinned at $0 (not on Medicare). - name: Case 2, all age 17 income follows the under eighteen exclusion boundary. period: 2026 absolute_error_margin: 0.01 @@ -52,6 +58,7 @@ ssi: 2_400 employment_income: 6_000 social_security: 1_200 + medicare_part_b_premium: 0 ordinary_dividend_income: 600 interest_income: 600 spm_units: @@ -68,7 +75,9 @@ output: ga_liheap_income: 12_000 -# Manual p62 excludes ALL under18 income. At18,6000wages+2400SSI+1200SSA+600dividends+300netinterest adds10500 to parent12000. +# Manual page 62 excludes all income of members under 18. At 18, $6,000 wages + $2,400 +# SSI + $1,200 Social Security + $600 dividends + $300 net interest add $10,500 to the +# parent's $12,000 = $22,500. The Part B premium is pinned at $0 (not on Medicare). - name: Case 3, all age 18 income follows the under eighteen exclusion boundary. period: 2026 absolute_error_margin: 0.01 @@ -85,6 +94,7 @@ ssi: 2_400 employment_income: 6_000 social_security: 1_200 + medicare_part_b_premium: 0 ordinary_dividend_income: 600 interest_income: 600 spm_units: @@ -101,7 +111,9 @@ output: ga_liheap_income: 22_500 -# Manual pp66,69 and contractv1 household interpretation: combined adultinterest400 minus annual300, plus600dividends =700; no per-person multiplication. +# Manual pages 66 and 69 do not say whether the $25 monthly interest disregard repeats +# per person, so the model applies it once to the household's adult interest: +# $400 combined interest - $300 a year + $600 dividends = $700. - name: Case 4, interest disregard is shared once across adults while dividends fully count. period: 2026 absolute_error_margin: 0.01 @@ -132,7 +144,8 @@ output: ga_liheap_income: 700 -# Monthly25 allowance annualizes to300; exact threshold is excluded and first dollar above it counts. +# The $25 monthly allowance is $300 a year; interest of exactly $300 is fully excluded +# and the first dollar above it counts. - name: Case 5, annual household interest allowance has an exact three hundred dollar boundary. period: 2026 absolute_error_margin: 0.01 @@ -147,7 +160,9 @@ output: ga_liheap_income: [0, 0, 1] -# Manual p67 TANF grant counts once at SPM level: caregiver wages1200 + annual ga_tanf3600 =4800; minorSSI2400 excluded. +# Manual page 67: the TANF grant counts once at SPM-unit level. Caregiver wages $1,200 +# + annual TANF $3,600 = $4,800; the minor's $2,400 SSI is excluded. ga_tanf reaches +# the income through the take-up-gated tanf, which defaults to take-up. - name: Case 6, caregiver TANF cash counts once despite excluding a minor SSI payment. period: 2026 absolute_error_margin: 0.01 @@ -176,8 +191,10 @@ output: ga_liheap_income: 4_800 -# Contractv1 preserves signed existing net-business inputs and floors the combined household income0; no additional business-expense treatment. -- name: Case 7, existing signed net losses cannot make the household total negative. +# Manual sections 1200.6-1200.7 (pages 64-66) net self-employment within the business, +# so each source is floored at zero before the sources are added: +# max($6,000, 0) + max(-$18,000, 0) = $6,000. +- name: Case 7, a self-employment loss does not offset wages. period: 2026 absolute_error_margin: 0.01 input: @@ -200,9 +217,12 @@ state_code: GA hud_utility_allowance: 0 output: - ga_liheap_income: 0 + ga_liheap_income: 6_000 -# Manual pp51–52,62: parent income counts in full, citizen child counts in size but all child income is excluded. Matrix1person electric LevelI=500; final eligibility is not asserted. +# Manual pages 51-52 and 62: the parent's income counts in full, and the citizen child +# counts in size but all of the child's income is excluded. The size 1 Level I electric +# matrix amount is $500; final eligibility is not asserted. The child's Part B premium +# is pinned at $0 (not on Medicare). - name: Case 8, nonqualified adult income counts while the citizen child determines size. period: 2026 absolute_error_margin: 0.01 @@ -218,6 +238,7 @@ immigration_status: CITIZEN ssi: 2_400 social_security: 3_600 + medicare_part_b_premium: 0 spm_units: spm_unit: members: [person1, person2] @@ -234,3 +255,133 @@ ga_liheap_income: 12_000 ga_liheap_income_limit: 34_549 ga_liheap_matrix_amount: 500 + +# FY2025 and FY2026 plans, section 1.9 (page 6), count Social Security "Excluding +# MediCare deduction"; manual section 1200.7 (page 69) excludes the premiums. The 2026 +# Part B premium is pinned: $202.90 x 12 = $2,434.80. +# Household 1: $36,000 - $2,434.80 = $33,565.20. +# Household 2: $1,200 of Social Security is below the premium, so the net benefit is +# floored at zero and does not reduce wages: max($1,200 - $2,434.80, 0) + $20,000 +# = $20,000. +- name: Case 9, Social Security counts net of the Medicare Part B premium, floored at zero. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [GA, GA] + age: [70, 66] + social_security_retirement: [36_000, 1_200] + employment_income: [0, 20_000] + medicare_part_b_premium: [2_434.8, 2_434.8] + ssi: [0, 0] + ga_tanf: [0, 0] + heating_type: [ELECTRICITY, ELECTRICITY] + pre_subsidy_electricity_expense: [1_800, 1_800] + output: + ga_liheap_income: [33_565.2, 20_000] + +# FY2026 plan page 6 counts retirement and pension benefits; manual page 66 counts a +# pension as unearned income. Household 1: $36,000 of military retirement pay. +# Household 2: $36,000 of railroad retirement counted gross; manual page 69 nets only +# Social Security, so the pinned $2,434.80 premium does not reduce it. +- name: Case 10, military retirement pay and gross railroad retirement count. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [GA, GA] + age: [50, 70] + military_retirement_pay: [36_000, 0] + railroad_benefits: [0, 36_000] + medicare_part_b_premium: [0, 2_434.8] + ssi: [0, 0] + ga_tanf: [0, 0] + heating_type: [NATURAL_GAS, ELECTRICITY] + gas_expense: [1_200, 0] + pre_subsidy_electricity_expense: [0, 1_500] + output: + ga_liheap_income: [36_000, 36_000] + +# Manual page 65 counts capital gains, not losses; pages 64-66 net business income +# within each business. Each source is floored at zero before the sources are added. +# Household 1: max($40,000, 0) + max(-$10,000, 0) = $40,000. +# Household 2: max($36,000, 0) + max(-$5,000, 0) = $36,000. +# Household 3: a farm loss is the only income, so max(-$8,000, 0) = $0; a negative +# source cannot make countable income negative. +- name: Case 11, capital, rental and farm losses do not offset other income. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [GA, GA, GA] + age: [40, 40, 55] + employment_income: [40_000, 36_000, 0] + long_term_capital_gains: [-10_000, 0, 0] + rental_income: [0, -5_000, 0] + farm_operations_income: [0, 0, -8_000] + ssi: [0, 0, 0] + ga_tanf: [0, 0, 0] + heating_type: [ELECTRICITY, ELECTRICITY, PROPANE] + pre_subsidy_electricity_expense: [1_500, 1_500, 0] + bottled_gas_expense: [0, 0, 1_500] + output: + ga_liheap_income: [40_000, 36_000, 0] + +# FY2026 plan page 6 and manual page 67 count TANF benefits received. A parent with two +# children and no earnings has the Georgia size 3 TANF maximum of $280 a month +# ($3,360 a year). Family 1 takes up TANF: tanf = $3,360, counted once. +# Family 2 does not take up TANF, so tanf = $0 and nothing is counted. +- name: Case 12, only TANF the family takes up counts. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + immigration_status: CITIZEN + ssi: 0 + person2: + age: 7 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 4 + immigration_status: CITIZEN + ssi: 0 + person4: + age: 30 + immigration_status: CITIZEN + ssi: 0 + person5: + age: 7 + immigration_status: CITIZEN + ssi: 0 + person6: + age: 4 + immigration_status: CITIZEN + ssi: 0 + tax_units: + tax_unit1: + members: [person1, person2, person3] + tax_unit2: + members: [person4, person5, person6] + spm_units: + spm_unit1: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ga_tanf: 3_360 + takes_up_tanf_if_eligible: true + spm_unit2: + members: [person4, person5, person6] + heating_type: NATURAL_GAS + gas_expense: 1_200 + ga_tanf: 3_360 + takes_up_tanf_if_eligible: false + households: + household1: + members: [person1, person2, person3] + state_code: GA + household2: + members: [person4, person5, person6] + state_code: GA + output: + tanf: [3_360, 0] + ga_liheap_income: [3_360, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml index 3323c2a6683..698c85748c3 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml @@ -1,6 +1,7 @@ # Source-derived Georgia independent components; no final eligibility or total award asserted. -# Original matrix lower-income cutoff and first cent above for each size1–16; preserve anomalous size4=25469 exactly. +# FY2026 matrix step 1: the Level I cutoff and the first cent above it for each size +# from 1 to 16. The unusual size 4 cutoff of $25,469 is preserved exactly. - name: Case 1, all sixteen published lower level upper bounds remain inclusive. period: 2026 absolute_error_margin: 0.01 @@ -28,7 +29,9 @@ ga_liheap_matrix_amount: [660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560, 660, 560] -# Original matrix heating column only: LevelI500/660/810/680/500; LevelII400/560/710/580/400. Cooling column is not an oracle. +# FY2026 matrix heating column only, for electric, natural gas, oil, propane and wood: +# Level I $500 / $660 / $810 / $680 / $500 and Level II $400 / $560 / $710 / $580 / $400. +# The cooling column is not used. - name: Case 2, all five priced fuels match both heating benefit levels including zero income. period: 2026 absolute_error_margin: 0.01 @@ -47,7 +50,8 @@ output: ga_liheap_matrix_amount: [500, 660, 810, 680, 500, 400, 560, 710, 580, 400] -# Ungated regular heating matrix gives660 for natural gas; no actual-expense cap is stated. +# The regular heating matrix gives $660 for natural gas; no cap at the actual expense +# is stated. - name: Case 3, a small heating bill does not cap the fixed matrix amount. period: 2026 absolute_error_margin: 0.01 @@ -61,7 +65,8 @@ output: ga_liheap_matrix_amount: 660 -# NONE affirmatively means no heating. No assertions here establish legal ineligibility for unpriced fuel or out-of-table size. +# NONE means the home has no heating. These assertions do not establish legal +# ineligibility for unpriced fuels or sizes above the table. - name: Case 4, explicit no heating has no supported matrix amount. period: 2026 absolute_error_margin: 0.01 @@ -74,9 +79,10 @@ output: ga_liheap_matrix_amount: 0 -# FY2026 plan p30 assigns direct applicant payments the lowest award within the -# income level. The matrix gives500 through13589 and400 above it for size1. -# A separately paid utility retains the ordinary gas award660; NONE remains zero. +# FY2026 plan page 30 gives direct applicant payments the lowest award within the +# income level. For size 1 the matrix gives $500 through $13,589 and $400 above it. +# A separately paid utility keeps the ordinary natural gas award of $660; NONE remains +# zero. - name: Case 5, direct rental payments use the income level minimum and preserve no heating. period: 2026 absolute_error_margin: 0.01 @@ -92,3 +98,34 @@ is_in_public_housing: [false, false, false, false] output: ga_liheap_matrix_amount: [500, 400, 660, 0] + +# FY2025 matrix step 1 Level I cutoffs: size 1 $12,995, size 4 $24,355 and size 16 +# $40,485 (first, middle-table and last sizes). Deliberate boundary case: each cutoff +# and the first cent above it. Step 2 natural gas pays $660 in Level I and $560 in +# Level II. +- name: Case 6, the FY2025 Level I cutoffs remain inclusive in 2025. + period: 2025 + absolute_error_margin: 0.01 + input: + state_code: [GA, GA, GA, GA, GA, GA] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200] + ga_liheap_household_size: [1, 1, 4, 4, 16, 16] + ga_liheap_income: [12_995, 12_995.01, 24_355, 24_355.01, 40_485, 40_485.01] + output: + ga_liheap_matrix_amount: [660, 560, 660, 560, 660, 560] + +# The FY2026 matrix publishes Level I cutoffs through size 16, so larger households use +# the size 16 cutoff of $42,336. Deliberate boundary case: sizes 17 and 25 at $42,336 +# pay Level I natural gas $660, and size 17 one cent above pays Level II $560. +- name: Case 7, households above the published table size use the size sixteen cutoff. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [GA, GA, GA] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_200, 1_200, 1_200] + ga_liheap_household_size: [17, 17, 25] + ga_liheap_income: [42_336, 42_336.01, 42_336] + output: + ga_liheap_matrix_amount: [660, 560, 660] diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/_ga_liheap_housing.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/_ga_liheap_housing.py index 4c3e68b231d..24f4b6ef1e5 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/_ga_liheap_housing.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/_ga_liheap_housing.py @@ -15,3 +15,27 @@ def ga_liheap_has_direct_payment_route(spm_unit, period): & ~spm_unit("receives_housing_assistance", period) & ~spm_unit.household("is_in_public_housing", period) ) + + +def ga_liheap_has_electric_vendor_route(spm_unit, period): + """Identify heat-in-rent renters billed directly for electric. + + Georgia's manual energy-burden table (physical page 89, third row) gives + unsubsidized renters with heat in rent and their own electric bill an + energy burden, "Paid to electric vendor because gas usage is + indeterminate". Page 4 makes subsidized renters eligible when "their + utility bill is in their name", so this route ignores subsidy status; + subsidized heat-in-rent renters without a bill stay ineligible. + tenant_pays_utilities separates these renters from the rows where all + utilities are in rent, and a positive electric bill is the utility-bill + documentation. The pre-subsidy bill matches heating_expense and avoids + the program-dependent subsidies in electricity_expense. + Manual pages 4 and 89: + https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=89 + """ + return ( + spm_unit("heat_expense_included_in_rent", period) + & spm_unit.household("tenant_pays_utilities", period) + & (add(spm_unit, period, ["rent"]) > 0) + & (spm_unit("pre_subsidy_electricity_expense", period) > 0) + ) diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py index 008955b7070..5890dbf7e0b 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py @@ -1,6 +1,7 @@ from policyengine_us.model_api import * from policyengine_us.variables.gov.states.ga.dfcs.liheap.eligibility._ga_liheap_housing import ( ga_liheap_has_direct_payment_route, + ga_liheap_has_electric_vendor_route, ) @@ -10,7 +11,7 @@ class ga_liheap_eligible(Variable): definition_period = YEAR label = "Georgia LIHEAP ordinary eligibility" defined_for = StateCode.GA - # State plan pages 5, 8, 9, 30; manual pages 3, 50, 56, 57, 75, 89. + # State plan pages 5, 8, 9, 30; manual pages 3, 4, 50, 56, 57, 75, 89. reference = ( "https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=5", "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=3", @@ -24,11 +25,15 @@ def formula(spm_unit, period, parameters): heating_expense = spm_unit("heating_expense", period) # Explicit heating responsibility can qualify with no current bill. # A positive primary bill also establishes burden with heat in rent, - # including subsidized housing. The rent flag alone is insufficient. + # including subsidized housing. Heat-in-rent renters also qualify when + # billed directly for electric, subsidized or not (manual pages 4 and + # 89), or when unsubsidized with all utilities in rent (page 89). The + # rent flag alone is insufficient. energy_burden = ( spm_unit("has_heating_expense", period) | (heating_expense > 0) | ga_liheap_has_direct_payment_route(spm_unit, period) + | ga_liheap_has_electric_vendor_route(spm_unit, period) ) # Pure-assistance categorical eligibility requires actual receipt by # every member and assistance-only income/resources. Aggregate TANF/SNAP diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py index 9bd6b139546..ef93ec604a9 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py @@ -12,6 +12,8 @@ class ga_liheap_income(Variable): # Manual pages 62-69; current plan pages 5-6 govern listed exclusions. "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=62", "https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=5", + # Section 1.9, page 6: Social Security "Excluding MediCare deduction". + "https://liheapch.acf.gov/docs/2025/state-plans/GA_Plan_2025.pdf#page=6", ) def formula(spm_unit, period, parameters): @@ -21,7 +23,21 @@ def formula(spm_unit, period, parameters): # Annual inputs approximate income in the application period. Georgia # excludes all income of under-18s, not only their earnings, and counts # otherwise countable income of nonqualified adults in full. - person_income = add(person, period, p.sources.person, options=[ADD]) + # Manual sections 1200.6-1200.7 (pages 64-66) net self-employment and + # farm income within the business and count capital gains, not losses, + # so each source is floored at zero and a loss in one source cannot + # offset another. + person_income = 0 + for source in p.sources.person: + person_income = person_income + max_(person(source, period), 0) + # Plan section 1.9 (FY2025 and FY2026) counts Social Security + # "Excluding MediCare deduction", and manual section 1200.7 (page 69) + # excludes the premiums. medicare_part_b_premium is zero for a person + # who is not enrolled and excludes the share a Medicare Savings + # Program pays, which is not withheld from the check. + premium = person("medicare_part_b_premium", period) + social_security = max_(person("social_security", period) - premium, 0) + adult_income = spm_unit.sum((person_income + social_security) * adult) adult_interest = spm_unit.sum(person("interest_income", period) * adult) # The manual excludes the first $25 of monthly interest without saying # this allowance repeats per person or account. Apply it once to the @@ -30,15 +46,10 @@ def formula(spm_unit, period, parameters): adult_interest - p.monthly_interest_disregard * MONTHS_IN_YEAR, 0, ) - # State TANF is included once at SPM level as a caregiver grant rather - # than projecting the household award onto every member. - household_income = add(spm_unit, period, p.sources.household, options=[ADD]) - # Keep existing signed net business inputs without another deduction; - # floor the combined total. Rental income is assumed nonnegative. - # Roomer/boarder expense allowances, Medicare premiums, mortgage-sale - # contracts, stipends, and recurring support lack the scoped handling. - # Current plan exclusions govern cash gifts and lump-sum receipts. - return max_( - spm_unit.sum(person_income * adult) + interest + household_income, - 0, - ) + # TANF is included once at SPM level as a caregiver grant rather than + # projecting the household award onto every member. + household_income = add(spm_unit, period, p.sources.household) + # Roomer/boarder expense allowances, mortgage-sale contracts, stipends, + # and recurring support lack the scoped handling. Current plan + # exclusions govern cash gifts and lump-sum receipts. + return adult_income + interest + household_income diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py index cc1680657d1..de550ee615b 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py @@ -45,7 +45,11 @@ def formula(spm_unit, period, parameters): base_amounts, default=0, ) - # Applicant payments use the lowest award in the income level. + # Applicant payments use the lowest award in the income level (plan + # page 30). Heat-in-rent renters billed directly for electric, subsidized + # or not, are paid to the electric vendor (manual pages 4 and 89), so + # they keep the matrix step 2 amount for their main heating fuel and an + # unspecified fuel pays 0. direct_payment = ga_liheap_has_direct_payment_route(spm_unit, period) base = where( direct_payment & (heating_type != fuel.NONE), From 7d15ec86df52bfe37f20ba6961a1ebc882cd89b4 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 6 Oct 2026 17:31:33 -0400 Subject: [PATCH 05/10] Count lottery winnings per the Georgia LIHEAP manual The manual's income table (section 1200.7) includes lottery winnings and cash gifts; plan section 1.9 leaves both unchecked. Follow the manual: count gambling_winnings as the closest input for lottery winnings, and note that cash gifts have no input. Also note the same plan-versus-manual conflict for disability payments and workers' compensation, which the manual counts (pages 65 and 68) and the model already follows. Co-Authored-By: Claude Opus 5.5 --- .../ga/dfcs/liheap/income/sources/person.yaml | 10 +++ .../gov/states/ga/dfcs/liheap/ga_liheap.yaml | 32 +++++++++ .../dfcs/liheap/income/ga_liheap_income.yaml | 70 +++++++++++++++++++ .../ga/dfcs/liheap/income/ga_liheap_income.py | 7 +- 4 files changed, 116 insertions(+), 3 deletions(-) diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml index 07f2fe9c65a..744585b2ef5 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml @@ -24,6 +24,10 @@ values: - railroad_benefits - survivor_benefits - veterans_benefits + # Plan vs manual: manual 1200.7 includes "Disability Payment" (page 65) and + # "Workers Compensation" (page 68), both paid by insurance companies, while + # FY2026 plan 1.9 (page 6) leaves "Insurance payments made directly to the + # insured" unchecked. This follows the manual. - disability_benefits - workers_compensation - strike_benefits @@ -32,6 +36,12 @@ values: - farm_rent_income - ordinary_dividend_income - capital_gains + # Plan vs manual: manual 1200.7 (page 66) includes "Lottery Winnings", while + # FY2026 plan 1.9 (page 6) leaves "winnings from lotteries" unchecked. This + # follows the manual; the input also covers non-lottery gambling. + - gambling_winnings + # Manual 1200.7 (page 65) includes "Contribution/Cash Gift" and the plan + # leaves "Cash gifts" unchecked; cash gifts have no input and are not modeled. - alimony_income - child_support_received metadata: diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml index 062aa6fb901..5107af281dc 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml @@ -629,3 +629,35 @@ ga_liheap_eligible: [true, true] ga_liheap_matrix_amount: [500, 400] ga_liheap: [500, 400] + +# Manual section 1200.7 (page 66) counts lottery winnings. Countable income is +# $10,000 wages + $2,000 gambling winnings = $12,000. That is at or below the size 1 +# Level I cutoff of $13,589 and the $34,549 limit, so FY2026 Level I electric pays $500. +- name: Case 18, gambling winnings count toward the Level I award. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + employment_income: 10_000 + gambling_winnings: 2_000 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + ga_tanf: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + households: + household: + members: [person1] + state_code: GA + output: + ga_liheap_household_size: 1 + ga_liheap_income: 12_000 + ga_liheap_income_limit: 34_549 + heating_expense: 1_200 + ga_liheap_matrix_amount: 500 + ga_liheap_eligible: true + ga_liheap: 500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml index b541e74a15b..de55ae82a95 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml @@ -385,3 +385,73 @@ output: tanf: [3_360, 0] ga_liheap_income: [3_360, 0] + +# Manual section 1200.7 (page 66) lists "Lottery Winnings" in the Include column; the +# model counts them through gambling_winnings. Each source is floored at zero, and +# income of members under 18 is excluded. +# Household 1: $30,000 wages + $6,000 winnings = $36,000. +# Household 2: $30,000 wages + max(-$6,000, 0) = $30,000. +# Household 3: the parent's $30,000 wages count; the 17-year-old's $6,000 raffle prize +# counts $0. +- name: Case 13, adult gambling winnings count, floored at zero, and a child's winnings are excluded. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + employment_income: 30_000 + gambling_winnings: 6_000 + ssi: 0 + person2: + age: 40 + immigration_status: CITIZEN + employment_income: 30_000 + gambling_winnings: -6_000 + ssi: 0 + person3: + age: 40 + immigration_status: CITIZEN + employment_income: 30_000 + ssi: 0 + person4: + age: 17 + immigration_status: CITIZEN + gambling_winnings: 6_000 + ssi: 0 + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2] + tax_unit3: + members: [person3, person4] + spm_units: + spm_unit1: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + ga_tanf: 0 + spm_unit2: + members: [person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + ga_tanf: 0 + spm_unit3: + members: [person3, person4] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + ga_tanf: 0 + households: + household1: + members: [person1] + state_code: GA + household2: + members: [person2] + state_code: GA + household3: + members: [person3, person4] + state_code: GA + output: + ga_liheap_income: [36_000, 30_000, 30_000] diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py index ef93ec604a9..315b2fb8299 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py @@ -9,7 +9,7 @@ class ga_liheap_income(Variable): label = "Georgia LIHEAP annual countable household income" defined_for = StateCode.GA reference = ( - # Manual pages 62-69; current plan pages 5-6 govern listed exclusions. + # Manual pages 62-69, followed where plan pages 5-6 conflict. "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=62", "https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=5", # Section 1.9, page 6: Social Security "Excluding MediCare deduction". @@ -50,6 +50,7 @@ def formula(spm_unit, period, parameters): # projecting the household award onto every member. household_income = add(spm_unit, period, p.sources.household) # Roomer/boarder expense allowances, mortgage-sale contracts, stipends, - # and recurring support lack the scoped handling. Current plan - # exclusions govern cash gifts and lump-sum receipts. + # recurring support and the manual's counted cash gifts lack the scoped + # handling. Lottery winnings follow the manual over the plan (see the + # person income sources parameter). return adult_income + interest + household_income From ea11147d254b6de347a97c09750c696f4c3437cb Mon Sep 17 00:00:00 2001 From: Ziming Date: Wed, 7 Oct 2026 22:56:53 -0400 Subject: [PATCH 06/10] Fix issues from review: document the noncitizen household-size conflict Quote manual 1100.14 (page 57) and the glossary (page 76) against 101.2, 1100.3 and 1100.5 a; the code keeps counting lawful immigrants in the size. Co-Authored-By: Claude Opus 5.5 --- .../ga/dfcs/liheap/eligibility/ga_liheap_eligible.py | 2 ++ .../gov/states/ga/dfcs/liheap/ga_liheap_household_size.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py index 5890dbf7e0b..2527a2da42d 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py @@ -41,6 +41,8 @@ def formula(spm_unit, period, parameters): # Account credits of $1,000+ and administrative household exclusions # also need unavailable inputs; the selected heating account is assumed # to satisfy those rules. Annual expense is not an account-credit proxy. + # A household of lawful immigrants only has size > 0 and qualifies; + # see the manual conflict noted in ga_liheap_household_size. return ( (size > 0) & (income <= limit) diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py index bae87678c05..2052d174f76 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py @@ -10,6 +10,13 @@ class ga_liheap_household_size(Variable): # Pages 51-52 exclude nonqualified members from size; page 62 counts income. reference = "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=51" + # Lawful immigrants count in the size; undocumented members do not + # (manual 101.2 page 3, 1100.3 page 50, 1100.5 a page 51: "Do not include + # the undocumented adults in the household size"). The manual conflicts + # with itself: 1100.14 (page 57) lists "Applicants where ALL household + # members are non-U.S. citizens" as ineligible, and the glossary (page 76) + # counts non-citizen members' income "but do not include in the household + # size". This follows the specific eligibility sections. # The SPM unit approximates the economic unit purchasing energy together. # Existing inputs do not identify all roomers, boarders, foster-child # elections, or excluded group-quarter arrangements. From 39c66c14d9cc53c073f2b479a2f76f8d55a59ea7 Mon Sep 17 00:00:00 2001 From: Ziming Date: Wed, 7 Oct 2026 23:24:32 -0400 Subject: [PATCH 07/10] 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. Also cite manual section 1100.4 (not 1100.5 a) for the undocumented-adults sentence in the household-size comment. Co-Authored-By: Claude Opus 5.5 --- .../ga/dfcs/liheap/eligibility/smi_rate.yaml | 2 +- .../ga/dfcs/liheap/income/minimum_age.yaml | 2 +- .../income/monthly_interest_disregard.yaml | 2 +- .../ga/dfcs/liheap/income/sources/household.yaml | 2 +- .../ga/dfcs/liheap/income/sources/person.yaml | 4 ++-- .../liheap/eligibility/ga_liheap_eligible.py | 3 ++- .../ga/dfcs/liheap/ga_liheap_household_size.py | 16 ++++++++-------- .../ga/dfcs/liheap/income/ga_liheap_income.py | 6 ++++-- 8 files changed, 20 insertions(+), 17 deletions(-) diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/smi_rate.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/smi_rate.yaml index 0474e764be4..6726118a293 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/smi_rate.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/smi_rate.yaml @@ -6,5 +6,5 @@ metadata: period: year label: Georgia LIHEAP state median income rate reference: - - title: Georgia FY2026 LIHEAP State Plan, section 2.1, page 8 + - title: Georgia FY2026 LIHEAP State Plan, section 2.1 href: https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=8 diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/minimum_age.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/minimum_age.yaml index 468baa09b29..09c00ef9835 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/minimum_age.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/minimum_age.yaml @@ -6,5 +6,5 @@ metadata: period: year label: Georgia LIHEAP countable income minimum age reference: - - title: Georgia LIHEAP Policy Manual, January 2022, section 1200.2, page 62 + - title: Georgia LIHEAP Policy Manual, January 2022, section 1200.2 href: https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=62 diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml index e37c144d984..b7ad5f9202d 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml @@ -6,5 +6,5 @@ metadata: period: month label: Georgia LIHEAP monthly interest disregard reference: - - title: Georgia LIHEAP Policy Manual, January 2022, section 1200.7, pages 66 and 69 (first interest amount disregarded) + - title: "Georgia LIHEAP Policy Manual, January 2022, section 1200.7 (first interest amount disregarded) #page 66,69" href: https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=66 diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml index edc8807616d..48beb71b8c1 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml @@ -9,5 +9,5 @@ metadata: period: year label: Georgia LIHEAP household income sources reference: - - title: Georgia FY2026 LIHEAP State Plan, section 1.9, page 6 (TANF counted) + - title: Georgia FY2026 LIHEAP State Plan, section 1.9 (TANF counted) href: https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml index 744585b2ef5..60d0bdf8a78 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml @@ -49,7 +49,7 @@ metadata: period: year label: Georgia LIHEAP adult person income sources reference: - - title: Georgia LIHEAP Policy Manual, January 2022, sections 1200.1-1200.7, pages 62-69, and application item 12, page 83 + - title: "Georgia LIHEAP Policy Manual, January 2022, sections 1200.1-1200.7 and application item 12 #page 62-69,83" href: https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=62 - - title: Georgia FY2026 LIHEAP State Plan, section 1.9, pages 5-6 (current included and excluded sources) + - title: "Georgia FY2026 LIHEAP State Plan, section 1.9 (current included and excluded sources) #page 5-6" href: https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=5 diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py index 2527a2da42d..f07b693e15f 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py @@ -11,9 +11,10 @@ class ga_liheap_eligible(Variable): definition_period = YEAR label = "Georgia LIHEAP ordinary eligibility" defined_for = StateCode.GA - # State plan pages 5, 8, 9, 30; manual pages 3, 4, 50, 56, 57, 75, 89. reference = ( + # PDF pages 5, 8-9, 30 "https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=5", + # PDF pages 3-4, 50, 56-57, 75, 89 "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=3", ) diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py index 2052d174f76..3dcc20ff90f 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py @@ -7,16 +7,16 @@ class ga_liheap_household_size(Variable): definition_period = YEAR label = "Georgia LIHEAP household size" defined_for = StateCode.GA - # Pages 51-52 exclude nonqualified members from size; page 62 counts income. + # PDF pages 51-52, 62 reference = "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=51" - # Lawful immigrants count in the size; undocumented members do not - # (manual 101.2 page 3, 1100.3 page 50, 1100.5 a page 51: "Do not include - # the undocumented adults in the household size"). The manual conflicts - # with itself: 1100.14 (page 57) lists "Applicants where ALL household - # members are non-U.S. citizens" as ineligible, and the glossary (page 76) - # counts non-citizen members' income "but do not include in the household - # size". This follows the specific eligibility sections. + # Lawful immigrants count in the size (manual 101.2 page 3, 1100.3 page + # 50, 1100.5 a page 51); undocumented members do not (1100.4 page 51: "Do + # not include the undocumented adults in the household size"). The manual + # conflicts with itself: 1100.14 (page 57) lists "Applicants where ALL + # household members are non-U.S. citizens" as ineligible, and the glossary + # (page 76) counts non-citizen members' income "but do not include in the + # household size". This follows the specific eligibility sections. # The SPM unit approximates the economic unit purchasing energy together. # Existing inputs do not identify all roomers, boarders, foster-child # elections, or excluded group-quarter arrangements. diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py index 315b2fb8299..97946b6697e 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py @@ -9,10 +9,12 @@ class ga_liheap_income(Variable): label = "Georgia LIHEAP annual countable household income" defined_for = StateCode.GA reference = ( - # Manual pages 62-69, followed where plan pages 5-6 conflict. + # The manual is followed where the plan conflicts. + # PDF pages 62-69 "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=62", + # PDF pages 5-6 "https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=5", - # Section 1.9, page 6: Social Security "Excluding MediCare deduction". + # Section 1.9: Social Security "Excluding MediCare deduction". "https://liheapch.acf.gov/docs/2025/state-plans/GA_Plan_2025.pdf#page=6", ) From 7df668b9fd0bad96dd8a266acfa252ad1b2a5843 Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 8 Oct 2026 00:32:03 -0400 Subject: [PATCH 08/10] Count cash support in Georgia LIHEAP income --- .../ga/dfcs/liheap/income/sources/person.yaml | 9 ++++- .../gov/states/ga/dfcs/liheap/ga_liheap.yaml | 40 +++++++++++++++++++ .../dfcs/liheap/income/ga_liheap_income.yaml | 15 +++++++ .../ga/dfcs/liheap/income/ga_liheap_income.py | 7 ++-- 4 files changed, 65 insertions(+), 6 deletions(-) diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml index 60d0bdf8a78..dcdd2415dd6 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml @@ -40,8 +40,11 @@ values: # FY2026 plan 1.9 (page 6) leaves "winnings from lotteries" unchecked. This # follows the manual; the input also covers non-lottery gambling. - gambling_winnings - # Manual 1200.7 (page 65) includes "Contribution/Cash Gift" and the plan - # leaves "Cash gifts" unchecked; cash gifts have no input and are not modeled. + # Manual 1200.7 (page 65) explicitly includes "Contribution/Cash Gift". + # The FY2022 plan already left gifts unchecked when this manual was issued; + # the same FY2026 checkbox does not establish a later policy change. Follow + # the detailed manual, as for lottery and insurance income above. + - financial_assistance - alimony_income - child_support_received metadata: @@ -53,3 +56,5 @@ metadata: href: https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=62 - title: "Georgia FY2026 LIHEAP State Plan, section 1.9 (current included and excluded sources) #page 5-6" href: https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=5 + - title: Georgia FY2022 LIHEAP State Plan, section 1.9 (contemporaneous unchecked gifts, lottery and insurance categories) + href: https://liheapch.acf.gov/sites/default/files/webfiles/docs/GA_Plan_2022.pdf#page=6 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml index 5107af281dc..464a6f15589 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml @@ -661,3 +661,43 @@ ga_liheap_matrix_amount: 500 ga_liheap_eligible: true ga_liheap: 500 + +# Manual section 1200.7, page 65 counts cash gifts from outside the household. +# FY2026 matrix: size-one Level I ends at $13,589 and eligibility at $34,549. +# Gifts of $12,000 give the $500 electric award; $24,000 gives $400; $36,000 +# exceeds the limit and gives $0. Without the counted gift, all three get $500. +- name: Case 19, cash contributions determine the benefit band and eligibility. + period: 2026 + input: + state_code: [GA, GA, GA] + age: [40, 40, 40] + immigration_status: [CITIZEN, CITIZEN, CITIZEN] + financial_assistance: [12_000, 24_000, 36_000] + ssi: [0, 0, 0] + ga_tanf: [0, 0, 0] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY] + pre_subsidy_electricity_expense: [1_200, 1_200, 1_200] + output: + ga_liheap_income: [12_000, 24_000, 36_000] + ga_liheap_income_limit: [34_549, 34_549, 34_549] + ga_liheap_eligible: [true, true, false] + ga_liheap: [500, 400, 0] + +# FY2025 uses the same detailed income rule. Its size-one limit is $33,038; +# $36,000 in outside cash contributions exceeds it and gives no heating benefit. +- name: Case 20, cash contributions above the prior year limit prevent an award. + period: 2025 + input: + state_code: GA + age: 40 + immigration_status: CITIZEN + financial_assistance: 36_000 + ssi: 0 + ga_tanf: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + output: + ga_liheap_income: 36_000 + ga_liheap_income_limit: 33_038 + ga_liheap_eligible: false + ga_liheap: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml index de55ae82a95..dc71549f679 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml @@ -455,3 +455,18 @@ state_code: GA output: ga_liheap_income: [36_000, 30_000, 30_000] + +# Manual section 1200.7, page 65 includes cash contributions and gifts. Section +# 1200.2, page 62 excludes all income of members under 18. The same $6,000 cash +# support therefore counts at age 18 but not 17. financial_assistance records +# support from outside the household, avoiding intra-household double counting. +- name: Case 14, cash contributions count from age eighteen. + period: 2026 + input: + state_code: [GA, GA] + age: [17, 18] + financial_assistance: [6_000, 6_000] + ssi: [0, 0] + ga_tanf: [0, 0] + output: + ga_liheap_income: [0, 6_000] diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py index 97946b6697e..cd9e894a6de 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py @@ -51,8 +51,7 @@ def formula(spm_unit, period, parameters): # TANF is included once at SPM level as a caregiver grant rather than # projecting the household award onto every member. household_income = add(spm_unit, period, p.sources.household) - # Roomer/boarder expense allowances, mortgage-sale contracts, stipends, - # recurring support and the manual's counted cash gifts lack the scoped - # handling. Lottery winnings follow the manual over the plan (see the - # person income sources parameter). + # Roomer/boarder expense allowances, mortgage-sale contracts and stipends + # lack scoped handling. Cash contributions use financial_assistance; + # gifts and lottery winnings follow the detailed manual (see sources). return adult_income + interest + household_income From 50aaa0096e2aee273aaae874711f9d0786a91ac0 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sun, 11 Oct 2026 23:45:52 -0400 Subject: [PATCH 09/10] Use the shared SMI helper for the Georgia LIHEAP income limit Calls liheap_smi_limit from PR 10019 instead of repeating the HHS floor-then-floor calculation. Limits are unchanged. Co-Authored-By: Claude Opus 5.5 --- .../liheap/eligibility/ga_liheap_income_limit.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.py index fd65d704f61..8f2195450fa 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.py @@ -1,5 +1,5 @@ from policyengine_us.model_api import * -from policyengine_us.variables.gov.hhs.hhs_smi import smi +from policyengine_us.variables.gov.hhs.hhs_smi import liheap_smi_limit class ga_liheap_income_limit(Variable): @@ -18,10 +18,6 @@ def formula(spm_unit, period, parameters): p = parameters(period).gov.states.ga.dfcs.liheap.eligibility size = spm_unit("ga_liheap_household_size", period) state = spm_unit.household("state_code_str", period) - four_person_smi = parameters(period).gov.hhs.smi.amount[state] - size_share = smi(size, state, period, parameters) / four_person_smi - # Flooring 60% of the four-person SMI before applying the federal size - # factor reproduces all 16 FY2026 published limits. This is a numerical - # reconciliation; the sources do not expressly specify the floor order. - four_person_limit = np.floor(four_person_smi * p.smi_rate) - return where(size > 0, np.floor(four_person_limit * size_share), 0) + # The HHS floor order reproduces all 16 published FY2026 limits. + limit = liheap_smi_limit(size, state, p.smi_rate, period, parameters) + return where(size > 0, limit, 0) From b36ba40f71ae9dd723b6ff9818b148f7c0bb6c23 Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 15 Oct 2026 16:11:36 -0400 Subject: [PATCH 10/10] Address review round 1 on Georgia LIHEAP - S1: Merge blocker: the person source list reads the raw unemployment_compensation, so main's #9558 guard fails - S2: Eight income sources and two smaller pieces of logic are not pinned by any test - S3: Six parameter descriptions fall outside the house template - REC-C: Federal 110%-of-poverty floor (coordinator recommendation C; not raised by David for GA) Also applies the in-scope suggestions. Tests not run locally; CI validates. Co-Authored-By: Claude Opus 5.5 --- .../parameters/gov/hhs/liheap/fpg_floor.yaml | 11 +++ .../dfcs/liheap/eligibility/fpg_year_lag.yaml | 15 +++ .../ga/dfcs/liheap/eligibility/smi_rate.yaml | 2 + .../ga/dfcs/liheap/income/minimum_age.yaml | 2 +- .../income/monthly_interest_disregard.yaml | 2 +- .../dfcs/liheap/income/sources/household.yaml | 2 +- .../ga/dfcs/liheap/income/sources/person.yaml | 6 +- .../ga/dfcs/liheap/payment/base_amount.yaml | 2 + .../liheap/payment/lower_income_uplift.yaml | 7 +- .../liheap/payment/maximum_table_size.yaml | 4 +- .../eligibility/ga_liheap_eligible.yaml | 35 +++++++ .../eligibility/ga_liheap_income_limit.yaml | 21 ++-- .../gov/states/ga/dfcs/liheap/ga_liheap.yaml | 98 +++++++++++++++++++ .../dfcs/liheap/ga_liheap_household_size.yaml | 6 -- .../dfcs/liheap/income/ga_liheap_income.yaml | 74 +++++++------- .../payment/ga_liheap_matrix_amount.yaml | 16 --- .../liheap/eligibility/_ga_liheap_housing.py | 23 +++-- .../liheap/eligibility/ga_liheap_eligible.py | 28 ++++-- .../dfcs/liheap/ga_liheap_household_size.py | 11 ++- .../ga/dfcs/liheap/income/ga_liheap_income.py | 2 +- .../liheap/payment/ga_liheap_matrix_amount.py | 17 ++-- 21 files changed, 281 insertions(+), 103 deletions(-) create mode 100644 policyengine_us/parameters/gov/hhs/liheap/fpg_floor.yaml create mode 100644 policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/fpg_year_lag.yaml diff --git a/policyengine_us/parameters/gov/hhs/liheap/fpg_floor.yaml b/policyengine_us/parameters/gov/hhs/liheap/fpg_floor.yaml new file mode 100644 index 00000000000..ae7cb296211 --- /dev/null +++ b/policyengine_us/parameters/gov/hhs/liheap/fpg_floor.yaml @@ -0,0 +1,11 @@ +description: The US Department of Health and Human Services limits income-based exclusion to households with income of at least this percentage of the poverty guideline under the Low Income Home Energy Assistance Program. +# Pub. L. 98-558, section 605(a)(2), added the floor for fiscal year 1986 and later. +values: + 1985-10-01: 1.1 +metadata: + period: year + unit: /1 + label: LIHEAP poverty guideline income floor + reference: + - title: 42 U.S.C. 8624(b)(2)(B) + href: https://www.law.cornell.edu/uscode/text/42/8624 diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/fpg_year_lag.yaml new file mode 100644 index 00000000000..bafbd3f84f7 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/fpg_year_lag.yaml @@ -0,0 +1,15 @@ +description: Georgia uses the poverty guidelines from this many years before the model year for the federal income floor under the Low Income Home Energy Assistance Program. +# HHS makes each January's guidelines mandatory for LIHEAP from the next +# October 1, so model period 2026 (FY2026) uses the 2025 guidelines and 2025 +# (FY2025) the 2024 guidelines. Georgia's own limit uses state median income. +values: + 2024-10-01: 1 +metadata: + unit: year + period: year + label: Georgia LIHEAP poverty guideline year lag + reference: + - title: LIHEAP IM 2025-02, Attachment 2, poverty guidelines for mandatory use in FY26 + href: https://acf.gov/sites/default/files/documents/ocs/COMM_LIHEAP_IM2025-02_FPGSte-Table_Att2.pdf#page=1 + - title: LIHEAP IM 2024-02, Attachment 2, poverty guidelines for mandatory use in FY25 + href: https://acf.gov/sites/default/files/documents/ocs/COMM_LIHEAP_IM%202024-02_Att2FPGTables_States.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/smi_rate.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/smi_rate.yaml index 6726118a293..532a39b82e3 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/smi_rate.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/smi_rate.yaml @@ -6,5 +6,7 @@ metadata: period: year label: Georgia LIHEAP state median income rate reference: + - title: Georgia FY2025 LIHEAP State Plan, section 2.1 + href: https://liheapch.acf.gov/docs/2025/state-plans/GA_Plan_2025.pdf#page=8 - title: Georgia FY2026 LIHEAP State Plan, section 2.1 href: https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=8 diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/minimum_age.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/minimum_age.yaml index 09c00ef9835..2febda5c356 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/minimum_age.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/minimum_age.yaml @@ -1,4 +1,4 @@ -description: Georgia counts household members' income starting at this age under the Low Income Home Energy Assistance Program. +description: Georgia excludes the income of household members below this age under the Low Income Home Energy Assistance Program. values: 2025-10-01: 18 metadata: diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml index b7ad5f9202d..0684f255f85 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml @@ -1,4 +1,4 @@ -description: Georgia disregards this amount of monthly interest income under the Low Income Home Energy Assistance Program. +description: Georgia excludes this amount of monthly interest income under the Low Income Home Energy Assistance Program. values: 2025-10-01: 25 metadata: diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml index 48beb71b8c1..c87dd3d39ff 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml @@ -1,4 +1,4 @@ -description: Georgia counts these household benefits once under the Low Income Home Energy Assistance Program. +description: Georgia uses these household-level benefits as countable income under the Low Income Home Energy Assistance Program. values: 2025-10-01: # The tanf aggregate includes ga_tanf and applies take-up; ga_tanf alone is diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml index dcdd2415dd6..a347a1d64c3 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml @@ -1,4 +1,4 @@ -description: Georgia counts these income sources of adult household members under the Low Income Home Energy Assistance Program. +description: Georgia uses these sources as countable income of adult household members under the Low Income Home Energy Assistance Program. # Interest is calculated separately after its disregard. TANF is counted once # at SPM-unit level. Each source is floored at zero in the formula, so a loss # in one source cannot offset another; net business inputs have no extra @@ -10,7 +10,7 @@ values: - sstb_self_employment_income - farm_operations_income - partnership_s_corp_income - - unemployment_compensation + - total_unemployment_compensation # Social Security is counted in the formula, net of the Medicare Part B # premium. - ssi @@ -54,7 +54,7 @@ metadata: reference: - title: "Georgia LIHEAP Policy Manual, January 2022, sections 1200.1-1200.7 and application item 12 #page 62-69,83" href: https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=62 - - title: "Georgia FY2026 LIHEAP State Plan, section 1.9 (current included and excluded sources) #page 5-6" + - title: "Georgia FY2026 LIHEAP State Plan, section 1.9 (current included and excluded sources) #page 5-7" href: https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=5 - title: Georgia FY2022 LIHEAP State Plan, section 1.9 (contemporaneous unchecked gifts, lottery and insurance categories) href: https://liheapch.acf.gov/sites/default/files/webfiles/docs/GA_Plan_2022.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/base_amount.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/base_amount.yaml index 815969b9e0d..843e4b57581 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/base_amount.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/base_amount.yaml @@ -15,5 +15,7 @@ metadata: label: Georgia LIHEAP Level II heating payment by fuel propagate_metadata_to_children: true reference: + - title: Georgia Benefit Matrix, FY2025 income guidelines, step 2 (heating amounts) + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/GA_BenefitMatrix_2025.pdf - title: Georgia Benefit Matrix, FY2026 income guidelines, step 2 (heating amounts) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/GA_BenefitMatrix_Heat-Cool_2026.pdf diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_uplift.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_uplift.yaml index 29aa5c064de..80e4d8d2244 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_uplift.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_uplift.yaml @@ -1,4 +1,7 @@ -description: Georgia increases the heating payment by this amount for households in income Level I under the Low Income Home Energy Assistance Program. +description: Georgia provides this additional heating payment to households in income Level I under the Low Income Home Energy Assistance Program. +# Inferred: every printed Level I heating amount is the Level II amount plus +# $100 on the FY2025 and FY2026 matrices; the matrices print both columns, +# not this rule. Encode the printed Level I column if a matrix breaks the pattern. values: 2025-10-01: 100 metadata: @@ -6,5 +9,7 @@ metadata: period: year label: Georgia LIHEAP lower income heating payment increase reference: + - title: Georgia Benefit Matrix, FY2025 income guidelines, step 2 (difference between Level I and Level II heating amounts) + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/GA_BenefitMatrix_2025.pdf - title: Georgia Benefit Matrix, FY2026 income guidelines, step 2 (difference between Level I and Level II heating amounts) href: https://liheapch.acf.gov/docs/2026/benefits-matricies/GA_BenefitMatrix_Heat-Cool_2026.pdf diff --git a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/maximum_table_size.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/maximum_table_size.yaml index 0765c12055c..38914c5ee48 100644 --- a/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/maximum_table_size.yaml +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/maximum_table_size.yaml @@ -1,4 +1,4 @@ -description: Georgia publishes Level I income thresholds through this household size under the Low Income Home Energy Assistance Program. +description: Georgia sets Level I income thresholds through this household size under the Low Income Home Energy Assistance Program. values: 2025-10-01: 16 metadata: @@ -6,5 +6,7 @@ metadata: period: year label: Georgia LIHEAP maximum published benefit table size reference: + - title: Georgia Benefit Matrix, FY2025 income guidelines, step 1 + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/GA_BenefitMatrix_2025.pdf - title: Georgia Benefit Matrix, FY2026 income guidelines, step 1 href: https://liheapch.acf.gov/docs/2026/benefits-matricies/GA_BenefitMatrix_Heat-Cool_2026.pdf diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml index 3965493457e..e3d9a8755f5 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml @@ -167,3 +167,38 @@ pre_subsidy_electricity_expense: [1_200, 1_200, 1_200, 1_200, 0, 1_200] output: ga_liheap_eligible: [true, true, true, true, false, false] + +# Manual page 4: "Renters who live in subsidized housing are eligible for assistance +# if their utility bill is in their name". Each household is one adult with $12,000 +# of wages (under the $34,549 limit), natural gas heat in rent, a subsidized tenant +# share of $3,000 ($250 a month), and tenant_pays_utilities false. +# Household 1: the $1,200 electric bill is the bill in the household's name, even +# though tenant_pays_utilities says utilities are in rent; the bill resolves the +# contradictory flag. The household is on the electric-vendor route, and the +# subsidy bars the direct-payment route. Natural gas Level II is $560; $12,000 is +# at or below the $13,589 Level I cutoff, so $560 + $100 = $660. +# Household 2: no bill. Page 4 ("Subsidized housing residents whose fuel cost is +# included in their rental payment are not eligible for assistance unless the +# applicant can verify an energy burden") and page 82 ("resides in subsidized +# housing and the rent includes utilities": "not eligible for the program") make it +# ineligible. +- name: Case 9, a subsidized heat in rent renter with an electric bill qualifies when utilities are flagged as in rent. + period: 2026 + input: + state_code: [GA, GA] + age: [40, 40] + immigration_status: [CITIZEN, CITIZEN] + employment_income: [12_000, 12_000] + ssi: [0, 0] + ga_tanf: [0, 0] + rent: [3_000, 3_000] + heating_type: [NATURAL_GAS, NATURAL_GAS] + heat_expense_included_in_rent: [true, true] + tenant_pays_utilities: [false, false] + receives_housing_assistance: [true, true] + is_in_public_housing: [false, false] + gas_expense: [0, 0] + pre_subsidy_electricity_expense: [1_200, 0] + output: + ga_liheap_eligible: [true, false] + ga_liheap: [660, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.yaml index 524ecaf70a8..0d772966a92 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.yaml @@ -7,11 +7,6 @@ absolute_error_margin: 0.01 input: state_code: [GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA] - heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, - ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, - ELECTRICITY] - 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] ga_liheap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] output: ga_liheap_income_limit: [34_549, 45_179, 55_810, 66_441, 77_071, 87_702, 89_695, 91_688, 93_681, @@ -24,8 +19,6 @@ absolute_error_margin: 0.01 input: state_code: [GA, AL] - heating_type: [ELECTRICITY, ELECTRICITY] - pre_subsidy_electricity_expense: [1_200, 1_200] ga_liheap_household_size: [1, 1] output: ga_liheap_income_limit: [34_549, 0] @@ -40,8 +33,18 @@ absolute_error_margin: 0.01 input: state_code: [GA, GA, GA] - heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY] - pre_subsidy_electricity_expense: [1_200, 1_200, 1_200] ga_liheap_household_size: [1, 4, 16] output: ga_liheap_income_limit: [33_038, 63_535, 102_926] + +# A unit with no qualified member has no Georgia income limit. Without the size-0 +# mask, the HHS size factor at size 0 (0.52) would give floor($66,441 x 0.52 = +# $34,549.32) = $34,549. +- name: Case 4, a unit with no qualified member has a zero income limit. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: GA + ga_liheap_household_size: 0 + output: + ga_liheap_income_limit: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml index 464a6f15589..7f6295d44b8 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml @@ -701,3 +701,101 @@ ga_liheap_income_limit: 33_038 ga_liheap_eligible: false ga_liheap: 0 + +# Manual section 1200.7 (page 67) and FY2026 plan item 1.9 (page 6) count +# unemployment compensation. The adult earned $12,000 before a layoff and drew +# Georgia's maximum weekly benefit, $365, for the maximum of 14 weeks: +# 14 x $365 = $5,110 (USDOL Significant Provisions of State UI Laws, January 2026, +# Georgia row: the weekly benefit is 1/42 of wages in the two highest base-period +# quarters, maximum $365; the maximum benefit is the "Lesser of 14 x WBA or 1/4 BPW"). +# The $365 maximum needs $15,330 in those two quarters ($365 x 42), about a $30,700 +# a year job before the layoff, and 1/4 of such base-period wages ($7,665) exceeds +# $5,110. Manual 1200.6 (page 64) also converts unemployment income with 14 weeks. +# Countable income: $12,000 + $5,110 = $17,110, above the $13,589 Level I cutoff and +# at or below the $34,549 limit, so FY2026 Level II electric pays $400. Without the +# unemployment compensation the award would be $500. +- name: Case 21, unemployment compensation counts toward the benefit level. + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + employment_income: 12_000 + unemployment_compensation: 5_110 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + ga_tanf: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_200 + households: + household: + members: [person1] + state_code: GA + output: + ga_liheap_income: 17_110 + ga_liheap_income_limit: 34_549 + ga_liheap_matrix_amount: 400 + ga_liheap_eligible: true + ga_liheap: 400 + +# 42 U.S.C. 8624(b)(2)(B) bars excluding a household on income below 110% of the +# poverty guideline. Four adults earn Georgia median annual wages (BLS OEWS May 2025): +# two cashiers at $29,070 and two fast food and counter workers at $26,920, so +# countable income is 2 x $29,070 + 2 x $26,920 = $58,140 + $53,840 = $111,980. +# The 13 children have no income, so the household size is 17. +# Printed-method limit: floor($66,441 x 1.65 = $109,627.65) = $109,627, where +# $66,441 = floor($110,736 x 0.6); the income exceeds it. +# Floor: LIHEAP IM 2025-02 Attachment 2 (2025 guidelines, mandatory in FY2026) gives +# 110% for 12 people as $83,765, plus $6,050 for each additional member: +# $83,765 + 5 x $6,050 = $114,015 (= 1.1 x ($15,650 + 16 x $5,500)). +# $111,980 is below $114,015, so the household stays eligible. +# $111,980 is above the size 16 Level I cutoff of $42,336, so Level II natural gas +# pays $560. +# Size 16 is not tested: its FY2026 floor exceeds the printed limit by only $331 +# ($107,634 to $107,965). +- name: Case 22, the federal poverty floor keeps a large household above the state median income limit eligible. + period: 2026 + input: + people: + person1: {age: 45, immigration_status: CITIZEN, employment_income: 29_070, ssi: 0} + person2: {age: 43, immigration_status: CITIZEN, employment_income: 29_070, ssi: 0} + person3: {age: 41, immigration_status: CITIZEN, employment_income: 26_920, ssi: 0} + person4: {age: 39, immigration_status: CITIZEN, employment_income: 26_920, ssi: 0} + person5: {age: 17, immigration_status: CITIZEN, ssi: 0} + person6: {age: 16, immigration_status: CITIZEN, ssi: 0} + person7: {age: 15, immigration_status: CITIZEN, ssi: 0} + person8: {age: 14, immigration_status: CITIZEN, ssi: 0} + person9: {age: 13, immigration_status: CITIZEN, ssi: 0} + person10: {age: 12, immigration_status: CITIZEN, ssi: 0} + person11: {age: 11, immigration_status: CITIZEN, ssi: 0} + person12: {age: 10, immigration_status: CITIZEN, ssi: 0} + person13: {age: 9, immigration_status: CITIZEN, ssi: 0} + person14: {age: 7, immigration_status: CITIZEN, ssi: 0} + person15: {age: 5, immigration_status: CITIZEN, ssi: 0} + person16: {age: 3, immigration_status: CITIZEN, ssi: 0} + person17: {age: 1, immigration_status: CITIZEN, ssi: 0} + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8, + person9, person10, person11, person12, person13, person14, person15, person16, + person17] + ga_tanf: 0 + heating_type: NATURAL_GAS + gas_expense: 1_800 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8, + person9, person10, person11, person12, person13, person14, person15, person16, + person17] + state_code: GA + output: + ga_liheap_household_size: 17 + ga_liheap_income: 111_980 + ga_liheap_income_limit: 109_627 + heating_expense: 1_800 + ga_liheap_matrix_amount: 560 + ga_liheap_eligible: true + ga_liheap: 560 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap_household_size.yaml index 410c036b1d5..bdb68c8e66b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap_household_size.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap_household_size.yaml @@ -30,14 +30,11 @@ spm_units: spm_unit: members: [person1, person2, person3, person4, person5] - heating_type: ELECTRICITY - pre_subsidy_electricity_expense: 1_200 ga_tanf: 0 households: household: members: [person1, person2, person3, person4, person5] state_code: GA - hud_utility_allowance: 0 output: ga_liheap_household_size: 3 @@ -58,13 +55,10 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: ELECTRICITY - pre_subsidy_electricity_expense: 1_200 ga_tanf: 0 households: household: members: [person1, person2] state_code: GA - hud_utility_allowance: 0 output: ga_liheap_household_size: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml index dc71549f679..cdf21f10d3d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml @@ -28,14 +28,11 @@ spm_units: spm_unit: members: [person1] - heating_type: ELECTRICITY - pre_subsidy_electricity_expense: 1_200 ga_tanf: 0 households: household: members: [person1] state_code: GA - hud_utility_allowance: 0 output: ga_liheap_income: 25_800 @@ -64,14 +61,11 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: ELECTRICITY - pre_subsidy_electricity_expense: 1_200 ga_tanf: 0 households: household: members: [person1, person2] state_code: GA - hud_utility_allowance: 0 output: ga_liheap_income: 12_000 @@ -100,14 +94,11 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: ELECTRICITY - pre_subsidy_electricity_expense: 1_200 ga_tanf: 0 households: household: members: [person1, person2] state_code: GA - hud_utility_allowance: 0 output: ga_liheap_income: 22_500 @@ -133,14 +124,11 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: ELECTRICITY - pre_subsidy_electricity_expense: 1_200 ga_tanf: 0 households: household: members: [person1, person2] state_code: GA - hud_utility_allowance: 0 output: ga_liheap_income: 700 @@ -151,8 +139,6 @@ absolute_error_margin: 0.01 input: state_code: [GA, GA, GA] - heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY] - pre_subsidy_electricity_expense: [1_200, 1_200, 1_200] age: [40, 40, 40] ssi: [0, 0, 0] ga_tanf: [0, 0, 0] @@ -180,14 +166,11 @@ spm_units: spm_unit: members: [person1, person2] - heating_type: ELECTRICITY - pre_subsidy_electricity_expense: 1_200 ga_tanf: 3_600 households: household: members: [person1, person2] state_code: GA - hud_utility_allowance: 0 output: ga_liheap_income: 4_800 @@ -208,14 +191,11 @@ spm_units: spm_unit: members: [person1] - heating_type: ELECTRICITY - pre_subsidy_electricity_expense: 1_200 ga_tanf: 0 households: household: members: [person1] state_code: GA - hud_utility_allowance: 0 output: ga_liheap_income: 6_000 @@ -243,13 +223,11 @@ spm_unit: members: [person1, person2] heating_type: ELECTRICITY - pre_subsidy_electricity_expense: 1_200 ga_tanf: 0 households: household: members: [person1, person2] state_code: GA - hud_utility_allowance: 0 output: ga_liheap_household_size: 1 ga_liheap_income: 12_000 @@ -274,8 +252,6 @@ medicare_part_b_premium: [2_434.8, 2_434.8] ssi: [0, 0] ga_tanf: [0, 0] - heating_type: [ELECTRICITY, ELECTRICITY] - pre_subsidy_electricity_expense: [1_800, 1_800] output: ga_liheap_income: [33_565.2, 20_000] @@ -294,9 +270,6 @@ medicare_part_b_premium: [0, 2_434.8] ssi: [0, 0] ga_tanf: [0, 0] - heating_type: [NATURAL_GAS, ELECTRICITY] - gas_expense: [1_200, 0] - pre_subsidy_electricity_expense: [0, 1_500] output: ga_liheap_income: [36_000, 36_000] @@ -318,9 +291,6 @@ farm_operations_income: [0, 0, -8_000] ssi: [0, 0, 0] ga_tanf: [0, 0, 0] - heating_type: [ELECTRICITY, ELECTRICITY, PROPANE] - pre_subsidy_electricity_expense: [1_500, 1_500, 0] - bottled_gas_expense: [0, 0, 1_500] output: ga_liheap_income: [40_000, 36_000, 0] @@ -365,14 +335,10 @@ spm_units: spm_unit1: members: [person1, person2, person3] - heating_type: NATURAL_GAS - gas_expense: 1_200 ga_tanf: 3_360 takes_up_tanf_if_eligible: true spm_unit2: members: [person4, person5, person6] - heating_type: NATURAL_GAS - gas_expense: 1_200 ga_tanf: 3_360 takes_up_tanf_if_eligible: false households: @@ -430,18 +396,12 @@ spm_units: spm_unit1: members: [person1] - heating_type: ELECTRICITY - pre_subsidy_electricity_expense: 1_200 ga_tanf: 0 spm_unit2: members: [person2] - heating_type: ELECTRICITY - pre_subsidy_electricity_expense: 1_200 ga_tanf: 0 spm_unit3: members: [person3, person4] - heating_type: ELECTRICITY - pre_subsidy_electricity_expense: 1_200 ga_tanf: 0 households: household1: @@ -470,3 +430,37 @@ ga_tanf: [0, 0] output: ga_liheap_income: [0, 6_000] + +# Deliberate source-pinning case: each remaining adult source has a distinct amount, +# so dropping any one source changes the total. Manual section 1200.7 includes +# alimony, child support and disability payments (page 65), strike benefits, +# unemployment compensation and veterans' benefits (page 67), and workers' +# compensation (page 68); FY2026 plan item 1.9 (page 6) counts General Assistance. +# $100 + $200 + $300 + $400 + $500 + $600 + $700 + $800 = $3,600. +- name: Case 15, the remaining adult income sources each count once. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + unemployment_compensation: 100 + veterans_benefits: 200 + disability_benefits: 300 + workers_compensation: 400 + strike_benefits: 500 + general_assistance: 600 + alimony_income: 700 + child_support_received: 800 + spm_units: + spm_unit: + members: [person1] + ga_tanf: 0 + households: + household: + members: [person1] + state_code: GA + output: + ga_liheap_income: 3_600 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml index 698c85748c3..13d335d7f77 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml @@ -13,12 +13,6 @@ NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] - 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] - 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] ga_liheap_household_size: [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16] ga_liheap_income: [13_589, 13589.01, 17_770, 17770.01, 21_952, 21952.01, 25_469, 25469.01, 30_315, @@ -39,12 +33,6 @@ state_code: [GA, GA, GA, GA, GA, GA, GA, GA, GA, GA] heating_type: [ELECTRICITY, NATURAL_GAS, FUEL_OIL, PROPANE, WOOD, ELECTRICITY, NATURAL_GAS, FUEL_OIL, PROPANE, WOOD] - pre_subsidy_electricity_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, - 1_200] - gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200] - fuel_oil_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200, 1_200] - bottled_gas_expense: [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] ga_liheap_household_size: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] ga_liheap_income: [0, 0, 0, 0, 0, 13589.01, 13589.01, 13589.01, 13589.01, 13589.01] output: @@ -58,7 +46,6 @@ input: state_code: GA heating_type: NATURAL_GAS - pre_subsidy_electricity_expense: 1_200 gas_expense: 50 ga_liheap_household_size: 1 ga_liheap_income: 0 @@ -73,7 +60,6 @@ input: state_code: GA heating_type: NONE - pre_subsidy_electricity_expense: 1_200 ga_liheap_household_size: 1 ga_liheap_income: 0 output: @@ -109,7 +95,6 @@ input: state_code: [GA, GA, GA, GA, GA, GA] heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] - gas_expense: [1_200, 1_200, 1_200, 1_200, 1_200, 1_200] ga_liheap_household_size: [1, 1, 4, 4, 16, 16] ga_liheap_income: [12_995, 12_995.01, 24_355, 24_355.01, 40_485, 40_485.01] output: @@ -124,7 +109,6 @@ input: state_code: [GA, GA, GA] heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] - gas_expense: [1_200, 1_200, 1_200] ga_liheap_household_size: [17, 17, 25] ga_liheap_income: [42_336, 42_336.01, 42_336] output: diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/_ga_liheap_housing.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/_ga_liheap_housing.py index 24f4b6ef1e5..963c7081bba 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/_ga_liheap_housing.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/_ga_liheap_housing.py @@ -24,18 +24,23 @@ def ga_liheap_has_electric_vendor_route(spm_unit, period): unsubsidized renters with heat in rent and their own electric bill an energy burden, "Paid to electric vendor because gas usage is indeterminate". Page 4 makes subsidized renters eligible when "their - utility bill is in their name", so this route ignores subsidy status; - subsidized heat-in-rent renters without a bill stay ineligible. - tenant_pays_utilities separates these renters from the rows where all - utilities are in rent, and a positive electric bill is the utility-bill - documentation. The pre-subsidy bill matches heating_expense and avoids - the program-dependent subsidies in electricity_expense. - Manual pages 4 and 89: - https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=89 + utility bill is in their name", so this route ignores subsidy status and + tenant_pays_utilities: a positive electric bill is the utility-bill + documentation. Page 82's ineligibility when "the rent includes utilities" + is read as rent covering every utility, which a separate electric bill + rules out. Unsubsidized renters whose rent includes all utilities take + the direct-payment route's lowest award instead. The FY2024 matrix note + ("The main fuel type is not always where the benefit is applied") pays + the main-fuel amount when the benefit goes to the electric bill. The + pre-subsidy bill matches heating_expense and avoids the + program-dependent subsidies in electricity_expense. + Manual pages 4, 82 and 89: + https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=4 + FY2024 matrix: + https://liheapch.acf.gov/docs/2024/benefits-matricies/GA_BenefitMatrix_2024.pdf """ return ( spm_unit("heat_expense_included_in_rent", period) - & spm_unit.household("tenant_pays_utilities", period) & (add(spm_unit, period, ["rent"]) > 0) & (spm_unit("pre_subsidy_electricity_expense", period) > 0) ) diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py index f07b693e15f..36ed4c7a6f3 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py @@ -1,4 +1,5 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg from policyengine_us.variables.gov.states.ga.dfcs.liheap.eligibility._ga_liheap_housing import ( ga_liheap_has_direct_payment_route, ga_liheap_has_electric_vendor_route, @@ -14,22 +15,37 @@ class ga_liheap_eligible(Variable): reference = ( # PDF pages 5, 8-9, 30 "https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=5", - # PDF pages 3-4, 50, 56-57, 75, 89 + # PDF pages 3-4, 50, 56-57, 75, 82, 89 "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=3", + "https://pamms.dhs.ga.gov/dfcs/_exports/liheap.pdf#page=4", + "https://www.law.cornell.edu/uscode/text/42/8624", ) def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ga.dfcs.liheap.eligibility + p_hhs = parameters(period).gov.hhs.liheap size = spm_unit("ga_liheap_household_size", period) income = spm_unit("ga_liheap_income", period) limit = spm_unit("ga_liheap_income_limit", period) + # 42 U.S.C. 8624(b)(2)(B) bars excluding a household on income below 110% + # of the poverty guideline. The 60% SMI limit falls below that floor from + # household size 16 in FY2025 and FY2026; the limit keeps the printed table. + state_group = spm_unit.household("state_group_str", period) + guideline = fpg(size, state_group, period, parameters, year_lag=p.fpg_year_lag) + income_eligible = (income <= limit) | (income < p_hhs.fpg_floor * guideline) heating_type = spm_unit("heating_type", period) heating_expense = spm_unit("heating_expense", period) # Explicit heating responsibility can qualify with no current bill. # A positive primary bill also establishes burden with heat in rent, - # including subsidized housing. Heat-in-rent renters also qualify when - # billed directly for electric, subsidized or not (manual pages 4 and - # 89), or when unsubsidized with all utilities in rent (page 89). The - # rent flag alone is insufficient. + # including subsidized housing (PAMMS chapter 100: "a vendor + # bill/statement for the main heating source"). Heat-in-rent renters + # also qualify when billed directly for electric, subsidized or not + # (manual pages 4 and 89), or when unsubsidized with all utilities in + # rent (page 89). The rent flag alone is insufficient. + # FY2026 plan item 2.3 (page 8) answers No to additional or differing + # eligibility policies for subsidized renters and renters with utilities in + # rent, while the manual applies energy-burden rules to both (pages 4, 57, + # 82 and 89); this follows the manual. energy_burden = ( spm_unit("has_heating_expense", period) | (heating_expense > 0) @@ -46,7 +62,7 @@ def formula(spm_unit, period, parameters): # see the manual conflict noted in ga_liheap_household_size. return ( (size > 0) - & (income <= limit) + & income_eligible & energy_burden & (heating_type != heating_type.possible_values.NONE) ) diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py index 3dcc20ff90f..a944a126407 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py @@ -7,8 +7,12 @@ class ga_liheap_household_size(Variable): definition_period = YEAR label = "Georgia LIHEAP household size" defined_for = StateCode.GA - # PDF pages 51-52, 62 - reference = "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=51" + reference = ( + # PDF pages 51-52, 62 + "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=51", + "https://pamms.dhs.ga.gov/dfcs/_exports/liheap.pdf#page=4", + "https://dfcs.georgia.gov/services/low-income-home-energy-assistance-program-liheap", + ) # Lawful immigrants count in the size (manual 101.2 page 3, 1100.3 page # 50, 1100.5 a page 51); undocumented members do not (1100.4 page 51: "Do @@ -17,6 +21,9 @@ class ga_liheap_household_size(Variable): # household members are non-U.S. citizens" as ineligible, and the glossary # (page 76) counts non-citizen members' income "but do not include in the # household size". This follows the specific eligibility sections. + # The current DFCS PAMMS chapter 100 requires households to "Be US citizens + # or lawfully admitted immigrant", and the DFCS LIHEAP page says "The + # applicant needs to be a U.S. citizen or legally admitted immigrant". # The SPM unit approximates the economic unit purchasing energy together. # Existing inputs do not identify all roomers, boarders, foster-child # elections, or excluded group-quarter arrangements. diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py index cd9e894a6de..76b433ef041 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py @@ -12,7 +12,7 @@ class ga_liheap_income(Variable): # The manual is followed where the plan conflicts. # PDF pages 62-69 "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=62", - # PDF pages 5-6 + # PDF pages 5-7 "https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=5", # Section 1.9: Social Security "Excluding MediCare deduction". "https://liheapch.acf.gov/docs/2025/state-plans/GA_Plan_2025.pdf#page=6", diff --git a/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py b/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py index de550ee615b..49cc22093ca 100644 --- a/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py @@ -15,6 +15,7 @@ class ga_liheap_matrix_amount(Variable): "https://liheapch.acf.gov/docs/2026/benefits-matricies/GA_BenefitMatrix_Heat-Cool_2026.pdf", "https://liheapch.acf.gov/docs/2026/state-plans/GA_Plan_2026.pdf#page=30", "https://liheapch.acf.gov/sites/default/files/webfiles/docs/2023/manuals/GA_PolicyManual_2023.pdf#page=89", + "https://liheapch.acf.gov/docs/2024/benefits-matricies/GA_BenefitMatrix_2024.pdf", ) def formula(spm_unit, period, parameters): @@ -45,11 +46,14 @@ def formula(spm_unit, period, parameters): base_amounts, default=0, ) - # Applicant payments use the lowest award in the income level (plan - # page 30). Heat-in-rent renters billed directly for electric, subsidized - # or not, are paid to the electric vendor (manual pages 4 and 89), so - # they keep the matrix step 2 amount for their main heating fuel and an - # unspecified fuel pays 0. + # Applicant payments use the lowest award in the income level (plan page 30). + # The uplift is the same for every fuel, so the lowest Level II amount plus + # the uplift is the lowest Level I amount. + # Heat-in-rent renters billed directly for electric, subsidized or not, are + # paid to the electric vendor (manual pages 4 and 89). The FY2024 matrix note + # says "The main fuel type is not always where the benefit is applied", so + # they keep the step 2 amount for their main heating fuel and an unspecified + # fuel pays 0. direct_payment = ga_liheap_has_direct_payment_route(spm_unit, period) base = where( direct_payment & (heating_type != fuel.NONE), @@ -59,5 +63,6 @@ def formula(spm_unit, period, parameters): # This is an ungated schedule amount with no cap at the reported bill. # Ordinary vendor payments for unpriced kerosene, coal, solar, other, # and unspecified fuels return an incomplete zero estimate; this is - # not a legal ineligibility finding. + # not a legal ineligibility finding. The unspecified default is + # tracked in #9754. return where(base > 0, base + lower_income * p.lower_income_uplift, 0)