diff --git a/changelog.d/ky-liheap.added.md b/changelog.d/ky-liheap.added.md new file mode 100644 index 00000000000..c5b821efd4a --- /dev/null +++ b/changelog.d/ky-liheap.added.md @@ -0,0 +1 @@ +Add Kentucky LIHEAP regular heating assistance for FY2025 and FY2026. diff --git a/policyengine_us/parameters/gov/states/ky/dcbs/liheap/earned_income_age.yaml b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/earned_income_age.yaml new file mode 100644 index 00000000000..f7ebe5568fc --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/earned_income_age.yaml @@ -0,0 +1,12 @@ +description: Kentucky excludes the earned income of household members below this age under the Low Income Home Energy Assistance Program. +values: + 2024-10-01: 18 +metadata: + unit: year + period: year + label: Kentucky LIHEAP earned income age + reference: + - title: Kentucky FY2026 state plan, Section 1.9, page 6 (earned income of a child under 18 not counted) + href: https://liheapch.acf.gov/docs/2026/state-plans/KY_Plan_2026.pdf#page=6 + - title: Kentucky FY2025 state plan, Section 1.9, page 6 + href: https://liheapch.acf.gov/docs/2025/state-plans/KY_Plan_2025.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/ky/dcbs/liheap/earned_income_sources.yaml b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/earned_income_sources.yaml new file mode 100644 index 00000000000..0d4219e68bb --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/earned_income_sources.yaml @@ -0,0 +1,21 @@ +description: Kentucky counts these earned income sources under the Low Income Home Energy Assistance Program. +values: + 2024-10-01: + - employment_income + # Net business and farm income are counted without another business + # deduction. + - self_employment_income + - sstb_self_employment_income + - partnership_s_corp_income + - farm_operations_income +metadata: + unit: list + period: year + label: Kentucky LIHEAP earned income sources + reference: + - title: Kentucky FY2026 state plan, Section 1.9, page 5 (wages, self-employment income and contract income) + href: https://liheapch.acf.gov/docs/2026/state-plans/KY_Plan_2026.pdf#page=5 + - title: Kentucky FY2025 state plan, Section 1.9, page 5 + href: https://liheapch.acf.gov/docs/2025/state-plans/KY_Plan_2025.pdf#page=5 + - title: 921 KAR 4:116 Section 1(8) + href: https://apps.legislature.ky.gov/law/kar/titles/921/004/116/ diff --git a/policyengine_us/parameters/gov/states/ky/dcbs/liheap/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/fpg_year_lag.yaml new file mode 100644 index 00000000000..5c8e3e3268e --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/fpg_year_lag.yaml @@ -0,0 +1,18 @@ +description: Kentucky uses the federal poverty guidelines from this many years before the heating season ending year under the Low Income Home Energy Assistance Program. +# The fact sheet publishes the limits without naming the guideline year. The +# lag is inferred from the amounts (1,957 = 150% of the 2025 one-person +# guideline / 12, rounded up); each workbook's poverty table shows the same +# one-year lag. +values: + 2024-10-01: 1 +metadata: + unit: year + period: year + label: Kentucky LIHEAP federal poverty guideline year lag + reference: + - title: Kentucky FY2025-2026 LIHEAP fact sheet, income eligibility + href: https://www.capky.org/wp-content/uploads/2026/01/2025-2026-LIHEAP-Fact-Sheet-V2.pdf + - title: Kentucky FY2026 benefit workbook, rev benefit income range worksheet, poverty guideline table + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/KY_BenefitMatrix_Heat-Cool_2026.xlsx + - title: Kentucky FY2025 benefit workbook, subsidy benefits worksheet, poverty guideline table + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/KY_BenefitMatrix_2025.xlsx diff --git a/policyengine_us/parameters/gov/states/ky/dcbs/liheap/income_limit.yaml b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/income_limit.yaml new file mode 100644 index 00000000000..fab713ae551 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/income_limit.yaml @@ -0,0 +1,14 @@ +description: Kentucky limits gross monthly household income to this share of the federal poverty guideline under the Low Income Home Energy Assistance Program. +values: + 2024-10-01: 1.5 +metadata: + unit: /1 + period: year + label: Kentucky LIHEAP income limit + reference: + - title: Kentucky FY2026 state plan, Section 2.1, page 8 + href: https://liheapch.acf.gov/docs/2026/state-plans/KY_Plan_2026.pdf#page=8 + - title: Kentucky FY2025 state plan, Section 2.1, page 8 + href: https://liheapch.acf.gov/docs/2025/state-plans/KY_Plan_2025.pdf#page=8 + - title: 921 KAR 4:116 Section 3(1)(b) (cabinet-approved increase above the 130 percent base) + href: https://apps.legislature.ky.gov/law/kar/titles/921/004/116/ diff --git a/policyengine_us/parameters/gov/states/ky/dcbs/liheap/maximum_table_size.yaml b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/maximum_table_size.yaml new file mode 100644 index 00000000000..64343a6fd4e --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/maximum_table_size.yaml @@ -0,0 +1,14 @@ +description: Kentucky sets income limits by household size up to this size under the Low Income Home Energy Assistance Program. +values: + 2024-10-01: 8 +metadata: + unit: person + period: year + label: Kentucky LIHEAP maximum table size + reference: + - title: Kentucky FY2025-2026 LIHEAP fact sheet, income eligibility (sizes 1 to 8) + href: https://www.capky.org/wp-content/uploads/2026/01/2025-2026-LIHEAP-Fact-Sheet-V2.pdf + - title: Kentucky FY2026 benefit workbook, rev benefit income range worksheet + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/KY_BenefitMatrix_Heat-Cool_2026.xlsx + - title: Kentucky FY2025 benefit workbook, subsidy benefits worksheet + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/KY_BenefitMatrix_2025.xlsx diff --git a/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/coal_adjustment.yaml b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/coal_adjustment.yaml new file mode 100644 index 00000000000..fc998431f15 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/coal_adjustment.yaml @@ -0,0 +1,25 @@ +description: Kentucky sets this coal heating point adjustment by household size under the Low Income Home Energy Assistance Program. +brackets: + - threshold: + 2024-10-01: 0 + amount: + 2024-10-01: 0 + - threshold: + 2024-10-01: 2 + amount: + 2024-10-01: -1 + - threshold: + 2024-10-01: 3 + amount: + 2024-10-01: 0 +metadata: + type: single_amount + threshold_unit: person + amount_unit: /1 + period: year + label: Kentucky LIHEAP coal household-size adjustment + reference: + - title: Kentucky FY2026 benefit workbook, Subsidy points worksheet, cells H8-H11 (two-person coal rows) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/KY_BenefitMatrix_Heat-Cool_2026.xlsx + - title: Kentucky FY2025 benefit workbook, Subsidy points worksheet, cells H8-H11 + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/KY_BenefitMatrix_2025.xlsx diff --git a/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/dollars_per_point.yaml b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/dollars_per_point.yaml new file mode 100644 index 00000000000..59e6fc81c09 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/dollars_per_point.yaml @@ -0,0 +1,12 @@ +description: Kentucky provides this amount per heating payment point, rounded up to whole dollars, under the Low Income Home Energy Assistance Program. +values: + 2024-10-01: 16.66 +metadata: + unit: currency-USD + period: year + label: Kentucky LIHEAP dollars per point + reference: + - title: Kentucky FY2026 benefit workbook, rev benefit income range worksheet, column L + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/KY_BenefitMatrix_Heat-Cool_2026.xlsx + - title: Kentucky FY2025 benefit workbook, subsidy benefits worksheet, column L + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/KY_BenefitMatrix_2025.xlsx diff --git a/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/fuel_points.yaml b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/fuel_points.yaml new file mode 100644 index 00000000000..fcb790ed2e6 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/fuel_points.yaml @@ -0,0 +1,34 @@ +description: Kentucky sets these heating payment points by primary heating fuel under the Low Income Home Energy Assistance Program. +UNSPECIFIED: + 2024-10-01: 0 +ELECTRICITY: + 2024-10-01: 6 +NATURAL_GAS: + 2024-10-01: 3 +FUEL_OIL: + 2024-10-01: 4 +KEROSENE: + 2024-10-01: 4 +PROPANE: + 2024-10-01: 5 +WOOD: + 2024-10-01: 1 +COAL: + 2024-10-01: 2 +SOLAR: + 2024-10-01: 0 +OTHER: + 2024-10-01: 0 +NONE: + 2024-10-01: 0 +metadata: + unit: /1 + period: year + label: Kentucky LIHEAP heating fuel points + breakdown: + - heating_type + reference: + - title: Kentucky FY2026 benefit workbook, Points Description worksheet, cells J2-J7 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/KY_BenefitMatrix_Heat-Cool_2026.xlsx + - title: Kentucky FY2025 benefit workbook, points worksheet, cells J2-J7 + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/KY_BenefitMatrix_2025.xlsx diff --git a/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/income_bands.yaml b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/income_bands.yaml new file mode 100644 index 00000000000..bae35f2a9b0 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/income_bands.yaml @@ -0,0 +1,12 @@ +description: Kentucky uses these federal poverty guideline shares as the upper boundaries of its first three heating payment income bands under the Low Income Home Energy Assistance Program. +values: + 2024-10-01: [0.75, 1, 1.25] +metadata: + unit: /1 + period: year + label: Kentucky LIHEAP income bands + reference: + - title: Kentucky FY2026 benefit workbook, rev benefit income range worksheet, income range boundaries (cells Q10-S10) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/KY_BenefitMatrix_Heat-Cool_2026.xlsx + - title: Kentucky FY2025 benefit workbook, subsidy benefits worksheet, income range boundaries (cells Q27-S27) + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/KY_BenefitMatrix_2025.xlsx diff --git a/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/income_points/subsidized.yaml b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/income_points/subsidized.yaml new file mode 100644 index 00000000000..0a99e65fe43 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/income_points/subsidized.yaml @@ -0,0 +1,38 @@ +description: Kentucky provides these income points by monthly income band to households in subsidized housing under the Low Income Home Energy Assistance Program. +# These values reproduce the published workbooks. Their subsidized rows +# reference the unsubsidized rows in reverse order (the lowest-income +# subsidized row takes 30% of the highest-income unsubsidized row, and so on), +# so the published subsidized payment rises with income. 921 KAR 4:116 Section +# 4(1)(c) says the household with the lowest income receives the highest +# benefit. The FY2025 and FY2026 workbooks both use the reversed ordering; +# which ordering agencies pay is unconfirmed. +brackets: + - threshold: + 2024-10-01: 0 + amount: + 2024-10-01: 1 + - threshold: + 2024-10-01: 1 + amount: + 2024-10-01: 2 + - threshold: + 2024-10-01: 2 + amount: + 2024-10-01: 3 + - threshold: + 2024-10-01: 3 + amount: + 2024-10-01: 4 +metadata: + type: single_amount + threshold_unit: /1 + amount_unit: /1 + period: year + label: Kentucky LIHEAP subsidized housing income points + reference: + - title: Kentucky FY2026 benefit workbook, rev benefit income range worksheet, cells E52-E55 (30 percent of the unsubsidized rows in reverse order) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/KY_BenefitMatrix_Heat-Cool_2026.xlsx + - title: Kentucky FY2025 benefit workbook, subsidy benefits worksheet, cells E45-E48 (same reversed ordering) + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/KY_BenefitMatrix_2025.xlsx + - title: 921 KAR 4:116 Section 4(1)(c) (lowest income receives the highest benefit; conflicts with the workbook ordering) + href: https://apps.legislature.ky.gov/law/kar/titles/921/004/116/ diff --git a/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/income_points/unsubsidized.yaml b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/income_points/unsubsidized.yaml new file mode 100644 index 00000000000..1b9da8a6a09 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/income_points/unsubsidized.yaml @@ -0,0 +1,29 @@ +description: Kentucky provides these income points by monthly income band to households outside subsidized housing under the Low Income Home Energy Assistance Program. +brackets: + - threshold: + 2024-10-01: 0 + amount: + 2024-10-01: 4 + - threshold: + 2024-10-01: 1 + amount: + 2024-10-01: 3 + - threshold: + 2024-10-01: 2 + amount: + 2024-10-01: 2 + - threshold: + 2024-10-01: 3 + amount: + 2024-10-01: 1 +metadata: + type: single_amount + threshold_unit: /1 + amount_unit: /1 + period: year + label: Kentucky LIHEAP unsubsidized housing income points + reference: + - title: Kentucky FY2026 benefit workbook, Points Description worksheet, cells H2-H5 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/KY_BenefitMatrix_Heat-Cool_2026.xlsx + - title: Kentucky FY2025 benefit workbook, points worksheet, cells H2-H5 + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/KY_BenefitMatrix_2025.xlsx diff --git a/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/maximum_household_points.yaml b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/maximum_household_points.yaml new file mode 100644 index 00000000000..927fd5a86cf --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/maximum_household_points.yaml @@ -0,0 +1,14 @@ +description: Kentucky limits household size points to this amount under the Low Income Home Energy Assistance Program. +values: + 2024-10-01: 5 +metadata: + unit: /1 + period: year + label: Kentucky LIHEAP maximum household points + reference: + - title: Kentucky FY2026 benefit workbook, Subsidy points worksheet, cell E28 (5 + 6 + 4) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/KY_BenefitMatrix_Heat-Cool_2026.xlsx + - title: Kentucky FY2025 benefit workbook, Subsidy points worksheet, cell E28 + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/KY_BenefitMatrix_2025.xlsx + - title: Kentucky FY2026 state plan, Section 2.6, page 9 ($250 maximum benefit) + href: https://liheapch.acf.gov/docs/2026/state-plans/KY_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/subsidized_housing_rate.yaml b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/subsidized_housing_rate.yaml new file mode 100644 index 00000000000..5743af5f2c7 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/payment/subsidized_housing_rate.yaml @@ -0,0 +1,12 @@ +description: Kentucky provides this share of the point-based heating payment to households in subsidized housing under the Low Income Home Energy Assistance Program. +values: + 2024-10-01: 0.3 +metadata: + unit: /1 + period: year + label: Kentucky LIHEAP subsidized housing rate + reference: + - title: Kentucky FY2026 benefit workbook, rev benefit income range worksheet, cell E52 (ROUNDUP(E8*0.3,0)) + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/KY_BenefitMatrix_Heat-Cool_2026.xlsx + - title: Kentucky FY2025 benefit workbook, subsidy benefits worksheet, cell E45 (ROUNDUP(E6*0.3,0)) + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/KY_BenefitMatrix_2025.xlsx diff --git a/policyengine_us/parameters/gov/states/ky/dcbs/liheap/social_security_medicare_premium_deduction.yaml b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/social_security_medicare_premium_deduction.yaml new file mode 100644 index 00000000000..25d94c72911 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/social_security_medicare_premium_deduction.yaml @@ -0,0 +1,15 @@ +description: Kentucky counts Social Security benefits net of the Medicare Part B premium under the Low Income Home Energy Assistance Program when this is true, and gross when it is false. +values: + # FY2025 plan, Section 1.9: "Including MediCare deduction" is checked. + 2024-10-01: false + # FY2026 plan, Section 1.9: "Excluding MediCare deduction" is checked. + 2025-10-01: true +metadata: + unit: bool + period: year + label: Kentucky LIHEAP Social Security Medicare premium deduction + reference: + - title: Kentucky FY2025 state plan, Section 1.9, page 6 + href: https://liheapch.acf.gov/docs/2025/state-plans/KY_Plan_2025.pdf#page=6 + - title: Kentucky FY2026 state plan, Section 1.9, page 6 + href: https://liheapch.acf.gov/docs/2026/state-plans/KY_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/ky/dcbs/liheap/unearned_income_sources.yaml b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/unearned_income_sources.yaml new file mode 100644 index 00000000000..4a6f6bb9760 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ky/dcbs/liheap/unearned_income_sources.yaml @@ -0,0 +1,46 @@ +description: Kentucky counts these unearned income sources under the Low Income Home Energy Assistance Program. +values: + 2024-10-01: + - unemployment_compensation + - strike_benefits + # Social Security is counted in the formula, gross for FY2025 and net of + # the Medicare Part B premium from FY2026 + # (social_security_medicare_premium_deduction). + - ssi + # Plan Section 17.4 lists the DCBS State Supplementation award letter as + # income documentation. + - ky_ssp + # Military retirement pay is a separate input from the pension inputs that + # pension_income adds, so both are listed; a household reports the amount + # in one or the other. + - pension_income + - military_retirement_pay + # Plan Section 17.4 lists Railroad Retirement records. + - railroad_benefits + - retirement_distributions + - financial_assistance + - gambling_winnings + - rental_income + # Plan Section 17.4 lists records of farm or rental income. + - farm_rent_income + - alimony_income + - child_support_received + - interest_income + - ordinary_dividend_income + # 921 KAR 4:116 Section 1(8) includes earned and unearned income; plan + # Section 1.9 counts insurance payments made directly to the insured. + - workers_compensation + - disability_benefits + - veterans_benefits + - salt_refund_income +metadata: + unit: list + period: year + label: Kentucky LIHEAP unearned income sources + reference: + - title: Kentucky FY2026 state plan, Section 1.9, page 6, and Section 17.4, page 34 + href: https://liheapch.acf.gov/docs/2026/state-plans/KY_Plan_2026.pdf#page=6 + - title: Kentucky FY2025 state plan, Section 1.9, page 6 + href: https://liheapch.acf.gov/docs/2025/state-plans/KY_Plan_2025.pdf#page=6 + - title: 921 KAR 4:116 Section 1(8) + href: https://apps.legislature.ky.gov/law/kar/titles/921/004/116/ diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 0f7c8b6ef14..ef4c5b64187 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -802,7 +802,7 @@ programs: category: Benefits agency: HHS status: partial - coverage: OR, DC, Riverside County, MA, IL, KS, MS, NE, TX + coverage: OR, DC, Riverside County, MA, IL, KS, KY, MS, NE, TX state_implementations: - state: OR status: in_progress @@ -831,6 +831,14 @@ programs: parameter_prefix: gov.states.ks.dcf.liheap verified_years: "2025-2026" notes: FY2025-FY2026 regular heating only. The FY2026 $130 minimum comes from the plan's estimated benefit levels; crisis, cooling and weatherization assistance are out of scope. + - state: KY + status: partial + name: Kentucky LIHEAP + full_name: Kentucky Low Income Home Energy Assistance Program + variable: ky_liheap + parameter_prefix: gov.states.ky.dcbs.liheap + verified_years: "2025-2026" + notes: FY2025-FY2026 regular heating only, from the published benefit workbooks. Crisis and cooling assistance are out of scope. - state: MS status: partial name: Mississippi LIHEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/integration.yaml new file mode 100644 index 00000000000..e1d621b5294 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/integration.yaml @@ -0,0 +1,324 @@ +# Sources: Kentucky FY2025 and FY2026 state plans, regular heating benefit +# workbooks, and the FY2025-2026 fact sheet. +- name: Case 1, the regular subsidy can exceed the bill and applies to heat included in rent. + period: 2026 + input: + state_code: [KY, KY, KY, KY, KY, IN] + age: [35, 35, 35, 35, 35, 35] + employment_income: [0, 0, 0, 30_000, 0, 0] + ssi: [0, 0, 0, 0, 0, 0] + social_security: [0, 0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0, 0] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [10, 0, 0, 1_000, 1_000, 1_000] + heat_expense_included_in_rent: [false, true, false, false, false, false] + is_citizen_or_legal_immigrant: [true, true, true, true, false, true] + output: + # Household 3 pays no heating cost, household 4 is over the one-person + # limit, household 5 has no citizen or qualified member, and household 6 + # lives in Indiana. + ky_liheap_eligible: [true, true, false, false, false, false] + ky_liheap: [134, 134, 0, 0, 0, 0] +- name: Case 2, public housing and housing assistance select the reduced schedule; a utility allowance schedule amount alone does not. + period: 2026 + input: + state_code: [KY, KY, KY] + ky_liheap_income: [0, 0, 0] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_000, 1_000, 1_000] + is_in_public_housing: [true, false, false] + receives_housing_assistance: [false, true, false] + hud_utility_allowance: [0, 0, 100] + output: + # One person, natural gas (3 fuel points), 0-75% band. + # Households 1-2 (subsidized): 1 + 1 + 3 = 5 points; + # ceil(5 * 16.66 = 83.3) = 84; ceil(84 * 0.3 = 25.2) = 26. + # Household 3: hud_utility_allowance is a schedule amount, not receipt of + # housing assistance, so the unsubsidized schedule applies: + # 1 + 4 + 3 = 8 points; ceil(8 * 16.66 = 133.28) = 134. + ky_liheap: [26, 26, 134] +# Backfilling checks the model convention, not verified historical Kentucky policy. +- name: Case 3, parameter backfill uses the 2021 guideline for a 2022 estimate. + period: 2022 + input: + state_code: KY + age: 40 + employment_income: 0 + ssi: 0 + social_security: 0 + tanf: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + receives_housing_assistance: false + is_in_public_housing: false + output: + ky_liheap: 184 + ky_liheap_fpg: 12_880 + ky_liheap_eligible: true +- name: Case 4, family of four with two earners heats with natural gas. + period: 2026 + input: + people: + person1: + age: 34 + # $15.00 an hour, 40 hours a week: 15 * 2_080 = 31_200. + employment_income: 31_200 + person2: + age: 32 + # $12.00 an hour, 20 hours a week: 12 * 1_040 = 12_480. + employment_income: 12_480 + person3: + age: 8 + person4: + age: 4 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + gas_expense: 840 + receives_housing_assistance: false + # The household does not receive K-TAP. + tanf: 0 + households: + household: + members: [person1, person2, person3, person4] + state_code: KY + is_in_public_housing: false + output: + spm_unit_size: 4 + # 2025 guideline (one-year lag): 15_650 + 3 * 5_500 = 32_150. + ky_liheap_fpg: 32_150 + # 31_200 + 12_480 = 43_680, or 3_640 a month. + ky_liheap_income: 43_680 + # Fact sheet limit for four: ceil(32_150 * 1.5 / 12 = 4_018.75) = 4_019. + ky_liheap_income_eligible: true + heating_expense: 840 + ky_liheap_eligible: true + # 3_640 a month is above ceil(32_150 * 1.25 / 12 = 3_348.96) = 3_349, so + # the 126.01-150% band gives 1 income point. + # 4 (size) + 1 (income) + 3 (natural gas) = 8 points; + # ceil(8 * 16.66 = 133.28) = 134, the workbook cell for four people, + # 126.01-150%, natural gas. + ky_liheap: 134 +- name: Case 5, parent and child with a housing voucher get the published $30 cell. + period: 2026 + input: + people: + person1: + age: 29 + # $12.00 an hour, 20 hours a week: 12 * 1_040 = 12_480. + employment_income: 12_480 + person2: + age: 5 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 720 + receives_housing_assistance: true + # The household does not receive K-TAP. + tanf: 0 + households: + household: + members: [person1, person2] + state_code: KY + is_in_public_housing: false + output: + spm_unit_size: 2 + # 2025 guideline (one-year lag): 15_650 + 5_500 = 21_150. + ky_liheap_fpg: 21_150 + # 12_480 a year, or 1_040 a month. + ky_liheap_income: 12_480 + # Fact sheet limit for two: ceil(21_150 * 1.5 / 12 = 2_643.75) = 2_644. + ky_liheap_income_eligible: true + heating_expense: 720 + ky_liheap_eligible: true + # 1_040 a month is not above ceil(21_150 * 0.75 / 12 = 1_321.875) = 1_322, + # so the 0-75% band applies; the subsidized schedule gives it 1 income + # point. + # 2 (size) + 1 (income) + 3 (natural gas) = 6 points; + # ceil(6 * 16.66 = 99.96) = 100; ceil(100 * 0.3 = 30) = 30. + # Workbook subsidized cell for two people, 0-75%, natural gas = 30, which + # is 30% of the unsubsidized two-person, 126.01-150%, natural gas cell + # (100). + ky_liheap: 30 +- name: Case 6, nine-person household qualifies under the additional-person limit. + period: 2026 + input: + people: + person1: + age: 41 + # $22.00 an hour, 40 hours a week: 22 * 2_080 = 45_760. + employment_income: 45_760 + person2: + age: 39 + # $16.00 an hour, 30 hours a week: 16 * 1_560 = 24_960. + employment_income: 24_960 + person3: + age: 70 + # $1_100 a month in Social Security retirement benefits. + social_security_retirement: 13_200 + ssi: 0 + person4: + age: 16 + # Earnings of a member under 18 are not counted. + employment_income: 2_400 + person5: + age: 14 + person6: + age: 11 + person7: + age: 9 + person8: + age: 6 + person9: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9] + heating_type: PROPANE + bottled_gas_expense: 1_800 + receives_housing_assistance: false + # The household does not receive K-TAP. + tanf: 0 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9] + state_code: KY + is_in_public_housing: false + output: + spm_unit_size: 9 + # 2025 guideline (one-year lag): 15_650 + 8 * 5_500 = 59_650. + ky_liheap_fpg: 59_650 + # 45_760 + 24_960 + 13_200 = 83_920, or 6_993.33 a month. + ky_liheap_income: 83_920 + # Eight-person limit: ceil(54_150 * 1.5 / 12 = 6_768.75) = 6_769. + # Additional person: ceil(5_500 * 1.5 / 12 = 687.5) = 688, the amount the + # fact sheet states. Nine-person limit: 6_769 + 688 = 7_457. + # 6_993.33 is above the eight-person limit and within the nine-person one. + ky_liheap_income_eligible: true + heating_expense: 1_800 + ky_liheap_eligible: true + # 6_993.33 a month is above ceil(59_650 * 1.25 / 12 = 6_213.54) = 6_214, so + # the 126.01-150% band gives 1 income point. + # min(9, 5) (size) + 1 (income) + 5 (propane) = 11 points; + # ceil(11 * 16.66 = 183.26) = 184, the workbook cell for five or more + # people, 126.01-150%, propane. + ky_liheap: 184 +- name: Case 7, FY2025 retiree with Social Security heats with natural gas. + period: 2025 + input: + people: + person1: + age: 68 + # $1_000 a month in Social Security retirement benefits. + social_security_retirement: 12_000 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + receives_housing_assistance: false + # The household does not receive K-TAP. + tanf: 0 + households: + household: + members: [person1] + state_code: KY + is_in_public_housing: false + output: + # 2024 guideline (one-year lag) for one person. + ky_liheap_fpg: 15_060 + # The FY2025 plan counts Social Security including the Medicare + # deduction, so the full 12_000 (1_000 a month) is counted. + ky_liheap_income: 12_000 + # FY2025 one-person limit: ceil(15_060 * 1.5 / 12 = 1_882.5) = 1_883, the + # FY2025 workbook's upper bound for the 126-150% band. + ky_liheap_income_eligible: true + heating_expense: 900 + ky_liheap_eligible: true + # 1_000 a month is above ceil(15_060 * 0.75 / 12 = 941.25) = 942 and not + # above ceil(15_060 / 12 = 1_255) = 1_255, so the 76-100% band gives 3 + # income points. + # 1 (size) + 3 (income) + 3 (natural gas) = 7 points; + # ceil(7 * 16.66 = 116.62) = 117, the FY2025 workbook cell for one + # person, 76-100%, natural gas. + ky_liheap: 117 + +- name: Case 8, FY2026 retiree qualifies once the Medicare Part B premium is deducted. + # FY2026 plan Section 1.9 (page 6) checks "Excluding MediCare deduction". + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + # $1_966.67 a month in Social Security retirement benefits. + social_security_retirement: 23_600 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + receives_housing_assistance: false + tanf: 0 + households: + household: + members: [person1] + state_code: KY + is_in_public_housing: false + output: + # 2025 guideline (one-year lag) for one person. + ky_liheap_fpg: 15_650 + # Countable income of 1_966.67 - 20 a month is above 135% of the poverty + # guideline, so no Medicare Savings Program pays the 2026 premium of + # 202.90 a month (2_434.80 a year). + medicare_part_b_premium: 2_434.8 + # 23_600 - 2_434.80 = 21_165.20, or 1_763.77 a month. + ky_liheap_income: 21_165.2 + # FY2026 one-person limit: ceil(15_650 * 1.5 / 12 = 1_956.25) = 1_957. + # The gross 1_966.67 would exceed it; the net 1_763.77 does not. + ky_liheap_income_eligible: true + heating_expense: 900 + ky_liheap_eligible: true + # 1_763.77 is above ceil(15_650 * 1.25 / 12 = 1_630.21) = 1_631, so the + # 126-150% band gives 1 income point. + # 1 (size) + 1 (income) + 3 (natural gas) = 5 points; + # ceil(5 * 16.66 = 83.3) = 84, the FY2026 workbook cell for one person, + # 126.01-150%, natural gas. + ky_liheap: 84 + +- name: Case 9, FY2025 retiree with the same premium is over the limit because FY2025 counts Social Security gross. + # FY2025 plan Section 1.9 (page 6) checks "Including MediCare deduction". + period: 2025 + input: + people: + person1: + age: 70 + # $1_891.67 a month in Social Security retirement benefits. + social_security_retirement: 22_700 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 900 + receives_housing_assistance: false + tanf: 0 + households: + household: + members: [person1] + state_code: KY + is_in_public_housing: false + output: + ky_liheap_fpg: 15_060 + # The 2025 premium of 185 a month (2_220 a year) is paid but not deducted. + medicare_part_b_premium: 2_220 + ky_liheap_income: 22_700 + # FY2025 one-person limit: ceil(15_060 * 1.5 / 12 = 1_882.5) = 1_883; + # 1_891.67 exceeds it. + ky_liheap_income_eligible: false + ky_liheap_eligible: false + ky_liheap: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/ky_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/ky_liheap.yaml new file mode 100644 index 00000000000..e34ce2c92ad --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/ky_liheap.yaml @@ -0,0 +1,116 @@ +# Sources: Kentucky FY2026 state plan, regular heating benefit workbook, and fact sheet. +- name: Case 1, published fuel amounts for one-person households. + period: 2026 + input: + state_code: [KY, KY, KY, KY, KY, KY] + ky_liheap_eligible: [true, true, true, true, true, true] + spm_unit_size: [1, 1, 1, 1, 1, 1] + heating_type: [ELECTRICITY, NATURAL_GAS, PROPANE, COAL, WOOD, FUEL_OIL] + ky_liheap_income: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + receives_housing_assistance: [false, false, false, false, false, false] + output: + ky_liheap: [184, 134, 167, 117, 100, 150] +- name: Case 2, two-person coal exception and the five-person points cap. + period: 2026 + input: + state_code: [KY, KY, KY, KY, KY, KY] + ky_liheap_eligible: [true, true, true, true, true, true] + spm_unit_size: [2, 4, 5, 6, 7, 8] + heating_type: [COAL, COAL, COAL, COAL, COAL, COAL] + ky_liheap_income: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + receives_housing_assistance: [false, false, false, false, false, false] + output: + ky_liheap: [117, 167, 184, 184, 184, 184] +- name: Case 3, the subsidized matrix increases across income bands. + period: 2026 + input: + state_code: [KY, KY, KY, KY] + ky_liheap_eligible: [true, true, true, true] + spm_unit_size: [1, 1, 1, 1] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY] + ky_liheap_income: [0.0, 11_748.12, 15_660.12, 19_572.12] + receives_housing_assistance: [true, true, true, true] + output: + ky_liheap: [41, 45, 51, 56] +- name: Case 4, the unsubsidized matrix decreases across income bands. + period: 2026 + input: + state_code: [KY, KY, KY, KY] + ky_liheap_eligible: [true, true, true, true] + spm_unit_size: [1, 1, 1, 1] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY] + ky_liheap_income: [0.0, 11_748.12, 15_660.12, 19_572.12] + receives_housing_assistance: [false, false, false, false] + output: + ky_liheap: [184, 167, 150, 134] +- name: Case 5, closed upper income bands use rounded monthly boundaries. + period: 2026 + input: + state_code: [KY, KY, KY, KY, KY, KY] + ky_liheap_eligible: [true, true, true, true, true, true] + spm_unit_size: [1, 1, 1, 1, 1, 1] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY] + ky_liheap_income: [11_748, 11_748.12, 15_660, 15_660.12, 19_572, 19_572.12] + output: + ky_liheap: [184, 167, 167, 150, 150, 134] +- name: Case 6, kerosene matches oil; unspecified and unsupported fuels have no schedule. + period: 2026 + input: + state_code: [KY, KY, KY, KY, KY] + ky_liheap_eligible: [true, true, true, true, true] + spm_unit_size: [1, 1, 1, 1, 1] + ky_liheap_income: [0, 0, 0, 0, 0] + heating_type: [KEROSENE, OTHER, SOLAR, NONE, UNSPECIFIED] + output: + ky_liheap: [150, 0, 0, 0, 0] +- name: Case 7, subsidized cells where 30 percent of the unsubsidized amount is a whole dollar. + period: 2026 + input: + state_code: [KY, KY, KY, KY] + ky_liheap_eligible: [true, true, true, true] + spm_unit_size: [1, 2, 5, 5] + heating_type: [WOOD, NATURAL_GAS, ELECTRICITY, ELECTRICITY] + # Monthly income: 900, 1_200, 2_000 and 4_300. + ky_liheap_income: [10_800, 14_400, 24_000, 51_600] + receives_housing_assistance: [true, true, false, false] + is_in_public_housing: [false, false, true, true] + output: + # Each published subsidized cell is 30% of an unsubsidized cell, rounded + # up. These four have a whole-dollar product, so rounding must not add a + # dollar. + # Household 1: one person, wood, 0-75% (900 <= 979). + # 1 + 1 + 1 = 3 points; ceil(3 * 16.66 = 49.98) = 50; 50 * 0.3 = 15. + # Workbook subsidized cell = 15; unsubsidized one-person, 126.01-150%, + # wood cell = 50. + # Household 2: two people, natural gas, 0-75% (1_200 <= 1_322). + # 2 + 1 + 3 = 6 points; ceil(6 * 16.66 = 99.96) = 100; 100 * 0.3 = 30. + # Workbook subsidized cell = 30; unsubsidized two-person, 126.01-150%, + # natural gas cell = 100. + # Household 3: five people, electricity, 0-75% (2_000 <= 2_354). + # 5 + 1 + 6 = 12 points; ceil(12 * 16.66 = 199.92) = 200; 200 * 0.3 = 60. + # Workbook subsidized cell = 60; unsubsidized five-person, 126.01-150%, + # electricity cell = 200. + # Household 4: five people, electricity, 126.01-150% (4_300 > 3_922). + # 5 + 4 + 6 = 15 points; ceil(15 * 16.66 = 249.9) = 250; 250 * 0.3 = 75. + # Workbook subsidized cell = 75; unsubsidized five-person, 0-75%, + # electricity cell = 250. + ky_liheap: [15, 30, 60, 75] +- name: Case 8, the coal adjustment ends at three people and carries into the subsidized schedule. + period: 2026 + input: + state_code: [KY, KY] + ky_liheap_eligible: [true, true] + spm_unit_size: [3, 2] + heating_type: [COAL, COAL] + ky_liheap_income: [0, 0] + receives_housing_assistance: [false, true] + output: + # Household 1: three people, coal, 0-75%, unsubsidized. + # 3 (size) + 4 (income) + 2 (coal) + 0 (adjustment) = 9 points; + # ceil(9 * 16.66 = 149.94) = 150, the workbook cell (rev benefit income + # range H15). + # Household 2: two people, coal, 0-75%, subsidized. + # 2 (size) + 1 (income) + 2 (coal) - 1 (two-person coal adjustment) = 4 + # points; ceil(4 * 16.66 = 66.64) = 67; ceil(67 * 0.3 = 20.1) = 21, the + # workbook cell (rev benefit income range H57). + ky_liheap: [150, 21] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/ky_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/ky_liheap_eligible.yaml new file mode 100644 index 00000000000..992a0cae29e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/ky_liheap_eligible.yaml @@ -0,0 +1,189 @@ +# Sources: 921 KAR 4:116 Sections 2, 3 and 8; Kentucky FY2026 state plan, +# regular heating benefit workbook, and fact sheet. +- name: Case 1, undocumented parents without a Social Security number make the household ineligible. + period: 2026 + input: + people: + person1: + age: 38 + immigration_status: UNDOCUMENTED + ssn_card_type: NONE + # $12.50 an hour, 40 hours a week: 12.5 * 2_080 = 26_000. + employment_income: 26_000 + person2: + age: 36 + immigration_status: UNDOCUMENTED + ssn_card_type: NONE + person3: + age: 10 + immigration_status: CITIZEN + person4: + age: 6 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_560 + receives_housing_assistance: false + # The household does not receive K-TAP. + tanf: 0 + households: + household: + members: [person1, person2, person3, person4] + state_code: KY + is_in_public_housing: false + output: + is_citizen_or_legal_immigrant: [false, false, true, true] + # Every member counts for household size and income. + spm_unit_size: 4 + ky_liheap_income: 26_000 + # Fact sheet limit for four: ceil(32_150 * 1.5 / 12 = 4_018.75) = 4_019; + # income is 2_166.67 a month. + ky_liheap_income_eligible: true + heating_expense: 1_560 + # 921 KAR 4:116 Section 2(1)(d) requires a Social Security number or a + # permanent residency card for each household member. The parents have + # neither, so the application is incomplete (Section 2(2)) and is denied + # (Section 8(3)). + ky_liheap_eligible: false + ky_liheap: 0 +- name: Case 2, a parent with a work-authorized Social Security number and a citizen child qualify. + period: 2026 + input: + people: + person1: + age: 30 + immigration_status: DACA + ssn_card_type: NON_CITIZEN_VALID_EAD + # $14.00 an hour, 40 hours a week: 14 * 2_080 = 29_120. + employment_income: 29_120 + person2: + age: 4 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_440 + receives_housing_assistance: false + # The household does not receive K-TAP. + tanf: 0 + households: + household: + members: [person1, person2] + state_code: KY + is_in_public_housing: false + output: + # The citizen child is the qualified member, and both members hold a + # Social Security number. + is_citizen_or_legal_immigrant: [false, true] + ky_liheap_income: 29_120 + # Fact sheet limit for two: ceil(21_150 * 1.5 / 12 = 2_643.75) = 2_644; + # income is 2_426.67 a month. + ky_liheap_income_eligible: true + ky_liheap_eligible: true + # 2_426.67 a month is above ceil(21_150 * 1.25 / 12 = 2_203.13) = 2_204, + # so the 126.01-150% band gives 1 income point. + # 2 (size) + 1 (income) + 6 (electricity) = 9 points; + # ceil(9 * 16.66 = 149.94) = 150, the workbook cell for two people, + # 126.01-150%, electricity. + ky_liheap: 150 +- name: Case 3, Social Security numbers alone do not qualify a household with no citizen or qualified noncitizen. + period: 2026 + input: + people: + person1: + age: 28 + immigration_status: DACA + ssn_card_type: NON_CITIZEN_VALID_EAD + # $11.00 an hour, 40 hours a week: 11 * 2_080 = 22_880. + employment_income: 22_880 + person2: + age: 27 + immigration_status: TPS + ssn_card_type: NON_CITIZEN_VALID_EAD + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 800 + receives_housing_assistance: false + # The household does not receive K-TAP. + tanf: 0 + households: + household: + members: [person1, person2] + state_code: KY + is_in_public_housing: false + output: + is_citizen_or_legal_immigrant: [false, false] + # 22_880 a year is 1_906.67 a month, within the two-person limit of 2_644. + ky_liheap_income_eligible: true + # Section 2(1)(d) is met, but LIHEAP is a federal public benefit and needs + # at least one citizen or qualified noncitizen member. + ky_liheap_eligible: false + ky_liheap: 0 +- name: Case 4, lawful permanent residents without a Social Security card qualify through the residency card. + period: 2026 + input: + people: + person1: + age: 64 + immigration_status: LEGAL_PERMANENT_RESIDENT + ssn_card_type: NONE + # An $800 a month pension. + taxable_private_pension_income: 9_600 + person2: + age: 62 + immigration_status: LEGAL_PERMANENT_RESIDENT + ssn_card_type: NONE + spm_units: + spm_unit: + members: [person1, person2] + heating_type: PROPANE + bottled_gas_expense: 1_200 + receives_housing_assistance: false + # The household does not receive K-TAP. + tanf: 0 + households: + household: + members: [person1, person2] + state_code: KY + is_in_public_housing: false + output: + is_citizen_or_legal_immigrant: [true, true] + ky_liheap_income: 9_600 + ky_liheap_income_eligible: true + # Section 2(1)(d) accepts a permanent residency card in place of a Social + # Security number. + ky_liheap_eligible: true + # 800 a month is not above ceil(21_150 * 0.75 / 12 = 1_321.875) = 1_322, + # so the 0-75% band gives 4 income points. + # 2 (size) + 4 (income) + 5 (propane) = 11 points; + # ceil(11 * 16.66 = 183.26) = 184, the workbook cell for two people, + # 0-75%, propane. + ky_liheap: 184 +- name: Case 5, heat included in rent with an unspecified fuel is eligible but has no payment schedule. + period: 2026 + input: + state_code: KY + age: 30 + # $12.00 an hour, 20 hours a week: 12 * 1_040 = 12_480. + employment_income: 12_480 + ssi: 0 + social_security: 0 + tanf: 0 + heating_type: UNSPECIFIED + heat_expense_included_in_rent: true + receives_housing_assistance: false + is_in_public_housing: false + output: + heating_expense: 0 + # 1_040 a month is within the one-person limit of 1_957. + ky_liheap_income_eligible: true + # 921 KAR 4:116 Section 3(2)(c): heating paid as an undesignated portion + # of the rent. + ky_liheap_eligible: true + # The workbook publishes no column for an unspecified fuel. + ky_liheap: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/ky_liheap_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/ky_liheap_income.yaml new file mode 100644 index 00000000000..696673bda4b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/ky_liheap_income.yaml @@ -0,0 +1,206 @@ +# Sources: Kentucky FY2025 and FY2026 state plans (Sections 1.8, 1.9 and +# 17.4) and 921 KAR 4:116 Section 1(8). +- name: Case 1, an adult business loss does not offset wages and child earnings are excluded. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 12_000 + self_employment_income: -3_000 + sstb_self_employment_income: -1_000 + unemployment_compensation: 1_200 + strike_benefits: 600 + ssi: 0 + social_security: 0 + person2: + age: 17 + employment_income: 4_000 + self_employment_income: 2_000 + sstb_self_employment_income: 2_000 + child_support_received: 2_400 + ssi: 0 + social_security: 0 + spm_units: + spm_unit: + members: [person1, person2] + tanf: 0 + households: + household: + members: [person1, person2] + state_code: KY + output: + ky_liheap_income: 16_200 +- name: Case 2, net business income is counted once without an additional expense deduction. + period: 2026 + input: + state_code: KY + age: 18 + employment_income: 12_000 + self_employment_income: 6_000 + sstb_self_employment_income: 3_000 + ssi: 0 + social_security: 0 + tanf: 0 + output: + # 12,000 wages + 6,000 ordinary business + 3,000 specified service business. + ky_liheap_income: 21_000 +- name: Case 3, Social Security is counted net of the Medicare Part B premium the person pays from FY2026. + # FY2026 plan Section 1.9 (page 6) checks "Excluding MediCare deduction". + # The 2026 standard Part B premium is 202.90 a month (2_434.80 a year). + # Household 1: 1_000 a month of Social Security. A Medicare Savings Program + # covers the premium (1_000 - 20 of countable income is under 135% of the + # poverty guideline), so nothing is withheld and 12_000 is counted. + # Household 2: 23_600 a year (1_966.67 a month) is above the Medicare + # Savings Program limit, so the premium is paid: + # 23_600 - 2_434.80 = 21_165.20. + # Household 3: 2_000 of Social Security and a 24_000 private pension. The + # premium exceeds the benefit, which is floored at zero: 0 + 24_000. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: [KY, KY, KY] + age: [70, 70, 70] + social_security_retirement: [12_000, 23_600, 2_000] + taxable_private_pension_income: [0, 0, 24_000] + ssi: [0, 0, 0] + tanf: [0, 0, 0] + output: + medicare_part_b_premium: [0, 2_434.8, 2_434.8] + ky_liheap_income: [12_000, 21_165.2, 24_000] +- name: Case 4, a rental loss or a farm loss does not offset other income. + period: 2026 + input: + state_code: [KY, KY] + age: [60, 45] + # Household 1: a 1_000 a month pension and a net loss on a rented house. + taxable_private_pension_income: [12_000, 0] + rental_income: [-3_000, 0] + # Household 2: $13.00 an hour, 40 hours a week (13 * 2_080 = 27_040) and + # a net loss from a small farm. + employment_income: [0, 27_040] + farm_operations_income: [0, -5_000] + ssi: [0, 0] + tanf: [0, 0] + output: + # Each source is floored at zero before the sources are added. + # Household 1: max(12_000, 0) + max(-3_000, 0) = 12_000. + # Household 2: max(27_040, 0) + max(-5_000, 0) = 27_040. + ky_liheap_income: [12_000, 27_040] +- name: Case 5, SSI, State Supplementation and K-TAP are counted. + period: 2026 + input: + people: + person1: + age: 61 + # 2026 SSI federal benefit rate: 994 a month. + ssi: 11_928 + # State Supplementation for caretaker services at home: the 1_056 + # standard less 994 of SSI is 62 a month. + ky_ssp: 744 + person2: + age: 35 + person3: + age: 9 + spm_units: + spm_unit: + members: [person1, person2, person3] + # K-TAP maximum payment for two eligible persons: 293 a month. + tanf: 3_516 + households: + household: + members: [person1, person2, person3] + state_code: KY + output: + # 11_928 (SSI) + 744 (State Supplementation) + 3_516 (K-TAP) = 16_188. + ky_liheap_income: 16_188 +- name: Case 6, farm operations income, farm rent and dividends are counted. + period: 2026 + input: + people: + person1: + age: 58 + farm_operations_income: 14_400 + farm_rent_income: 3_600 + qualified_dividend_income: 240 + non_qualified_dividend_income: 60 + ssi: 0 + person2: + age: 55 + person3: + age: 16 + # Farm earnings of a member under 18 are not counted. + farm_operations_income: 1_500 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: KY + output: + # Adult farm earnings: 14_400. + # Unearned: 3_600 (farm rent) + 240 + 60 (ordinary dividends) = 3_900. + # 14_400 + 3_900 = 18_300. + ky_liheap_income: 18_300 +- name: Case 7, service business profits and wage replacement income can exceed the income limit. + # 921 KAR 4:116 Section 1(8) counts earned and unearned income. Plan + # Section 1.9 includes insurance payments made directly to the insured. + period: 2026 + input: + state_code: [KY, KY, KY] + age: [40, 40, 40] + sstb_self_employment_income: [60_000, 0, 0] + workers_compensation: [0, 30_000, 0] + disability_benefits: [0, 0, 30_000] + ssi: [0, 0, 0] + social_security: [0, 0, 0] + tanf: [0, 0, 0] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_000, 1_000, 1_000] + output: + ky_liheap_income: [60_000, 30_000, 30_000] + # Each exceeds the one-person monthly ceiling of 1,957, annualized. + ky_liheap_eligible: [false, false, false] + ky_liheap: [0, 0, 0] +- name: Case 8, public pensions, railroad benefits and pass-through business income are counted. + # Plan Section 1.9 counts retirement and pension benefits and + # self-employment income; Section 17.4 lists Railroad Retirement records. + period: 2026 + input: + state_code: [KY, KY, KY] + age: [62, 45, 45] + # Household 1: $1_500 a month in military retirement pay, reported in + # military_retirement_pay, and $500 a month in railroad retirement + # benefits. + military_retirement_pay: [18_000, 0, 0] + railroad_benefits: [6_000, 0, 0] + # Households 2-3: $10.00 an hour, 40 hours a week (10 * 2_080 = 20_800) + # and a partnership share, a profit for one and a loss for the other. + employment_income: [0, 20_800, 20_800] + partnership_s_corp_income: [0, 15_000, -4_000] + ssi: [0, 0, 0] + social_security: [0, 0, 0] + tanf: [0, 0, 0] + output: + # Household 1: 18_000 + 6_000 = 24_000. + # Household 2: 20_800 + 15_000 = 35_800. + # Household 3: 20_800 + max(-4_000, 0) = 20_800. + ky_liheap_income: [24_000, 35_800, 20_800] + +- name: Case 9, FY2025 counts Social Security before the Medicare premium is withheld. + # FY2025 plan Section 1.9 (page 6) checks "Including MediCare deduction". + # 22_700 a year (1_891.67 a month) is above the Medicare Savings Program + # limit, so the 2025 premium of 185 a month (2_220 a year) is paid, but + # FY2025 does not deduct it. + period: 2025 + input: + state_code: KY + age: 70 + social_security_retirement: 22_700 + ssi: 0 + tanf: 0 + output: + medicare_part_b_premium: 2_220 + ky_liheap_income: 22_700 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/ky_liheap_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/ky_liheap_income_eligible.yaml new file mode 100644 index 00000000000..fc6cd3d9dd1 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/ky_liheap_income_eligible.yaml @@ -0,0 +1,32 @@ +# Sources: Kentucky FY2026 state plan, regular heating benefit workbook, and fact sheet. +- name: Case 1, published monthly limits, including the additional-person rule. + period: 2026 + input: + state_code: [KY, KY, KY, KY, KY, KY, KY, KY, KY, KY] + spm_unit_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + ky_liheap_income: [23_484, 31_728, 39_984, 48_228, 56_484, 64_728, 72_984, 81_228, 89_484, 97_740] + output: + ky_liheap_income_eligible: [true, true, true, true, true, true, true, true, true, true] +- name: Case 2, one dollar per month above the published limits. + period: 2026 + input: + state_code: [KY, KY, KY, KY, KY, KY, KY, KY, KY, KY] + spm_unit_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + ky_liheap_income: [23_496, 31_740, 39_996, 48_240, 56_496, 64_740, 72_996, 81_240, 89_496, 97_752] + output: + ky_liheap_income_eligible: [false, false, false, false, false, false, false, false, false, false] +- name: Case 3, twelve-person limit adds four additional-person amounts. + period: 2026 + input: + state_code: [KY, KY] + spm_unit_size: [12, 12] + # 9_521 and 9_522 a month. + ky_liheap_income: [114_252, 114_264] + output: + # 2025 guideline (one-year lag): 54_150 for eight people, 5_500 for each + # additional person. + # Eight-person limit: ceil(54_150 * 1.5 / 12 = 6_768.75) = 6_769. + # Additional person: ceil(5_500 * 1.5 / 12 = 687.5) = 688, the amount the + # fact sheet states. + # Twelve-person limit: 6_769 + 4 * 688 = 9_521 a month. + ky_liheap_income_eligible: [true, false] diff --git a/policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap.py b/policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap.py new file mode 100644 index 00000000000..8a98037829f --- /dev/null +++ b/policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap.py @@ -0,0 +1,66 @@ +from policyengine_us.model_api import * + + +class ky_liheap(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Kentucky LIHEAP regular heating assistance" + documentation = ( + "Verified for FY2025 and FY2026. Earlier years use model parameter " + "backfilling and are unverified historical estimates." + ) + defined_for = "ky_liheap_eligible" + reference = ( + "https://liheapch.acf.gov/docs/2026/benefits-matricies/KY_BenefitMatrix_Heat-Cool_2026.xlsx", + "https://liheapch.acf.gov/docs/2025/benefits-matricies/KY_BenefitMatrix_2025.xlsx", + # Sections 2.4-2.6 (pages 8-9). + "https://liheapch.acf.gov/docs/2026/state-plans/KY_Plan_2026.pdf#page=8", + # Section 4(1). + "https://apps.legislature.ky.gov/law/kar/titles/921/004/116/", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ky.dcbs.liheap.payment + income = spm_unit("ky_liheap_income", period) / MONTHS_IN_YEAR + fpg = spm_unit("ky_liheap_fpg", period) / MONTHS_IN_YEAR + size = spm_unit("spm_unit_size", period) + fuel = spm_unit("heating_type", period) + types = fuel.possible_values + # 921 KAR 4:116 Section 4(1)(e) lowers benefits for federally assisted + # housing or receipt of a utility allowance. A household receiving a + # utility allowance is already an assisted household. + subsidized = spm_unit( + "receives_housing_assistance", period + ) | spm_unit.household("is_in_public_housing", period) + band = 0 + for rate in p.income_bands: + band = band + (income > np.ceil(fpg * rate)) + income_points = where( + subsidized, + p.income_points.subsidized.calc(band), + p.income_points.unsubsidized.calc(band), + ) + fuel_points = p.fuel_points[fuel] + coal_adjustment = (fuel == types.COAL) * p.coal_adjustment.calc(size) + points = ( + min_(size, p.maximum_household_points) + + income_points + + fuel_points + + coal_adjustment + ) + # Reproduces every heating cell of the FY2026 "rev benefit income + # range" and FY2025 "subsidy benefits" worksheets, including the + # two-person coal exception and the reversed subsidized income points + # that conflict with 921 KAR 4:116 Section 4(1)(c) (see + # income_points/subsidized). The FY2026 sheet uses 2025 guidelines + # despite its stale title and matches the plan's $15-$250 range; the + # conditional "if no funding FFY26" sheet is not used. + amount = np.ceil(points * p.dollars_per_point) + amount = where(subsidized, np.ceil(amount * p.subsidized_housing_rate), amount) + # Fuels without a published column (OTHER, SOLAR, UNSPECIFIED, NONE) + # carry zero points, which also marks them as having no schedule. The + # full seasonal subsidy may credit the account, so it is not capped at + # the current bill. + return where(fuel_points > 0, amount, 0) diff --git a/policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap_eligible.py b/policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap_eligible.py new file mode 100644 index 00000000000..83a2b1cc83d --- /dev/null +++ b/policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap_eligible.py @@ -0,0 +1,48 @@ +from policyengine_us.model_api import * + + +class ky_liheap_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Kentucky LIHEAP regular heating eligibility" + defined_for = StateCode.KY + reference = ( + # Sections 2(1)(d), 2(2), 3(2) and 8(3). + "https://apps.legislature.ky.gov/law/kar/titles/921/004/116/", + # Sections 17.2-17.3 (pages 33-34). + "https://liheapch.acf.gov/docs/2026/state-plans/KY_Plan_2026.pdf#page=33", + ) + + def formula(spm_unit, period, parameters): + income_eligible = spm_unit("ky_liheap_income_eligible", period) + # 921 KAR 4:116 Section 3(2): the household pays for home heating + # directly or as an undesignated portion of the rent. + responsible = (spm_unit("heating_expense", period) > 0) | spm_unit( + "heat_expense_included_in_rent", period + ) + # LIHEAP is a federal public benefit, so at least one member must be a + # citizen or qualified noncitizen; plan Section 17.3 verifies that + # status. + has_qualified_member = ( + add(spm_unit, period, ["is_citizen_or_legal_immigrant"]) > 0 + ) + # 921 KAR 4:116 Section 2(1)(d) requires "a Social Security number, or + # a permanent residency card, for each household member". Section 2(2) + # treats the application as incomplete until it is received and + # Section 8(3) denies it after five working days, so a household with + # a member who has neither is ineligible. The plan's Section 17.2 + # checklist requires the card from adults and requests it from other + # members; the regulation's per-member rule applies here. Section 1(10) + # defines the household with no immigration carve-out, so every member + # counts for size and income. + person = spm_unit.members + ssn_card_type = person("ssn_card_type", period) + immigration_status = person("immigration_status", period) + has_ssn = ssn_card_type != ssn_card_type.possible_values.NONE + is_permanent_resident = ( + immigration_status + == immigration_status.possible_values.LEGAL_PERMANENT_RESIDENT + ) + all_documented = spm_unit.all(has_ssn | is_permanent_resident) + return income_eligible & responsible & has_qualified_member & all_documented diff --git a/policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap_fpg.py b/policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap_fpg.py new file mode 100644 index 00000000000..aaae247a81b --- /dev/null +++ b/policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap_fpg.py @@ -0,0 +1,22 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class ky_liheap_fpg(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Kentucky LIHEAP annual federal poverty guideline" + defined_for = StateCode.KY + reference = "https://www.capky.org/wp-content/uploads/2026/01/2025-2026-LIHEAP-Fact-Sheet-V2.pdf" + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ky.dcbs.liheap + size = spm_unit("spm_unit_size", period) + state_group = spm_unit.household("state_group_str", period) + # The annual period represents the heating season ending in that year. + # Only the guideline is lagged; household composition remains current. + return fpg( + max_(size, 1), state_group, period, parameters, year_lag=p.fpg_year_lag + ) diff --git a/policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap_income.py b/policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap_income.py new file mode 100644 index 00000000000..d4baf590fcd --- /dev/null +++ b/policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap_income.py @@ -0,0 +1,54 @@ +from policyengine_us.model_api import * + + +class ky_liheap_income(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "Kentucky LIHEAP countable household income" + defined_for = StateCode.KY + reference = ( + # Sections 1.8-1.9 (pages 5-6) and Section 17.4 (page 34). + "https://liheapch.acf.gov/docs/2026/state-plans/KY_Plan_2026.pdf#page=5", + # Sections 1.8-1.9 (pages 5-6). + "https://liheapch.acf.gov/docs/2025/state-plans/KY_Plan_2025.pdf#page=5", + "https://apps.legislature.ky.gov/law/kar/titles/921/004/116/", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ky.dcbs.liheap + person = spm_unit.members + # Annual income divided by twelve approximates the calendar month before + # application. SPM units approximate the energy-purchasing household. + adult = person("age", period) >= p.earned_income_age + # Use existing net business and farm income, without another business + # deduction. 921 KAR 4:116 Section 1(8) counts gross income received, so + # each earned and unearned source is floored at zero and a loss in one + # source cannot offset another. + earned = 0 + for source in p.earned_income_sources: + earned = earned + max_(person(source, period), 0) + earned = earned * adult + unearned = 0 + for source in p.unearned_income_sources: + unearned = unearned + max_(person(source, period), 0) + # Plan Section 1.9 counts Social Security "Including MediCare deduction" + # for FY2025 and "Excluding MediCare deduction" from FY2026, so from + # FY2026 the benefit is counted net of the Part B premium the person + # pays. 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. + social_security = max_(person("social_security", period), 0) + if p.social_security_medicare_premium_deduction: + premium = person("medicare_part_b_premium", period) + social_security = max_(social_security - premium, 0) + # Excluded WIA and work-study earnings, jury duty, settlements, other + # insurance payments, royalties, deposits and non-taxable refunds cannot + # be isolated with existing inputs. + # The annual tanf aggregate includes ky_ktap and applies take-up; ky_ktap + # alone represents monthly entitlement and would count benefits a + # nonrecipient could get. + return spm_unit.sum(earned + unearned + social_security) + spm_unit( + "tanf", period + ) diff --git a/policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap_income_eligible.py b/policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap_income_eligible.py new file mode 100644 index 00000000000..15e74332cb8 --- /dev/null +++ b/policyengine_us/variables/gov/states/ky/dcbs/liheap/ky_liheap_income_eligible.py @@ -0,0 +1,49 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class ky_liheap_income_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Kentucky LIHEAP income eligibility" + defined_for = StateCode.KY + reference = ( + "https://www.capky.org/wp-content/uploads/2026/01/2025-2026-LIHEAP-Fact-Sheet-V2.pdf", + # Section 2.1 (page 8). + "https://liheapch.acf.gov/docs/2026/state-plans/KY_Plan_2026.pdf#page=8", + # Section 3(1). + "https://apps.legislature.ky.gov/law/kar/titles/921/004/116/", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ky.dcbs.liheap + size = spm_unit("spm_unit_size", period) + state_group = spm_unit.household("state_group_str", period) + table_size = clip(size, 1, p.maximum_table_size) + additional_people = max_(size - p.maximum_table_size, 0) + guideline = fpg( + table_size, state_group, period, parameters, year_lag=p.fpg_year_lag + ) + additional_guideline = ( + fpg( + table_size + 1, + state_group, + period, + parameters, + year_lag=p.fpg_year_lag, + ) + - guideline + ) + # The fact sheet states "Add $688 for each additional family member" + # without a derivation. Rounding the additional-person guideline up at + # the income limit rate reproduces $688 for FY2026 and all eight + # published limits. The workbook's own table for sizes 9-14 rounds each + # whole limit instead and differs by up to $3 (for example $8,144 + # against $8,145 at size 10); the fact sheet is followed. + monthly_limit = np.ceil(guideline * p.income_limit / MONTHS_IN_YEAR) + monthly_increment = np.ceil( + additional_guideline * p.income_limit / MONTHS_IN_YEAR + ) + income = spm_unit("ky_liheap_income", period) / MONTHS_IN_YEAR + return income <= monthly_limit + additional_people * monthly_increment