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/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 new file mode 100644 index 00000000000..532a39b82e3 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/eligibility/smi_rate.yaml @@ -0,0 +1,12 @@ +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 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 new file mode 100644 index 00000000000..2febda5c356 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/minimum_age.yaml @@ -0,0 +1,10 @@ +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: + unit: year + period: year + label: Georgia LIHEAP countable income minimum age + reference: + - 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 new file mode 100644 index 00000000000..0684f255f85 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/monthly_interest_disregard.yaml @@ -0,0 +1,10 @@ +description: Georgia excludes 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.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 new file mode 100644 index 00000000000..c87dd3d39ff --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/household.yaml @@ -0,0 +1,13 @@ +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 + # the entitlement and would count benefits a nonrecipient could get. + - tanf +metadata: + unit: list + period: year + label: Georgia LIHEAP household income sources + reference: + - 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 new file mode 100644 index 00000000000..a347a1d64c3 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/income/sources/person.yaml @@ -0,0 +1,60 @@ +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 +# deduction. +values: + 2025-10-01: + - employment_income + - self_employment_income + - sstb_self_employment_income + - farm_operations_income + - partnership_s_corp_income + - total_unemployment_compensation + # 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 + # 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 + - general_assistance + - rental_income + - 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) 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: + unit: list + period: year + label: Georgia LIHEAP adult person income sources + 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-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 new file mode 100644 index 00000000000..843e4b57581 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/base_amount.yaml @@ -0,0 +1,21 @@ +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, 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_limit.yaml b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_limit.yaml new file mode 100644 index 00000000000..391374081a6 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_limit.yaml @@ -0,0 +1,63 @@ +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: + 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 + 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, 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/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..80e4d8d2244 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/lower_income_uplift.yaml @@ -0,0 +1,15 @@ +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: + unit: currency-USD + 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 new file mode 100644 index 00000000000..38914c5ee48 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ga/dfcs/liheap/payment/maximum_table_size.yaml @@ -0,0 +1,12 @@ +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: + unit: person + 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/programs.yaml b/policyengine_us/programs.yaml index 5250ab3aa89..525fad3fa7d 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -808,8 +808,16 @@ programs: category: Benefits agency: HHS status: partial - coverage: DC, Riverside County, MA, IL, IN, KS, KY, MD, MS, NC, NE, NJ, NY, OR, TX + coverage: DC, GA, Riverside County, MA, IL, IN, KS, KY, MD, MS, NC, NE, NJ, NY, OR, 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: "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: DC status: complete name: DC 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..e3d9a8755f5 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.yaml @@ -0,0 +1,204 @@ +# Georgia ordinary heating route only. Categorical recipient facts, utility credits, +# unpriced fuels and size-above-16 coverage remain documented limitations. + +# 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: + 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 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: + 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 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: + 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 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: + 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 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: + 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 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: + 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] + +# 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. +- 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] + +# 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] + +# 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 new file mode 100644 index 00000000000..0d772966a92 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.yaml @@ -0,0 +1,50 @@ +# Source-derived Georgia independent components; no final eligibility or total award asserted. + +# 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 + input: + state_code: [GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA, GA] + 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] + +# 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 + input: + state_code: [GA, AL] + 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] + 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 new file mode 100644 index 00000000000..7f6295d44b8 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap.yaml @@ -0,0 +1,801 @@ +# Georgia ordinary heating route only. Categorical recipient facts, utility credits, +# unpriced fuels and size-above-16 coverage remain documented limitations. + +# 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: + 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 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: + 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 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: + 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 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: + 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 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: + 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 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: + 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 + +# 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: + 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: 6_000 + ga_liheap_household_size: 1 + ga_liheap_matrix_amount: 500 + ga_liheap_eligible: true + ga_liheap: 500 + +# 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: + 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 + +# 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: + 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] + +# 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] + +# 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 + +# 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 + +# 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 new file mode 100644 index 00000000000..bdb68c8e66b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/ga_liheap_household_size.yaml @@ -0,0 +1,64 @@ +# Source-derived Georgia independent components; no final eligibility or total award asserted. + +# 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 + 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] + ga_tanf: 0 + households: + household: + members: [person1, person2, person3, person4, person5] + state_code: GA + 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] + ga_tanf: 0 + households: + household: + members: [person1, person2] + state_code: GA + 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..cdf21f10d3d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/income/ga_liheap_income.yaml @@ -0,0 +1,466 @@ +# Source-derived Georgia independent components; no final eligibility or total award asserted. + +# 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 + 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 + medicare_part_b_premium: 0 + capital_gains: 600 + spm_units: + spm_unit: + members: [person1] + ga_tanf: 0 + households: + household: + members: [person1] + state_code: GA + output: + ga_liheap_income: 25_800 + +# 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 + 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 + medicare_part_b_premium: 0 + ordinary_dividend_income: 600 + interest_income: 600 + spm_units: + spm_unit: + members: [person1, person2] + ga_tanf: 0 + households: + household: + members: [person1, person2] + state_code: GA + output: + ga_liheap_income: 12_000 + +# 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 + 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 + medicare_part_b_premium: 0 + ordinary_dividend_income: 600 + interest_income: 600 + spm_units: + spm_unit: + members: [person1, person2] + ga_tanf: 0 + households: + household: + members: [person1, person2] + state_code: GA + output: + ga_liheap_income: 22_500 + +# 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 + 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] + ga_tanf: 0 + households: + household: + members: [person1, person2] + state_code: GA + output: + ga_liheap_income: 700 + +# 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 + input: + state_code: [GA, GA, GA] + 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 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 + 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] + ga_tanf: 3_600 + households: + household: + members: [person1, person2] + state_code: GA + output: + ga_liheap_income: 4_800 + +# 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: + people: + person1: + age: 40 + immigration_status: CITIZEN + ssi: 0 + employment_income: 6_000 + self_employment_income: -18_000 + spm_units: + spm_unit: + members: [person1] + ga_tanf: 0 + households: + household: + members: [person1] + state_code: GA + output: + ga_liheap_income: 6_000 + +# 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 + 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 + medicare_part_b_premium: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + ga_tanf: 0 + 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: 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] + 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] + 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] + 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] + ga_tanf: 3_360 + takes_up_tanf_if_eligible: true + spm_unit2: + members: [person4, person5, person6] + 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] + +# 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] + ga_tanf: 0 + spm_unit2: + members: [person2] + ga_tanf: 0 + spm_unit3: + members: [person3, person4] + 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] + +# 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] + +# 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 new file mode 100644 index 00000000000..13d335d7f77 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.yaml @@ -0,0 +1,115 @@ +# Source-derived Georgia independent components; no final eligibility or total award asserted. + +# 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 + 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] + 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] + +# 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 + 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] + 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] + +# 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 + input: + state_code: GA + heating_type: NATURAL_GAS + gas_expense: 50 + ga_liheap_household_size: 1 + ga_liheap_income: 0 + output: + ga_liheap_matrix_amount: 660 + +# 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 + input: + state_code: GA + heating_type: NONE + ga_liheap_household_size: 1 + ga_liheap_income: 0 + output: + ga_liheap_matrix_amount: 0 + +# 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 + 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] + +# 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] + 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] + 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 new file mode 100644 index 00000000000..963c7081bba --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/_ga_liheap_housing.py @@ -0,0 +1,46 @@ +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) + ) + + +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 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) + & (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 new file mode 100644 index 00000000000..36ed4c7a6f3 --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_eligible.py @@ -0,0 +1,68 @@ +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, +) + + +class ga_liheap_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Georgia LIHEAP ordinary eligibility" + defined_for = StateCode.GA + 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, 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 (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) + | 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 + # 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. + # 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_eligible + & 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..8f2195450fa --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/eligibility/ga_liheap_income_limit.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.hhs_smi import liheap_smi_limit + + +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) + # 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) 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..a944a126407 --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/ga_liheap_household_size.py @@ -0,0 +1,30 @@ +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 + 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 + # 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 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. + 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..76b433ef041 --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/income/ga_liheap_income.py @@ -0,0 +1,57 @@ +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 = ( + # 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-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", + ) + + 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. + # 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 + # household's adult interest; dividends remain fully countable. + interest = max_( + adult_interest - p.monthly_interest_disregard * MONTHS_IN_YEAR, + 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 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 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..49cc22093ca --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/dfcs/liheap/payment/ga_liheap_matrix_amount.py @@ -0,0 +1,68 @@ +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): + 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", + "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): + 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_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, + heating_type == fuel.NATURAL_GAS, + heating_type == fuel.FUEL_OIL, + heating_type == fuel.PROPANE, + heating_type == fuel.WOOD, + ], + base_amounts, + default=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), + np.minimum.reduce(base_amounts), + base, + ) + # 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. The unspecified default is + # tracked in #9754. + return where(base > 0, base + lower_income * p.lower_income_uplift, 0)