From 0cf624a11ade45eb0b3fbcd732c56e08334b9a47 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sat, 3 Oct 2026 19:58:26 -0400 Subject: [PATCH 1/6] Add South Carolina LIHEAP regular heating assistance --- changelog.d/sc-liheap-wave2c.added.md | 1 + .../gov/states/sc/oeo/liheap/README.md | 57 ++ .../sc/oeo/liheap/eligibility/smi_rate.yaml | 10 + .../sc/oeo/liheap/income/earned_min_age.yaml | 10 + .../sc/oeo/liheap/income/earned_sources.yaml | 15 + .../oeo/liheap/income/unearned_sources.yaml | 21 + .../states/sc/oeo/liheap/payment/ages.yaml | 12 + .../states/sc/oeo/liheap/payment/amounts.yaml | 26 + .../sc/oeo/liheap/payment/burden_rate.yaml | 10 + .../liheap/payment/energy_cost_sources.yaml | 18 + .../liheap/payment/low_income_smi_rate.yaml | 10 + .../oeo/liheap/payment/supplement_fuels.yaml | 14 + policyengine_us/programs.yaml | 10 +- .../gov/states/sc/oeo/liheap/sc_liheap.yaml | 938 ++++++++++++++++++ .../liheap/sc_liheap_countable_income.yaml | 263 +++++ .../sc/oeo/liheap/sc_liheap_eligible.yaml | 296 ++++++ .../oeo/liheap/sc_liheap_household_size.yaml | 82 ++ .../sc/oeo/liheap/sc_liheap_income_limit.yaml | 302 ++++++ .../gov/states/sc/oeo/liheap/sc_liheap.py | 54 + .../oeo/liheap/sc_liheap_countable_income.py | 26 + .../sc/oeo/liheap/sc_liheap_eligible.py | 28 + .../sc/oeo/liheap/sc_liheap_household_size.py | 15 + .../sc/oeo/liheap/sc_liheap_income_limit.py | 29 + 23 files changed, 2246 insertions(+), 1 deletion(-) create mode 100644 changelog.d/sc-liheap-wave2c.added.md create mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/README.md create mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/eligibility/smi_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_min_age.yaml create mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_sources.yaml create mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml create mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/ages.yaml create mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/amounts.yaml create mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/burden_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/energy_cost_sources.yaml create mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_smi_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/supplement_fuels.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_household_size.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_income_limit.yaml create mode 100644 policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap.py create mode 100644 policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_countable_income.py create mode 100644 policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_eligible.py create mode 100644 policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_household_size.py create mode 100644 policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_income_limit.py diff --git a/changelog.d/sc-liheap-wave2c.added.md b/changelog.d/sc-liheap-wave2c.added.md new file mode 100644 index 00000000000..8b26ec3d654 --- /dev/null +++ b/changelog.d/sc-liheap-wave2c.added.md @@ -0,0 +1 @@ +Add South Carolina FY2026 regular heating assistance, including countable income, qualified household size, eligibility and statewide benefit supplements. diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/README.md b/policyengine_us/parameters/gov/states/sc/oeo/liheap/README.md new file mode 100644 index 00000000000..a0ce8ba7395 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/README.md @@ -0,0 +1,57 @@ +# South Carolina regular heating assistance + +The accepted FY2026 plan governs the statewide regular-heating schedule: base +$200 plus the specified demographic, fuel, low-income and high-burden additions, +up to $850. The plan repeatedly uses 30% SMI and burden of 20% or more. Those +specific adopted definitions take precedence over the matrix's older-vintage +42% SMI and conflicting strict electric/tank-fill burden language. The matrix's +explicit gas/oil/kerosene/propane fuel definition is retained where consistent. + +The model calculates countable income, the eligible household size, the annual +ceiling, eligibility and the regular heating award. The agency's approved 2026 +WAP plan (physical page 23) expressly attributes to LIHEAP guidance the rule that +ineligible members' income counts but those members do not count toward household +size. The approved 2024 plan (physical page 21) states the same rule, supporting +continuity for FY2026. Only that explicitly attributed LIHEAP rule is used from +those plans; their weatherization eligibility and payment rules are not used. +The qualified-size ceiling and low-income threshold use this rule. Demographic +supplements retain the LIHEAP plan's explicit household-member conditions; that +plan does not restrict these conditions to qualified members. There is no +categorical income bypass or asset test in the adopted LIHEAP plan. + +Income uses the accepted plan's checked categories. Minor earnings are excluded; +minor unearned income is counted because adult verification requirements are +not an express income exclusion. Monthly SSI aggregates once over the year. +Pension and Social Security aggregates include their components; private +survivor receipts and farm rent are separate. Net business inputs receive no +second deduction. Cash from friends/relatives uses financial_assistance. Legal +settlements lack an exact input. TANF, general assistance, interest/dividends, +strike, insurance, one-time lump sums and foster payments are not counted under +the current checklist. Medicare adjustments are deferred. Application-month +income timing and work-study embedded in wages remain input limitations. + +The annual income ceiling reuses requested-year federal SMI. Rounding the +four-person 60% base and then flooring the size-adjusted result reproduces all +eight current agency annual limits, including sizes seven and eight. That order +is inferred from published amounts, not an explicit legal rounding direction. +The inconsistent one-person monthly figure is not used. The 30% supplement +boundary uses the plan percentage without rounding the income ratio. + +Energy burden uses the sum of existing electric, gas and fuel bills. Water, +sewer, trash and telephone are excluded from home-energy cost. This reuses +represented bills more faithfully than primary heating cost alone, but annual +flows still approximate the current month and rent-embedded charges are missing. +Positive expenses at zero income qualify for high burden; zero expense does not +prove a burden. There is no general benefit-to-bill cap or subsidized-renter +exclusion in the supported schedule. Heating responsibility uses has_heating_expense or heat_expense_included_in_rent, +with an explicit NONE heating type always excluded. The existing responsibility +input supports cases where a household pays for secondary heating. There is no +extra exclusion for subsidized housing or heat included in rent. The model does +not split households across SPM units, adjudicate applications, or model local +CAA restrictions, priority and available funding. + +Only FY2026 is researched. The revised FY2027 plan is a draft. Earlier parameter +backfills are unverified historical estimates. Crisis, cooling, weatherization +and equipment are outside this work. The SPM unit approximates the energy household. Application-month income and +expense data and detailed legal immigration facts remain limitations of existing +inputs; parameter backfilling does not verify earlier years. 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 new file mode 100644 index 00000000000..e7a42f1f14a --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/eligibility/smi_rate.yaml @@ -0,0 +1,10 @@ +description: South Carolina limits income to this share of state median income under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 0.6 +metadata: + unit: /1 + period: year + label: South Carolina LIHEAP SMI limit rate + reference: + - title: South Carolina FY2026 LIHEAP policy + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_min_age.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_min_age.yaml new file mode 100644 index 00000000000..d79f502e4ed --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_min_age.yaml @@ -0,0 +1,10 @@ +description: South Carolina counts earned income at or above this age under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 18 +metadata: + unit: year + period: year + label: South Carolina LIHEAP minimum earned income age + reference: + - title: South Carolina FY2026 LIHEAP policy + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_sources.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_sources.yaml new file mode 100644 index 00000000000..309e49b50a3 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_sources.yaml @@ -0,0 +1,15 @@ +description: South Carolina counts these earned income sources under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: + - employment_income + - self_employment_income + - sstb_self_employment_income + - farm_operations_income + - partnership_s_corp_income +metadata: + unit: list + period: year + label: South Carolina LIHEAP earned income sources + reference: + - title: South Carolina FY2026 LIHEAP policy + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=5,6 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml new file mode 100644 index 00000000000..6360ec95276 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml @@ -0,0 +1,21 @@ +description: South Carolina counts these unearned income sources under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: + - unemployment_compensation + - social_security + - ssi + - pension_income + - survivor_benefits + - rental_income + - farm_rent_income + - alimony_income + - child_support_received + - veterans_benefits + - financial_assistance +metadata: + unit: list + period: year + label: South Carolina LIHEAP unearned income sources + reference: + - title: South Carolina FY2026 LIHEAP policy + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=6 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 new file mode 100644 index 00000000000..045d1210507 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/ages.yaml @@ -0,0 +1,12 @@ +description: South Carolina uses these age thresholds for regular heating supplements under the Low Income Home Energy Assistance Program. +elderly_min: + 2025-10-01: 60 +child_max: + 2025-10-01: 5 +metadata: + unit: year + period: year + label: South Carolina LIHEAP supplement age thresholds + reference: + - title: South Carolina FY2026 LIHEAP policy + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8,9 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/amounts.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/amounts.yaml new file mode 100644 index 00000000000..5ea7b0ecc37 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/amounts.yaml @@ -0,0 +1,26 @@ +description: South Carolina provides these base and supplemental regular heating amounts under the Low Income Home Energy Assistance Program. +base: + 2025-10-01: 200 +elderly: + 2025-10-01: 75 +child: + 2025-10-01: 50 +disabled: + 2025-10-01: 50 +veteran: + 2025-10-01: 50 +low_income: + 2025-10-01: 175 +high_burden: + 2025-10-01: 175 +fuel: + 2025-10-01: 75 +maximum: + 2025-10-01: 850 +metadata: + unit: currency-USD + period: year + label: South Carolina LIHEAP regular heating amounts + reference: + - title: South Carolina FY2026 LIHEAP policy + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8,9 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 new file mode 100644 index 00000000000..ab432d49d86 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/burden_rate.yaml @@ -0,0 +1,10 @@ +description: South Carolina awards a high-burden supplement at or above this energy-cost share of income under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 0.2 +metadata: + unit: /1 + period: year + label: South Carolina LIHEAP high energy burden rate + reference: + - title: South Carolina FY2026 LIHEAP policy + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.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 new file mode 100644 index 00000000000..d9806116967 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/energy_cost_sources.yaml @@ -0,0 +1,18 @@ +description: South Carolina uses these represented home-energy bills for the energy-burden calculation under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: + - pre_subsidy_electricity_expense + - gas_expense + - bottled_gas_expense + - fuel_oil_expense + - wood_expense + - coal_expense + - other_heating_fuel_expense + - cooking_fuel_expense +metadata: + unit: list + period: year + label: South Carolina LIHEAP home energy cost sources + reference: + - title: South Carolina FY2026 LIHEAP policy + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_smi_rate.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_smi_rate.yaml new file mode 100644 index 00000000000..c905f2dd642 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_smi_rate.yaml @@ -0,0 +1,10 @@ +description: South Carolina awards a low-income supplement at or below this share of state median income under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 0.3 +metadata: + unit: /1 + period: year + label: South Carolina LIHEAP low-income SMI rate + reference: + - title: South Carolina FY2026 LIHEAP policy + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8,9 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/supplement_fuels.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/supplement_fuels.yaml new file mode 100644 index 00000000000..c1896653e9e --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/supplement_fuels.yaml @@ -0,0 +1,14 @@ +description: South Carolina awards a supplement for these heating fuels under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: + - NATURAL_GAS + - FUEL_OIL + - KEROSENE + - PROPANE +metadata: + unit: list + period: year + label: South Carolina LIHEAP supplement heating fuels + reference: + - title: South Carolina FY2026 LIHEAP policy + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/SC_BenefitMatrix_2026.pdf#page=2 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 756dc2c4988..ff174c4ced2 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, TX + coverage: OR, DC, Riverside County, MA, IL, KS, MS, TX, SC state_implementations: - state: OR status: in_progress @@ -846,6 +846,14 @@ programs: variable: tx_ceap parameter_prefix: gov.states.tx.tdhca.ceap notes: Models the FPG income limit, the FY2024-only SMI limit, benefit amounts, and categorical eligibility through TANF enrollment, SNAP eligibility, and SSI receipt. Not modeled - net rather than gross gambling winnings, the Medicare premium deduction from counted Social Security, and the exclusion of means-tested veterans' pensions from counted income (10 TAC 6.4(c)) + - state: SC + status: partial + name: South Carolina LIHEAP + full_name: South Carolina Low Income Home Energy Assistance Program + variable: sc_liheap + parameter_prefix: gov.states.sc.oeo.liheap + verified_years: "2026" + notes: FY2026 regular heating statewide schedule, qualified household size and countable income. Annual inputs approximate application-month income and energy burden; SPM units approximate energy households. Local agency funding and priority decisions and detailed income-source exclusions are not represented. Earlier backfills are unverified historical estimates; crisis, cooling, weatherization and equipment are outside scope. # --- SSA programs --- - id: ssi diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml new file mode 100644 index 00000000000..d30a963e2bd --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml @@ -0,0 +1,938 @@ +# Accepted FY 2026 plan: regular heating base 200 and seven supplements, cap 850. +# https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8,9 +# Matrix used only for named fuels; its conflicting income/burden rules are not used. +# https://liheapch.acf.gov/docs/2026/benefits-matricies/SC_BenefitMatrix_2026.pdf#page=2 +# Agency's explicit LIHEAP household rule counts all income but qualified size. +# https://oeo.sc.gov/managedsites/prd/oeo/documents/WAP%202026%20State%20Plan.pdf#page=23 +# These are annual state-schedule awards, before unmodeled local funding discretion. +# All intermediate outputs below are calculated from raw facts, never forced inputs. + +- name: Case 1, source base and combined low-income high-burden examples. + period: 2026 + # 40_000 income and 4_000 electric cost give only base 200. At 30_000 income, + # low-income 175 applies and 6_000/30_000=20% adds burden 175: total 550. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 40_000 + person2: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person3: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person4: + age: 12 + is_citizen_or_legal_immigrant: true + ssi: 0 + person5: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 30_000 + person6: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person7: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person8: + age: 12 + is_citizen_or_legal_immigrant: true + ssi: 0 + spm_units: + spm_unit1: + members: [person1, person2, person3, person4] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 4_000 + sc_tanf: 0 + spm_unit2: + members: [person5, person6, person7, person8] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 6_000 + sc_tanf: 0 + households: + household1: + members: [person1, person2, person3, person4] + state_code: SC + household2: + members: [person5, person6, person7, person8] + state_code: SC + output: + sc_liheap_household_size: [4, 4] + sc_liheap_countable_income: [40_000, 30_000] + sc_liheap_income_limit: [61_525, 61_525] + sc_liheap_eligible: [true, true] + sc_liheap: [200, 550] + +- name: Case 2, accepted thirty-percent SMI boundary governs the supplement. + period: 2026 + absolute_error_margin: 0.01 + # Using shared raw four-person SMI 102_542,30%=30_762.60. At or below the + # boundary add 175 to base 200; a cent above receives only 200. Income 35_000 + # rejects the older matrix 42% rule. Raw SMI base is a shared-contract input, + # not independently reverified federal data in this test-author role. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 30_762.59 + person2: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person3: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person4: + age: 12 + is_citizen_or_legal_immigrant: true + ssi: 0 + person5: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 30_762.6 + person6: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person7: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person8: + age: 12 + is_citizen_or_legal_immigrant: true + ssi: 0 + person9: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 30_762.61 + person10: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person11: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person12: + age: 12 + is_citizen_or_legal_immigrant: true + ssi: 0 + person13: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 35_000 + person14: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person15: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person16: + age: 12 + is_citizen_or_legal_immigrant: true + ssi: 0 + spm_units: + spm_unit1: + members: [person1, person2, person3, person4] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit2: + members: [person5, person6, person7, person8] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit3: + members: [person9, person10, person11, person12] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit4: + members: [person13, person14, person15, person16] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + households: + household1: + members: [person1, person2, person3, person4] + state_code: SC + household2: + members: [person5, person6, person7, person8] + state_code: SC + household3: + members: [person9, person10, person11, person12] + state_code: SC + household4: + members: [person13, person14, person15, person16] + state_code: SC + output: + sc_liheap: [375, 375, 200, 200] + +- name: Case 3, twenty-percent burden sums energy bills and includes equality. + period: 2026 + # Gas 500 + electric 3_499/3_500/3_501 gives costs 3_999/4_000/4_001. + # At income 20_000,20%=4_000. Base 200+gas-fuel 75 gains burden 175 at equality. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person2: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person3: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + spm_units: + spm_unit1: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 500 + sc_tanf: 0 + pre_subsidy_electricity_expense: 3_499 + spm_unit2: + members: [person2] + heating_type: NATURAL_GAS + gas_expense: 500 + sc_tanf: 0 + pre_subsidy_electricity_expense: 3_500 + spm_unit3: + members: [person3] + heating_type: NATURAL_GAS + gas_expense: 500 + sc_tanf: 0 + pre_subsidy_electricity_expense: 3_501 + households: + household1: + members: [person1] + state_code: SC + household2: + members: [person2] + state_code: SC + household3: + members: [person3] + state_code: SC + output: + sc_liheap: [275, 450, 450] + +- name: Case 4, nonenergy utility bills do not establish home-energy burden. + period: 2026 + # Home energy 1_000/20_000=5%. Water, trash and phone bills do not earn 175. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + spm_units: + spm_unit1: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + water_expense: 5_000 + trash_expense: 2_000 + phone_expense: 2_000 + households: + household1: + members: [person1] + state_code: SC + output: + sc_liheap: 200 + +- name: Case 5, named supplement fuels and other heating fuels are distinguished. + period: 2026 + # Matrixp 2 names gas, oil/kerosene and propane for 75. Every scenario has + # its actual matching bill 1_000 and income 20_000, so no low-income/burden + # supplement applies. Other represented heating fuels receive the base 200. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person2: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person3: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person4: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person5: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person6: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person7: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person8: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person9: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + spm_units: + spm_unit1: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_000 + sc_tanf: 0 + spm_unit2: + members: [person2] + heating_type: FUEL_OIL + fuel_oil_expense: 1_000 + sc_tanf: 0 + spm_unit3: + members: [person3] + heating_type: KEROSENE + fuel_oil_expense: 1_000 + sc_tanf: 0 + spm_unit4: + members: [person4] + heating_type: PROPANE + bottled_gas_expense: 1_000 + sc_tanf: 0 + spm_unit5: + members: [person5] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit6: + members: [person6] + heating_type: WOOD + wood_expense: 1_000 + sc_tanf: 0 + spm_unit7: + members: [person7] + heating_type: COAL + coal_expense: 1_000 + sc_tanf: 0 + spm_unit8: + members: [person8] + heating_type: SOLAR + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit9: + members: [person9] + heating_type: OTHER + other_heating_fuel_expense: 1_000 + sc_tanf: 0 + households: + household1: + members: [person1] + state_code: SC + household2: + members: [person2] + state_code: SC + household3: + members: [person3] + state_code: SC + household4: + members: [person4] + state_code: SC + household5: + members: [person5] + state_code: SC + household6: + members: [person6] + state_code: SC + household7: + members: [person7] + state_code: SC + household8: + members: [person8] + state_code: SC + household9: + members: [person9] + state_code: SC + output: + sc_liheap: [275, 275, 275, 275, 200, 200, 200, 200, 200] + +- name: Case 6, age disability and veteran supplements apply once per category. + period: 2026 + # Two-person income 30_000 exceeds its 30%SMI level and is below 41_837. + # Base 200 gains 75 at age 60;50 for child<=5, any disabled member, or any + # veteran. Two veterans still earn a single 50 supplement. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 30_000 + person2: + age: 59 + is_citizen_or_legal_immigrant: true + ssi: 0 + person3: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 30_000 + person4: + age: 60 + is_citizen_or_legal_immigrant: true + ssi: 0 + person5: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 30_000 + person6: + age: 5 + is_citizen_or_legal_immigrant: true + ssi: 0 + person7: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 30_000 + person8: + age: 6 + is_citizen_or_legal_immigrant: true + ssi: 0 + person9: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 30_000 + person10: + age: 8 + is_citizen_or_legal_immigrant: true + ssi: 0 + is_disabled: true + person11: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 30_000 + person12: + age: 40 + is_citizen_or_legal_immigrant: true + ssi: 0 + is_disabled: true + person13: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 30_000 + person14: + age: 40 + is_citizen_or_legal_immigrant: true + ssi: 0 + is_veteran: true + person15: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 30_000 + is_veteran: true + person16: + age: 40 + is_citizen_or_legal_immigrant: true + ssi: 0 + is_veteran: true + spm_units: + spm_unit1: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit2: + members: [person3, person4] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit3: + members: [person5, person6] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit4: + members: [person7, person8] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit5: + members: [person9, person10] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit6: + members: [person11, person12] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit7: + members: [person13, person14] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit8: + members: [person15, person16] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + households: + household1: + members: [person1, person2] + state_code: SC + household2: + members: [person3, person4] + state_code: SC + household3: + members: [person5, person6] + state_code: SC + household4: + members: [person7, person8] + state_code: SC + household5: + members: [person9, person10] + state_code: SC + household6: + members: [person11, person12] + state_code: SC + household7: + members: [person13, person14] + state_code: SC + household8: + members: [person15, person16] + state_code: SC + output: + sc_liheap: [200, 275, 250, 200, 250, 250, 250, 250] + +- name: Case 7, all supplements total eight hundred fifty without multiplying people. + period: 2026 + # 200 base+75 elderly+50 disabled+50 young child+175 low income + # +175 high burden+75 propane+50 veteran=850. Repeated vulnerable members + # do not multiply categories. Negative net income cannot exceed the 850 cap. + input: + people: + person1: + age: 65 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 24_000 + person2: + age: 40 + is_citizen_or_legal_immigrant: true + ssi: 0 + is_disabled: true + is_veteran: true + person3: + age: 5 + is_citizen_or_legal_immigrant: true + ssi: 0 + person4: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person5: + age: 65 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 24_000 + person6: + age: 60 + is_citizen_or_legal_immigrant: true + ssi: 0 + person7: + age: 40 + is_citizen_or_legal_immigrant: true + ssi: 0 + is_disabled: true + is_veteran: true + person8: + age: 38 + is_citizen_or_legal_immigrant: true + ssi: 0 + is_disabled: true + is_veteran: true + person9: + age: 5 + is_citizen_or_legal_immigrant: true + ssi: 0 + person10: + age: 4 + is_citizen_or_legal_immigrant: true + ssi: 0 + person11: + age: 65 + is_citizen_or_legal_immigrant: true + ssi: 0 + self_employment_income: -1_000_000 + person12: + age: 40 + is_citizen_or_legal_immigrant: true + ssi: 0 + is_disabled: true + is_veteran: true + person13: + age: 5 + is_citizen_or_legal_immigrant: true + ssi: 0 + person14: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + spm_units: + spm_unit1: + members: [person1, person2, person3, person4] + heating_type: PROPANE + bottled_gas_expense: 6_000 + sc_tanf: 0 + spm_unit2: + members: [person5, person6, person7, person8, person9, person10] + heating_type: PROPANE + bottled_gas_expense: 6_000 + sc_tanf: 0 + spm_unit3: + members: [person11, person12, person13, person14] + heating_type: PROPANE + bottled_gas_expense: 6_000 + sc_tanf: 0 + households: + household1: + members: [person1, person2, person3, person4] + state_code: SC + household2: + members: [person5, person6, person7, person8, person9, person10] + state_code: SC + household3: + members: [person11, person12, person13, person14] + state_code: SC + output: + sc_liheap: [850, 850, 850] + +- name: Case 8, nonqualified residents may trigger household demographic supplements. + period: 2026 + # Qualified size is one for each threshold. LIHEAP plan demographics refer + # to a household member without a qualified-status restriction: add 75 for + # the nonqualified elder or 50 for the child, disabled person or veteran. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person2: + age: 60 + is_citizen_or_legal_immigrant: false + ssi: 0 + person3: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person4: + age: 5 + is_citizen_or_legal_immigrant: false + ssi: 0 + person5: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person6: + age: 40 + is_citizen_or_legal_immigrant: false + ssi: 0 + is_disabled: true + person7: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person8: + age: 40 + is_citizen_or_legal_immigrant: false + ssi: 0 + is_veteran: true + spm_units: + spm_unit1: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit2: + members: [person3, person4] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit3: + members: [person5, person6] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit4: + members: [person7, person8] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + households: + household1: + members: [person1, person2] + state_code: SC + household2: + members: [person3, person4] + state_code: SC + household3: + members: [person5, person6] + state_code: SC + household4: + members: [person7, person8] + state_code: SC + output: + sc_liheap_household_size: [1, 1, 1, 1] + sc_liheap_countable_income: [20_000, 20_000, 20_000, 20_000] + sc_liheap_income_limit: [31_993, 31_993, 31_993, 31_993] + sc_liheap: [275, 250, 250, 250] + +- name: Case 9, all resident income is compared with qualified-size limits. + period: 2026 + # Qualified child gives size one and ceiling 31_993. Parent income 30_000 + # qualifies:200 base+50 child+75 gas=325, without further 1/2 proration. + # Parent income 33_000 fails; an all-nonqualified household also receives zero. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: false + ssi: 0 + employment_income: 30_000 + person2: + age: 5 + is_citizen_or_legal_immigrant: true + ssi: 0 + person3: + age: 35 + is_citizen_or_legal_immigrant: false + ssi: 0 + employment_income: 33_000 + person4: + age: 5 + is_citizen_or_legal_immigrant: true + ssi: 0 + person5: + age: 35 + is_citizen_or_legal_immigrant: false + ssi: 0 + spm_units: + spm_unit1: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_000 + sc_tanf: 0 + spm_unit2: + members: [person3, person4] + heating_type: NATURAL_GAS + gas_expense: 1_000 + sc_tanf: 0 + spm_unit3: + members: [person5] + heating_type: NATURAL_GAS + gas_expense: 1_000 + sc_tanf: 0 + households: + household1: + members: [person1, person2] + state_code: SC + household2: + members: [person3, person4] + state_code: SC + household3: + members: [person5] + state_code: SC + output: + sc_liheap_household_size: [1, 1, 0] + sc_liheap_countable_income: [30_000, 33_000, 0] + sc_liheap_eligible: [true, false, false] + sc_liheap: [325, 0, 0] + +- name: Case 10, housing treatment and low bills do not create an expense cap. + period: 2026 + # Base 200 can exceed the actual 100 bill; housing subsidy does not exclude. + # Heat in rent permits aid without a separately quantified bill; known gas + # still adds 75. Zero income with unmeasured rent heat gets low-income 175, + # but zero measured energy cost does not prove burden. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person2: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person3: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person4: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person5: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + spm_units: + spm_unit1: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 100 + sc_tanf: 0 + spm_unit2: + members: [person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 100 + sc_tanf: 0 + receives_housing_assistance: true + spm_unit3: + members: [person3] + heating_type: UNSPECIFIED + sc_tanf: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + spm_unit4: + members: [person4] + heating_type: NATURAL_GAS + gas_expense: 0 + sc_tanf: 0 + heat_expense_included_in_rent: true + spm_unit5: + members: [person5] + heating_type: UNSPECIFIED + sc_tanf: 0 + heat_expense_included_in_rent: true + households: + household1: + members: [person1] + state_code: SC + household2: + members: [person2] + state_code: SC + household3: + members: [person3] + state_code: SC + household4: + members: [person4] + state_code: SC + household5: + members: [person5] + state_code: SC + output: + sc_liheap: [200, 200, 200, 275, 375] + +- name: Case 11, no heating missing matching bills and other states receive zero. + period: 2026 + # Eligible SC gas heating earns 200+75; the same NC unit is state-guarded. + # NONE has no heat. An electric bill does not supply the missing gas bill. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person2: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person3: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + person4: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + spm_units: + spm_unit1: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 1_000 + sc_tanf: 0 + spm_unit2: + members: [person2] + heating_type: NATURAL_GAS + gas_expense: 1_000 + sc_tanf: 0 + spm_unit3: + members: [person3] + heating_type: NONE + sc_tanf: 0 + spm_unit4: + members: [person4] + heating_type: NATURAL_GAS + gas_expense: 0 + sc_tanf: 0 + pre_subsidy_electricity_expense: 1_000 + households: + household1: + members: [person1] + state_code: SC + household2: + members: [person2] + state_code: NC + household3: + members: [person3] + state_code: SC + household4: + members: [person4] + state_code: SC + output: + sc_liheap: [275, 0, 0, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml new file mode 100644 index 00000000000..0b49a7b3a7c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml @@ -0,0 +1,263 @@ +# Accepted FY 2026 plan checks selected income streams and excludes minor earnings. +# https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=5,6 +# Unearned income of minors counts: only their earned income is expressly excluded. +# Annual flows approximate application-month income; monthly SSI is summed once. + +- name: Case 1, earnings count at eighteen while minor unearned income still counts. + period: 2026 + absolute_error_margin: 0.01 + # Age 17 wages 12_000 are excluded but survivor Social Security 1_000 counts. + # At 18 the same wages count: 10_000+1_000=11_000 versus 23_000. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 10_000 + person2: + age: 17 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 12_000 + social_security_survivors: 1_000 + person3: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 10_000 + person4: + age: 18 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 12_000 + social_security_survivors: 1_000 + spm_units: + spm_unit1: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit2: + members: [person3, person4] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + households: + household1: + members: [person1, person2] + state_code: SC + household2: + members: [person3, person4] + state_code: SC + output: + sc_liheap_countable_income: [11_000, 23_000] + +- name: Case 2, adult net business receipts and wages remain disjoint. + period: 2026 + absolute_error_margin: 0.01 + # 12_000 wages +2_000 net business +300 SSTB +700 farm +1_000 partnership + # =16_000. Existing business receipts are already net of expenses. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 12_000 + self_employment_income: 2_000 + sstb_self_employment_income: 300 + farm_operations_income: 700 + partnership_s_corp_income: 1_000 + spm_units: + spm_unit1: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + households: + household1: + members: [person1] + state_code: SC + output: + sc_liheap_countable_income: 16_000 + +- name: Case 3, pension Social Security survivor rental and gift receipts enter once. + period: 2026 + absolute_error_margin: 0.01 + # Pension 3_000 + Social Security 2_000 + other survivor 700 + rent 3_000 + # + unemployment 1_000 + alimony 400 + support 500 + VA 800 + gifts 600 =12_000. + # Neither pension nor Social Security subcomponents are counted twice. + input: + people: + person1: + age: 65 + is_citizen_or_legal_immigrant: true + ssi: 0 + taxable_pension_income: 2_000 + tax_exempt_pension_income: 1_000 + social_security_retirement: 500 + social_security_disability: 300 + social_security_survivors: 1_000 + social_security_dependents: 200 + survivor_benefits: 700 + rental_income: 1_800 + farm_rent_income: 1_200 + unemployment_compensation: 1_000 + alimony_income: 400 + child_support_received: 500 + veterans_benefits: 800 + financial_assistance: 600 + spm_units: + spm_unit1: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + households: + household1: + members: [person1] + state_code: SC + output: + sc_liheap_countable_income: 12_000 + +- name: Case 4, unchecked income types and assets do not inflate countable income. + period: 2026 + absolute_error_margin: 0.01 + # Only the 1_000 wages count. Planp 6 leaves interest/dividends, GA, TANF, + # strike pay, lottery lump sums, direct insurance and asset balances unchecked. + # SNAP is not cash income. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 1_000 + interest_income: 5_000 + ordinary_dividend_income: 5_000 + general_assistance: 500 + strike_benefits: 600 + gambling_winnings: 700 + life_insurance_benefits: 800 + spm_units: + spm_unit1: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 3_600 + snap: 3_600 + spm_unit_assets: 500_000 + households: + household1: + members: [person1] + state_code: SC + output: + sc_liheap_countable_income: 1_000 + +- name: Case 5, nonuniform SSI for an adult and child is annualized once. + period: 2026 + absolute_error_margin: 0.01 + # Adult SSI=6*100+6*200=1_800; child SSI=6*50+6*100=900. + # Annual countable income=2_700, with matching explicit monthly keys. + input: + people: + person1: + age: 65 + is_citizen_or_legal_immigrant: true + ssi: + 2026-01: 100 + 2026-02: 100 + 2026-03: 100 + 2026-04: 100 + 2026-05: 100 + 2026-06: 100 + 2026-07: 200 + 2026-08: 200 + 2026-09: 200 + 2026-10: 200 + 2026-11: 200 + 2026-12: 200 + person2: + age: 8 + is_citizen_or_legal_immigrant: true + ssi: + 2026-01: 50 + 2026-02: 50 + 2026-03: 50 + 2026-04: 50 + 2026-05: 50 + 2026-06: 50 + 2026-07: 100 + 2026-08: 100 + 2026-09: 100 + 2026-10: 100 + 2026-11: 100 + 2026-12: 100 + spm_units: + spm_unit1: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + households: + household1: + members: [person1, person2] + state_code: SC + output: + sc_liheap_countable_income: 2_700 + +- name: Case 6, nonqualified adult income counts and other state components remain zero. + period: 2026 + absolute_error_margin: 0.01 + # Nonqualified adult earnings 20_000 remain in household income. Negative net + # resources alone do not produce negative countable income. NC has no SC amount. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: false + ssi: 0 + employment_income: 20_000 + person2: + age: 5 + is_citizen_or_legal_immigrant: true + ssi: 0 + person3: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + self_employment_income: -2_000 + person4: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + spm_units: + spm_unit1: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit2: + members: [person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit3: + members: [person4] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + households: + household1: + members: [person1, person2] + state_code: SC + household2: + members: [person3] + state_code: SC + household3: + members: [person4] + state_code: NC + output: + sc_liheap_countable_income: [20_000, 0, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_eligible.yaml new file mode 100644 index 00000000000..67183d86d56 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_eligible.yaml @@ -0,0 +1,296 @@ +# Accepted plan has no categorical/asset or additional renter exclusions. +# https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=5,8 +# Current agency annual limits and demonstrated energy-need requirement. +# https://oeo.sc.gov/managedsites/prd/oeo/liheap.html +# These exact Boolean cases intentionally have no absolute_error_margin. + +- name: Case 1, annual published income ceiling is inclusive. + period: 2026 + # Four-person published ceiling is 61_525. That amount qualifies, but a cent + # above does not. This also distinguishes the rounded ceiling from 61_525.20. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 61_524 + person2: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person3: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person4: + age: 12 + is_citizen_or_legal_immigrant: true + ssi: 0 + person5: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 61_525 + person6: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person7: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person8: + age: 12 + is_citizen_or_legal_immigrant: true + ssi: 0 + person9: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 61_525.01 + person10: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person11: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person12: + age: 12 + is_citizen_or_legal_immigrant: true + ssi: 0 + person13: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 61_526 + person14: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person15: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person16: + age: 12 + is_citizen_or_legal_immigrant: true + ssi: 0 + spm_units: + spm_unit1: + members: [person1, person2, person3, person4] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit2: + members: [person5, person6, person7, person8] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit3: + members: [person9, person10, person11, person12] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit4: + members: [person13, person14, person15, person16] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + households: + household1: + members: [person1, person2, person3, person4] + state_code: SC + household2: + members: [person5, person6, person7, person8] + state_code: SC + household3: + members: [person9, person10, person11, person12] + state_code: SC + household4: + members: [person13, person14, person15, person16] + state_code: SC + output: + sc_liheap_eligible: [true, true, false, false] + +- name: Case 2, known fuel requires its matching bill unless heat is included in rent. + period: 2026 + # An electricity bill alone does not establish a gas-heating expense. NONE + # is an affirmative no-heating fact. Heat-in-rent establishes responsibility + # without a separately measured fuel bill; housing subsidy does not exclude. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person2: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person3: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person4: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person5: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person6: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + spm_units: + spm_unit1: + members: [person1] + heating_type: NATURAL_GAS + gas_expense: 0 + sc_tanf: 0 + pre_subsidy_electricity_expense: 1_000 + spm_unit2: + members: [person2] + heating_type: NATURAL_GAS + gas_expense: 1_000 + sc_tanf: 0 + spm_unit3: + members: [person3] + heating_type: NONE + sc_tanf: 0 + spm_unit4: + members: [person4] + heating_type: NONE + sc_tanf: 0 + heat_expense_included_in_rent: true + spm_unit5: + members: [person5] + heating_type: UNSPECIFIED + sc_tanf: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + spm_unit6: + members: [person6] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + receives_housing_assistance: true + households: + household1: + members: [person1] + state_code: SC + household2: + members: [person2] + state_code: SC + household3: + members: [person3] + state_code: SC + household4: + members: [person4] + state_code: SC + household5: + members: [person5] + state_code: SC + household6: + members: [person6] + state_code: SC + output: + sc_liheap_eligible: [false, true, false, false, true, true] + +- name: Case 3, cash program receipt does not override income and assets do not bar aid. + period: 2026 + # First income=106_000, above 31_993 even with SSI/SNAP/TANF receipt. + # The second has eligible income 20_000; no asset test applies. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 6_000 + employment_income: 100_000 + person2: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + spm_units: + spm_unit1: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 3_600 + snap: 3_600 + spm_unit2: + members: [person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit_assets: 1_000_000 + households: + household1: + members: [person1] + state_code: SC + household2: + members: [person2] + state_code: SC + output: + sc_liheap_eligible: [false, true] + +- name: Case 4, zero qualified size and residence prevent eligibility. + period: 2026 + # A positive actual heating bill cannot qualify an all-nonqualified household + # or a nonresident. The comparable qualified SC resident qualifies. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: false + ssi: 0 + person2: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person3: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + spm_units: + spm_unit1: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit2: + members: [person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit3: + members: [person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + households: + household1: + members: [person1] + state_code: SC + household2: + members: [person2] + state_code: SC + household3: + members: [person3] + state_code: NC + output: + sc_liheap_eligible: [false, true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_household_size.yaml new file mode 100644 index 00000000000..44ea28e299f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_household_size.yaml @@ -0,0 +1,82 @@ +# Approved agency plan expressly attributes this household rule to LIHEAP. +# https://oeo.sc.gov/managedsites/prd/oeo/documents/WAP%202026%20State%20Plan.pdf#page=23 +# Only its LIHEAP attribution is used; no WAP income or benefit rules are borrowed. + +- name: Case 1, qualified members determine size independently of all resident income. + period: 2026 + # Three residents with two nonqualified adults leave one qualified child. + # All-qualified size is two; zero qualified residents and another state give zero. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: false + ssi: 0 + person2: + age: 32 + is_citizen_or_legal_immigrant: false + ssi: 0 + person3: + age: 5 + is_citizen_or_legal_immigrant: true + ssi: 0 + person4: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + person5: + age: 32 + is_citizen_or_legal_immigrant: true + ssi: 0 + person6: + age: 35 + is_citizen_or_legal_immigrant: false + ssi: 0 + person7: + age: 60 + is_citizen_or_legal_immigrant: false + ssi: 0 + person8: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + person9: + age: 32 + is_citizen_or_legal_immigrant: true + ssi: 0 + spm_units: + spm_unit1: + members: [person1, person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit2: + members: [person4, person5] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit3: + members: [person6, person7] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit4: + members: [person8, person9] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + households: + household1: + members: [person1, person2, person3] + state_code: SC + household2: + members: [person4, person5] + state_code: SC + household3: + members: [person6, person7] + state_code: SC + household4: + members: [person8, person9] + state_code: NC + output: + sc_liheap_household_size: [1, 2, 0, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_income_limit.yaml new file mode 100644 index 00000000000..d99da8906d3 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_income_limit.yaml @@ -0,0 +1,302 @@ +# Current agency table publishes all eight annual income ceilings below. +# https://oeo.sc.gov/managedsites/prd/oeo/liheap.html +# Annual figures govern; the inconsistent one-person monthly figure is not used. +# Tests verify the published values without treating inferred rounding as law. + +- name: Case 1, all eight agency annual ceilings use actual qualified household sizes. + period: 2026 + absolute_error_margin: 0.01 + # Agency annual rows for sizes one through eight are transcribed independently. + # The seventh and eighth rows distinguish the published schedule from rounding + # the unrounded federal SMI threshold only at the final step. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 0 + person2: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 0 + person3: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person4: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 0 + person5: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person6: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person7: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 0 + person8: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person9: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person10: + age: 12 + is_citizen_or_legal_immigrant: true + ssi: 0 + person11: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 0 + person12: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person13: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person14: + age: 12 + is_citizen_or_legal_immigrant: true + ssi: 0 + person15: + age: 14 + is_citizen_or_legal_immigrant: true + ssi: 0 + person16: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 0 + person17: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person18: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person19: + age: 12 + is_citizen_or_legal_immigrant: true + ssi: 0 + person20: + age: 14 + is_citizen_or_legal_immigrant: true + ssi: 0 + person21: + age: 16 + is_citizen_or_legal_immigrant: true + ssi: 0 + person22: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 0 + person23: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person24: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person25: + age: 12 + is_citizen_or_legal_immigrant: true + ssi: 0 + person26: + age: 14 + is_citizen_or_legal_immigrant: true + ssi: 0 + person27: + age: 16 + is_citizen_or_legal_immigrant: true + ssi: 0 + person28: + age: 18 + is_citizen_or_legal_immigrant: true + ssi: 0 + person29: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 0 + person30: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person31: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + person32: + age: 12 + is_citizen_or_legal_immigrant: true + ssi: 0 + person33: + age: 14 + is_citizen_or_legal_immigrant: true + ssi: 0 + person34: + age: 16 + is_citizen_or_legal_immigrant: true + ssi: 0 + person35: + age: 18 + is_citizen_or_legal_immigrant: true + ssi: 0 + person36: + age: 20 + is_citizen_or_legal_immigrant: true + ssi: 0 + spm_units: + spm_unit1: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit2: + members: [person2, person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit3: + members: [person4, person5, person6] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit4: + members: [person7, person8, person9, person10] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit5: + members: [person11, person12, person13, person14, person15] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit6: + members: [person16, person17, person18, person19, person20, person21] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit7: + members: [person22, person23, person24, person25, person26, person27, person28] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit8: + members: + - person29 + - person30 + - person31 + - person32 + - person33 + - person34 + - person35 + - person36 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + households: + household1: + members: [person1] + state_code: SC + household2: + members: [person2, person3] + state_code: SC + household3: + members: [person4, person5, person6] + state_code: SC + household4: + members: [person7, person8, person9, person10] + state_code: SC + household5: + members: [person11, person12, person13, person14, person15] + state_code: SC + household6: + members: [person16, person17, person18, person19, person20, person21] + state_code: SC + household7: + members: [person22, person23, person24, person25, person26, person27, person28] + state_code: SC + household8: + members: + - person29 + - person30 + - person31 + - person32 + - person33 + - person34 + - person35 + - person36 + state_code: SC + output: + sc_liheap_income_limit: [31_993, 41_837, 51_681, 61_525, 71_369, 81_213, 83_058, 84_904] + +- name: Case 2, mixed status reduces the threshold and zero qualified size is guarded. + period: 2026 + absolute_error_margin: 0.01 + # One qualified child uses the one-person annual ceiling, without counting + # the nonqualified parent in size. No qualified residents gives zero. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: false + ssi: 0 + person2: + age: 5 + is_citizen_or_legal_immigrant: true + ssi: 0 + person3: + age: 35 + is_citizen_or_legal_immigrant: false + ssi: 0 + person4: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + spm_units: + spm_unit1: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit2: + members: [person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + spm_unit3: + members: [person4] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + households: + household1: + members: [person1, person2] + state_code: SC + household2: + members: [person3] + state_code: SC + household3: + members: [person4] + state_code: NC + output: + sc_liheap_household_size: [1, 0, 0] + sc_liheap_income_limit: [31_993, 0, 0] 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 new file mode 100644 index 00000000000..25cfebfef4e --- /dev/null +++ b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap.py @@ -0,0 +1,54 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.hhs_smi import smi + + +class sc_liheap(Variable): + value_type = float + entity = SPMUnit + label = "South Carolina LIHEAP regular heating assistance" + unit = USD + definition_period = YEAR + defined_for = StateCode.SC + reference = ( + "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8,9", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/SC_BenefitMatrix_2026.pdf#page=2", + ) + + 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) + state = spm_unit.household("state_code_str", period) + # The accepted plan's 30% SMI threshold governs over the older-vintage + # matrix's conflicting 42% threshold. Keep the percentage boundary exact. + low_income = income.astype(np.float64) <= p.low_income_smi_rate * smi( + size, state, period, parameters + ) + person = spm_unit.members + age = person("age", period) + elderly = spm_unit.any(age >= p.ages.elderly_min) + child = spm_unit.any(age <= p.ages.child_max) + disabled = spm_unit.any(person("is_disabled", period)) + veteran = spm_unit.any(person("is_veteran", period)) + # The plan measures home-energy utilities; aggregate existing energy + # bills, excluding water/sewer/trash/phone. 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 + ) + eligible = spm_unit("sc_liheap_eligible", period) + # Local funding and priority decisions are not represented. The state + # schedule does not impose a general benefit-to-bill cap. + return where(eligible, min_(amount, a.maximum), 0) diff --git a/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_countable_income.py b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_countable_income.py new file mode 100644 index 00000000000..f958c383ad3 --- /dev/null +++ b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_countable_income.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class sc_liheap_countable_income(Variable): + value_type = float + entity = SPMUnit + label = "South Carolina LIHEAP annual countable income" + unit = USD + definition_period = YEAR + defined_for = StateCode.SC + reference = ( + "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=5,6", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.sc.oeo.liheap.income + person = spm_unit.members + # Existing net business inputs approximate self-employment receipts + # without a second expense deduction. Annual flows approximate 30 days. + earned = sum(max_(person(source, period), 0) for source in p.earned_sources) + adult = person("age", period) >= p.earned_min_age + unearned = add(person, period, p.unearned_sources) + # The plan excludes minor earnings, not their unearned income. Adult-only + # verification requirements are not a general minor-income exclusion. + # Missing legal-settlement and earmarking facts are documented in README. + return max_(spm_unit.sum(where(adult, earned, 0) + unearned), 0) 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 new file mode 100644 index 00000000000..262a5301394 --- /dev/null +++ b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_eligible.py @@ -0,0 +1,28 @@ +from policyengine_us.model_api import * + + +class sc_liheap_eligible(Variable): + value_type = bool + entity = SPMUnit + label = "Eligible for South Carolina LIHEAP regular heating assistance" + definition_period = YEAR + defined_for = StateCode.SC + reference = ( + "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8,9", + "https://oeo.sc.gov/managedsites/prd/oeo/liheap.html", + ) + + def formula(spm_unit, period, parameters): + size = spm_unit("sc_liheap_household_size", period) + income = spm_unit("sc_liheap_countable_income", period) + limit = spm_unit("sc_liheap_income_limit", period) + fuel = spm_unit("heating_type", period) + heating_responsibility = spm_unit("has_heating_expense", period) | spm_unit( + "heat_expense_included_in_rent", period + ) + return ( + (size > 0) + & (income <= limit) + & (fuel != fuel.possible_values.NONE) + & heating_responsibility + ) diff --git a/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_household_size.py b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_household_size.py new file mode 100644 index 00000000000..6ca4fe23324 --- /dev/null +++ b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_household_size.py @@ -0,0 +1,15 @@ +from policyengine_us.model_api import * + + +class sc_liheap_household_size(Variable): + value_type = int + entity = SPMUnit + label = "South Carolina LIHEAP eligible household size" + definition_period = YEAR + defined_for = StateCode.SC + reference = "https://oeo.sc.gov/managedsites/prd/oeo/documents/WAP%202026%20State%20Plan.pdf#page=23" + + def formula(spm_unit, period, parameters): + # This agency plan expressly attributes the size exclusion, while + # retaining all members' income, to South Carolina LIHEAP guidance. + return spm_unit.sum(spm_unit.members("is_citizen_or_legal_immigrant", period)) diff --git a/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_income_limit.py b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_income_limit.py new file mode 100644 index 00000000000..9c0931d2fd4 --- /dev/null +++ b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_income_limit.py @@ -0,0 +1,29 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.hhs_smi import smi + + +class sc_liheap_income_limit(Variable): + value_type = float + entity = SPMUnit + label = "South Carolina LIHEAP annual income limit" + unit = USD + definition_period = YEAR + defined_for = StateCode.SC + reference = ( + "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8", + "https://oeo.sc.gov/managedsites/prd/oeo/liheap.html", + ) + + def formula(spm_unit, period, parameters): + size = spm_unit("sc_liheap_household_size", period) + state = spm_unit.household("state_code_str", period) + p = parameters(period) + base = p.gov.hhs.smi.amount[state] + factor = smi(size, state, period, parameters) / base + # Round the four-person base, then floor after the federal size factor. + # This inferred order reproduces all eight agency annual limits, including + # the published size-seven/eight values. It is not a legal rounding rule. + base_limit = np.floor( + base * p.gov.states.sc.oeo.liheap.eligibility.smi_rate + 0.5 + ) + return where(size > 0, np.floor(base_limit * factor), 0) From 3773ef3e551bee154c1d61be5db337da758e78de Mon Sep 17 00:00:00 2001 From: Ziming Date: Sat, 3 Oct 2026 20:24:40 -0400 Subject: [PATCH 2/6] Count recurring retirement distributions in South Carolina LIHEAP --- .../gov/states/sc/oeo/liheap/README.md | 4 +- .../oeo/liheap/income/unearned_sources.yaml | 3 ++ .../gov/states/sc/oeo/liheap/sc_liheap.yaml | 54 +++++++++++++++++++ .../liheap/sc_liheap_countable_income.yaml | 27 ++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/README.md b/policyengine_us/parameters/gov/states/sc/oeo/liheap/README.md index a0ce8ba7395..fde4a21a5c1 100644 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/README.md +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/README.md @@ -22,7 +22,9 @@ categorical income bypass or asset test in the adopted LIHEAP plan. Income uses the accepted plan's checked categories. Minor earnings are excluded; minor unearned income is counted because adult verification requirements are not an express income exclusion. Monthly SSI aggregates once over the year. -Pension and Social Security aggregates include their components; private +Pension and Social Security aggregates include their components; recurring +IRA/401k receipts enter through the separate retirement_distributions aggregate. +Annual inputs cannot distinguish excluded one-time retirement lump sums. Private survivor receipts and farm rent are separate. Net business inputs receive no second deduction. Cash from friends/relatives uses financial_assistance. Legal settlements lack an exact input. TANF, general assistance, interest/dividends, diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml index 6360ec95276..13169c167c1 100644 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml @@ -5,6 +5,9 @@ values: - social_security - ssi - pension_income + # Ongoing IRA/401k receipts are separate from pension_income. + # Annual inputs cannot identify excluded one-time retirement lump sums. + - retirement_distributions - survivor_benefits - rental_income - farm_rent_income diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml index d30a963e2bd..68febfe0f67 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml @@ -936,3 +936,57 @@ state_code: SC output: sc_liheap: [275, 0, 0, 0] + +# Recurring payments are countable; this does not represent a one-time withdrawal. +# At40,000 income exceeds31,993. At12,000:200base+75elderly+175low-income=450; +# the1,000 annual bill is below20percent, so no burden supplement applies. +- name: Recurring IRA payments above the income ceiling. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 70 + immigration_status: CITIZEN + # Isolate recurring retirement receipts; no SSI is received. + ssi: 0 + taxable_ira_distributions: 40000 + spm_units: + spm_unit: + members: [person] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + households: + household: + members: [person] + state_code: SC + output: + sc_liheap_countable_income: 40000 + sc_liheap: 0 + +# Recurring payments are countable; this does not represent a one-time withdrawal. +# At40,000 income exceeds31,993. At12,000:200base+75elderly+175low-income=450; +# the1,000 annual bill is below20percent, so no burden supplement applies. +- name: Recurring IRA payments without high energy burden. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 70 + immigration_status: CITIZEN + # Isolate recurring retirement receipts; no SSI is received. + ssi: 0 + taxable_ira_distributions: 12000 + spm_units: + spm_unit: + members: [person] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + households: + household: + members: [person] + state_code: SC + output: + sc_liheap_countable_income: 12000 + sc_liheap: 450 diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml index 0b49a7b3a7c..ee809675be0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml @@ -261,3 +261,30 @@ state_code: NC output: sc_liheap_countable_income: [20_000, 0, 0] + +# Pension1,000 + taxable recurring IRA12,000 + exempt recurring IRA3,000 =16,000. +# These disjoint aggregates enter once; no one-time lump sum is supplied. +- name: Recurring retirement account subtypes count beside pension income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 70 + immigration_status: CITIZEN + ssi: 0 + pension_income: 1_000 + taxable_ira_distributions: 12_000 + tax_exempt_ira_distributions: 3_000 + spm_units: + spm_unit: + members: [person] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + sc_tanf: 0 + households: + household: + members: [person] + state_code: SC + output: + sc_liheap_countable_income: 16_000 From 67df50cc8ef4f27e62a340fd90c749bf4937f0ce Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 2 Nov 2026 11:57:06 -0500 Subject: [PATCH 3/6] Apply Wave 2C sweep and review fixes Co-Authored-By: Claude Opus 5.5 --- changelog.d/sc-liheap-wave2c.added.md | 2 +- .../gov/states/sc/oeo/liheap/README.md | 70 ----- .../sc/oeo/liheap/eligibility/smi_rate.yaml | 2 +- .../sc/oeo/liheap/income/adult_min_age.yaml | 12 + .../sc/oeo/liheap/income/earned_min_age.yaml | 10 - .../sc/oeo/liheap/income/earned_sources.yaml | 4 +- .../liheap/income/minor_unearned_sources.yaml | 15 + .../oeo/liheap/income/unearned_sources.yaml | 8 +- .../states/sc/oeo/liheap/payment/ages.yaml | 4 +- .../states/sc/oeo/liheap/payment/amounts.yaml | 4 +- .../sc/oeo/liheap/payment/burden_rate.yaml | 2 +- .../liheap/payment/energy_cost_sources.yaml | 3 +- .../liheap/payment/low_income_smi_rate.yaml | 4 +- .../oeo/liheap/payment/supplement_fuels.yaml | 2 +- policyengine_us/programs.yaml | 2 +- .../gov/states/sc/oeo/liheap/sc_liheap.yaml | 280 +++++++++++++----- .../liheap/sc_liheap_countable_income.yaml | 91 +++++- .../sc/oeo/liheap/sc_liheap_eligible.yaml | 31 +- .../oeo/liheap/sc_liheap_household_size.yaml | 4 - .../sc/oeo/liheap/sc_liheap_income_limit.yaml | 11 - .../gov/states/sc/oeo/liheap/sc_liheap.py | 31 +- .../oeo/liheap/sc_liheap_countable_income.py | 26 +- .../sc/oeo/liheap/sc_liheap_eligible.py | 2 +- .../sc/oeo/liheap/sc_liheap_household_size.py | 12 +- .../sc/oeo/liheap/sc_liheap_income_limit.py | 16 +- source_conflicts/candidates.yaml | 28 ++ 26 files changed, 419 insertions(+), 257 deletions(-) delete mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/README.md create mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/income/adult_min_age.yaml delete mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_min_age.yaml create mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/income/minor_unearned_sources.yaml diff --git a/changelog.d/sc-liheap-wave2c.added.md b/changelog.d/sc-liheap-wave2c.added.md index d9a444b1aa5..cf898f6f143 100644 --- a/changelog.d/sc-liheap-wave2c.added.md +++ b/changelog.d/sc-liheap-wave2c.added.md @@ -1 +1 @@ -Add South Carolina FY2026 regular heating assistance, including received income, federal income protection and statewide supplements through the full eligible child-age interval. +Add South Carolina LIHEAP regular heating assistance for FY2026, with countable income, the federal 110% poverty floor and statewide supplements. diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/README.md b/policyengine_us/parameters/gov/states/sc/oeo/liheap/README.md deleted file mode 100644 index c293593f234..00000000000 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# South Carolina regular heating assistance - -The accepted FY2026 plan governs the statewide regular-heating schedule: base -$200 plus the specified demographic, fuel, low-income and high-burden additions, -up to $850. The plan repeatedly uses 30% SMI and burden of 20% or more. Those -specific adopted definitions take precedence over the matrix's older-vintage -42% SMI and conflicting strict electric/tank-fill burden language. The matrix's -explicit gas/oil/kerosene/propane fuel definition is retained where consistent. - -The model calculates countable income, the eligible household size, the annual -ceiling, eligibility and the regular heating award. The agency's approved 2026 -WAP plan (physical page 23) expressly attributes to LIHEAP guidance the rule that -ineligible members' income counts but those members do not count toward household -size. The approved 2024 plan (physical page 21) states the same rule, supporting -continuity for FY2026. Only that explicitly attributed LIHEAP rule is used from -those plans; their weatherization eligibility and payment rules are not used. -The qualified-size ceiling and low-income threshold use this rule. Demographic -supplements retain the LIHEAP plan's explicit household-member conditions; that -plan does not restrict these conditions to qualified members. There is no -categorical income bypass or asset test in the adopted LIHEAP plan. - -Income uses the accepted plan's checked categories. Minor earnings are excluded; -minor unearned income is counted because adult verification requirements are -not an express income exclusion. Monthly SSI aggregates once over the year. -Pension and Social Security aggregates include their components; military -retirement pay is separate and enters once. The cross-state military-survivor -aggregate migration remains deferred. Recurring -IRA/401k receipts enter through the separate retirement_distributions aggregate. -Annual inputs cannot distinguish excluded one-time retirement lump sums. Private -survivor receipts and farm rent are separate. Net business inputs receive no -second deduction. Received unearned sources are floored separately per person, -so a rental loss cannot cancel a positive pension or another receipt. UI uses -the shared reported-or-modeled unemployment amount. Cash from friends/relatives -uses financial_assistance. Legal -settlements lack an exact input. TANF, general assistance, interest/dividends, -strike, insurance, one-time lump sums and foster payments are not counted under -the current checklist. Social Security remains gross, including Medicare, as -the accepted plan specifies; no Medicare deduction is applied. Application-month -income timing and work-study embedded in wages remain input limitations. - -The annual income ceiling reuses requested-year federal SMI. Rounding the -four-person 60% base and then flooring the size-adjusted result reproduces all -eight current agency annual limits, including sizes seven and eight. That order -is inferred from published amounts, not an explicit legal rounding direction. -The inconsistent one-person monthly figure is not used. The 30% supplement -boundary uses the plan percentage without rounding the income ratio. -A separate income gate preserves the strict federal 110% FPG protection using -the same actual qualified household size and prior-year guidelines. The state -SMI ceiling remains unchanged; exact 110% income has no federal protection. -Young-child supplements use completed age, retaining age-five children until -their sixth birthday. - -Energy burden uses the sum of existing electric, gas and fuel bills. Water, -sewer, trash and telephone are excluded from home-energy cost. This reuses -represented bills more faithfully than primary heating cost alone, but annual -flows still approximate the current month and rent-embedded charges are missing. -Positive expenses at zero income qualify for high burden; zero expense does not -prove a burden. There is no general benefit-to-bill cap or subsidized-renter -exclusion in the supported schedule. Heating responsibility uses has_heating_expense or heat_expense_included_in_rent, -with an explicit NONE heating type always excluded. The existing responsibility -input supports cases where a household pays for secondary heating. There is no -extra exclusion for subsidized housing or heat included in rent. The model does -not split households across SPM units, adjudicate applications, or model local -CAA restrictions, priority and available funding. - -Only FY2026 is researched. The revised FY2027 plan is a draft. Earlier parameter -backfills are unverified historical estimates. Crisis, cooling, weatherization -and equipment are outside this work. The SPM unit approximates the energy household. Application-month income and -expense data and detailed legal immigration facts remain limitations of existing -inputs; parameter backfilling does not verify earlier years. 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 e7a42f1f14a..257297b9d61 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,5 @@ metadata: period: year label: South Carolina LIHEAP SMI limit rate reference: - - title: South Carolina FY2026 LIHEAP policy + - title: South Carolina FY2026 LIHEAP Detailed Model Plan, item 2.1 href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/adult_min_age.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/adult_min_age.yaml new file mode 100644 index 00000000000..10e9c7543b9 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/adult_min_age.yaml @@ -0,0 +1,12 @@ +description: South Carolina counts the earned and unearned income sources at or above this age, and only the minor unearned income sources below it, under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 18 +metadata: + unit: year + period: year + label: South Carolina LIHEAP adult income age + reference: + - title: South Carolina FY2026 LIHEAP Detailed Model Plan, item 1.9 + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=6 + - title: South Carolina 2024 Administrative Guide, income from minors + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=529 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_min_age.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_min_age.yaml deleted file mode 100644 index d79f502e4ed..00000000000 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_min_age.yaml +++ /dev/null @@ -1,10 +0,0 @@ -description: South Carolina counts earned income at or above this age under the Low Income Home Energy Assistance Program. -values: - 2025-10-01: 18 -metadata: - unit: year - period: year - label: South Carolina LIHEAP minimum earned income age - reference: - - title: South Carolina FY2026 LIHEAP policy - href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_sources.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_sources.yaml index 557ac02be01..88bc8453e78 100644 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_sources.yaml +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_sources.yaml @@ -11,5 +11,5 @@ metadata: period: year label: South Carolina LIHEAP earned income sources reference: - - title: "South Carolina FY2026 LIHEAP policy #page 5-6" - href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=5 \ No newline at end of file + - title: "South Carolina FY2026 LIHEAP Detailed Model Plan, items 1.8-1.9 #page 5-6" + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/minor_unearned_sources.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/minor_unearned_sources.yaml new file mode 100644 index 00000000000..9fa33582576 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/minor_unearned_sources.yaml @@ -0,0 +1,15 @@ +description: South Carolina counts these unearned income sources of household members younger than the adult income age under the Low Income Home Energy Assistance Program. +# The guide names "Social Security or Disability income". SSI is read as a child's +# disability income, and disability_benefits carries other disability income. +values: + 2025-10-01: + - social_security + - ssi + - disability_benefits +metadata: + unit: list + period: year + label: South Carolina LIHEAP minor unearned income sources + reference: + - title: South Carolina 2024 Administrative Guide, income from minors + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=529 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml index 957789a99de..e5505ecbffd 100644 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml @@ -1,11 +1,13 @@ -description: South Carolina counts these unearned income sources under the Low Income Home Energy Assistance Program. +description: South Carolina counts these unearned income sources of adult household members under the Low Income Home Energy Assistance Program. values: 2025-10-01: - total_unemployment_compensation - social_security - ssi - pension_income - - military_retirement_pay + # Military retirement includes Survivor Benefit Plan annuities. + - military_retirement_income + - railroad_benefits # Ongoing IRA/401k receipts are separate from pension_income. # Annual inputs cannot identify excluded one-time retirement lump sums. - retirement_distributions @@ -21,5 +23,5 @@ metadata: period: year label: South Carolina LIHEAP unearned income sources reference: - - title: South Carolina FY2026 LIHEAP policy + - title: South Carolina FY2026 LIHEAP Detailed Model Plan, item 1.9 href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=6 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 c3c12418668..fe03cbbf206 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 @@ -9,5 +9,5 @@ metadata: period: year label: South Carolina LIHEAP supplement age thresholds reference: - - title: "South Carolina FY2026 LIHEAP policy #page 8-9" - href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8 \ No newline at end of file + - title: "South Carolina FY2026 LIHEAP Detailed Model Plan, items 2.3-2.5 #page 8-9" + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/amounts.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/amounts.yaml index 710186be962..bff26ad685c 100644 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/amounts.yaml +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/amounts.yaml @@ -22,5 +22,5 @@ metadata: period: year label: South Carolina LIHEAP regular heating amounts reference: - - title: "South Carolina FY2026 LIHEAP policy #page 8-9" - href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8 \ No newline at end of file + - title: "South Carolina FY2026 LIHEAP Detailed Model Plan, items 2.3, 2.5 and 2.6 #page 8-9" + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.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 ab432d49d86..f4f10183689 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,5 @@ metadata: period: year label: South Carolina LIHEAP high energy burden rate reference: - - title: South Carolina FY2026 LIHEAP policy + - title: South Carolina FY2026 LIHEAP Detailed Model Plan, item 2.5 href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.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 d9806116967..41867f7cef1 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 @@ -8,11 +8,10 @@ values: - wood_expense - coal_expense - other_heating_fuel_expense - - cooking_fuel_expense metadata: unit: list period: year label: South Carolina LIHEAP home energy cost sources reference: - - title: South Carolina FY2026 LIHEAP policy + - title: South Carolina FY2026 LIHEAP Detailed Model Plan, item 2.5 href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_smi_rate.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_smi_rate.yaml index e2f4b9e10be..174eee4f301 100644 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_smi_rate.yaml +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_smi_rate.yaml @@ -6,5 +6,5 @@ metadata: period: year label: South Carolina LIHEAP low-income SMI rate reference: - - title: "South Carolina FY2026 LIHEAP policy #page 8-9" - href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8 \ No newline at end of file + - title: "South Carolina FY2026 LIHEAP Detailed Model Plan, items 2.3 and 2.5 #page 8-9" + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8 diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/supplement_fuels.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/supplement_fuels.yaml index c1896653e9e..2f592d03cc4 100644 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/supplement_fuels.yaml +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/supplement_fuels.yaml @@ -10,5 +10,5 @@ metadata: period: year label: South Carolina LIHEAP supplement heating fuels reference: - - title: South Carolina FY2026 LIHEAP policy + - title: South Carolina 2026 Benefit Matrix, item 6 href: https://liheapch.acf.gov/docs/2026/benefits-matricies/SC_BenefitMatrix_2026.pdf#page=2 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 8dd9506bea1..d43831044f1 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -967,7 +967,7 @@ programs: variable: sc_liheap parameter_prefix: gov.states.sc.oeo.liheap verified_years: "2026" - notes: FY2026 regular heating statewide schedule, qualified household size and countable income, including strict federal income protection, received UI, separate military pay and per-source unearned floors. Young-child supplements continue until the sixth birthday. Social Security remains gross including Medicare, and TANF remains excluded. Annual inputs approximate application-month income and energy burden; SPM units approximate energy households. Local agency funding and priority decisions and detailed income-source exclusions are not represented. Earlier backfills are unverified historical estimates; crisis, cooling, weatherization and equipment are outside scope. + 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. # --- SSA programs --- - id: ssi diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml index 9b4714f9cb1..a58f265472a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml @@ -4,8 +4,9 @@ # https://liheapch.acf.gov/docs/2026/benefits-matricies/SC_BenefitMatrix_2026.pdf#page=2 # Agency's explicit LIHEAP household rule counts all income but qualified size. # https://oeo.sc.gov/managedsites/prd/oeo/documents/WAP%202026%20State%20Plan.pdf#page=23 +# OEO's 2024 guide limits supplements to eligible members and prorates mixed-status awards. +# https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=537 # These are annual state-schedule awards, before unmodeled local funding discretion. -# All intermediate outputs below are calculated from raw facts, never forced inputs. - name: Case 1, source base and combined low-income high-burden examples. period: 2026 @@ -52,12 +53,10 @@ members: [person1, person2, person3, person4] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 4_000 - sc_tanf: 0 spm_unit2: members: [person5, person6, person7, person8] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 6_000 - sc_tanf: 0 households: household1: members: [person1, person2, person3, person4] @@ -75,10 +74,9 @@ - name: Case 2, accepted thirty-percent SMI boundary governs the supplement. period: 2026 absolute_error_margin: 0.01 - # Using shared raw four-person SMI 102_542,30%=30_762.60. At or below the - # boundary add 175 to base 200; a cent above receives only 200. Income 35_000 - # rejects the older matrix 42% rule. Raw SMI base is a shared-contract input, - # not independently reverified federal data in this test-author role. + # Four-person SMI 102_542 x 30% = 30_762.60. At or below the boundary add 175 + # to base 200; a cent above receives only 200. Income 35_000 rejects the older + # matrix 42% rule. input: people: person1: @@ -154,22 +152,18 @@ members: [person1, person2, person3, person4] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit2: members: [person5, person6, person7, person8] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit3: members: [person9, person10, person11, person12] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit4: members: [person13, person14, person15, person16] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 households: household1: members: [person1, person2, person3, person4] @@ -212,19 +206,16 @@ members: [person1] heating_type: NATURAL_GAS gas_expense: 500 - sc_tanf: 0 pre_subsidy_electricity_expense: 3_499 spm_unit2: members: [person2] heating_type: NATURAL_GAS gas_expense: 500 - sc_tanf: 0 pre_subsidy_electricity_expense: 3_500 spm_unit3: members: [person3] heating_type: NATURAL_GAS gas_expense: 500 - sc_tanf: 0 pre_subsidy_electricity_expense: 3_501 households: household1: @@ -254,7 +245,6 @@ members: [person1] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 water_expense: 5_000 trash_expense: 2_000 phone_expense: 2_000 @@ -267,7 +257,7 @@ - name: Case 5, named supplement fuels and other heating fuels are distinguished. period: 2026 - # Matrixp 2 names gas, oil/kerosene and propane for 75. Every scenario has + # Matrix page 2 names gas, oil/kerosene and propane for 75. Every scenario has # its actual matching bill 1_000 and income 20_000, so no low-income/burden # supplement applies. Other represented heating fuels receive the base 200. input: @@ -322,47 +312,38 @@ members: [person1] heating_type: NATURAL_GAS gas_expense: 1_000 - sc_tanf: 0 spm_unit2: members: [person2] heating_type: FUEL_OIL fuel_oil_expense: 1_000 - sc_tanf: 0 spm_unit3: members: [person3] heating_type: KEROSENE fuel_oil_expense: 1_000 - sc_tanf: 0 spm_unit4: members: [person4] heating_type: PROPANE bottled_gas_expense: 1_000 - sc_tanf: 0 spm_unit5: members: [person5] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit6: members: [person6] heating_type: WOOD wood_expense: 1_000 - sc_tanf: 0 spm_unit7: members: [person7] heating_type: COAL coal_expense: 1_000 - sc_tanf: 0 spm_unit8: members: [person8] heating_type: SOLAR pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit9: members: [person9] heating_type: OTHER other_heating_fuel_expense: 1_000 - sc_tanf: 0 households: household1: members: [person1] @@ -396,8 +377,8 @@ - name: Case 6, age disability and veteran supplements apply once per category. period: 2026 - # Two-person income 30_000 exceeds its 30%SMI level and is below 41_837. - # Base 200 gains 75 at age 60;50 for child<=5, any disabled member, or any + # Two-person income 30_000 exceeds its 30% SMI level and is below 41_837. + # Base 200 gains 75 at age 60; 50 for child <= 5, any disabled member, or any # veteran. Two veterans still earn a single 50 supplement. input: people: @@ -483,42 +464,34 @@ members: [person1, person2] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit2: members: [person3, person4] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit3: members: [person5, person6] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit4: members: [person7, person8] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit5: members: [person9, person10] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit6: members: [person11, person12] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit7: members: [person13, person14] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit8: members: [person15, person16] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 households: household1: members: [person1, person2] @@ -626,17 +599,14 @@ members: [person1, person2, person3, person4] heating_type: PROPANE bottled_gas_expense: 6_000 - sc_tanf: 0 spm_unit2: members: [person5, person6, person7, person8, person9, person10] heating_type: PROPANE bottled_gas_expense: 6_000 - sc_tanf: 0 spm_unit3: members: [person11, person12, person13, person14] heating_type: PROPANE bottled_gas_expense: 6_000 - sc_tanf: 0 households: household1: members: [person1, person2, person3, person4] @@ -650,11 +620,11 @@ output: sc_liheap: [850, 850, 850] -- name: Case 8, nonqualified residents may trigger household demographic supplements. +- name: Case 8, nonqualified residents earn no supplement and the award is prorated. period: 2026 - # Qualified size is one for each threshold. LIHEAP plan demographics refer - # to a household member without a qualified-status restriction: add 75 for - # the nonqualified elder or 50 for the child, disabled person or veteran. + # Qualified size is one for each threshold. The 2024 guide counts only eligible + # members for the supplements, so the nonqualified elder, child, disabled person + # and veteran add nothing: base 200 / 2 members x 1 eligible member = 100. input: people: person1: @@ -700,22 +670,18 @@ members: [person1, person2] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit2: members: [person3, person4] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit3: members: [person5, person6] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit4: members: [person7, person8] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 households: household1: members: [person1, person2] @@ -733,12 +699,12 @@ sc_liheap_household_size: [1, 1, 1, 1] sc_liheap_countable_income: [20_000, 20_000, 20_000, 20_000] sc_liheap_income_limit: [31_993, 31_993, 31_993, 31_993] - sc_liheap: [275, 250, 250, 250] + sc_liheap: [100, 100, 100, 100] - name: Case 9, all resident income is compared with qualified-size limits. period: 2026 # Qualified child gives size one and ceiling 31_993. Parent income 30_000 - # qualifies:200 base+50 child+75 gas=325, without further 1/2 proration. + # qualifies: 200 base + 50 child + 75 gas = 325, / 2 members x 1 eligible = 162.50. # Parent income 33_000 fails; an all-nonqualified household also receives zero. input: people: @@ -769,17 +735,14 @@ members: [person1, person2] heating_type: NATURAL_GAS gas_expense: 1_000 - sc_tanf: 0 spm_unit2: members: [person3, person4] heating_type: NATURAL_GAS gas_expense: 1_000 - sc_tanf: 0 spm_unit3: members: [person5] heating_type: NATURAL_GAS gas_expense: 1_000 - sc_tanf: 0 households: household1: members: [person1, person2] @@ -794,7 +757,7 @@ sc_liheap_household_size: [1, 1, 0] sc_liheap_countable_income: [30_000, 33_000, 0] sc_liheap_eligible: [true, false, false] - sc_liheap: [325, 0, 0] + sc_liheap: [162.5, 0, 0] - name: Case 10, housing treatment and low bills do not create an expense cap. period: 2026 @@ -833,29 +796,24 @@ members: [person1] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 100 - sc_tanf: 0 spm_unit2: members: [person2] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 100 - sc_tanf: 0 receives_housing_assistance: true spm_unit3: members: [person3] heating_type: UNSPECIFIED - sc_tanf: 0 heat_expense_included_in_rent: true receives_housing_assistance: true spm_unit4: members: [person4] heating_type: NATURAL_GAS gas_expense: 0 - sc_tanf: 0 heat_expense_included_in_rent: true spm_unit5: members: [person5] heating_type: UNSPECIFIED - sc_tanf: 0 heat_expense_included_in_rent: true households: household1: @@ -905,21 +863,17 @@ members: [person1] heating_type: NATURAL_GAS gas_expense: 1_000 - sc_tanf: 0 spm_unit2: members: [person2] heating_type: NATURAL_GAS gas_expense: 1_000 - sc_tanf: 0 spm_unit3: members: [person3] heating_type: NONE - sc_tanf: 0 spm_unit4: members: [person4] heating_type: NATURAL_GAS gas_expense: 0 - sc_tanf: 0 pre_subsidy_electricity_expense: 1_000 households: household1: @@ -938,9 +892,8 @@ sc_liheap: [275, 0, 0, 0] # Recurring payments are countable; this does not represent a one-time withdrawal. -# At40,000 income exceeds31,993. At12,000:200base+75elderly+175low-income=450; -# the1,000 annual bill is below20percent, so no burden supplement applies. -- name: Recurring IRA payments above the income ceiling. +# Income 40,000 exceeds the one-person 31,993 ceiling, so the award is 0. +- name: Case 12, recurring IRA payments above the income ceiling. period: 2026 absolute_error_margin: 0.01 input: @@ -965,9 +918,9 @@ sc_liheap: 0 # Recurring payments are countable; this does not represent a one-time withdrawal. -# At40,000 income exceeds31,993. At12,000:200base+75elderly+175low-income=450; -# the1,000 annual bill is below20percent, so no burden supplement applies. -- name: Recurring IRA payments without high energy burden. +# Income 12,000: 200 base + 75 elderly + 175 low income = 450. The 1,000 annual +# bill is below 20 percent, so no burden supplement applies. +- name: Case 13, recurring IRA payments without high energy burden. period: 2026 absolute_error_margin: 0.01 input: @@ -991,11 +944,10 @@ sc_liheap_countable_income: 12000 sc_liheap: 450 - -# Military retirement is a separate receipt; a rental loss cannot cancel a -#33,000 pension. The last two rows are existing-aggregate UI controls because -#there is no SC state UI model. Reported UI replaces the fallback, never adds -#to it. Every row exceeds the one-person31,993 ordinary income ceiling. +# Military retirement is a separate receipt; a rental loss cannot cancel a 33,000 +# pension. The last two rows are existing-aggregate unemployment controls because +# there is no SC state unemployment model. Reported unemployment replaces the +# fallback, never adds to it. Every row exceeds the one-person 31,993 ceiling. - name: Case 14, distinct received income prevents excess regular awards. period: 2026 input: @@ -1017,10 +969,10 @@ sc_liheap_eligible: [false, false, false, false] sc_liheap: [0, 0, 0, 0] -# Agefiveandunder includes5.5 until the sixthbirthday. With two qualified -#members,30,000income is above30%SMI but below the41,837 ceiling. Electric -#1,000 is below20%burden and has no fuel supplement:200base+50child=250; -#the same household at age6 receives200. +# Age five and under includes 5.5 until the sixth birthday. With two qualified +# members, 30,000 income is above 30% SMI but below the 41,837 ceiling. Electric +# 1,000 is below the 20% burden and has no fuel supplement: 200 base + 50 child = +# 250; the same household at age 6 receives 200. - name: Case 15, the young-child supplement lasts until the sixth birthday. period: 2026 input: @@ -1051,10 +1003,10 @@ sc_liheap_eligible: [true, true] sc_liheap: [250, 200] -# Actualqualifiedsize25uses2025FPG15,650+24*5,500=147,650; -#strict110%floor162,415 protects150,000income above ordinarySMI116,282. -#No vulnerable member, veteran, fuel or burden supplement applies, and income -#exceeds30%SMI: base200 reaches the final award. Exact162,415 is unprotected. +# Qualified size 25 uses the 2025 FPG 15,650 + 24 x 5,500 = 147,650. The strict +# 110% floor of 162,415 protects 150,000 income above the ordinary SMI limit of +# 116,282. No vulnerable member, veteran, fuel or burden supplement applies, and +# income exceeds 30% SMI: base 200 is the award. Exactly 162,415 is unprotected. - name: Case 16, large households retain strict federal income protection. period: 2026 input: @@ -1334,3 +1286,171 @@ sc_liheap_income_limit: [116_282, 116_282] sc_liheap_eligible: [true, false] sc_liheap: [200, 0] + +- name: Case 17, heating fuel bills reach the burden supplement and cooking fuel does not. + period: 2026 + # At income 20_000, 20% = 4_000 and the one-person 30% SMI level is 15_996.55. + # Fuel oil: 200 + 75 fuel + 175 burden = 450. Wood, coal and other fuel: 200 + 175 + # = 375. Electric 3_000 plus cooking fuel 1_000 stays below 4_000: 200. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person2: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person3: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person4: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person5: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + spm_units: + spm_unit1: + members: [person1] + heating_type: FUEL_OIL + fuel_oil_expense: 4_000 + spm_unit2: + members: [person2] + heating_type: WOOD + wood_expense: 4_000 + spm_unit3: + members: [person3] + heating_type: COAL + coal_expense: 4_000 + spm_unit4: + members: [person4] + heating_type: OTHER + other_heating_fuel_expense: 4_000 + spm_unit5: + members: [person5] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 3_000 + cooking_fuel_expense: 1_000 + households: + household1: + members: [person1] + state_code: SC + household2: + members: [person2] + state_code: SC + household3: + members: [person3] + state_code: SC + household4: + members: [person4] + state_code: SC + household5: + members: [person5] + state_code: SC + output: + sc_liheap: [450, 375, 375, 375, 200] + +- name: Case 18, state-placed foster children are not household members. + period: 2026 + # Each foster child is left out of size, income and the supplements, so both + # adults are one-person households with the 31_993 ceiling. Wages 35_000 fail + # the ceiling. Wages 20_000 pass, the disabled child's 10_000 survivor benefits + # do not count, and neither child supplement applies: 200 + 75 gas = 275. The + # child also leaves the proration count, so one eligible of one member keeps 275. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 35_000 + person2: + age: 3 + is_citizen_or_legal_immigrant: true + ssi: 0 + is_in_foster_care: + 2026-01: true + person3: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person4: + age: 3 + is_citizen_or_legal_immigrant: true + ssi: 0 + is_disabled: true + social_security_survivors: 10_000 + is_in_foster_care: + 2026-01: true + spm_units: + spm_unit1: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_000 + spm_unit2: + members: [person3, person4] + heating_type: NATURAL_GAS + gas_expense: 1_000 + households: + household1: + members: [person1, person2] + state_code: SC + household2: + members: [person3, person4] + state_code: SC + output: + sc_liheap_household_size: [1, 1] + sc_liheap_countable_income: [35_000, 20_000] + sc_liheap_income_limit: [31_993, 31_993] + sc_liheap_eligible: [false, true] + sc_liheap: [0, 275] + +- name: Case 19, the 2024 guide's prorated example pays eligible members only. + period: 2026 + # Guide page 537: 425 / 4 members = 106.25, x 3 eligible = 318.75. Here 200 base + + # 50 child + 175 low income = 425. The nonqualified adult's 20_000 wages count and + # stay below the three-person 30% SMI level of 25_840.58; 1_000 is under 20%. + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: false + ssi: 0 + employment_income: 20_000 + person2: + age: 33 + is_citizen_or_legal_immigrant: true + ssi: 0 + person3: + age: 5 + is_citizen_or_legal_immigrant: true + ssi: 0 + person4: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 0 + spm_units: + spm_unit1: + members: [person1, person2, person3, person4] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + households: + household1: + members: [person1, person2, person3, person4] + state_code: SC + output: + sc_liheap_household_size: 3 + sc_liheap_countable_income: 20_000 + sc_liheap_income_limit: 51_681 + sc_liheap: 318.75 diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml index a170e0712d1..9e5e6deced4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml @@ -1,9 +1,10 @@ # Accepted FY 2026 plan checks selected income streams and excludes minor earnings. # https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=5 -# Unearned income of minors counts: only their earned income is expressly excluded. +# OEO's 2024 guide counts only Social Security or disability income under age 18. +# https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=529 # Annual flows approximate application-month income; monthly SSI is summed once. -- name: Case 1, earnings count at eighteen while minor unearned income still counts. +- name: Case 1, earnings count at eighteen while a minor's Social Security still counts. period: 2026 absolute_error_margin: 0.01 # Age 17 wages 12_000 are excluded but survivor Social Security 1_000 counts. @@ -37,12 +38,10 @@ members: [person1, person2] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit2: members: [person3, person4] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 households: household1: members: [person1, person2] @@ -74,7 +73,6 @@ members: [person1] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 households: household1: members: [person1] @@ -113,7 +111,6 @@ members: [person1] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 households: household1: members: [person1] @@ -124,7 +121,7 @@ - name: Case 4, unchecked income types and assets do not inflate countable income. period: 2026 absolute_error_margin: 0.01 - # Only the 1_000 wages count. Planp 6 leaves interest/dividends, GA, TANF, + # Only the 1_000 wages count. Plan page 6 leaves interest/dividends, GA, TANF, # strike pay, lottery lump sums, direct insurance and asset balances unchecked. # SNAP is not cash income. input: @@ -199,7 +196,6 @@ members: [person1, person2] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 households: household1: members: [person1, person2] @@ -238,17 +234,14 @@ members: [person1, person2] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit2: members: [person3] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit3: members: [person4] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 households: household1: members: [person1, person2] @@ -262,9 +255,9 @@ output: sc_liheap_countable_income: [20_000, 0, 0] -# Pension1,000 + taxable recurring IRA12,000 + exempt recurring IRA3,000 =16,000. +# Pension 1,000 + taxable recurring IRA 12,000 + exempt recurring IRA 3,000 = 16,000. # These disjoint aggregates enter once; no one-time lump sum is supplied. -- name: Recurring retirement account subtypes count beside pension income. +- name: Case 7, recurring retirement account subtypes count beside pension income. period: 2026 absolute_error_margin: 0.01 input: @@ -281,10 +274,80 @@ members: [person] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 households: household: members: [person] state_code: SC output: sc_liheap_countable_income: 16_000 + +# Plan item 1.9 counts retirement and pension benefits: railroad retirement 6,000. +- name: Case 8, railroad retirement counts as a retirement benefit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 60 + immigration_status: CITIZEN + ssi: 0 + railroad_benefits: 6_000 + spm_units: + spm_unit: + members: [person] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + households: + household: + members: [person] + state_code: SC + output: + sc_liheap_countable_income: 6_000 + +# 2024 guide: under 18, only Social Security or disability income counts. The child's +# 1,000 survivors + 1,200 SSI + 500 disability count with the adult's 10,000 wages: +# 12,700. The child's pension, child support and other survivor benefits do not. The +# adult's 500 of disability benefits has no plan 1.9 box, so that unit stays at 10,000. +- name: Case 9, a minor counts only Social Security SSI and disability income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 10_000 + person2: + age: 10 + is_citizen_or_legal_immigrant: true + ssi: 1_200 + social_security_survivors: 1_000 + disability_benefits: 500 + pension_income: 700 + child_support_received: 400 + survivor_benefits: 300 + person3: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 10_000 + disability_benefits: 500 + spm_units: + spm_unit1: + members: [person1, person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + spm_unit2: + members: [person3] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + households: + household1: + members: [person1, person2] + state_code: SC + household2: + members: [person3] + state_code: SC + output: + sc_liheap_countable_income: [12_700, 10_000] diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_eligible.yaml index 440e6271c80..d219389a8ae 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_eligible.yaml @@ -7,7 +7,7 @@ - name: Case 1, annual published income ceiling is inclusive. period: 2026 # Four-person published ceiling is 61_525. That amount qualifies, but a cent - # above does not. This also distinguishes the rounded ceiling from 61_525.20. + # above does not. This also distinguishes the floored ceiling from 61_525.20. input: people: person1: @@ -83,22 +83,18 @@ members: [person1, person2, person3, person4] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit2: members: [person5, person6, person7, person8] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit3: members: [person9, person10, person11, person12] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit4: members: [person13, person14, person15, person16] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 households: household1: members: [person1, person2, person3, person4] @@ -120,6 +116,7 @@ # An electricity bill alone does not establish a gas-heating expense. NONE # is an affirmative no-heating fact. Heat-in-rent establishes responsibility # without a separately measured fuel bill; housing subsidy does not exclude. + # UNSPECIFIED with a bill and no heat in rent has no heating responsibility. input: people: person1: @@ -152,39 +149,42 @@ is_citizen_or_legal_immigrant: true ssi: 0 employment_income: 20_000 + person7: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 spm_units: spm_unit1: members: [person1] heating_type: NATURAL_GAS gas_expense: 0 - sc_tanf: 0 pre_subsidy_electricity_expense: 1_000 spm_unit2: members: [person2] heating_type: NATURAL_GAS gas_expense: 1_000 - sc_tanf: 0 spm_unit3: members: [person3] heating_type: NONE - sc_tanf: 0 spm_unit4: members: [person4] heating_type: NONE - sc_tanf: 0 heat_expense_included_in_rent: true spm_unit5: members: [person5] heating_type: UNSPECIFIED - sc_tanf: 0 heat_expense_included_in_rent: true receives_housing_assistance: true spm_unit6: members: [person6] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 receives_housing_assistance: true + spm_unit7: + members: [person7] + heating_type: UNSPECIFIED + pre_subsidy_electricity_expense: 1_000 households: household1: members: [person1] @@ -204,8 +204,11 @@ household6: members: [person6] state_code: SC + household7: + members: [person7] + state_code: SC output: - sc_liheap_eligible: [false, true, false, false, true, true] + sc_liheap_eligible: [false, true, false, false, true, true, false] - name: Case 3, cash program receipt does not override income and assets do not bar aid. period: 2026 @@ -234,7 +237,6 @@ members: [person2] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit_assets: 1_000_000 households: household1: @@ -271,17 +273,14 @@ members: [person1] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit2: members: [person2] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit3: members: [person3] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 households: household1: members: [person1] diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_household_size.yaml index 44ea28e299f..9328c8979c1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_household_size.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_household_size.yaml @@ -49,22 +49,18 @@ members: [person1, person2, person3] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit2: members: [person4, person5] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit3: members: [person6, person7] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit4: members: [person8, person9] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 households: household1: members: [person1, person2, person3] diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_income_limit.yaml index d99da8906d3..c77c6622d6f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_income_limit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_income_limit.yaml @@ -168,37 +168,30 @@ members: [person1] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit2: members: [person2, person3] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit3: members: [person4, person5, person6] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit4: members: [person7, person8, person9, person10] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit5: members: [person11, person12, person13, person14, person15] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit6: members: [person16, person17, person18, person19, person20, person21] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit7: members: [person22, person23, person24, person25, person26, person27, person28] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit8: members: - person29 @@ -211,7 +204,6 @@ - person36 heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 households: household1: members: [person1] @@ -276,17 +268,14 @@ members: [person1, person2] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit2: members: [person3] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 spm_unit3: members: [person4] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - sc_tanf: 0 households: household1: members: [person1, person2] 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 50dbe266a61..2faa4093b55 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 @@ -8,11 +8,13 @@ class sc_liheap(Variable): label = "South Carolina LIHEAP regular heating assistance" unit = USD definition_period = YEAR - defined_for = StateCode.SC + defined_for = "sc_liheap_eligible" reference = ( # PDF pages 8-9 "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8", "https://liheapch.acf.gov/docs/2026/benefits-matricies/SC_BenefitMatrix_2026.pdf#page=2", + # PDF pages 529,537,546 + "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=529", ) def formula(spm_unit, period, parameters): @@ -22,18 +24,23 @@ def formula(spm_unit, period, parameters): state = spm_unit.household("state_code_str", period) # The accepted plan's 30% SMI threshold governs over the older-vintage # matrix's conflicting 42% threshold. Keep the percentage boundary exact. - low_income = income.astype(np.float64) <= p.low_income_smi_rate * smi( + low_income = income <= p.low_income_smi_rate * smi( size, state, period, parameters ) person = spm_unit.members age = person("age", period) - elderly = spm_unit.any(age >= p.ages.elderly_min) - child = spm_unit.any(np.floor(age) <= p.ages.child_max) - disabled = spm_unit.any(person("is_disabled", period)) - veteran = spm_unit.any(person("is_veteran", period)) - # The plan measures home-energy utilities; aggregate existing energy - # bills, excluding water/sewer/trash/phone. Annual expenses approximate - # current monthly bills, and rent-embedded charges are not available. + # 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) @@ -49,7 +56,9 @@ def formula(spm_unit, period, parameters): + a.high_burden * high_burden + a.fuel * fuel_supplement ) - eligible = spm_unit("sc_liheap_eligible", period) # Local funding and priority decisions are not represented. The state # schedule does not impose a general benefit-to-bill cap. - return where(eligible, min_(amount, a.maximum), 0) + 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. + return award * size / max_(spm_unit.sum(member), 1) diff --git a/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_countable_income.py b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_countable_income.py index 8f8f8fcfdcf..45999e3f005 100644 --- a/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_countable_income.py +++ b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_countable_income.py @@ -11,6 +11,7 @@ class sc_liheap_countable_income(Variable): reference = ( # PDF pages 5-6 "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=5", + "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=529", ) def formula(spm_unit, period, parameters): @@ -18,12 +19,19 @@ def formula(spm_unit, period, parameters): person = spm_unit.members # Existing net business inputs approximate self-employment receipts # without a second expense deduction. Annual flows approximate 30 days. - earned = sum(max_(person(source, period), 0) for source in p.earned_sources) - adult = person("age", period) >= p.earned_min_age - unearned = sum( - max_(add(person, period, [source]), 0) for source in p.unearned_sources - ) - # The plan excludes minor earnings, not their unearned income. Adult-only - # verification requirements are not a general minor-income exclusion. - # Missing legal-settlement and earmarking facts are documented in README. - return max_(spm_unit.sum(where(adult, earned, 0) + unearned), 0) + earned = 0 + for source in p.earned_sources: + earned = earned + max_(person(source, period), 0) + unearned = 0 + for source in p.unearned_sources: + unearned = unearned + max_(add(person, period, [source]), 0) + # OEO's 2024 guide calculates no income under age 18 apart from Social Security + # or disability income. Legal settlements and earmarked receipts lack inputs. + minor_income = 0 + for source in p.minor_unearned_sources: + minor_income = minor_income + max_(add(person, period, [source]), 0) + adult = person("age", period) >= p.adult_min_age + income = where(adult, earned + unearned, minor_income) + # State-placed foster children are not household members. + foster = person("is_in_foster_care", period.first_month) + return spm_unit.sum(where(foster, 0, income)) 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 afb071f989f..779badd449c 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 @@ -21,7 +21,7 @@ def formula(spm_unit, period, parameters): income = spm_unit("sc_liheap_countable_income", period) limit = spm_unit("sc_liheap_income_limit", period) guideline = fpg( - max_(size, 1), + size, spm_unit.household("state_group_str", period), period, parameters, diff --git a/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_household_size.py b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_household_size.py index 6ca4fe23324..5369b6776d0 100644 --- a/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_household_size.py +++ b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_household_size.py @@ -7,9 +7,17 @@ class sc_liheap_household_size(Variable): label = "South Carolina LIHEAP eligible household size" definition_period = YEAR defined_for = StateCode.SC - reference = "https://oeo.sc.gov/managedsites/prd/oeo/documents/WAP%202026%20State%20Plan.pdf#page=23" + reference = ( + "https://oeo.sc.gov/managedsites/prd/oeo/documents/WAP%202026%20State%20Plan.pdf#page=23", + "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=529", + ) def formula(spm_unit, period, parameters): + person = spm_unit.members # This agency plan expressly attributes the size exclusion, while # retaining all members' income, to South Carolina LIHEAP guidance. - return spm_unit.sum(spm_unit.members("is_citizen_or_legal_immigrant", period)) + qualified = person("is_citizen_or_legal_immigrant", period) + # OEO's 2024 guide leaves state-placed foster children out of the household. + # The input reads "qualifying foster care institution", the closest available. + foster = person("is_in_foster_care", period.first_month) + return spm_unit.sum(qualified & ~foster) diff --git a/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_income_limit.py b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_income_limit.py index 9c0931d2fd4..7a697342c82 100644 --- a/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_income_limit.py +++ b/policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_income_limit.py @@ -1,5 +1,5 @@ from policyengine_us.model_api import * -from policyengine_us.variables.gov.hhs.hhs_smi import smi +from policyengine_us.variables.gov.hhs.hhs_smi import liheap_smi_limit class sc_liheap_income_limit(Variable): @@ -15,15 +15,9 @@ class sc_liheap_income_limit(Variable): ) def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.sc.oeo.liheap.eligibility size = spm_unit("sc_liheap_household_size", period) state = spm_unit.household("state_code_str", period) - p = parameters(period) - base = p.gov.hhs.smi.amount[state] - factor = smi(size, state, period, parameters) / base - # Round the four-person base, then floor after the federal size factor. - # This inferred order reproduces all eight agency annual limits, including - # the published size-seven/eight values. It is not a legal rounding rule. - base_limit = np.floor( - base * p.gov.states.sc.oeo.liheap.eligibility.smi_rate + 0.5 - ) - return where(size > 0, np.floor(base_limit * factor), 0) + # The HHS floor order reproduces all eight OEO annual limits. + limit = liheap_smi_limit(size, state, p.smi_rate, period, parameters) + return where(size > 0, limit, 0) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 7d8c41658c6..ba283439f1e 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -4807,6 +4807,34 @@ candidates: state: unreviewed note: 'PE-US follows the manual; verify whether the unchecked insurance category covers these payments. Code not yet on main: ri_liheap countable income.' +- id: us-sc:liheap-income-floor-vs-smi-limit + program: liheap + years: + from: 2026 + to: 2026 + type: federal-fiscal + question: Can South Carolina's 60% SMI heating limit exclude a household of 15 or more whose income is below the + federal 110% poverty-guideline floor? + sources: + - kind: statute + citation: 42 U.S.C. 8624(b)(2)(B) + url: https://www.law.cornell.edu/uscode/text/42/8624#b_2_B + - kind: plan + citation: South Carolina FY2026 LIHEAP State Plan, income eligibility, p.8 + url: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8 + readings: + statute: Income below 110% of the poverty guideline cannot by itself exclude the household. + other: The plan's 60% SMI limit falls below that floor from 15 members. + example: Twenty-five people with $150,000 exceed the $116,282 limit but are below the $162,415 floor (2025 guideline, + one-year lag), so they keep the $200 base award. + pe_us: + follows: statute + code: + - policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_eligible.py + - policyengine_us/parameters/gov/hhs/liheap/fpg_floor.yaml + - policyengine_us/parameters/gov/states/sc/oeo/liheap/eligibility/fpg_year_lag.yaml + state: unreviewed + note: PE-US applies the floor, rounded to cents with the statute's strict "less than" (sc_liheap_eligible.py). - id: us-sd:liheap-2025-published-income-limit program: liheap years: From 0e732302fde70b93ccff4201e4b23a86905770cf Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 2 Nov 2026 12:01:28 -0500 Subject: [PATCH 4/6] Link the South Carolina income floor candidate to its PR Co-Authored-By: Claude Opus 5.5 --- source_conflicts/candidates.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index c3f28b93fdd..53628df08d3 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -8262,6 +8262,8 @@ candidates: - policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_eligible.py - policyengine_us/parameters/gov/hhs/liheap/fpg_floor.yaml - policyengine_us/parameters/gov/states/sc/oeo/liheap/eligibility/fpg_year_lag.yaml + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10616 state: unreviewed note: PE-US applies the floor, rounded to cents with the statute's strict "less than" (sc_liheap_eligible.py). - id: us-sd:liheap-2025-published-income-limit From c05e26595ee9a2934feb87ce66a0575464b35dce Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 3 Nov 2026 16:44:51 -0500 Subject: [PATCH 5/6] Apply review fixes to South Carolina LIHEAP - Measure the $175 low-income tier against 30% of the 60% income limit and rename its rate parameter to low_income_limit_rate. - Accept any counted home energy bill with a known fuel, or heat in rent. - Count military_basic_pay and sc_ssi_state_supplement. - Record five source choices in the source-conflict candidates. - Move South Carolina between Oregon and South Dakota in programs.yaml. Co-Authored-By: Claude Opus 5.5 --- .../sc/oeo/liheap/income/earned_sources.yaml | 3 + .../oeo/liheap/income/unearned_sources.yaml | 2 + .../liheap/payment/energy_cost_sources.yaml | 4 +- .../liheap/payment/low_income_limit_rate.yaml | 12 ++ .../liheap/payment/low_income_smi_rate.yaml | 10 - policyengine_us/programs.yaml | 16 +- .../gov/states/sc/oeo/liheap/sc_liheap.yaml | 126 ++++++------ .../liheap/sc_liheap_countable_income.yaml | 13 +- .../sc/oeo/liheap/sc_liheap_eligible.yaml | 40 +++- .../gov/states/sc/oeo/liheap/sc_liheap.py | 18 +- .../sc/oeo/liheap/sc_liheap_eligible.py | 23 ++- source_conflicts/candidates.yaml | 181 ++++++++++++++++++ 12 files changed, 347 insertions(+), 101 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_limit_rate.yaml delete mode 100644 policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_smi_rate.yaml diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_sources.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_sources.yaml index 88bc8453e78..10a8b2447d6 100644 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_sources.yaml +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_sources.yaml @@ -2,6 +2,9 @@ description: South Carolina counts these earned income sources under the Low Inc values: 2025-10-01: - employment_income + # Active-duty basic pay, entered apart from employment_income; enter it in one + # of the two. + - military_basic_pay - self_employment_income - sstb_self_employment_income - farm_operations_income diff --git a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml index e5505ecbffd..b6d07a37219 100644 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml @@ -4,6 +4,8 @@ values: - total_unemployment_compensation - social_security - ssi + # Optional State Supplementation, paid with SSI to care-facility residents. + - sc_ssi_state_supplement - pension_income # Military retirement includes Survivor Benefit Plan annuities. - military_retirement_income 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 41867f7cef1..a83e8bb3e8c 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 @@ -1,4 +1,4 @@ -description: South Carolina uses these represented home-energy bills for the energy-burden calculation under the Low Income Home Energy Assistance Program. +description: South Carolina uses these represented home-energy bills for the energy-bill requirement and the energy-burden calculation under the Low Income Home Energy Assistance Program. values: 2025-10-01: - pre_subsidy_electricity_expense @@ -15,3 +15,5 @@ metadata: reference: - title: South Carolina FY2026 LIHEAP Detailed Model Plan, item 2.5 href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.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 new file mode 100644 index 00000000000..7ee2326d89b --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_limit_rate.yaml @@ -0,0 +1,12 @@ +description: South Carolina awards a low-income supplement at or below this share of the household's income limit under the Low Income Home Energy Assistance Program. +values: + 2025-10-01: 0.3 +metadata: + unit: /1 + 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" + href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.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/low_income_smi_rate.yaml b/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_smi_rate.yaml deleted file mode 100644 index 174eee4f301..00000000000 --- a/policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_smi_rate.yaml +++ /dev/null @@ -1,10 +0,0 @@ -description: South Carolina awards a low-income supplement at or below this share of state median income under the Low Income Home Energy Assistance Program. -values: - 2025-10-01: 0.3 -metadata: - unit: /1 - period: year - label: South Carolina LIHEAP low-income SMI rate - reference: - - title: "South Carolina FY2026 LIHEAP Detailed Model Plan, items 2.3 and 2.5 #page 8-9" - href: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 3005954e1a0..9de8ce9c6f6 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -1244,6 +1244,14 @@ programs: parameter_prefix: gov.states.or.ohcs.liheap verified_years: "2025-2027" notes: PY2025-PY2027 regular heating only, with the two county regions, five fuel columns and the roomer half payment. Crisis, cooling, Fuel Payment, equipment and OEAP assistance are out of scope. + - state: SC + status: partial + name: South Carolina LIHEAP + full_name: South Carolina Low Income Home Energy Assistance Program + 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. - state: SD status: partial name: South Dakota LIEAP @@ -1266,14 +1274,6 @@ programs: variable: tx_ceap parameter_prefix: gov.states.tx.tdhca.ceap notes: Models the 150% FPG income limit, the alternative 60% SMI limit from the FY2024 state plan (applied through 2024), benefit amounts, and categorical eligibility through TANF enrollment, SNAP eligibility, and SSI receipt. Not modeled - net rather than gross gambling winnings, the Medicare premium deduction from counted Social Security, and the exclusion of means-tested veterans' pensions from counted income (10 TAC 6.4(c)) - - state: SC - status: partial - name: South Carolina LIHEAP - full_name: South Carolina Low Income Home Energy Assistance Program - 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. - state: UT status: partial name: Utah HEAT diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml index a58f265472a..d3937e003f6 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap.yaml @@ -6,12 +6,13 @@ # https://oeo.sc.gov/managedsites/prd/oeo/documents/WAP%202026%20State%20Plan.pdf#page=23 # OEO's 2024 guide limits supplements to eligible members and prorates mixed-status awards. # https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=537 +# Its draft 2026 guide measures the plan's 30% low-income tier against the 60% limit. # These are annual state-schedule awards, before unmodeled local funding discretion. - name: Case 1, source base and combined low-income high-burden examples. period: 2026 - # 40_000 income and 4_000 electric cost give only base 200. At 30_000 income, - # low-income 175 applies and 6_000/30_000=20% adds burden 175: total 550. + # 40_000 income and 4_000 electric cost give only base 200. At 15_000 income, + # low-income 175 applies and 3_000/15_000=20% adds burden 175: total 550. input: people: person1: @@ -35,7 +36,7 @@ age: 35 is_citizen_or_legal_immigrant: true ssi: 0 - employment_income: 30_000 + employment_income: 15_000 person6: age: 33 is_citizen_or_legal_immigrant: true @@ -56,7 +57,7 @@ spm_unit2: members: [person5, person6, person7, person8] heating_type: ELECTRICITY - pre_subsidy_electricity_expense: 6_000 + pre_subsidy_electricity_expense: 3_000 households: household1: members: [person1, person2, person3, person4] @@ -66,24 +67,24 @@ state_code: SC output: sc_liheap_household_size: [4, 4] - sc_liheap_countable_income: [40_000, 30_000] + sc_liheap_countable_income: [40_000, 15_000] sc_liheap_income_limit: [61_525, 61_525] sc_liheap_eligible: [true, true] sc_liheap: [200, 550] -- name: Case 2, accepted thirty-percent SMI boundary governs the supplement. +- name: Case 2, thirty percent of the income limit is the supplement boundary. period: 2026 absolute_error_margin: 0.01 - # Four-person SMI 102_542 x 30% = 30_762.60. At or below the boundary add 175 - # to base 200; a cent above receives only 200. Income 35_000 rejects the older - # matrix 42% rule. + # Four-person limit 61_525 x 30% = 18_457.50. At or below the boundary add 175 + # to base 200; a cent above receives only 200, which rejects both 30% of the full + # SMI (30_762.60) and the older matrix's 70% of the limit (43_067.50). input: people: person1: age: 35 is_citizen_or_legal_immigrant: true ssi: 0 - employment_income: 30_762.59 + employment_income: 18_457.49 person2: age: 33 is_citizen_or_legal_immigrant: true @@ -100,7 +101,7 @@ age: 35 is_citizen_or_legal_immigrant: true ssi: 0 - employment_income: 30_762.6 + employment_income: 18_457.5 person6: age: 33 is_citizen_or_legal_immigrant: true @@ -117,7 +118,7 @@ age: 35 is_citizen_or_legal_immigrant: true ssi: 0 - employment_income: 30_762.61 + employment_income: 18_457.51 person10: age: 33 is_citizen_or_legal_immigrant: true @@ -130,23 +131,6 @@ age: 12 is_citizen_or_legal_immigrant: true ssi: 0 - person13: - age: 35 - is_citizen_or_legal_immigrant: true - ssi: 0 - employment_income: 35_000 - person14: - age: 33 - is_citizen_or_legal_immigrant: true - ssi: 0 - person15: - age: 10 - is_citizen_or_legal_immigrant: true - ssi: 0 - person16: - age: 12 - is_citizen_or_legal_immigrant: true - ssi: 0 spm_units: spm_unit1: members: [person1, person2, person3, person4] @@ -160,10 +144,6 @@ members: [person9, person10, person11, person12] heating_type: ELECTRICITY pre_subsidy_electricity_expense: 1_000 - spm_unit4: - members: [person13, person14, person15, person16] - heating_type: ELECTRICITY - pre_subsidy_electricity_expense: 1_000 households: household1: members: [person1, person2, person3, person4] @@ -174,11 +154,8 @@ household3: members: [person9, person10, person11, person12] state_code: SC - household4: - members: [person13, person14, person15, person16] - state_code: SC output: - sc_liheap: [375, 375, 200, 200] + sc_liheap: [375, 375, 200] - name: Case 3, twenty-percent burden sums energy bills and includes equality. period: 2026 @@ -377,7 +354,8 @@ - name: Case 6, age disability and veteran supplements apply once per category. period: 2026 - # Two-person income 30_000 exceeds its 30% SMI level and is below 41_837. + # Two-person income 30_000 exceeds its 12_551.10 low-income level and is below + # 41_837. # Base 200 gains 75 at age 60; 50 for child <= 5, any disabled member, or any # veteran. Two veterans still earn a single 50 supplement. input: @@ -531,7 +509,7 @@ age: 65 is_citizen_or_legal_immigrant: true ssi: 0 - employment_income: 24_000 + employment_income: 18_000 person2: age: 40 is_citizen_or_legal_immigrant: true @@ -550,7 +528,7 @@ age: 65 is_citizen_or_legal_immigrant: true ssi: 0 - employment_income: 24_000 + employment_income: 18_000 person6: age: 60 is_citizen_or_legal_immigrant: true @@ -834,10 +812,11 @@ output: sc_liheap: [200, 200, 200, 275, 375] -- name: Case 11, no heating missing matching bills and other states receive zero. +- name: Case 11, a home with no energy bill and other states receive zero. period: 2026 # Eligible SC gas heating earns 200+75; the same NC unit is state-guarded. - # NONE has no heat. An electric bill does not supply the missing gas bill. + # NONE with no bill has no obligation to pay. An electric bill qualifies a gas-heated + # home with no gas bill: 200 + 175 low income + 175 burden on no income + 75 fuel = 625. input: people: person1: @@ -889,7 +868,7 @@ members: [person4] state_code: SC output: - sc_liheap: [275, 0, 0, 0] + sc_liheap: [275, 0, 0, 625] # Recurring payments are countable; this does not represent a one-time withdrawal. # Income 40,000 exceeds the one-person 31,993 ceiling, so the award is 0. @@ -918,8 +897,9 @@ sc_liheap: 0 # Recurring payments are countable; this does not represent a one-time withdrawal. -# Income 12,000: 200 base + 75 elderly + 175 low income = 450. The 1,000 annual -# bill is below 20 percent, so no burden supplement applies. +# Income 12,000 exceeds the one-person 9,597.90 low-income level: 200 base + 75 +# elderly = 275. The 1,000 annual bill is below 20 percent, so no burden supplement +# applies. - name: Case 13, recurring IRA payments without high energy burden. period: 2026 absolute_error_margin: 0.01 @@ -942,7 +922,7 @@ state_code: SC output: sc_liheap_countable_income: 12000 - sc_liheap: 450 + sc_liheap: 275 # Military retirement is a separate receipt; a rental loss cannot cancel a 33,000 # pension. The last two rows are existing-aggregate unemployment controls because @@ -970,8 +950,9 @@ sc_liheap: [0, 0, 0, 0] # Age five and under includes 5.5 until the sixth birthday. With two qualified -# members, 30,000 income is above 30% SMI but below the 41,837 ceiling. Electric -# 1,000 is below the 20% burden and has no fuel supplement: 200 base + 50 child = +# members, 30,000 income is above the 12,551.10 low-income level but below the +# 41,837 ceiling. Electric 1,000 is below the 20% burden and has no fuel supplement: +# 200 base + 50 child = # 250; the same household at age 6 receives 200. - name: Case 15, the young-child supplement lasts until the sixth birthday. period: 2026 @@ -1006,7 +987,8 @@ # Qualified size 25 uses the 2025 FPG 15,650 + 24 x 5,500 = 147,650. The strict # 110% floor of 162,415 protects 150,000 income above the ordinary SMI limit of # 116,282. No vulnerable member, veteran, fuel or burden supplement applies, and -# income exceeds 30% SMI: base 200 is the award. Exactly 162,415 is unprotected. +# income exceeds 34,884.60, 30% of the limit: base 200 is the award. Exactly 162,415 +# is unprotected. - name: Case 16, large households retain strict federal income protection. period: 2026 input: @@ -1289,7 +1271,7 @@ - name: Case 17, heating fuel bills reach the burden supplement and cooking fuel does not. period: 2026 - # At income 20_000, 20% = 4_000 and the one-person 30% SMI level is 15_996.55. + # At income 20_000, 20% = 4_000 and the one-person low-income level is 9_597.90. # Fuel oil: 200 + 75 fuel + 175 burden = 450. Wood, coal and other fuel: 200 + 175 # = 375. Electric 3_000 plus cooking fuel 1_000 stays below 4_000: 200. input: @@ -1419,15 +1401,15 @@ - name: Case 19, the 2024 guide's prorated example pays eligible members only. period: 2026 # Guide page 537: 425 / 4 members = 106.25, x 3 eligible = 318.75. Here 200 base + - # 50 child + 175 low income = 425. The nonqualified adult's 20_000 wages count and - # stay below the three-person 30% SMI level of 25_840.58; 1_000 is under 20%. + # 50 child + 175 low income = 425. The nonqualified adult's 15_000 wages count and + # stay below the three-person low-income level of 15_504.30; 1_000 is under 20%. input: people: person1: age: 35 is_citizen_or_legal_immigrant: false ssi: 0 - employment_income: 20_000 + employment_income: 15_000 person2: age: 33 is_citizen_or_legal_immigrant: true @@ -1451,6 +1433,42 @@ state_code: SC output: sc_liheap_household_size: 3 - sc_liheap_countable_income: 20_000 + sc_liheap_countable_income: 15_000 sc_liheap_income_limit: 51_681 sc_liheap: 318.75 + +- name: Case 20, an electric bill pays a home without heating or with gathered wood. + period: 2026 + # OEO asks for an obligation to pay a home energy bill. 20_000 is above the low-income + # tier, 1_000 is under 20% of it, and neither fuel earns the supplement: base 200. + input: + people: + person1: + age: 40 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person2: + age: 40 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + spm_units: + spm_unit1: + members: [person1] + heating_type: NONE + pre_subsidy_electricity_expense: 1_000 + spm_unit2: + members: [person2] + heating_type: WOOD + wood_expense: 0 + pre_subsidy_electricity_expense: 1_000 + households: + household1: + members: [person1] + state_code: SC + household2: + members: [person2] + state_code: SC + output: + sc_liheap: [200, 200] diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml index 9e5e6deced4..ba77d66d69c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_countable_income.yaml @@ -55,8 +55,8 @@ - name: Case 2, adult net business receipts and wages remain disjoint. period: 2026 absolute_error_margin: 0.01 - # 12_000 wages +2_000 net business +300 SSTB +700 farm +1_000 partnership - # =16_000. Existing business receipts are already net of expenses. + # 12_000 wages +500 military basic pay +2_000 net business +300 SSTB +700 farm + # +1_000 partnership =16_500. Existing business receipts are already net of expenses. input: people: person1: @@ -64,6 +64,7 @@ is_citizen_or_legal_immigrant: true ssi: 0 employment_income: 12_000 + military_basic_pay: 500 self_employment_income: 2_000 sstb_self_employment_income: 300 farm_operations_income: 700 @@ -78,13 +79,14 @@ members: [person1] state_code: SC output: - sc_liheap_countable_income: 16_000 + sc_liheap_countable_income: 16_500 - name: Case 3, pension Social Security survivor rental and gift receipts enter once. period: 2026 absolute_error_margin: 0.01 # Pension 3_000 + Social Security 2_000 + other survivor 700 + rent 3_000 - # + unemployment 1_000 + alimony 400 + support 500 + VA 800 + gifts 600 =12_000. + # + unemployment 1_000 + alimony 400 + support 500 + VA 800 + gifts 600 + # + Optional State Supplementation 1_200 =13_200. # Neither pension nor Social Security subcomponents are counted twice. input: people: @@ -106,6 +108,7 @@ child_support_received: 500 veterans_benefits: 800 financial_assistance: 600 + sc_ssi_state_supplement: 1_200 spm_units: spm_unit1: members: [person1] @@ -116,7 +119,7 @@ members: [person1] state_code: SC output: - sc_liheap_countable_income: 12_000 + sc_liheap_countable_income: 13_200 - name: Case 4, unchecked income types and assets do not inflate countable income. period: 2026 diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_eligible.yaml index d219389a8ae..0c26d04ece2 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/oeo/liheap/sc_liheap_eligible.yaml @@ -2,6 +2,8 @@ # https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=5 # Current agency annual limits and demonstrated energy-need requirement. # https://oeo.sc.gov/managedsites/prd/oeo/liheap.html +# OEO's 2024 guide asks for an obligation to pay a home energy bill. +# https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=536 # These exact Boolean cases intentionally have no absolute_error_margin. - name: Case 1, annual published income ceiling is inclusive. @@ -111,12 +113,13 @@ output: sc_liheap_eligible: [true, true, false, false] -- name: Case 2, known fuel requires its matching bill unless heat is included in rent. +- name: Case 2, any counted energy bill or heat in rent shows the obligation to pay. period: 2026 - # An electricity bill alone does not establish a gas-heating expense. NONE - # is an affirmative no-heating fact. Heat-in-rent establishes responsibility - # without a separately measured fuel bill; housing subsidy does not exclude. - # UNSPECIFIED with a bill and no heat in rent has no heating responsibility. + # An electric bill qualifies a gas-heated home with no gas bill, a home without + # heating and a home heating with gathered wood. NONE with no bill has no obligation. + # Heat in rent qualifies without a separately measured bill; housing subsidy does not + # exclude. UNSPECIFIED with a bill and no heat in rent gets nothing: heating_type is + # a required input. input: people: person1: @@ -154,6 +157,16 @@ is_citizen_or_legal_immigrant: true ssi: 0 employment_income: 20_000 + person8: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 + person9: + age: 35 + is_citizen_or_legal_immigrant: true + ssi: 0 + employment_income: 20_000 spm_units: spm_unit1: members: [person1] @@ -185,6 +198,15 @@ members: [person7] heating_type: UNSPECIFIED pre_subsidy_electricity_expense: 1_000 + spm_unit8: + members: [person8] + heating_type: NONE + pre_subsidy_electricity_expense: 1_000 + spm_unit9: + members: [person9] + heating_type: WOOD + wood_expense: 0 + pre_subsidy_electricity_expense: 1_000 households: household1: members: [person1] @@ -207,8 +229,14 @@ household7: members: [person7] state_code: SC + household8: + members: [person8] + state_code: SC + household9: + members: [person9] + state_code: SC output: - sc_liheap_eligible: [false, true, false, false, true, true, false] + sc_liheap_eligible: [true, true, false, true, true, true, false, true, true] - name: Case 3, cash program receipt does not override income and assets do not bar aid. period: 2026 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 2faa4093b55..9b0490bab01 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,5 +1,4 @@ from policyengine_us.model_api import * -from policyengine_us.variables.gov.hhs.hhs_smi import smi class sc_liheap(Variable): @@ -13,20 +12,19 @@ class sc_liheap(Variable): # PDF pages 8-9 "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8", "https://liheapch.acf.gov/docs/2026/benefits-matricies/SC_BenefitMatrix_2026.pdf#page=2", - # PDF pages 529,537,546 - "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=529", + # PDF pages 311-312,338,529,537,546 + "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=311", ) 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) - state = spm_unit.household("state_code_str", period) - # The accepted plan's 30% SMI threshold governs over the older-vintage - # matrix's conflicting 42% threshold. Keep the percentage boundary exact. - low_income = income <= p.low_income_smi_rate * smi( - size, state, period, parameters - ) + # 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 @@ -58,7 +56,9 @@ def formula(spm_unit, period, parameters): ) # 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 779badd449c..000ec310b1c 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 @@ -13,10 +13,12 @@ class sc_liheap_eligible(Variable): # PDF pages 8-9 "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8", "https://oeo.sc.gov/managedsites/prd/oeo/liheap.html", + # PDF pages 526,536-537 + "https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=526", ) def formula(spm_unit, period, parameters): - p = parameters(period) + p = parameters(period).gov.states.sc.oeo.liheap size = spm_unit("sc_liheap_household_size", period) income = spm_unit("sc_liheap_countable_income", period) limit = spm_unit("sc_liheap_income_limit", period) @@ -25,16 +27,21 @@ def formula(spm_unit, period, parameters): spm_unit.household("state_group_str", period), period, parameters, - year_lag=p.gov.states.sc.oeo.liheap.eligibility.fpg_year_lag, + year_lag=p.eligibility.fpg_year_lag, ) - federal_floor = np.round(guideline * p.gov.hhs.liheap.fpg_floor, 2) - fuel = spm_unit("heating_type", period) - heating_responsibility = spm_unit("has_heating_expense", period) | spm_unit( - "heat_expense_included_in_rent", period + 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. + 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 + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) return ( (size > 0) & ((income <= limit) | (income < federal_floor)) - & (fuel != fuel.possible_values.NONE) - & heating_responsibility + & ((has_energy_cost & known_fuel) | heat_in_rent) ) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 53628df08d3..aa60d290bb0 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -8236,6 +8236,40 @@ candidates: note: The model computes the couple jointly at $90, which the couple.yaml comment derives from the per-individual allowance in ยง40-6-27(a)(3); ri_ssp.yaml Case 18 pins [45, 45] for a couple with no income. SSA administered Category E only through December 31, 2010. +- id: us-sc:liheap-energy-burden-test + program: liheap + years: + from: 2026 + to: 2026 + 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? + sources: + - kind: plan + citation: South Carolina FY2026 LIHEAP State Plan, item 2.5, p.9 + url: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=9 + - kind: manual + citation: South Carolina 2024 LIHEAP Administrative Guide, plan file PDF page 538 + url: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=538 + - kind: guidance + citation: South Carolina 2026 Benefit Matrix, item 5, p.2 + url: https://liheapch.acf.gov/docs/2026/benefits-matricies/SC_BenefitMatrix_2026.pdf#page=2 + readings: + statute: No statute is supplied; plan item 2.5 pays the supplement at "20% or more of income used for utilities". + other: The guide and the matrix test the current month's bill and demonstrate a burden when "the current energy bill + is higher than the PRODUCT". + example: One person with $20,000 of income and $4,000 of gas bills receives $450 under the plan and $275 under the + guide's "higher than". + pe_us: + follows: gap-fill + code: + - policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap.py + - policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/burden_rate.yaml + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10616 + state: unreviewed + note: PE-US follows the plan's inclusive test, with annual bills against annual income. + - id: us-sc:liheap-income-floor-vs-smi-limit program: liheap years: @@ -8266,6 +8300,153 @@ candidates: - https://github.com/PolicyEngine/policyengine-us/pull/10616 state: unreviewed note: PE-US applies the floor, rounded to cents with the statute's strict "less than" (sc_liheap_eligible.py). +- id: us-sc:liheap-low-income-tier-base + program: liheap + years: + from: 2026 + to: 2026 + 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? + sources: + - kind: plan + citation: South Carolina FY2026 LIHEAP State Plan, items 2.3 and 2.5, pp.8-9 + url: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8 + - kind: manual + citation: South Carolina draft 2026 LIHEAP Administrative Guide (plan attachment), plan file PDF pages 311, 312 and 338 + url: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=311 + - kind: manual + citation: South Carolina 2024 LIHEAP Administrative Guide, plan file PDF page 538 + url: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=538 + - kind: guidance + citation: South Carolina 2026 Benefit Matrix, pp.1-2 + url: https://liheapch.acf.gov/docs/2026/benefits-matricies/SC_BenefitMatrix_2026.pdf#page=1 + readings: + statute: No statute is supplied; plan item 2.3 gives $175 "at or below 30% of the State's Median Income guidelines" and + item 2.5 "household income 30% of the SMI or less". + other: 'The draft guide says "must be at or below the 30% SMI. Note: SMI is 60% of the State''s Median Income" and "30 + of 60 percent of the SMI", which is 18% of the state median income. Its page 338 definition compares the tier "with + the same 30% variance when compared to the State''s 60% Median Income guidelines".' + example: A person aged 70 with $12,000 of income and a $1,000 electric bill receives $275 with the 60% limit as the base + (limit $9,597.90) and $450 with the full SMI as the base (limit $15,996.55). + pe_us: + follows: gap-fill + code: + - policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap.py + - policyengine_us/parameters/gov/states/sc/oeo/liheap/payment/low_income_limit_rate.yaml + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10616 + 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 + the FY2026 plan replaced. The guide with the 30% text is a draft, and OEO has not been asked.' + +- id: us-sc:liheap-minor-income + program: liheap + years: + from: 2026 + to: 2026 + type: federal-fiscal + question: Does South Carolina LIHEAP count only a minor's Social Security or disability income, as the 2024 guide + provides, the unearned income plan item 1.9 checks, or no income of members under 18, as the OEO page reads? + sources: + - kind: manual + citation: South Carolina 2024 LIHEAP Administrative Guide, Income from Minors, plan file PDF page 529 + url: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=529 + - kind: manual + citation: South Carolina draft 2026 LIHEAP Administrative Guide (plan attachment), plan file PDF page 304 + url: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=304 + - kind: plan + citation: South Carolina FY2026 LIHEAP State Plan, item 1.9, pp.5-6 + url: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=5 + - kind: guidance + citation: South Carolina OEO LIHEAP page + url: https://oeo.sc.gov/managedsites/prd/oeo/liheap.html + readings: + statute: No statute is supplied; the guide says "Income is not calculated for persons under age 18" but "If household + members under 18 receive Social Security or Disability income, the gross amount must be included". + other: Plan item 1.9 checks Social Security, SSI, pensions, child support and VA benefits and leaves only "Earned income + of a child under the age of 18" unchecked. The OEO page asks for "Gross 30-day income for all household members 18 + and older". + example: A parent aged 35 with $36,000 of wages and a 10-year-old with $8,000 of veterans benefits count $36,000 and + receive $200 with a $1,000 electric bill under the guide, and count $44,000 and receive $0 under plan item 1.9. + pe_us: + follows: gap-fill + code: + - policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_countable_income.py + - policyengine_us/parameters/gov/states/sc/oeo/liheap/income/adult_min_age.yaml + - policyengine_us/parameters/gov/states/sc/oeo/liheap/income/minor_unearned_sources.yaml + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10616 + state: ruled + note: 'Ruled 2026-11-02: follow the 2024 guide. PE-US reads SSI as a child''s disability income and uses + disability_benefits for other disability income.' + +- id: us-sc:liheap-mixed-status-household + program: liheap + years: + from: 2026 + to: 2026 + 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? + sources: + - kind: manual + citation: South Carolina 2024 LIHEAP Administrative Guide, plan file PDF pages 529, 537 and 546 + url: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=529 + - kind: plan + citation: South Carolina FY2026 LIHEAP State Plan, items 2.3 and 2.5, pp.8-9 + url: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=8 + readings: + statute: No statute is supplied; the guide says to "only consider eligible household members" for the itemized + benefits and then "apply the Direct Assistance Prorated Calculation". + other: The plan's supplements apply if "the household/a member of the household" qualifies, and it states no proration. + example: A nonqualified parent with $30,000 of income and a qualified child with gas heat receive (200 + 50 + 75) / 2 + members x 1 eligible = $162.50 under the guide, and $325 with no proration. + pe_us: + follows: gap-fill + code: + - policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap.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 + 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.' + +- id: us-sc:liheap-plan-income-checklist + program: liheap + years: + from: 2026 + to: 2026 + type: federal-fiscal + question: Does South Carolina LIHEAP count all income, as the 2024 guide opens, or only the income forms plan item 1.9 + checks? + sources: + - kind: plan + citation: South Carolina FY2026 LIHEAP State Plan, item 1.9, pp.5-6 + url: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20LIHEAP%20State%20Plan.pdf#page=5 + - kind: manual + citation: South Carolina 2024 LIHEAP Administrative Guide, How to Calculate Income, plan file PDF page 526 + url: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=526 + readings: + statute: No statute is supplied; plan item 1.9 leaves interest and dividends unchecked, has no box for workers' + compensation or employment disability benefits, and leaves its Other box unchecked. + other: The guide says "When calculating total household income, include all income". + example: One person with a $1,000 electric bill, $20,000 of wages and $15,000 of interest counts $20,000 and receives + $200 under the plan, and counts $35,000 and receives $0 under the guide. + pe_us: + follows: gap-fill + code: + - policyengine_us/variables/gov/states/sc/oeo/liheap/sc_liheap_countable_income.py + - policyengine_us/parameters/gov/states/sc/oeo/liheap/income/earned_sources.yaml + - policyengine_us/parameters/gov/states/sc/oeo/liheap/income/unearned_sources.yaml + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10616 + state: ruled + note: 'Ruled 2026-11-02: income other than minors'' income follows the plan.' + - id: us-sd:liheap-2025-published-income-limit program: liheap years: From df20847f16b3b4ac86fe6a64f163be4b26e8c75c Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 5 Nov 2026 11:42:26 -0500 Subject: [PATCH 6/6] Record the basis for the South Carolina minor income ruling Writes the reasons into us-sc:liheap-minor-income and adds the draft 2026 guide to us-sc:liheap-energy-burden-test. Co-Authored-By: Claude Opus 5.5 --- source_conflicts/candidates.yaml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index d3f565ab8a6..c16d5edb3ed 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -8670,13 +8670,16 @@ candidates: - kind: manual citation: South Carolina 2024 LIHEAP Administrative Guide, plan file PDF page 538 url: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=538 + - kind: manual + citation: South Carolina draft 2026 LIHEAP Administrative Guide (plan attachment), plan file PDF page 312 + url: https://oeo.sc.gov/managedsites/prd/oeo/documents/2026%20South%20Carolina%20LIHEAP%20Model%20Plan%20w%20Attachments.pdf#page=312 - kind: guidance citation: South Carolina 2026 Benefit Matrix, item 5, p.2 url: https://liheapch.acf.gov/docs/2026/benefits-matricies/SC_BenefitMatrix_2026.pdf#page=2 readings: statute: No statute is supplied; plan item 2.5 pays the supplement at "20% or more of income used for utilities". - other: The guide and the matrix test the current month's bill and demonstrate a burden when "the current energy bill - is higher than the PRODUCT". + other: The 2024 and draft 2026 guides and the matrix test the current month's bill and demonstrate a burden when "the + current energy bill is higher than the PRODUCT". example: One person with $20,000 of income and $4,000 of gas bills receives $450 under the plan and $275 under the guide's "higher than". pe_us: @@ -8798,8 +8801,10 @@ candidates: prs: - https://github.com/PolicyEngine/policyengine-us/pull/10616 state: ruled - note: 'Ruled 2026-11-02: follow the 2024 guide. PE-US reads SSI as a child''s disability income and uses - disability_benefits for other disability income.' + note: 'Ruled 2026-11-02: follow the 2024 guide, the edition in force. Plan 1.9 is a checklist of income types, while + the guide''s "Income from Minors" section is the specific rule for members under 18. The draft 2026 guide repeats it, + and the OEO page asks for income only from members 18 and older. PE-US reads SSI as a child''s disability income and + uses disability_benefits for other disability income.' - id: us-sc:liheap-mixed-status-household program: liheap