diff --git a/changelog.d/sc-liheap-cooling.added.md b/changelog.d/sc-liheap-cooling.added.md new file mode 100644 index 00000000000..b0c411f556b --- /dev/null +++ b/changelog.d/sc-liheap-cooling.added.md @@ -0,0 +1 @@ +Add South Carolina LIHEAP regular cooling assistance (the cooling-season nonemergency award). diff --git a/policyengine_us/parameters/gov/hhs/liheap/regular_payments.yaml b/policyengine_us/parameters/gov/hhs/liheap/regular_payments.yaml index e811bea6fbd..a9d25199e90 100644 --- a/policyengine_us/parameters/gov/hhs/liheap/regular_payments.yaml +++ b/policyengine_us/parameters/gov/hhs/liheap/regular_payments.yaml @@ -44,6 +44,7 @@ values: - pa_liheap # Pennsylvania LIHEAP - ri_liheap # Rhode Island LIHEAP - sc_liheap # South Carolina LIHEAP + - sc_liheap_cooling # South Carolina LIHEAP cooling - sd_liheap # South Dakota LIEAP - tn_liheap # Tennessee LIHEAP - tx_ceap # Texas CEAP diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/cooling/amounts.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/cooling/amounts.yaml new file mode 100644 index 00000000000..204d07e3e8e --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/cooling/amounts.yaml @@ -0,0 +1,39 @@ +description: South Carolina provides these base and supplemental regular cooling amounts under the Low Income Home Energy Assistance Program. +# The cooling season (May 1 to September 30) pays no fuel supplement. The FY2027 +# plan keeps the child, disabled and veteran supplements at 50. Its items 3.3, +# 3.5 and 3.6 give a 300 base; only the item 3.3 "Other" row prints 200. The +# 2026 Benefit Matrix covers FY2026 only, so the FY2027 values rest on the plan. +base: + 2025-10-01: 200 + 2026-10-01: 300 +elderly: + 2025-10-01: 75 + 2026-10-01: 50 +child: + 2025-10-01: 50 +disabled: + 2025-10-01: 50 +veteran: + 2025-10-01: 50 +low_income: + 2025-10-01: 175 + 2026-10-01: 150 +high_burden: + 2025-10-01: 175 + 2026-10-01: 200 +maximum: + 2025-10-01: 775 + 2026-10-01: 850 +metadata: + unit: currency-USD + period: year + label: South Carolina LIHEAP regular cooling amounts + reference: + - title: "South Carolina FY2026 LIHEAP Detailed Model Plan, items 3.3, 3.5 and 3.6 #page 10-11" + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=10 + - title: "South Carolina FY2027 LIHEAP Detailed Model Plan, items 3.3, 3.5 and 3.6 #page 10-11" + href: https://liheapch.acf.gov/docs/2027/state-plans/SC_Plan_2027.pdf#page=10 + - title: South Carolina 2026 Benefit Matrix, nonemergency benefits + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/SC_BenefitMatrix_2026.pdf#page=1 + - title: "South Carolina 2024 Administrative Guide, Section 3A, Direct Assistance benefit levels #page 536,538" + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=536 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/eligibility/smi_rate.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/eligibility/smi_rate.yaml index 257297b9d61..5ac52f3eb70 100644 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/eligibility/smi_rate.yaml +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/eligibility/smi_rate.yaml @@ -6,5 +6,7 @@ metadata: period: year label: South Carolina LIHEAP SMI limit rate reference: - - title: South Carolina FY2026 LIHEAP Detailed Model Plan, item 2.1 + - title: "South Carolina FY2026 LIHEAP Detailed Model Plan, items 2.1 and 3.1 #page 8,10" href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8 + - title: "South Carolina FY2027 LIHEAP Detailed Model Plan, items 2.1 and 3.1 #page 8,10" + href: https://liheapch.acf.gov/docs/2027/state-plans/SC_Plan_2027.pdf#page=8 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/ages.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/ages.yaml index fe03cbbf206..c6242dfb718 100644 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/ages.yaml +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/ages.yaml @@ -1,4 +1,4 @@ -description: South Carolina uses these age thresholds for regular heating supplements under the Low Income Home Energy Assistance Program. +description: South Carolina uses these age thresholds for regular heating and cooling supplements under the Low Income Home Energy Assistance Program. elderly_min: 2025-10-01: 60 # The child maximum is completed age; five-year-olds remain covered until six. @@ -9,5 +9,7 @@ metadata: period: year label: South Carolina LIHEAP supplement age thresholds reference: - - title: "South Carolina FY2026 LIHEAP Detailed Model Plan, items 2.3-2.5 #page 8-9" + - title: "South Carolina FY2026 LIHEAP Detailed Model Plan, items 2.3-2.5 and 3.3-3.5 #page 8-11" href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8 + - title: "South Carolina FY2027 LIHEAP Detailed Model Plan, items 2.3-2.5 and 3.3-3.5 #page 8-11" + href: https://liheapch.acf.gov/docs/2027/state-plans/SC_Plan_2027.pdf#page=8 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/burden_rate.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/burden_rate.yaml index f4f10183689..e1877e10a1a 100644 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/burden_rate.yaml +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/burden_rate.yaml @@ -6,5 +6,7 @@ metadata: period: year label: South Carolina LIHEAP high energy burden rate reference: - - title: South Carolina FY2026 LIHEAP Detailed Model Plan, item 2.5 + - title: "South Carolina FY2026 LIHEAP Detailed Model Plan, items 2.5 and 3.5 #page 9,11" href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=9 + - title: "South Carolina FY2027 LIHEAP Detailed Model Plan, items 2.5 and 3.5 #page 9,11" + href: https://liheapch.acf.gov/docs/2027/state-plans/SC_Plan_2027.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/energy_cost_sources.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/energy_cost_sources.yaml index a83e8bb3e8c..f43827620a6 100644 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/energy_cost_sources.yaml +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/energy_cost_sources.yaml @@ -13,7 +13,9 @@ metadata: period: year label: South Carolina LIHEAP home energy cost sources reference: - - title: South Carolina FY2026 LIHEAP Detailed Model Plan, item 2.5 + - title: "South Carolina FY2026 LIHEAP Detailed Model Plan, items 2.5 and 3.5 #page 9,11" href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=9 + - title: "South Carolina FY2027 LIHEAP Detailed Model Plan, items 2.5 and 3.5 #page 9,11" + href: https://liheapch.acf.gov/docs/2027/state-plans/SC_Plan_2027.pdf#page=9 - title: "South Carolina 2024 Administrative Guide, home energy bill #page 536-537" href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=536 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_limit_rate.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_limit_rate.yaml index 7ee2326d89b..aace5a43901 100644 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_limit_rate.yaml +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_limit_rate.yaml @@ -6,7 +6,9 @@ metadata: period: year label: South Carolina LIHEAP low-income limit rate reference: - - title: "South Carolina FY2026 LIHEAP Detailed Model Plan, items 2.3 and 2.5 #page 8-9" + - title: "South Carolina FY2026 LIHEAP Detailed Model Plan, items 2.3, 2.5, 3.3 and 3.5 #page 8-11" href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8 + - title: "South Carolina FY2027 LIHEAP Detailed Model Plan, items 2.3, 2.5, 3.3 and 3.5 #page 8-11" + href: https://liheapch.acf.gov/docs/2027/state-plans/SC_Plan_2027.pdf#page=8 - title: "South Carolina draft 2026 LIHEAP Administrative Guide, plan attachment #page 311-312,338" href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=311 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/max_awards.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/max_awards.yaml new file mode 100644 index 00000000000..fed64ad20dd --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/max_awards.yaml @@ -0,0 +1,17 @@ +description: South Carolina limits the number of awards per program year to this amount under the Low Income Home Energy Assistance Program. +# The limit also counts crisis assistance, which is not modeled, so here it +# limits the heating and cooling awards. +values: + 2025-10-01: 2 + 2026-10-01: 1 +metadata: + unit: int + period: year + label: South Carolina LIHEAP maximum awards per program year + reference: + - title: South Carolina FY2026 LIHEAP Detailed Model Plan, item 11.5 + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=26 + - title: "South Carolina draft 2026 and 2024 LIHEAP Administrative Guides, Section 3A, Frequency of LIHEAP Services #page 310,536" + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=310 + - title: "South Carolina FY2027 LIHEAP Detailed Model Plan, items 1.1 and 11.5 #page 4,26" + href: https://liheapch.acf.gov/docs/2027/state-plans/SC_Plan_2027.pdf#page=4 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 6f704d520fb..0f38d580dbe 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -1357,7 +1357,15 @@ programs: variable: sc_liheap parameter_prefix: gov.states.sc.oeo.liheap verified_years: "2026" - notes: FY2026 regular heating base amount with demographic, fuel, low-income and high-burden supplements. Crisis, cooling, weatherization and equipment assistance are out of scope. + notes: FY2026 regular heating base amount with demographic, fuel, low-income and high-burden supplements; the cooling-season award is the separate South Carolina LIHEAP cooling entry. Crisis, weatherization and equipment assistance are out of scope. + - state: SC + status: partial + name: South Carolina LIHEAP cooling + full_name: South Carolina Low Income Home Energy Assistance Program cooling-season Direct Assistance + variable: sc_liheap_cooling + parameter_prefix: gov.states.sc.oeo.liheap + verified_years: "2026-2027" + notes: FY2026-FY2027 cooling-season nonemergency award, the base plus demographic, low-income and energy-burden supplements with no fuel supplement, up to the cooling maximum, paid alongside the heating award in FY2026. Local frequency limits, local funding decisions, crisis and equipment assistance are out of scope. - state: SD status: partial name: South Dakota LIEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/cooling/sc_liheap_cooling.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/cooling/sc_liheap_cooling.yaml new file mode 100644 index 00000000000..b0d6645bee9 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/cooling/sc_liheap_cooling.yaml @@ -0,0 +1,398 @@ +# Sources: South Carolina FY2026 and FY2027 LIHEAP plans, items 1.1, 3.3-3.6 +# and 11.5; 2026 Benefit Matrix; OEO 2024 Administrative Guide, Section 3A. +# FY2026 allows two awards in the program year, so cooling is paid in full. +# FY2027 allows one, so in 2027 cooling pays only its excess over the heating +# award, which uses the FY2026 heating amounts carried forward: base 200, +# elderly 75, disabled 50, child 50, veteran 50, low income 175, energy burden +# 175, gas, oil, kerosene or propane fuel 75, maximum 850. +# FY2026 cooling: base 200, elderly 75, disabled 50, child 50, veteran 50, +# low income 175, energy burden 175, no fuel supplement, maximum 775. +# FY2027 cooling: base 300, elderly 50, disabled 50, child 50, veteran 50, +# low income 150, energy burden 200, maximum 850. +# Income limits (60% of SMI): FY2026 31,993 for one person and 41,837 for two; +# FY2027 33,218 and 43,439. The low-income tier is 30% of the limit: FY2026 +# 9,597.90 and 12,551.10; FY2027 9,965.40 and 13,031.70. Energy burden: annual +# energy bills at least 20% of income. +# Wages are hourly pay times hours. Electric bills of 1,800 are 150 a month +# and gas bills of 600 are 50 a month. + +- name: Case 1, the cooling base pays no fuel supplement, including for homes without heating. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + # 10.00 an hour, 40 hours a week: 10 * 2_080 = 20_800. + employment_income_before_lsr: 20_800 + person2: + age: 40 + employment_income_before_lsr: 20_800 + spm_units: + spm_unit1: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 600 + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + spm_unit2: + members: [person2] + heating_type: NONE + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + households: + household1: + members: [person1] + state_code: SC + household2: + members: [person2] + state_code: SC + output: + # 20_800 is above 9_597.90, and bills of 2_400 and 1_800 are under + # 20_800 * 0.2 = 4_160, so only the base applies. + sc_liheap_cooling_eligible: [true, true] + sc_liheap_cooling: [200, 200] + # Heating adds the 75 natural gas supplement: 200 + 75 = 275. + sc_liheap: [275, 200] + +- name: Case 2, each FY2026 cooling supplement applies alone. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 67 + # 2_000 a month. + social_security_retirement: 24_000 + person2: + age: 30 + employment_income_before_lsr: 20_800 + person3: + age: 3 + person4: + age: 45 + is_disabled: true + employment_income_before_lsr: 20_800 + # Does not receive SSI. + ssi: 0 + person5: + age: 45 + is_veteran: true + employment_income_before_lsr: 20_800 + person6: + age: 40 + # 7.25 an hour, 24 hours a week: 7.25 * 1_248 = 9_048. + employment_income_before_lsr: 9_048 + person7: + age: 40 + # 10.00 an hour, 20 hours a week: 10 * 1_040 = 10_400. + employment_income_before_lsr: 10_400 + spm_units: + spm_unit1: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + spm_unit2: + members: [person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + spm_unit3: + members: [person4] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + spm_unit4: + members: [person5] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + spm_unit5: + members: [person6] + heating_type: ELECTRICITY + # 125 a month. + pre_subsidy_electricity_expense: 1_500 + has_cooling_expense: true + spm_unit6: + members: [person7] + heating_type: ELECTRICITY + # 200 a month. + pre_subsidy_electricity_expense: 2_400 + has_cooling_expense: true + households: + household1: + members: [person1] + state_code: SC + household2: + members: [person2, person3] + state_code: SC + household3: + members: [person4] + state_code: SC + household4: + members: [person5] + state_code: SC + household5: + members: [person6] + state_code: SC + household6: + members: [person7] + state_code: SC + output: + # Households 1-4 have incomes above the low-income tier and bills under + # 20% of income (1_800 < 24_000 * 0.2 = 4_800; 1_800 < 4_160). + # Household 1, elderly: 200 + 75 = 275. + # Household 2, child aged 3 (two-person tier 12_551.10): 200 + 50 = 250. + # Household 3, disabled: 200 + 50 = 250. + # Household 4, veteran: 200 + 50 = 250. + # Household 5, low income: 9_048 <= 9_597.90, and 1_500 < 9_048 * 0.2 = + # 1_809.60: 200 + 175 = 375. + # Household 6, energy burden: 10_400 > 9_597.90, and 2_400 >= 10_400 * + # 0.2 = 2_080: 200 + 175 = 375. + sc_liheap_cooling: [275, 250, 250, 250, 375, 375] + +- name: Case 3, all FY2026 cooling supplements reach the 775 cooling maximum. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 64 + is_disabled: true + is_veteran: true + # 800 a month. + social_security_disability: 9_600 + # Does not receive SSI. + ssi: 0 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 600 + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + households: + household: + members: [person1, person2] + state_code: SC + output: + # 9_600 <= 12_551.10, and bills of 2_400 >= 9_600 * 0.2 = 1_920. + # 200 + 75 + 50 + 50 + 50 + 175 + 175 = 775, the cooling maximum. + sc_liheap_cooling: 775 + # Heating adds the 75 natural gas supplement: 775 + 75 = 850, the heating + # maximum. + sc_liheap: 850 + # FY2026 allows two awards, so the national LIHEAP total adds both: + # 850 + 775. + liheap: 1_625 + +- name: Case 4, each FY2027 cooling supplement applies alone. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 67 + social_security_retirement: 24_000 + person2: + age: 30 + employment_income_before_lsr: 20_800 + person3: + age: 3 + person4: + age: 45 + is_disabled: true + employment_income_before_lsr: 20_800 + # Does not receive SSI. + ssi: 0 + person5: + age: 45 + is_veteran: true + employment_income_before_lsr: 20_800 + person6: + age: 40 + employment_income_before_lsr: 9_048 + person7: + age: 40 + employment_income_before_lsr: 10_400 + spm_units: + spm_unit1: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + spm_unit2: + members: [person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + spm_unit3: + members: [person4] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + spm_unit4: + members: [person5] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + spm_unit5: + members: [person6] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_500 + has_cooling_expense: true + spm_unit6: + members: [person7] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 2_400 + has_cooling_expense: true + households: + household1: + members: [person1] + state_code: SC + household2: + members: [person2, person3] + state_code: SC + household3: + members: [person4] + state_code: SC + household4: + members: [person5] + state_code: SC + household5: + members: [person6] + state_code: SC + household6: + members: [person7] + state_code: SC + output: + # Cooling award and heating award (FY2026 amounts carried forward): + # Household 1, elderly: 300 + 50 = 350; heating 200 + 75 = 275. + # Household 2, child aged 3 (two-person tier 13_031.70): 300 + 50 = 350; + # heating 200 + 50 = 250. + # Household 3, disabled: 300 + 50 = 350; heating 250. + # Household 4, veteran: 300 + 50 = 350; heating 250. + # Household 5, low income: 9_048 <= 9_965.40, and 1_500 < 1_809.60: + # 300 + 150 = 450; heating 200 + 175 = 375. + # Household 6, energy burden: 10_400 > 9_965.40, and 2_400 >= 2_080: + # 300 + 200 = 500; heating 200 + 175 = 375. + sc_liheap: [275, 250, 250, 250, 375, 375] + # Was [350, 350, 350, 350, 450, 500], now [75, 100, 100, 100, 75, 125]: + # FY2027 plan items 1.1 and 11.5 allow one award in the 2027 program year, + # so cooling pays only its excess over the heating award. + sc_liheap_cooling: [75, 100, 100, 100, 75, 125] + +- name: Case 5, all FY2027 cooling supplements reach the 850 cooling maximum, which the heating award already pays. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 64 + is_disabled: true + is_veteran: true + social_security_disability: 9_600 + # Does not receive SSI. + ssi: 0 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 600 + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + households: + household: + members: [person1, person2] + state_code: SC + output: + # 9_600 <= 13_031.70, and 2_400 >= 1_920. + # Cooling award: 300 + 50 + 50 + 50 + 50 + 150 + 200 = 850, the cooling + # maximum. Heating, FY2026 amounts carried forward: 200 + 75 + 50 + 50 + + # 50 + 175 + 175 + 75 = 850, the heating maximum. + sc_liheap: 850 + # Was 850, now 0: one award is allowed in the 2027 program year, and the + # cooling award does not exceed the 850 heating award. + sc_liheap_cooling: 0 + +- name: Case 6, in 2027 one award is allowed, so cooling pays only its excess over the heating award. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income_before_lsr: 20_800 + person2: + age: 62 + # 800 a month; not old enough for SSI. + social_security_retirement: 9_600 + spm_units: + spm_unit1: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 600 + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + spm_unit2: + members: [person2] + heating_type: NATURAL_GAS + gas_expense: 600 + # 100 a month. + pre_subsidy_electricity_expense: 1_200 + has_cooling_expense: true + households: + household1: + members: [person1] + state_code: SC + household2: + members: [person2] + state_code: SC + output: + # Household 1: 20_800 > 9_965.40, and 2_400 < 20_800 * 0.2 = 4_160. + # Heating 200 + 75 gas = 275; cooling award 300, so cooling pays + # 300 - 275 = 25. + # Household 2, aged 62: 9_600 <= 9_965.40, and 1_800 < 9_600 * 0.2 = + # 1_920. Heating 200 + 75 elderly + 175 low income + 75 gas = 525; + # cooling award 300 + 50 + 150 = 500, below the heating award, so + # cooling pays 0. + sc_liheap_cooling_eligible: [true, true] + sc_liheap: [275, 525] + sc_liheap_cooling: [25, 0] + # The total is the larger award: 275 + 25 = 300 and 525 + 0 = 525. + liheap: [300, 525] + +- name: Case 7, a mixed-status household's cooling award is prorated to its eligible members. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + is_citizen_or_legal_immigrant: false + employment_income_before_lsr: 20_800 + person2: + age: 3 + is_citizen_or_legal_immigrant: true + spm_units: + spm_unit: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + households: + household: + members: [person1, person2] + state_code: SC + output: + # One eligible member, so the one-person limit of 31_993 and tier of + # 9_597.90 apply; the parent's 20_800 counts. 20_800 > 9_597.90, and + # 1_800 < 4_160. The eligible child adds 50: (200 + 50) / 2 members x 1 + # eligible member = 125. + sc_liheap_household_size: 1 + sc_liheap_cooling: 125 diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/cooling/sc_liheap_cooling_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/cooling/sc_liheap_cooling_eligible.yaml new file mode 100644 index 00000000000..c991c824993 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/cooling/sc_liheap_cooling_eligible.yaml @@ -0,0 +1,63 @@ +# Sources: South Carolina FY2026 and FY2027 LIHEAP plans, items 1.1 and 3.1; +# OEO 2024 Administrative Guide, Section 3A (an obligation to pay a home energy +# bill; Direct Assistance may pay home heating or cooling costs). +# Cooling eligibility is regular LIHEAP eligibility plus cooling costs. FY2026 +# income limit for one person: 31,993 (60% of SMI). Wages are hourly pay times +# hours; electric bills of 1,800 are 150 a month. + +- name: Case 1, no cooling costs or income over the limit pays no cooling award; heat in the rent with an unspecified fuel is paid. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income_before_lsr: 20_800 + person2: + age: 40 + employment_income_before_lsr: 20_800 + person3: + age: 40 + # 15.50 an hour, 40 hours a week: 15.5 * 2_080 = 32_240. + employment_income_before_lsr: 32_240 + spm_units: + spm_unit1: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: false + spm_unit2: + members: [person2] + heating_type: UNSPECIFIED + heat_expense_included_in_rent: true + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + spm_unit3: + members: [person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + has_cooling_expense: true + households: + household1: + members: [person1] + state_code: SC + household2: + members: [person2] + state_code: SC + household3: + members: [person3] + state_code: SC + output: + # Household 1 has no cooling costs but keeps its 200 heating base. + # Household 2 qualifies for heating through heat in the rent (base 200), + # and the cooling gate adds no heating-fuel condition, so it also receives + # the 200 cooling base (20_800 > 9_597.90; 1_800 < 4_160). + # Household 3: 32_240 > 31_993 and above 110% of the 2025 guideline + # (15_650 * 1.1 = 17_215). + sc_liheap_eligible: [true, true, false] + # Was [false, false, false], now [false, true, false]: the cooling gate no + # longer excludes an unspecified heating fuel. + sc_liheap_cooling_eligible: [false, true, false] + # Was [0, 0, 0], now [0, 200, 0]: the same change. + sc_liheap_cooling: [0, 200, 0] + sc_liheap: [200, 200, 0] diff --git a/policyengine_us/variables/gov/states/sc/oeo/liheap/cooling/sc_liheap_cooling.py b/policyengine_us/variables/gov/states/sc/oeo/liheap/cooling/sc_liheap_cooling.py new file mode 100644 index 00000000000..184383e5a62 --- /dev/null +++ b/policyengine_us/variables/gov/states/sc/oeo/liheap/cooling/sc_liheap_cooling.py @@ -0,0 +1,45 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.sc.oeo.liheap.sc_liheap import ( + sc_liheap_award, +) + + +class sc_liheap_cooling(Variable): + value_type = float + entity = SPMUnit + label = "South Carolina LIHEAP regular cooling assistance" + unit = USD + definition_period = YEAR + documentation = ( + "Cooling-season (May 1 to September 30) nonemergency Direct Assistance " + "award; when a program year allows one award (FY2027), only its excess " + "over the heating award. Verified for FY2026 and FY2027; earlier years " + "use model parameter backfilling and are unverified historical " + "estimates." + ) + defined_for = "sc_liheap_cooling_eligible" + reference = ( + # PDF pages 4,10-11,26 + "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=4", + # PDF pages 4,10-11,26 + "https://liheapch.acf.gov/docs/2027/state-plans/SC_Plan_2027.pdf#page=4", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/SC_BenefitMatrix_2026.pdf#page=1", + # PDF pages 523,536,538 + "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=523", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.sc.oeo.liheap + # The cooling season pays the base and household supplements, with no + # fuel supplement, up to the cooling maximum. + award = sc_liheap_award(spm_unit, period, parameters, p.cooling.amounts, 0) + # FY2026 plan item 11.5 allows two LIHEAP services in the program year, + # so the cooling award is paid alongside the heating award. FY2027 plan + # items 1.1 and 11.5 allow one assistance in the 2027 program year, the + # calendar year that holds both seasons, so cooling pays only its excess + # over the heating award and the household receives the larger of the + # two. The 2027 heating amounts are FY2026 carry-forwards. + if p.payment.max_awards > 1: + return award + heating = spm_unit("sc_liheap", period) + return max_(award - heating, 0) diff --git a/policyengine_us/variables/gov/states/sc/oeo/liheap/cooling/sc_liheap_cooling_eligible.py b/policyengine_us/variables/gov/states/sc/oeo/liheap/cooling/sc_liheap_cooling_eligible.py new file mode 100644 index 00000000000..1897a3df28f --- /dev/null +++ b/policyengine_us/variables/gov/states/sc/oeo/liheap/cooling/sc_liheap_cooling_eligible.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class sc_liheap_cooling_eligible(Variable): + value_type = bool + entity = SPMUnit + label = "Eligible for South Carolina LIHEAP regular cooling assistance" + definition_period = YEAR + defined_for = StateCode.SC + reference = ( + # PDF pages 4,10 + "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=4", + # PDF pages 4,10 + "https://liheapch.acf.gov/docs/2027/state-plans/SC_Plan_2027.pdf#page=4", + # PDF pages 523,536-537 + "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=523", + ) + + def formula(spm_unit, period, parameters): + # Cooling-season Direct Assistance uses the regular income and + # energy-bill tests, which pay an unspecified heating fuel only with + # heat in the rent; the cooling gate adds no heating-fuel condition. + # OEO's texts set no separate cooling-cost test; requiring + # has_cooling_expense is a PolicyEngine convention. + eligible = spm_unit("sc_liheap_eligible", period) + return eligible & spm_unit("has_cooling_expense", period) diff --git a/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap.py b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap.py index 9b0490bab01..0f67f28a5a2 100644 --- a/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap.py +++ b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap.py @@ -1,6 +1,59 @@ from policyengine_us.model_api import * +def sc_liheap_award(spm_unit, period, parameters, amounts, fuel_amount): + """Return a seasonal nonemergency award from the supplied amount schedule. + + Heating and cooling share the household supplements and the mixed-status + proration. Each season has its own amounts and maximum; only heating adds + a fuel supplement, passed as fuel_amount. + """ + p = parameters(period).gov.states.sc.oeo.liheap.payment + income = spm_unit("sc_liheap_countable_income", period) + size = spm_unit("sc_liheap_household_size", period) + # The accepted plan's 30% governs over the older matrix's 70%. Both are shares + # of the income limit: OEO's draft guide writes "30 of 60 percent of the SMI" + # and "SMI is 60% of the State's Median Income". + limit = spm_unit("sc_liheap_income_limit", period) + low_income = income <= p.low_income_limit_rate * limit + person = spm_unit.members + age = person("age", period) + # OEO's 2024 guide leaves state-placed foster children out of the household + # and counts only eligible members for the itemized supplements. + member = ~person("is_in_foster_care", period.first_month) + eligible = member & person("is_citizen_or_legal_immigrant", period) + elderly = spm_unit.any(eligible & (age >= p.ages.elderly_min)) + child = spm_unit.any(eligible & (np.floor(age) <= p.ages.child_max)) + disabled = spm_unit.any(eligible & person("is_disabled", period)) + veteran = spm_unit.any(eligible & person("is_veteran", period)) + # The plan measures home-energy utilities; aggregate existing heating and + # electric bills, excluding water, sewer, trash, phone and separate cooking + # fuel. Annual expenses approximate current monthly bills, and rent-embedded + # charges are not available. + cost = add(spm_unit, period, p.energy_cost_sources) + high_burden = (cost > 0) & (cost >= income * p.burden_rate) + a = amounts + amount = ( + a.base + + a.elderly * elderly + + a.child * child + + a.disabled * disabled + + a.veteran * veteran + + a.low_income * low_income + + a.high_burden * high_burden + + fuel_amount + ) + # Local funding and priority decisions are not represented. The state + # schedule does not impose a general benefit-to-bill cap. + # The plans state each season's maximum; that season's amounts sum to + # exactly it. + award = min_(amount, a.maximum) + # A mixed-status award is the total benefit / all household members x + # eligible members; both counts leave out state-placed foster children. + # An eligible unit always has a member, so the floor only avoids dividing by zero. + return award * size / max_(spm_unit.sum(member), 1) + + class sc_liheap(Variable): value_type = float entity = SPMUnit @@ -18,47 +71,8 @@ class sc_liheap(Variable): def formula(spm_unit, period, parameters): p = parameters(period).gov.states.sc.oeo.liheap.payment - income = spm_unit("sc_liheap_countable_income", period) - size = spm_unit("sc_liheap_household_size", period) - # The accepted plan's 30% governs over the older matrix's 70%. Both are shares - # of the income limit: OEO's draft guide writes "30 of 60 percent of the SMI" - # and "SMI is 60% of the State's Median Income". - limit = spm_unit("sc_liheap_income_limit", period) - low_income = income <= p.low_income_limit_rate * limit - person = spm_unit.members - age = person("age", period) - # OEO's 2024 guide leaves state-placed foster children out of the household - # and counts only eligible members for the itemized supplements. - member = ~person("is_in_foster_care", period.first_month) - eligible = member & person("is_citizen_or_legal_immigrant", period) - elderly = spm_unit.any(eligible & (age >= p.ages.elderly_min)) - child = spm_unit.any(eligible & (np.floor(age) <= p.ages.child_max)) - disabled = spm_unit.any(eligible & person("is_disabled", period)) - veteran = spm_unit.any(eligible & person("is_veteran", period)) - # The plan measures home-energy utilities; aggregate existing heating and - # electric bills, excluding water, sewer, trash, phone and separate cooking - # fuel. Annual expenses approximate current monthly bills, and rent-embedded - # charges are not available. - cost = add(spm_unit, period, p.energy_cost_sources) - high_burden = (cost > 0) & (cost >= income * p.burden_rate) fuel = spm_unit("heating_type", period) fuel_supplement = np.isin(fuel.decode_to_str(), p.supplement_fuels) - a = p.amounts - amount = ( - a.base - + a.elderly * elderly - + a.child * child - + a.disabled * disabled - + a.veteran * veteran - + a.low_income * low_income - + a.high_burden * high_burden - + a.fuel * fuel_supplement + return sc_liheap_award( + spm_unit, period, parameters, p.amounts, p.amounts.fuel * fuel_supplement ) - # Local funding and priority decisions are not represented. The state - # schedule does not impose a general benefit-to-bill cap. - # The plan states this maximum; the eight FY2026 amounts sum to exactly it. - award = min_(amount, a.maximum) - # A mixed-status award is the total benefit / all household members x - # eligible members; both counts leave out state-placed foster children. - # An eligible unit always has a member, so the floor only avoids dividing by zero. - return award * size / max_(spm_unit.sum(member), 1) diff --git a/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_eligible.py b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_eligible.py index 000ec310b1c..1af0ee855ba 100644 --- a/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_eligible.py +++ b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_eligible.py @@ -32,10 +32,10 @@ def formula(spm_unit, period, parameters): federal_floor = np.round( guideline * parameters(period).gov.hhs.liheap.fpg_floor, 2 ) - # OEO asks for an obligation to pay a home energy bill, and the one regular - # award pays heating or cooling costs, so any counted bill qualifies, including - # in a home without heating. heating_type is a required input, so an unspecified - # fuel qualifies only through heat in the rent. + # OEO asks for an obligation to pay a home energy bill, and Direct Assistance + # may pay home heating or cooling costs, so any counted bill qualifies, + # including in a home without heating. heating_type is a required input, so an + # unspecified fuel qualifies only through heat in the rent. has_energy_cost = add(spm_unit, period, p.payment.energy_cost_sources) > 0 fuel = spm_unit("heating_type", period) known_fuel = fuel != fuel.possible_values.UNSPECIFIED diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 352bbc3a578..d4a7019016d 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -10541,7 +10541,7 @@ candidates: program: liheap years: from: 2026 - to: 2026 + to: 2027 type: federal-fiscal question: Is South Carolina's high energy burden supplement paid when energy costs equal 20% of income, as the FY2026 plan reads, or only when the current bill is higher than 20% of income, as the 2024 guide and the matrix read? @@ -10568,11 +10568,15 @@ candidates: follows: gap-fill code: - policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap.py + - policyengine_us/variables/gov/states/sc/oeo/liheap/cooling/sc_liheap_cooling.py - policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/burden_rate.yaml prs: - https://github.com/PolicyEngine/policyengine-us/pull/10616 + - https://github.com/PolicyEngine/policyengine-us/pull/10754 state: unreviewed - note: PE-US follows the plan's inclusive test, with annual bills against annual income. + note: PE-US follows the plan's inclusive test, with annual bills against annual income. The cooling award (PR 10754) + applies the same annual test, so winter fuel bills count toward the cooling-season burden; the guides' current-month + test would count only the summer bill. - id: us-sc:liheap-income-floor-vs-smi-limit program: liheap @@ -10608,7 +10612,7 @@ candidates: program: liheap years: from: 2026 - to: 2026 + to: 2027 type: federal-fiscal question: Is South Carolina's $175 low-income tier 30% of the full size-adjusted state median income, or 30% of the 60% income limit that OEO's guides call the SMI? @@ -10637,9 +10641,11 @@ candidates: follows: gap-fill code: - policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap.py + - policyengine_us/variables/gov/states/sc/oeo/liheap/cooling/sc_liheap_cooling.py - policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_limit_rate.yaml prs: - https://github.com/PolicyEngine/policyengine-us/pull/10616 + - https://github.com/PolicyEngine/policyengine-us/pull/10754 state: ruled note: 'Ruled 2026-11-03: use 30% of the 60% limit. The plan''s words allow either base, and OEO''s own texts define the SMI it means here, so the agency breaks the tie. The 2024 guide and the matrix use 70% of the same limit, a tier @@ -10692,7 +10698,7 @@ candidates: program: liheap years: from: 2026 - to: 2026 + to: 2027 type: federal-fiscal question: Do South Carolina's itemized supplements look only at eligible members, with the award prorated, and are state-placed foster children left out of the household, as the 2024 guide provides where the FY2026 plan is silent? @@ -10713,10 +10719,12 @@ candidates: follows: gap-fill code: - policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap.py + - policyengine_us/variables/gov/states/sc/oeo/liheap/cooling/sc_liheap_cooling.py - policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_household_size.py - policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_countable_income.py prs: - https://github.com/PolicyEngine/policyengine-us/pull/10616 + - https://github.com/PolicyEngine/policyengine-us/pull/10754 state: ruled note: 'Ruled 2026-11-02: follow the 2024 guide. The veteran supplement also looks only at eligible members, although the guide''s list does not name veterans, and the prorated award keeps its cents.'