diff --git a/changelog.d/nc-lieap.added.md b/changelog.d/nc-lieap.added.md new file mode 100644 index 00000000000..3ce01f7ddc4 --- /dev/null +++ b/changelog.d/nc-lieap.added.md @@ -0,0 +1 @@ +Add North Carolina Low Income Energy Assistance Program (LIEAP) regular heating benefits. diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_deduction.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_deduction.yaml new file mode 100644 index 00000000000..f1424527b8b --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_deduction.yaml @@ -0,0 +1,39 @@ +description: North Carolina deducts this amount from monthly earned income, based on the earnings band, under the Low Income Energy Assistance Program. +brackets: + - threshold: + 2025-10-01: -.inf + amount: + 2025-10-01: 0 + - threshold: + 2025-10-01: 0 + amount: + 2025-10-01: 10 + - threshold: + 2025-10-01: 50 + amount: + 2025-10-01: 20 + - threshold: + 2025-10-01: 100 + amount: + 2025-10-01: 30 + - threshold: + 2025-10-01: 150 + amount: + 2025-10-01: 40 + - threshold: + 2025-10-01: 200 + amount: + 2025-10-01: 60 + - threshold: + 2025-10-01: 300 + amount: + 2025-10-01: 80 +metadata: + type: single_amount + threshold_unit: currency-USD + amount_unit: currency-USD + period: month + label: North Carolina LIEAP monthly earned income deduction + reference: + - title: North Carolina Energy Programs Manual, Section 300.09 B.3, pages 12-13 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=12 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_deduction_rate.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_deduction_rate.yaml new file mode 100644 index 00000000000..7f27f1405db --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_deduction_rate.yaml @@ -0,0 +1,10 @@ +description: North Carolina deducts this share of total monthly earnings when earnings exceed the deduction threshold under the Low Income Energy Assistance Program. +values: + 2025-10-01: 0.2 +metadata: + unit: /1 + period: year + label: North Carolina LIEAP earned income deduction rate + reference: + - title: North Carolina Energy Programs Manual, Section 300.09 B.3, page 13 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=13 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_deduction_threshold.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_deduction_threshold.yaml new file mode 100644 index 00000000000..e5ca44f42c1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_deduction_threshold.yaml @@ -0,0 +1,10 @@ +description: North Carolina sets this threshold of monthly earned income, above which the work deduction is a share of total earnings, under the Low Income Energy Assistance Program. +values: + 2025-10-01: 400 +metadata: + unit: currency-USD + period: month + label: North Carolina LIEAP earned income deduction threshold + reference: + - title: North Carolina Energy Programs Manual, Section 300.09 B.3, page 13 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=13 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_sources.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_sources.yaml new file mode 100644 index 00000000000..f14deea0503 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_sources.yaml @@ -0,0 +1,16 @@ +description: North Carolina counts these wage, business and farm income sources as earned income under the Low Income Energy Assistance Program. +values: + 2025-10-01: + - employment_income + - self_employment_income + - sstb_self_employment_income + - farm_operations_income +metadata: + unit: list + period: year + label: North Carolina LIEAP earned income sources + reference: + - title: North Carolina Energy Programs Manual, Section 300.09 A (page 10) and Section 300.09 B.3 (page 12) + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10 + - title: North Carolina Food and Nutrition Services Manual, FNS 300.02 sources of income chart, pages 5, 21 and 26 + href: https://policies.ncdhhs.gov/wp-content/uploads/fns-300-sources-of-income.pdf#page=5 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/elderly_age.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/elderly_age.yaml new file mode 100644 index 00000000000..63f32e7657b --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/elderly_age.yaml @@ -0,0 +1,10 @@ +description: North Carolina sets this age as the elderly threshold under the Low Income Energy Assistance Program. +values: + 2025-10-01: 60 +metadata: + unit: year + period: year + label: North Carolina LIEAP elderly age + reference: + - title: North Carolina Energy Programs Manual, Sections 300.02 A.1 (page 1), 300.09 (page 10) and 300.09 B.4.a (page 13) + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/fpg_year_lag.yaml new file mode 100644 index 00000000000..23798e7d1bf --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/fpg_year_lag.yaml @@ -0,0 +1,18 @@ +description: North Carolina uses the federal poverty guidelines published this many years before the year in which the heating season ends under the Low Income Energy Assistance Program. +# The change notices put each federal fiscal year's poverty level into effect +# on October 1 (FY2026 on October 1, 2025 and FY2027 on October 1, 2026). The +# income limit tables for those years equal the 2025 and 2026 guidelines, the +# latest ones published on each effective date. +values: + 2025-10-01: 1 +metadata: + unit: year + period: year + label: North Carolina LIEAP federal poverty guideline year lag + reference: + - title: North Carolina Energy Programs Manual Change Notice 05-2025, FY2026 federal poverty level effective October 1, 2025, page 1 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-CN-05-2025.pdf#page=1 + - title: North Carolina Energy Programs Manual Change Notice 03-2026, FY2027 federal poverty level effective October 1, 2026, page 1 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-CN-03-2026.pdf#page=1 + - title: North Carolina Energy Programs Manual, Section 300.09, maximum countable income tables, pages 14-15 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=14 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/income_limit.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/income_limit.yaml new file mode 100644 index 00000000000..6e6f96ab7e8 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/income_limit.yaml @@ -0,0 +1,10 @@ +description: North Carolina limits regular household countable income to this share of the federal poverty guideline under the Low Income Energy Assistance Program. +values: + 2025-10-01: 1.3 +metadata: + unit: /1 + period: year + label: North Carolina LIEAP income limit + reference: + - title: North Carolina Energy Programs Manual, Section 300.09, page 10 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/medical_deduction.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/medical_deduction.yaml new file mode 100644 index 00000000000..cd3262ac461 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/medical_deduction.yaml @@ -0,0 +1,10 @@ +description: North Carolina provides this amount as a monthly standard medical deduction for each eligible specified person under the Low Income Energy Assistance Program. +values: + 2025-10-01: 85 +metadata: + unit: currency-USD + period: month + label: North Carolina LIEAP medical deduction + reference: + - title: North Carolina Energy Programs Manual, Section 300.09 B.4.a, page 13 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=13 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/coal_wood.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/coal_wood.yaml new file mode 100644 index 00000000000..c0c7c598054 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/coal_wood.yaml @@ -0,0 +1,10 @@ +description: North Carolina provides this amount as the regular heating payment to households heating with coal or wood under the Low Income Energy Assistance Program. +values: + 2025-10-01: 300 +metadata: + unit: currency-USD + period: year + label: North Carolina LIEAP coal or wood heating payment + reference: + - title: North Carolina Energy Programs Manual, Section 300.12 A (page 18) and Section 300.12 B chart (page 20) + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=18 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/higher_income.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/higher_income.yaml new file mode 100644 index 00000000000..8ec5443af89 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/higher_income.yaml @@ -0,0 +1,21 @@ +description: North Carolina provides this amount as the regular heating payment to higher income households, by eligible household size, under the Low Income Energy Assistance Program. +brackets: + - threshold: + 2025-10-01: 1 + amount: + 2025-10-01: 300 + - threshold: + 2025-10-01: 4 + amount: + 2025-10-01: 400 +metadata: + type: single_amount + threshold_unit: person + amount_unit: currency-USD + period: year + label: North Carolina LIEAP higher income heating payment + reference: + - title: North Carolina Energy Programs Manual, Section 300.12 B payment charts, pages 19-20 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=19 + - title: North Carolina FY2026 heating benefit matrix, pages 1-3 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NC_BenefitMatrix_Heat-Cool_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/lower_income.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/lower_income.yaml new file mode 100644 index 00000000000..d6361502580 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/lower_income.yaml @@ -0,0 +1,21 @@ +description: North Carolina provides this amount as the regular heating payment to lower income households, by eligible household size, under the Low Income Energy Assistance Program. +brackets: + - threshold: + 2025-10-01: 1 + amount: + 2025-10-01: 400 + - threshold: + 2025-10-01: 4 + amount: + 2025-10-01: 500 +metadata: + type: single_amount + threshold_unit: person + amount_unit: currency-USD + period: year + label: North Carolina LIEAP lower income heating payment + reference: + - title: North Carolina Energy Programs Manual, Section 300.12 B payment charts, pages 19-20 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=19 + - title: North Carolina FY2026 heating benefit matrix, pages 1-3 + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NC_BenefitMatrix_Heat-Cool_2026.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/lower_income_share.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/lower_income_share.yaml new file mode 100644 index 00000000000..7af7cfaf478 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/lower_income_share.yaml @@ -0,0 +1,10 @@ +description: North Carolina uses this share of the unrounded income limit to separate heating payment bands under the Low Income Energy Assistance Program. +values: + 2025-10-01: 0.5 +metadata: + unit: /1 + period: year + label: North Carolina LIEAP lower payment band share of the income limit + reference: + - title: North Carolina Energy Programs Manual, Section 300.12 B payment charts, pages 19-20 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=19 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/rental_income_sources.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/rental_income_sources.yaml new file mode 100644 index 00000000000..4e2848a3d44 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/rental_income_sources.yaml @@ -0,0 +1,19 @@ +description: North Carolina counts these rental income sources as earned income under the Low Income Energy Assistance Program. +values: + 2025-10-01: + # Section 300.09 B.3 gives rental income the earned income deduction. + - rental_income + # Farm rent (Form 4835) is not named in the manual. It is rent from + # property the person owns, so it is treated as rental income. + - farm_rent_income +metadata: + unit: list + period: year + label: North Carolina LIEAP rental income sources + reference: + - title: North Carolina Energy Programs Manual, Section 300.09 B.3 (page 12) + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=12 + - title: North Carolina Food and Nutrition Services Manual, FNS 300.02 sources of income chart, Rental Income, pages 17-18 + href: https://policies.ncdhhs.gov/wp-content/uploads/fns-300-sources-of-income.pdf#page=17 + - title: North Carolina FY2026 state plan, item 1.9 countable income checklist, page 6 + href: https://liheapch.acf.gov/docs/2026/state-plans/NC_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_limit.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_limit.yaml new file mode 100644 index 00000000000..77b037e80cc --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_limit.yaml @@ -0,0 +1,10 @@ +description: North Carolina limits non-special household countable resources to this amount under the Low Income Energy Assistance Program. +values: + 2025-10-01: 3_000 +metadata: + unit: currency-USD + period: year + label: North Carolina LIEAP resource limit + reference: + - title: North Carolina Energy Programs Manual, Section 300.11, pages 17-18 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=17 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_test.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_test.yaml new file mode 100644 index 00000000000..ca9df12cf7a --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_test.yaml @@ -0,0 +1,21 @@ +description: North Carolina uses a resource test under the Low Income Energy Assistance Program if this is true. +# Section 300.11 waives the test for July 1, 2025 through June 30, 2026, and +# the values follow those literal dates, so the test applies again from July +# 1, 2026. The October 2026 edition of the manual (page 18) still prints the +# expired waiver line and no FY2027 state plan was posted as of October 2026, +# so the FY2027 value is provisional. +# The first value also applies to earlier years, which are unverified backfill. +# It follows the state plans, which report no assets test in item 2.3 (FY2025 +# plan page 8, FY2026 plan page 9). The October 1, 2025 edition of the manual +# printed the limits without a waiver (page 17) until Change Notice 06-2025, +# effective November 1, 2025, added it. +values: + 2025-07-01: false + 2026-07-01: true +metadata: + unit: bool + period: year + label: North Carolina LIEAP resource test applies + reference: + - title: North Carolina Energy Programs Manual, Section 300.11, FY2025-2026 waiver, page 17 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=17 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/special_income_limit.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/special_income_limit.yaml new file mode 100644 index 00000000000..65babe7945c --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/special_income_limit.yaml @@ -0,0 +1,10 @@ +description: North Carolina limits special-population household countable income to this share of the federal poverty guideline under the Low Income Energy Assistance Program. +values: + 2025-10-01: 1.5 +metadata: + unit: /1 + period: year + label: North Carolina LIEAP special income limit + reference: + - title: North Carolina Energy Programs Manual, Section 300.09, page 10 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/special_resource_limit.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/special_resource_limit.yaml new file mode 100644 index 00000000000..d7768c87bb5 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/special_resource_limit.yaml @@ -0,0 +1,10 @@ +description: North Carolina limits elderly or disabled household countable resources to this amount under the Low Income Energy Assistance Program. +values: + 2025-10-01: 4_500 +metadata: + unit: currency-USD + period: year + label: North Carolina LIEAP special resource limit + reference: + - title: North Carolina Energy Programs Manual, Section 300.11, pages 17-18 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=17 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/unearned_income_sources.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/unearned_income_sources.yaml new file mode 100644 index 00000000000..da067718336 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/unearned_income_sources.yaml @@ -0,0 +1,55 @@ +description: North Carolina counts these unearned income sources under the Low Income Energy Assistance Program. +values: + 2025-10-01: + # Section 300.09 A takes the types of income to count from the FNS manual. + # Each source names its row in the FNS 300.02 chart and, where there is + # one, its box in the state plan's item 1.9 checklist. + # "Supplemental Security Income" (page 22); plan "Supplemental Security + # Income (SSI)". + - ssi + # "Social Security Benefits" (page 20). The plan checks "Including + # MediCare deduction", so the benefit is counted gross. + - social_security + # "General Assistance (GA)" (page 9); plan "General Assistance benefits". + - general_assistance + # "Pensions" (page 16); plan "Retirement / pension benefits". + - pension_income + # "Retirement Benefits" (page 18). + - retirement_distributions + # "Annuities" (page 3) and "Pensions" (page 16): survivor benefits other + # than Social Security. + - survivor_benefits + # "Veterans" (page 25); plan "Veterans Administration (VA) benefits". + - veterans_benefits + # "Unemployment" (page 24); plan "Unemployment insurance". + - unemployment_compensation + # "Worker's Compensation" (page 26) and "Disability Payments" (page 6). + # The plan leaves "Insurance payments made directly to the insured" + # unchecked; the model follows the manual. + - workers_compensation + - disability_benefits + # "Child Support" (page 5); plan "Child support". + - child_support_received + # "Alimony / Spousal Support" (page 3); plan "Alimony". + - alimony_income + # "Interest and Dividend" (page 11); plan "Interest, dividends, or + # royalties". + - interest_income + - ordinary_dividend_income + # "Contributions" (page 6) and "Gifts (Monetary)" (page 9); plan "Cash + # gifts". + - financial_assistance + # No single chart row. FNS 300.01 B (page 1) defines unearned income as + # all income that is not earned. + - miscellaneous_income +metadata: + unit: list + period: year + label: North Carolina LIEAP unearned income sources + reference: + - title: North Carolina Energy Programs Manual, Section 300.09 A (pages 10-11) + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10 + - title: North Carolina Food and Nutrition Services Manual, FNS 300.01 B (page 1) and FNS 300.02 sources of income chart, pages 3, 5, 6, 9, 11, 16, 18, 20, 22, 24, 25 and 26 + href: https://policies.ncdhhs.gov/wp-content/uploads/fns-300-sources-of-income.pdf#page=1 + - title: North Carolina FY2026 state plan, item 1.9 countable income checklist, pages 6-7 + href: https://liheapch.acf.gov/docs/2026/state-plans/NC_Plan_2026.pdf#page=6 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 72d585a3538..33096ded8c4 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, IN, KS, KY, MS, NE, TX + coverage: OR, DC, Riverside County, MA, IL, IN, KS, KY, MS, NC, NE, TX state_implementations: - state: OR status: in_progress @@ -855,6 +855,14 @@ programs: parameter_prefix: gov.states.ms.mdhs.liheap verified_years: "2026" notes: FY2026 regular assistance for heating fuel and electric bills, using the published matrix as printed. Crisis assistance and energy equipment are out of scope. + - state: NC + status: partial + name: North Carolina LIEAP + full_name: North Carolina Low Income Energy Assistance Program + variable: nc_lieap + parameter_prefix: gov.states.nc.ncdhhs.lieap + verified_years: "2026-2027" + notes: FY2026-FY2027 regular heating only, with the dated resource waiver. Crisis, cooling and weatherization assistance are out of scope. - state: NE status: partial name: Nebraska LIHEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/integration.yaml new file mode 100644 index 00000000000..e1995f37483 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/integration.yaml @@ -0,0 +1,335 @@ +# Sources: EP-300 Sections 300.08-300.12 and the FY2026 benefit matrix. +# Page numbers are those of EP-300 Change 2-2026 (May 1, 2026) for FY2026 +# cases and of Change 04-2026 (October 1, 2026) for the FY2027 case. +- name: Case 1, vulnerability by housing arrangement, with nothing for a South Carolina household. + # EP-300.08 (page 9). One-person households with no income are in the lower + # band: $400 (page 19). + # Household 1: private housing with a separate gas bill -> vulnerable, $400. + # Household 2: heat in rent outside public housing -> not vulnerable, $0. + # Household 3: public housing with heat in rent and excess heat charges paid + # -> vulnerable (item 4), $400. + # Household 4: public housing with heat in rent and no excess paid -> $0. + # Household 5: lives in South Carolina -> $0. + period: 2026 + input: + state_code: [NC, NC, NC, NC, SC] + age: [40, 40, 40, 40, 40] + employment_income: [0, 0, 0, 0, 0] + ssi: [0, 0, 0, 0, 0] + social_security: [0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0] + childcare_expenses: [0, 0, 0, 0, 0] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + gas_expense: [10, 100, 100, 0, 100] + heat_expense_included_in_rent: [false, true, true, true, false] + is_in_public_housing: [false, false, true, true, false] + nc_lieap_has_paid_excess_heating_costs: [false, false, true, false, false] + output: + nc_lieap: [400, 0, 400, 0, 0] +- name: Case 2, a household with no eligible member gets no payment. + # EP-175.01 (page 1): a household must include at least one citizen or + # eligible alien. + period: 2026 + input: + state_code: NC + age: 40 + immigration_status: UNDOCUMENTED + employment_income: 0 + ssi: 0 + social_security: 0 + tanf: 0 + childcare_expenses: 0 + heating_type: NATURAL_GAS + gas_expense: 1_000 + output: + nc_lieap_household_size: 0 + nc_lieap: 0 +# Backfilling checks the model convention, not verified historical NC policy. +- name: Case 3, parameter backfill uses the 2021 guideline for a 2022 estimate. + # 2021 guideline for one person: 12_880 x 1.3 = 16_744. No income -> lower + # band -> $400. + period: 2022 + input: + state_code: NC + age: 40 + employment_income: 0 + ssi: 0 + social_security: 0 + tanf: 0 + childcare_expenses: 0 + bank_account_assets: 0 + heating_type: NATURAL_GAS + gas_expense: 1_000 + output: + nc_lieap: 400 + nc_lieap_household_size: 1 + nc_lieap_income_limit: 16_744 + nc_lieap_eligible: true + +# Cases 4-9 run from household inputs (wages, benefits received, ages, +# immigration status, fuel and its bill, resources) through to the payment. +# ssi and tanf are reported amounts here, so the cases do not depend on those +# program models. Monthly limits: EP-300 pages 14-15. Payment bands: pages +# 19-20. +# 2025 poverty guideline (one-year lag): $15,650 plus $5,500 per added person. +- name: Case 4, public housing family with heat in rent and an unspecified fuel gets the lower-band payment. + # EP-300.08 item 4 (page 9): public housing with heat included in the rent is + # fully vulnerable when excess heat charges were paid in the last 12 months + # at the current address. The tenant does not know the building's fuel, so + # heating_type is UNSPECIFIED; the payment depends on size and income only + # (EP-300.12 A, page 18). Case 5 is the same household with the fuel reported. + # Wages: $12.00 an hour x 30 hours x 52 weeks = 18_720, or $1,560 a month. + # Work deduction: over $400 a month -> 20% = $312 (page 13). + # nc_lieap_income = (1_560 - 312) x 12 = 14_976, or $1,248 a month. + # Limit, size 3: (15_650 + 2 x 5_500) x 1.3 = 34_645; / 12 = 2,887.08 -> $2,887. + # $1,248 <= $2,887 -> eligible. + # Lower band, size 3: 2,887.08 x 0.5 = 1,443.54 -> $1,444 (page 19). + # $1,248 <= $1,444 -> $400 for a household of 1 to 3. + period: 2026 + input: + people: + person1: + age: 34 + employment_income: 18_720 + ssi: 0 + person2: + age: 8 + ssi: 0 + person3: + age: 5 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + childcare_expenses: 0 + heating_type: UNSPECIFIED + heat_expense_included_in_rent: true + nc_lieap_has_paid_excess_heating_costs: true + households: + household: + members: [person1, person2, person3] + state_code: NC + is_in_public_housing: true + output: + nc_lieap_household_size: 3 + nc_lieap_earned_income: [18_720, 0, 0] + nc_lieap_gross_income_person: [18_720, 0, 0] + nc_lieap_income: 14_976 + nc_lieap_income_limit: 34_645 + has_heating_expense: false + nc_lieap_eligible: true + nc_lieap: 400 + +- name: Case 5, the same public housing family with electric heat reported gets the same payment. + # Same household and arithmetic as Case 4; only heating_type differs. + period: 2026 + input: + people: + person1: + age: 34 + employment_income: 18_720 + ssi: 0 + person2: + age: 8 + ssi: 0 + person3: + age: 5 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + childcare_expenses: 0 + heating_type: ELECTRICITY + heat_expense_included_in_rent: true + nc_lieap_has_paid_excess_heating_costs: true + households: + household: + members: [person1, person2, person3] + state_code: NC + is_in_public_housing: true + output: + nc_lieap_household_size: 3 + nc_lieap_gross_income_person: [18_720, 0, 0] + nc_lieap_income: 14_976 + nc_lieap_income_limit: 34_645 + has_heating_expense: false + nc_lieap_eligible: true + nc_lieap: 400 + +- name: Case 6, working couple with a toddler, child care costs and natural gas heat. + # person1: $13.00 x 40 hours x 52 weeks = 27_040 ($2,253.33 a month). + # Work deduction 20% = 5_408 a year -> 21_632. + # person2: $12.00 x 15 hours x 52 weeks = 9_360 ($780 a month). + # Work deduction 20% = 1_872 a year -> 7_488. + # Out-of-pocket child care (page 11): $250 a month = 3_000. + # nc_lieap_income = 21_632 + 7_488 - 3_000 = 26_120, or $2,176.67 a month. + # Limit, size 3: 34_645 a year -> $2,887 a month; $2,176.67 <= $2,887. + # Gross wages of $3,033.33 a month exceed $2,887: the household qualifies + # only because of the deductions. + # Lower band ends at $1,444; $2,176.67 is above it -> $300 for size 1 to 3. + period: 2026 + input: + people: + person1: + age: 31 + employment_income: 27_040 + ssi: 0 + person2: + age: 29 + employment_income: 9_360 + ssi: 0 + person3: + age: 3 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + childcare_expenses: 3_000 + heating_type: NATURAL_GAS + gas_expense: 960 + households: + household: + members: [person1, person2, person3] + state_code: NC + output: + nc_lieap_household_size: 3 + nc_lieap_earned_income: [27_040, 9_360, 0] + nc_lieap_gross_income_person: [27_040, 9_360, 0] + nc_lieap_income: 26_120 + nc_lieap_income_limit: 34_645 + has_heating_expense: true + nc_lieap_eligible: true + nc_lieap: 300 + +- name: Case 7, one-person households between 130 and 150 percent of poverty with and without the special population. + # Limits, size 1: 15_650 x 1.3 = 20_345 -> $1,695 a month; + # 15_650 x 1.5 = 23_475 -> $1,956 a month (EP-300 pages 10, 14). + # Household 1, age 62: wages $12.00 x 15 hours x 52 = 9_360 ($780 a month, + # 20% deduction = 1_872 a year) plus Social Security retirement of $1,300 + # a month = 15_600. Standard medical deduction for age 60+: 85 x 12 = 1_020. + # nc_lieap_income = 9_360 + 15_600 - 1_872 - 1_020 = 22_068 ($1,839). + # $1,695 < $1,839 <= $1,956 -> eligible through the 150 percent limit. + # Special lower band ends at $978 (page 20) -> $300. + # Households 2 and 3, age 52: Social Security disability of $1,850 a month + # = 22_200; medical deduction 1_020 (disability payment recipient). + # nc_lieap_income = 22_200 - 1_020 = 21_180 ($1,765). + # Household 2 receives DAAS services -> $1,956 limit -> eligible, $300. + # Household 3 does not -> $1,695 limit -> ineligible, $0. + period: 2026 + input: + state_code: [NC, NC, NC] + age: [62, 52, 52] + employment_income: [9_360, 0, 0] + social_security_retirement: [15_600, 0, 0] + social_security_disability: [0, 22_200, 22_200] + nc_lieap_is_daas_disabled: [false, true, false] + ssi: [0, 0, 0] + tanf: [0, 0, 0] + childcare_expenses: [0, 0, 0] + heating_type: [PROPANE, ELECTRICITY, NATURAL_GAS] + bottled_gas_expense: [1_500, 0, 0] + pre_subsidy_electricity_expense: [1_200, 1_680, 1_200] + gas_expense: [0, 0, 900] + output: + nc_lieap_household_size: [1, 1, 1] + nc_lieap_gross_income_person: [24_960, 22_200, 22_200] + nc_lieap_income: [22_068, 21_180, 21_180] + nc_lieap_income_limit: [23_475, 23_475, 20_345] + has_heating_expense: [true, true, true] + nc_lieap_eligible: [true, true, false] + nc_lieap: [300, 300, 0] + +- name: Case 8, mixed-immigration family prorates the excluded parent's wages and is paid for three eligible members. + # person1 is undocumented: excluded from the size, income tested gross and + # then prorated (EP-300.10, pages 15-17). Three of four members are eligible. + # Wages: $12.50 x 40 hours x 52 weeks = 26_000 ($2,166.67 a month). + # Limit, size 3: 34_645 a year -> $2,887 a month. + # Gross test: $2,166.67 <= $2,887 -> passes. + # Proration: 26_000 x 3/4 = 19_500 ($1,625 a month). + # Work deduction on the prorated amount: 20% = $325 -> $1,300 a month. + # nc_lieap_income = 1_300 x 12 = 15_600; $1,300 <= $2,887 -> eligible. + # Lower band, size 3, ends at $1,444; $1,300 <= $1,444 -> $400. + # (Unprorated, the net would be $1,733.33 and the payment $300.) + period: 2026 + input: + people: + person1: + age: 37 + employment_income: 26_000 + immigration_status: UNDOCUMENTED + ssi: 0 + person2: + age: 35 + immigration_status: CITIZEN + ssi: 0 + person3: + age: 9 + immigration_status: CITIZEN + ssi: 0 + person4: + age: 6 + immigration_status: CITIZEN + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + tanf: 0 + childcare_expenses: 0 + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_800 + households: + household: + members: [person1, person2, person3, person4] + state_code: NC + output: + spm_unit_size: 4 + nc_lieap_household_size: 3 + nc_lieap_gross_income_person: [26_000, 0, 0, 0] + nc_lieap_income: 15_600 + nc_lieap_income_limit: 34_645 + has_heating_expense: true + nc_lieap_eligible: true + nc_lieap: 400 + +- name: Case 9, FY2027 one-person households under and over the resource limits. + # Cited from EP-300, Change 04-2026 (October 1, 2026). + # The resource waiver ends June 30, 2026 (EP-300.11, page 18): $3,000, or + # $4,500 with a member aged 60 or older or disabled. FY2027 limits use the + # 2026 guideline of $15,960 (limits on pages 14-15, payment bands on page + # 20). + # Households 1 and 2, age 45: wages $11.00 x 20 hours x 52 = 11_440 + # ($953.33 a month); 20% deduction = 2_288 -> nc_lieap_income 9_152 + # ($762.67). Limit 15_960 x 1.3 = 20_748 -> $1,729. Lower band ends at + # $865 -> $400 when eligible. + # Household 1: $2,400 in the bank <= $3,000 -> eligible, $400. + # Household 2: $3,600 in the bank > $3,000 -> ineligible, $0. + # Households 3 and 4, age 67: Social Security retirement of $1,400 a month + # = 16_800; medical deduction 1_020 -> nc_lieap_income 15_780 ($1,315). + # Limit 15_960 x 1.5 = 23_940 -> $1,995. Special lower band ends at $998 + # -> $300 when eligible. + # Household 3: $3,600 in the bank <= $4,500 -> eligible, $300. + # Household 4: $4,200 in the bank + $600 cash = $4,800 > $4,500 -> $0. + period: 2027 + input: + state_code: [NC, NC, NC, NC] + age: [45, 45, 67, 67] + employment_income: [11_440, 11_440, 0, 0] + social_security_retirement: [0, 0, 16_800, 16_800] + ssi: [0, 0, 0, 0] + tanf: [0, 0, 0, 0] + childcare_expenses: [0, 0, 0, 0] + bank_account_assets: [2_400, 3_600, 3_600, 4_200] + nc_lieap_nonbank_resources: [0, 0, 0, 600] + heating_type: [KEROSENE, KEROSENE, FUEL_OIL, FUEL_OIL] + fuel_oil_expense: [1_100, 1_100, 1_400, 1_400] + output: + nc_lieap_household_size: [1, 1, 1, 1] + nc_lieap_gross_income_person: [11_440, 11_440, 16_800, 16_800] + nc_lieap_income: [9_152, 9_152, 15_780, 15_780] + nc_lieap_income_limit: [20_748, 20_748, 23_940, 23_940] + has_heating_expense: [true, true, true, true] + nc_lieap_eligible: [true, false, true, false] + nc_lieap: [400, 0, 300, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap.yaml new file mode 100644 index 00000000000..a197ab4f764 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap.yaml @@ -0,0 +1,219 @@ +# Sources: EP-300 Sections 300.08-300.12 and the FY2026 benefit matrix. +- name: Case 1, regular heating payments at the top of each published lower band. + # Annual incomes are 12 times the top of each lower band in the 130% chart + # for 1 to 15 eligible members (EP-300 pages 19-20): $848, $1,146, $1,444, + # $1,741 and so on to $5,019. The lower band pays $400 for 1 to 3 members + # and $500 for 4 or more. + period: 2026 + input: + state_code: [NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC] + age: [40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40] + nc_lieap_eligible: [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true] + nc_lieap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + nc_lieap_income: [10_176, 13_752, 17_328, 20_892, 24_468, 28_044, 31_620, 35_196, 38_772, 42_348, 45_924, 49_500, + 53_076, 56_652, 60_228] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY] + output: + nc_lieap: [400, 400, 400, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500] +- name: Case 2, regular heating payments one dollar into each published upper band. + # Annual incomes are 12 times the bottom of each upper band in the same + # chart: $849, $1,147, $1,445, $1,742 and so on to $5,020. The upper band + # pays $300 for 1 to 3 members and $400 for 4 or more. + period: 2026 + input: + state_code: [NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC] + age: [40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40] + nc_lieap_eligible: [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true] + nc_lieap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + nc_lieap_income: [10_188, 13_764, 17_340, 20_904, 24_480, 28_056, 31_632, 35_208, 38_784, 42_360, 45_936, 49_512, + 53_088, 56_664, 60_240] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY] + output: + nc_lieap: [300, 300, 300, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400] +# Cases 3 and 4 use the special-population bands for 1 to 11 eligible members +# in the FY2026 benefit matrix (pages 2-3), which the EP-300 150% chart (page +# 20) repeats. The sources contain typos that the expected values do not follow: +# - EP-300 page 20 prints the size-6 lower band as "$0-$2,267". The matrix +# (page 3) prints $2,697, which agrees with the upper band starting at $2,698 +# in both documents. +# - The matrix (page 3) prints the size-10 upper band as ending at "$8,1444". +# The limit is $8,144 (EP-300 page 15). +# - The matrix (page 2) describes the special population as "aged 65 or +# older". The manual says 60 (EP-300 page 10). +- name: Case 3, elderly heating payments at the top of each published lower band. + # Annual incomes are 12 times the top of each lower band: $978, $1,322, + # $1,666, $2,009, $2,353, $2,697 and so on to $4,416. The lower band pays + # $400 for 1 to 3 members and $500 for 4 or more. + period: 2026 + input: + state_code: [NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC] + age: [60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60] + nc_lieap_eligible: [true, true, true, true, true, true, true, true, true, true, true] + nc_lieap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + nc_lieap_income: [11_736, 15_864, 19_992, 24_108, 28_236, 32_364, 36_492, 40_608, 44_736, 48_864, 52_992] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY] + output: + nc_lieap: [400, 400, 400, 500, 500, 500, 500, 500, 500, 500, 500] +- name: Case 4, elderly heating payments one dollar into each published upper band. + # Annual incomes are 12 times the bottom of each upper band: $979, $1,323, + # $1,667, $2,010, $2,354, $2,698 and so on to $4,417. The upper band pays + # $300 for 1 to 3 members and $400 for 4 or more. + period: 2026 + input: + state_code: [NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC] + age: [60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60] + nc_lieap_eligible: [true, true, true, true, true, true, true, true, true, true, true] + nc_lieap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + nc_lieap_income: [11_748, 15_876, 20_004, 24_120, 28_248, 32_376, 36_504, 40_620, 44_748, 48_876, 53_004] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY] + output: + nc_lieap: [300, 300, 300, 400, 400, 400, 400, 400, 400, 400, 400] +- name: Case 5, coal and wood households get the flat payment at any eligible income. + # Fourth household: age 60, 20_352 / 12 = $1,696 a month, above the $1,695 + # regular limit but under the $1,956 special-population limit (EP-300 pages + # 10 and 14), so it is income eligible and receives the flat $300 (page 20). + period: 2026 + input: + state_code: [NC, NC, NC, NC] + age: [40, 60, 60, 60] + nc_lieap_eligible: [true, true, true, true] + nc_lieap_household_size: [1, 1, 1, 1] + nc_lieap_income: [0, 0, 20_340, 20_352] + heating_type: [COAL, WOOD, WOOD, COAL] + output: + nc_lieap: [300, 300, 300, 300] +- name: Case 6, other known fuels share the schedule, no heat pays nothing and an unknown fuel is paid only with heat in rent. + # One-person households with no income are in the lower band: $400 (EP-300 + # page 19). An unspecified fuel is the reported value when heat is included + # in rent and the tenant does not know the building's fuel; the payment + # depends on size and income only (EP-300.12 A, page 18). Without heat in + # rent an unspecified fuel gets no payment. + period: 2026 + input: + state_code: [NC, NC, NC, NC, NC, NC, NC, NC, NC] + age: [40, 40, 40, 40, 40, 40, 40, 40, 40] + nc_lieap_eligible: [true, true, true, true, true, true, true, true, true] + nc_lieap_income: [0, 0, 0, 0, 0, 0, 0, 0, 0] + heating_type: [NATURAL_GAS, FUEL_OIL, KEROSENE, PROPANE, SOLAR, OTHER, NONE, UNSPECIFIED, UNSPECIFIED] + heat_expense_included_in_rent: [false, false, false, false, false, false, false, false, true] + output: + nc_lieap: [400, 400, 400, 400, 400, 400, 0, 0, 400] + +- name: Case 7, a DAAS-disabled member gives the special payment bands and the flat coal and wood payment up to 150 percent. + # Special lower band ends at $978/month ($979 pays the next band). Coal and + # wood pay the flat $300 to every income-eligible household: the special + # population's limit is $1,956 (150% FPG), so $1,695 and $1,696 a month both + # receive it (EP-300 pages 10, 18 and 20). + period: 2026 + input: + state_code: [NC, NC, NC, NC] + age: [40, 40, 40, 40] + nc_lieap_is_daas_disabled: [true, true, true, true] + nc_lieap_eligible: [true, true, true, true] + nc_lieap_household_size: [1, 1, 1, 1] + nc_lieap_income: [11_736, 11_748, 20_340, 20_352] + heating_type: [NATURAL_GAS, NATURAL_GAS, WOOD, COAL] + output: + nc_lieap: [400, 300, 300, 300] + +- name: Case 8, coal and wood households are paid up to their own income limit and not above it. + # Eligibility is computed here, so the 130 or 150 percent limit is applied by + # nc_lieap_eligible; the payment formula itself does not test income. + # One-person households on the 2025 guideline of $15,650. + # Special population (age 62, wood): 15_650 x 1.5 = 23_475 a year; + # 23_475 / 12 = 1,956.25, rounded to $1,956 a month (EP-300 pages 10, 14). + # 23_472 / 12 = $1,956 -> eligible, flat $300 (page 20). + # 23_484 / 12 = $1,957 -> ineligible, $0. + # Regular (age 40, coal): 15_650 x 1.3 = 20_345 a year; + # 20_345 / 12 = 1,695.42, rounded to $1,695 a month. + # 20_340 / 12 = $1,695 -> eligible, flat $300. + # 20_352 / 12 = $1,696 -> between 130 and 150 percent with no member aged + # 60 or DAAS-disabled -> ineligible, $0. + period: 2026 + input: + state_code: [NC, NC, NC, NC] + age: [62, 62, 40, 40] + nc_lieap_income: [23_472, 23_484, 20_340, 20_352] + nc_lieap_gross_income_person: [0, 0, 0, 0] + tanf: [0, 0, 0, 0] + heating_type: [WOOD, WOOD, COAL, COAL] + wood_expense: [900, 900, 0, 0] + coal_expense: [0, 0, 700, 700] + output: + nc_lieap_household_size: [1, 1, 1, 1] + nc_lieap_income_limit: [23_475, 23_475, 20_345, 20_345] + has_heating_expense: [true, true, true, true] + nc_lieap_eligible: [true, false, true, false] + nc_lieap: [300, 0, 300, 0] + +- name: Case 9, FY2027 payment band edges that fall on an exact half dollar round up. + # FY2027 uses the 2026 poverty guideline: $15,960 plus $5,680 per added + # person. The lower band ends at half of the monthly limit, and a half + # dollar rounds up. Published bands: EP-300, Change 04-2026 (October 1, + # 2026), pages 20-21; limits on pages 14-15 of that edition. + # Households 1 and 2, age 40, one member (130% chart, page 20): + # 15_960 x 1.3 = 20_748 a year, or $1,729 a month; half is $864.50, + # published as "$0-$865" for $400 and "$866-$1729" for $300. + # 10_380 / 12 = $865 -> $400. 10_392 / 12 = $866 -> $300. + # Households 3 and 4, age 60, one member (150% chart, page 20): + # 15_960 x 1.5 = 23_940 a year, or $1,995 a month; half is $997.50, + # published as "$0-$998" for $400 and "$999-$1,995" for $300. + # 11_976 / 12 = $998 -> $400. 11_988 / 12 = $999 -> $300. + # Households 5 and 6, age 60, six eligible members (150% chart, page 21): + # (15_960 + 5 x 5_680) x 1.5 = 66_540 a year, or $5,545 a month; half is + # $2,772.50, published as "$0-$2,773" for $500 and "$2,774-$5545" for $400. + # 33_276 / 12 = $2,773 -> $500. 33_288 / 12 = $2,774 -> $400. + period: 2027 + input: + state_code: [NC, NC, NC, NC, NC, NC] + age: [40, 40, 60, 60, 60, 60] + nc_lieap_household_size: [1, 1, 1, 1, 6, 6] + nc_lieap_income: [10_380, 10_392, 11_976, 11_988, 33_276, 33_288] + nc_lieap_gross_income_person: [0, 0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0, 0] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY] + pre_subsidy_electricity_expense: [1_500, 1_500, 1_500, 1_500, 1_500, 1_500] + output: + nc_lieap_income_limit: [20_748, 20_748, 23_940, 23_940, 66_540, 66_540] + nc_lieap_eligible: [true, true, true, true, true, true] + nc_lieap: [400, 300, 400, 300, 500, 400] + +- name: Case 10, households larger than the published payment charts use extrapolated bands. + # EP-300 publishes income limits for 1 to 26 eligible members (pages 14-15) + # but payment bands only for 1 to 15 members at 130% and 1 to 11 members at + # 150% (pages 19-20). The band edges below are extrapolated, not published: + # they extend the charts' rule that the lower band ends at half of the + # monthly limit and that households of four or more get $500 in the lower + # band and $400 above it. 2025 guideline: $15,650 plus $5,500 per added + # person. + # Households 1 and 2, age 40, 16 eligible members: + # (15_650 + 15 x 5_500) x 1.3 = 127_595 a year; / 12 = 10,632.92, the + # published $10,633 limit (page 14). Half is 5,316.46 -> $5,316. + # 63_792 / 12 = $5,316 -> $500. 63_804 / 12 = $5,317 -> $400. + # Households 3 and 4, age 60, 12 eligible members: + # (15_650 + 11 x 5_500) x 1.5 = 114_225 a year; / 12 = 9,518.75, the + # published $9,519 limit (page 15). Half is 4,759.38 -> $4,759. + # 57_108 / 12 = $4,759 -> $500. 57_120 / 12 = $4,760 -> $400. + # Households 5 and 6, age 40, 27 eligible members, one more than the + # published limits cover, so the limit is extrapolated too: + # (15_650 + 26 x 5_500) x 1.3 = 206_245 a year; / 12 = 17,187.08 -> + # $17,187. Half is 8,593.54 -> $8,594. + # 103_128 / 12 = $8,594 -> $500. 103_140 / 12 = $8,595 -> $400. + period: 2026 + input: + state_code: [NC, NC, NC, NC, NC, NC] + age: [40, 40, 60, 60, 40, 40] + nc_lieap_household_size: [16, 16, 12, 12, 27, 27] + nc_lieap_income: [63_792, 63_804, 57_108, 57_120, 103_128, 103_140] + nc_lieap_gross_income_person: [0, 0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0, 0] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY] + pre_subsidy_electricity_expense: [1_500, 1_500, 1_500, 1_500, 1_500, 1_500] + output: + nc_lieap_income_limit: [127_595, 127_595, 114_225, 114_225, 206_245, 206_245] + nc_lieap_eligible: [true, true, true, true, true, true] + nc_lieap: [500, 400, 500, 400, 500, 400] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_eligible.yaml new file mode 100644 index 00000000000..2c83a98fd80 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_eligible.yaml @@ -0,0 +1,352 @@ +# Sources: EP-300 Sections 300.08-300.12 and the FY2026 benefit matrix. +# Page numbers are those of EP-300 Change 2-2026 (May 1, 2026) for FY2026 +# cases and of Change 04-2026 (October 1, 2026) for FY2027 cases. +- name: Case 1, regular households at the published monthly limits are eligible. + # Annual incomes are 12 times the 130% maximum countable income for 1 to 26 + # eligible members (EP-300 page 14): $1,695, $2,291, $2,887 and so on to + # $16,591. + period: 2026 + input: + state_code: [NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, + NC] + age: [40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40] + nc_lieap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26] + nc_lieap_income: [20_340, 27_492, 34_644, 41_796, 48_948, 56_100, 63_240, 70_392, 77_544, 84_696, 91_848, 99_000, + 106_140, 113_292, 120_444, 127_596, 134_748, 141_900, 149_040, 156_192, 163_344, 170_496, 177_648, 184_800, 191_940, + 199_092] + nc_lieap_gross_income_person: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY] + heating_expense: [1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, + 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000] + output: + nc_lieap_eligible: [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true] +- name: Case 2, regular households one dollar above the published monthly limits are ineligible. + # Annual incomes are 12 times one dollar more than each 130% limit (EP-300 + # page 14): $1,696, $2,292, $2,888 and so on to $16,592. + period: 2026 + input: + state_code: [NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, + NC] + age: [40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40] + nc_lieap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26] + nc_lieap_income: [20_352, 27_504, 34_656, 41_808, 48_960, 56_112, 63_252, 70_404, 77_556, 84_708, 91_860, 99_012, + 106_152, 113_304, 120_456, 127_608, 134_760, 141_912, 149_052, 156_204, 163_356, 170_508, 177_660, 184_812, 191_952, + 199_104] + nc_lieap_gross_income_person: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY] + heating_expense: [1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, + 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000] + output: + nc_lieap_eligible: [false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false] +- name: Case 3, elderly households at the published monthly limits are eligible. + # Annual incomes are 12 times the 150% maximum countable income for 1 to 26 + # eligible members (EP-300 pages 14-15): $1,956, $2,644, $3,331 and so on to + # $19,144. + period: 2026 + input: + state_code: [NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, + NC] + age: [60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60] + nc_lieap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26] + nc_lieap_income: [23_472, 31_728, 39_972, 48_228, 56_472, 64_728, 72_972, 81_228, 89_472, 97_728, 105_972, 114_228, + 122_472, 130_728, 138_972, 147_228, 155_472, 163_728, 171_972, 180_228, 188_472, 196_728, 204_972, 213_228, 221_472, + 229_728] + nc_lieap_gross_income_person: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY] + heating_expense: [1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, + 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000] + output: + nc_lieap_eligible: [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true] +- name: Case 4, elderly households one dollar above the published monthly limits are ineligible. + # Annual incomes are 12 times one dollar more than each 150% limit (EP-300 + # pages 14-15): $1,957, $2,645, $3,332 and so on to $19,145. + period: 2026 + input: + state_code: [NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, + NC] + age: [60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60] + nc_lieap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26] + nc_lieap_income: [23_484, 31_740, 39_984, 48_240, 56_484, 64_740, 72_984, 81_240, 89_484, 97_740, 105_984, 114_240, + 122_484, 130_740, 138_984, 147_240, 155_484, 163_740, 171_984, 180_240, 188_484, 196_740, 204_984, 213_240, 221_484, + 229_740] + nc_lieap_gross_income_person: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, + ELECTRICITY, ELECTRICITY, ELECTRICITY] + heating_expense: [1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, + 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000, 1_000] + output: + nc_lieap_eligible: [false, false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, false, false] +- name: Case 5, a disabled member not served by DAAS does not get the special income limit. + # EP-300.09 (page 10): the 150% limit needs a member aged 60 or over or a + # disabled person receiving DAAS services. 22_200 / 12 = $1,850 a month is + # over the $1,695 regular limit for one person (page 14). + period: 2026 + input: + state_code: NC + age: 59 + is_disabled: true + nc_lieap_income: 22_200 + nc_lieap_gross_income_person: 0 + tanf: 0 + heating_type: NATURAL_GAS + heating_expense: 1_000 + output: + nc_lieap_eligible: false +- name: Case 6, large resources do not affect eligibility during the FY2026 waiver. + # EP-300.11 (page 17): resources are not countable from July 1, 2025 through + # June 30, 2026. + period: 2026 + input: + state_code: NC + age: 40 + bank_account_assets: 100_000 + nc_lieap_nonbank_resources: 100_000 + nc_lieap_income: 0 + nc_lieap_gross_income_person: 0 + tanf: 0 + heating_type: ELECTRICITY + heating_expense: 1_000 + output: + nc_lieap_eligible: true +- name: Case 7, the resource limits apply again in FY2027. + # EP-300.11 (page 18): the waiver ends June 30, 2026. The limit is $4,500 + # with a member aged 60 or older or disabled and $3,000 otherwise. + period: 2027 + input: + state_code: [NC, NC, NC, NC] + age: [40, 40, 60, 60] + bank_account_assets: [3_000, 3_001, 4_500, 4_501] + nc_lieap_income: [0, 0, 0, 0] + nc_lieap_gross_income_person: [0, 0, 0, 0] + tanf: [0, 0, 0, 0] + heating_type: [PROPANE, PROPANE, PROPANE, PROPANE] + heating_expense: [1_000, 1_000, 1_000, 1_000] + output: + nc_lieap_eligible: [true, false, true, false] +- name: Case 8, FY2027 income limits for one person, regular and special. + # EP-300 pages 14-15: one eligible member has a limit of $1,729 at 130% + # (page 14) and $1,995 at 150% (page 15), from the 2026 guideline + # of $15,960. 20_748 / 12 = $1,729 and 20_760 / 12 = $1,730; + # 23_940 / 12 = $1,995 and 23_952 / 12 = $1,996. + period: 2027 + input: + state_code: [NC, NC, NC, NC] + age: [40, 40, 60, 60] + nc_lieap_income: [20_748, 20_760, 23_940, 23_952] + nc_lieap_gross_income_person: [0, 0, 0, 0] + tanf: [0, 0, 0, 0] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + heating_expense: [1_000, 1_000, 1_000, 1_000] + output: + nc_lieap_eligible: [true, false, true, false] + +- name: Case 9, public housing with heat in rent requires paid excess heating charges. + # EP-300.08 (page 9): a bill alone is insufficient; a qualifying past payment suffices + # even if the household has no separate heating bill in the modeled year. + period: 2026 + input: + state_code: [NC, NC, NC, NC, NC, NC] + age: [40, 40, 40, 40, 40, 40] + nc_lieap_income: [0, 0, 0, 0, 0, 0] + nc_lieap_gross_income_person: [0, 0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0, 0] + heating_type: [ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY, ELECTRICITY] + heating_expense: [100, 100, 0, 100, 100, 100] + heat_expense_included_in_rent: [true, true, true, true, false, false] + is_in_public_housing: [true, true, true, false, true, false] + nc_lieap_has_paid_excess_heating_costs: [false, true, true, true, false, false] + output: + nc_lieap_eligible: [false, true, true, false, true, true] + +- name: Case 10, countable resources include cash outside banks and net bank balances. + # EP-300.11 (page 18): $3,000 ordinary limit; bank exclusions do not offset + # cash. + period: 2027 + input: + state_code: [NC, NC, NC, NC, NC, NC] + age: [40, 40, 40, 40, 40, 40] + nc_lieap_income: [0, 0, 0, 0, 0, 0] + nc_lieap_gross_income_person: [0, 0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0, 0] + heating_type: [FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL] + heating_expense: [100, 100, 100, 100, 100, 100] + bank_account_assets: [2_500, 2_500, 5_000, 5_000, 0, 0] + nc_lieap_nonbank_resources: [500, 501, 1_000, 1_001, 3_001, 3_000] + nc_lieap_bank_account_exclusions: [0, 0, 3_000, 3_000, 9_999, 9_999] + output: + nc_lieap_eligible: [true, false, true, false, false, true] + +- name: Case 11, an excluded member's resources count in the household resource test. + # EP-300.11 (page 18) expressly counts ineligible aliens' resources: + # $2,000 + $1,001 = $3,001, over the $3,000 limit. + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + bank_account_assets: 2_000 + nc_lieap_gross_income_person: 0 + person2: + age: 40 + immigration_status: UNDOCUMENTED + nc_lieap_nonbank_resources: 1_001 + nc_lieap_gross_income_person: 0 + spm_units: + spm_unit: + members: [person1, person2] + nc_lieap_income: 0 + tanf: 0 + heating_type: ELECTRICITY + heating_expense: 100 + households: + household: + members: [person1, person2] + state_code: NC + output: + nc_lieap_eligible: false + +- name: Case 12, a DAAS-disabled member gives the higher resource limit. + # EP-300.11 (page 18): $4,500 with a member aged 60 or older or disabled. + period: 2027 + input: + state_code: [NC, NC] + age: [40, 40] + nc_lieap_is_daas_disabled: [true, true] + is_usda_disabled: [false, false] + nc_lieap_income: [0, 0] + nc_lieap_gross_income_person: [0, 0] + tanf: [0, 0] + heating_type: [KEROSENE, KEROSENE] + heating_expense: [100, 100] + bank_account_assets: [4_500, 4_501] + output: + nc_lieap_eligible: [true, false] + +- name: Case 13, excluded parent's wages pass the prorated net test but fail the unprorated gross test. + # EP-300.10 A.2.b (page 15): count all of a non-qualified member's income in + # the manual gross income test at the eligible household size, before any + # proration; if it exceeds the limit, deny. Two of three members are eligible. + # Limit, size 2: (15_650 + 5_500) x 1.3 = 27_495 a year; 27_495 / 12 = 2,291.25, + # rounded to $2,291 a month (EP-300 page 14). + # Gross test: 36_000 / 12 = $3,000 > $2,291 -> fails. + # Net test, taken alone, passes: 36_000 x 2/3 = 24_000 prorated (page 16); + # $2,000 a month is over $400, so the work deduction is 20% = $400; + # net $1,600 a month <= $2,291. nc_lieap_income = 1_600 x 12 = 19_200. + # Only the gross test makes this household ineligible. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 36_000 + immigration_status: UNDOCUMENTED + ssi: 0 + person2: + age: 38 + ssi: 0 + person3: + age: 10 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + childcare_expenses: 0 + heating_type: NATURAL_GAS + gas_expense: 1_000 + households: + household: + members: [person1, person2, person3] + state_code: NC + output: + nc_lieap_household_size: 2 + nc_lieap_gross_income_person: [36_000, 0, 0] + nc_lieap_income: 19_200 + nc_lieap_income_limit: 27_495 + has_heating_expense: true + nc_lieap_eligible: false + nc_lieap: 0 + +- name: Case 14, a disabled member not served by DAAS gets the higher resource limit but the regular income limit. + # EP-300.11 (page 18): $4,500 for "households with member aged 60 or older + # or disabled", $3,000 for all others. The 150% income limit instead needs + # a member aged 60 or over or a disabled person receiving DAAS services + # (EP-300.09, page 10), so these one-person households keep the 130% limit. + # Social Security disability of $1,300 a month = 15_600. Standard medical + # deduction for a disability payment recipient: 85 x 12 = 1_020 (page 13). + # nc_lieap_income = 15_600 - 1_020 = 14_580, or $1,215 a month. + # Limit, one member: 15_960 x 1.3 = 20_748 a year -> $1,729 a month (page + # 14); $1,215 <= $1,729. + # Household 1: $4,500 in the bank <= $4,500 -> eligible. + # Household 2: $4,501 in the bank > $4,500 -> ineligible. + period: 2027 + input: + state_code: [NC, NC] + age: [40, 40] + is_usda_disabled: [true, true] + nc_lieap_is_daas_disabled: [false, false] + social_security_disability: [15_600, 15_600] + ssi: [0, 0] + tanf: [0, 0] + childcare_expenses: [0, 0] + bank_account_assets: [4_500, 4_501] + heating_type: [NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_000, 1_000] + output: + nc_lieap_income: [14_580, 14_580] + nc_lieap_income_limit: [20_748, 20_748] + nc_lieap_eligible: [true, false] + +- name: Case 15, a household that pays no heating bill and does not have heat in rent is not vulnerable. + # EP-300.08 (page 9): a household is vulnerable if it has a heating source + # billed separately. Item 3: a private living arrangement with no separate + # bill from the energy provider is not vulnerable. Heat is not included in + # the rent, so the public housing rule in item 4 does not apply. + # Wages: $12.00 an hour x 25 hours x 52 weeks = 15_600, or $1,300 a month. + # Work deduction: over $400 a month -> 20% = $260 (page 13). + # nc_lieap_income = (1_300 - 260) x 12 = 12_480, or $1,040 a month. + # Limit, one member: 15_650 x 1.3 = 20_345 a year -> $1,695 a month (page + # 14); $1,040 <= $1,695, so income does not decide the result. + # Household 1 pays nothing for natural gas -> not vulnerable -> ineligible. + # Household 2 pays $900 a year -> vulnerable -> eligible. The lower band for + # one member ends at $848 (page 19); $1,040 is above it -> $300. + period: 2026 + input: + state_code: [NC, NC] + age: [45, 45] + employment_income: [15_600, 15_600] + ssi: [0, 0] + tanf: [0, 0] + childcare_expenses: [0, 0] + heating_type: [NATURAL_GAS, NATURAL_GAS] + gas_expense: [0, 900] + heat_expense_included_in_rent: [false, false] + output: + nc_lieap_income: [12_480, 12_480] + has_heating_expense: [false, true] + nc_lieap_eligible: [false, true] + nc_lieap: [0, 300] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_household_size.yaml new file mode 100644 index 00000000000..85f7e727fda --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_household_size.yaml @@ -0,0 +1,27 @@ +# Sources: EP-150.01, EP-175.01 and 175.05, and EP-300.10. +- name: Case 1, permanent residents and refugees count in the household size and an undocumented member does not. + # EP-175.05 chart: a permanent resident is "Eligible for Energy Programs" + # (page 4) and so is a refugee (page 5). EP-175.01 D (page 1): illegal aliens + # are not eligible. EP-150.01: do not include an ineligible alien in the + # number in the household. Three of the four members count. + period: 2026 + input: + people: + person1: + immigration_status: LEGAL_PERMANENT_RESIDENT + person2: + immigration_status: REFUGEE + person3: + immigration_status: CITIZEN + person4: + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + households: + household: + members: [person1, person2, person3, person4] + state_code: NC + output: + is_citizen_or_legal_immigrant: [true, true, true, false] + nc_lieap_household_size: 3 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_income.yaml new file mode 100644 index 00000000000..e6a07b8b9a1 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_income.yaml @@ -0,0 +1,325 @@ +# Sources: EP-300 Sections 300.08-300.12 and the FY2026 benefit matrix. +- name: Case 1, work deductions use closed upper bands, then twenty percent of earnings. + # EP-300.09 B.3 table (pages 12-13), by monthly earnings: $1-$50 -> $10, + # $51-$100 -> $20, $101-$150 -> $30, $151-$200 -> $40, $201-$300 -> $60, + # $301-$400 -> $80, $401 and over -> 20%. + # Monthly earnings of $0, $50, $50.01, $100, $100.01, $150, $150.01, $200, + # $200.01, $300, $300.01, $400 and $400.01 leave $0, $40, $30.01, $80, + # $70.01, $120, $110.01, $160, $140.01, $240, $220.01, $320 and $320.008 a + # month, times 12. + period: 2026 + input: + state_code: [NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC, NC] + age: [40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40] + employment_income: [0, 600, 600.12, 1_200, 1_200.12, 1_800, 1_800.12, 2_400, 2_400.12, 3_600, 3_600.12, 4_800, 4_800.12] + ssi: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + social_security: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + tanf: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + childcare_expenses: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + output: + nc_lieap_income: [0, 480, 360.12, 960, 840.12, 1_440, 1_320.12, 1_920, 1_680.12, 2_880, 2_640.12, 3_840, 3_840.1] + absolute_error_margin: 0.02 +- name: Case 2, business, farm and rental income get the work deduction once. + period: 2026 + input: + state_code: NC + age: 40 + self_employment_income: 6_000 + sstb_self_employment_income: 3_000 + farm_operations_income: 3_000 + rental_income: 6_000 + ssi: 0 + social_security: 0 + tanf: 0 + childcare_expenses: 0 + output: + # EP-300.09 B.3 counts farm, self-employment and rental income for + # the work deduction: (6,000 + 3,000 + 3,000 + 6,000) * 0.8 = 14,400. + nc_lieap_earned_income: 18_000 + nc_lieap_gross_income_person: 18_000 + nc_lieap_income: 14_400 +- name: Case 3, the medical deduction, child support paid and care costs reduce income. + # Wages of $1,000 a month: 20% work deduction leaves $800, or 9_600 a year + # (EP-300.09 B.3, page 13). Standard medical deduction at age 60: + # 85 x 12 = 1_020 (B.4, page 13). Child support paid 1_200 (B.2, page 12), + # adult care 600 and child care 1_200 (B.1, page 11). + # nc_lieap_income = 9_600 - 1_020 - 1_200 - 600 - 1_200 = 5_580. + period: 2026 + input: + state_code: NC + age: 60 + employment_income: 12_000 + ssi: 0 + social_security: 0 + tanf: 0 + child_support_expense: 1_200 + childcare_expenses: 1_200 + care_expenses: 600 + output: + nc_lieap_income: 5_580 +- name: Case 4, a K-12 child's earnings are excluded and business losses do not offset wages. + # Household 1, a 17-year-old K-12 student: earned income is excluded (FNS + # 350.01 D, page 2) -> 0. + # Household 2, age 35: wages of $1,000 a month with each business or farm + # loss counted as zero; 20% work deduction leaves 12_000 x 0.8 = 9_600. + period: 2026 + input: + state_code: [NC, NC] + age: [17, 35] + is_in_k12_school: [true, false] + employment_income: [12_000, 12_000] + self_employment_income: [-6_000, -6_000] + sstb_self_employment_income: [2_000, -2_000] + farm_operations_income: [3_000, -3_000] + ssi: [0, 0] + social_security: [0, 0] + tanf: [0, 0] + childcare_expenses: [0, 0] + output: + nc_lieap_income: [0, 9_600] +- name: Case 5, an excluded member's wages are prorated before the work deduction and pass the gross test. + # EP-300.10 (pages 15-17): two of three members are eligible. + # Gross test: 3_600 / 12 = $300 <= the $2,291 limit for two (page 14). + # Proration: 3_600 x 2/3 = 2_400, or $200 a month. Work deduction on the + # prorated amount: $151-$200 -> $40 (page 12). (200 - 40) x 12 = 1_920. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 3_600 + immigration_status: UNDOCUMENTED + ssi: 0 + social_security: 0 + person2: + age: 30 + ssi: 0 + social_security: 0 + person3: + age: 30 + ssi: 0 + social_security: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + childcare_expenses: 0 + heating_type: ELECTRICITY + heating_expense: 1_000 + households: + household: + members: [person1, person2, person3] + state_code: NC + output: + nc_lieap_household_size: 2 + nc_lieap_income: 1_920 + nc_lieap_eligible: true +- name: Case 6, an excluded member's wages are prorated before the work deduction and fail the gross test. + # Gross test: 60_000 / 12 = $5,000 > the $2,291 limit for two (page 14), so + # the household is ineligible (EP-300.10 A.2.b, page 15). + # Proration: 60_000 x 2/3 = 40_000; 20% work deduction leaves 32_000. + period: 2026 + input: + people: + person1: + age: 40 + employment_income: 60_000 + immigration_status: UNDOCUMENTED + ssi: 0 + social_security: 0 + person2: + age: 30 + ssi: 0 + social_security: 0 + person3: + age: 30 + ssi: 0 + social_security: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + tanf: 0 + childcare_expenses: 0 + heating_type: ELECTRICITY + heating_expense: 1_000 + households: + household: + members: [person1, person2, person3] + state_code: NC + output: + nc_lieap_household_size: 2 + nc_lieap_income: 32_000 + nc_lieap_eligible: false + +- name: Case 7, service business and farm profits exceed the income limit after the work deduction. + period: 2026 + input: + state_code: [NC, NC] + age: [40, 40] + sstb_self_employment_income: [60_000, 0] + farm_operations_income: [0, 60_000] + ssi: [0, 0] + social_security: [0, 0] + tanf: [0, 0] + heating_type: [NATURAL_GAS, NATURAL_GAS] + gas_expense: [1_000, 1_000] + output: + nc_lieap_earned_income: [60_000, 60_000] + nc_lieap_gross_income_person: [60_000, 60_000] + # 60,000 * (1 - 0.2) = 48,000, above the regular one-person limit. + nc_lieap_income: [48_000, 48_000] + nc_lieap_eligible: [false, false] + nc_lieap: [0, 0] + +- name: Case 8, an excluded member aged 61 gets no medical deduction and has support and care payments prorated. + # EP-300.10 B.1 (page 17): no medical deduction for an ineligible alien, even + # a specified person. EP-300.10 A.2.d-e (page 16) and B.3 (page 17): prorate + # the member's income and the payments they make by eligible members over + # all members, here 3 of 4. + # person1, age 61 and undocumented: wages of $1,600 a month = 19_200. + # Prorated: 19_200 x 3/4 = 14_400, or $1,200 a month. + # Work deduction on the prorated amount: over $400 -> 20% = $240 a month + # (page 13); 240 x 12 = 2_880. + # Child support paid: $200 a month = 2_400; x 3/4 = 1_800. + # Adult care paid: $100 a month = 1_200; x 3/4 = 900. + # nc_lieap_income = 14_400 - 2_880 - 1_800 - 900 = 8_820. + # (An $85 medical deduction would give 7_800; unprorated payments, 7_920.) + period: 2026 + input: + people: + person1: + age: 61 + immigration_status: UNDOCUMENTED + employment_income: 19_200 + child_support_expense: 2_400 + care_expenses: 1_200 + ssi: 0 + social_security: 0 + person2: + age: 36 + ssi: 0 + social_security: 0 + person3: + age: 9 + ssi: 0 + social_security: 0 + person4: + age: 6 + ssi: 0 + social_security: 0 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + tanf: 0 + childcare_expenses: 0 + households: + household: + members: [person1, person2, person3, person4] + state_code: NC + output: + nc_lieap_household_size: 3 + nc_lieap_gross_income_person: [19_200, 0, 0, 0] + nc_lieap_income: 8_820 + +- name: Case 9, the work deduction cannot exceed monthly earnings under ten dollars. + # EP-300.09 B.3 (page 12) deducts $10 from monthly earnings of $1 to $50. It + # does not say what happens below $10; the model caps the deduction at the + # earnings so that it cannot reduce other income. + # Each household is one person aged 67 with Social Security retirement of + # $1,200 a month = 14_400 and the standard medical deduction of + # 85 x 12 = 1_020 (page 13): 14_400 - 1_020 = 13_380 before earnings. + # Household 1: wages of 84 a year, or $7 a month; the deduction is capped + # at $7 -> net earnings $0 -> 13_380 (an uncapped $10 would give 13_344). + # Household 2: wages of 120, or $10 a month; deduction $10 -> $0 -> 13_380. + # Household 3: wages of 132, or $11 a month; deduction $10 -> $1 a month, + # 12 a year -> 13_392. + period: 2026 + input: + state_code: [NC, NC, NC] + age: [67, 67, 67] + employment_income: [84, 120, 132] + social_security_retirement: [14_400, 14_400, 14_400] + ssi: [0, 0, 0] + tanf: [0, 0, 0] + childcare_expenses: [0, 0, 0] + output: + nc_lieap_earned_income: [84, 120, 132] + nc_lieap_gross_income_person: [14_484, 14_520, 14_532] + nc_lieap_income: [13_380, 13_380, 13_392] + +- name: Case 10, farm rent is counted as rental income and gets the work deduction. + # EP-300.09 B.3 (page 12) gives the earned income deduction to "farm, + # self-employment, and rental income". Farm rent of $800 a month = 9_600 is + # over $400 a month, so 20% is deducted (page 13): 9_600 x 0.8 = 7_680. + period: 2026 + input: + state_code: NC + age: 40 + farm_rent_income: 9_600 + ssi: 0 + social_security: 0 + tanf: 0 + childcare_expenses: 0 + output: + nc_lieap_earned_income: 9_600 + nc_lieap_gross_income_person: 9_600 + nc_lieap_income: 7_680 + absolute_error_margin: 0.01 + +- name: Case 11, a K-12 student's rental income is counted in full while the student's wages are excluded. + # A 17-year-old K-12 student. Wages of 3_000 are excluded (FNS 350.01 D, + # page 2). Rental income of $200 a month = 2_400 is not subject to that + # exclusion; the $151-$200 band deducts $40 a month (EP-300.09 B.3, + # page 12): (200 - 40) x 12 = 1_920. + period: 2026 + input: + state_code: NC + age: 17 + is_in_k12_school: true + employment_income: 3_000 + rental_income: 2_400 + ssi: 0 + social_security: 0 + tanf: 0 + childcare_expenses: 0 + output: + nc_lieap_earned_income: 2_400 + nc_lieap_gross_income_person: 2_400 + nc_lieap_income: 1_920 + absolute_error_margin: 0.01 + +- name: Case 12, each unearned source is counted once with no work deduction. + # Two one-person households under age 60, so no medical deduction applies. + # Household 1, age 45: unemployment 5_200 + child support 3_600 + alimony + # 2_400 + interest 60 + dividends 140 + cash gifts 1_200 + general + # assistance 600 + other income 300 = 13_500. + # Household 2, age 55: pension 6_000 + IRA distributions 2_400 + survivor + # benefits 1_800 + veterans' benefits 2_400 + workers' compensation 3_000 + # + disability payments 1_200 = 16_800. + period: 2026 + input: + state_code: [NC, NC] + age: [45, 55] + unemployment_compensation: [5_200, 0] + child_support_received: [3_600, 0] + alimony_income: [2_400, 0] + taxable_interest_income: [60, 0] + qualified_dividend_income: [140, 0] + financial_assistance: [1_200, 0] + general_assistance: [600, 0] + miscellaneous_income: [300, 0] + taxable_private_pension_income: [0, 6_000] + taxable_ira_distributions: [0, 2_400] + survivor_benefits: [0, 1_800] + veterans_benefits: [0, 2_400] + workers_compensation: [0, 3_000] + disability_benefits: [0, 1_200] + ssi: [0, 0] + social_security: [0, 0] + tanf: [0, 0] + childcare_expenses: [0, 0] + output: + nc_lieap_earned_income: [0, 0] + nc_lieap_gross_income_person: [13_500, 16_800] + nc_lieap_income: [13_500, 16_800] + absolute_error_margin: 0.01 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_income_limit.yaml new file mode 100644 index 00000000000..4c341da21e9 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_income_limit.yaml @@ -0,0 +1,43 @@ +# EP-300.02 and 300.09: age 60+ or qualified disability plus DAAS services. +- name: Case 1, a DAAS-disabled member or a member aged 60 gives the special income limit. + # EP-300.09 (page 10), one eligible member on the 2025 guideline of $15,650: + # 15_650 x 1.5 = 23_475 with a DAAS-disabled member or a member aged 60; + # 15_650 x 1.3 = 20_345 for a disabled member not served by DAAS. + period: 2026 + input: + state_code: [NC, NC, NC] + age: [40, 40, 60] + is_disabled: [true, true, false] + is_citizen_or_legal_immigrant: [true, true, true] + nc_lieap_household_size: [1, 1, 1] + nc_lieap_is_daas_disabled: [true, false, false] + output: + nc_lieap_income_limit: [23_475, 20_345, 23_475] + +- name: Case 2, an excluded member's DAAS disability does not give the household the special limit. + # The size is computed: person1 is a citizen; person2 holds Temporary + # Protected Status, which is not a qualified status (8 USC 1641), so one + # member is eligible (EP-300.10, page 15). + # The only DAAS-disabled member is excluded and nobody is aged 60 or over, + # so the regular 130 percent limit applies at size 1: + # 15_650 x 1.3 = 20_345 (not 15_650 x 1.5 = 23_475). + period: 2026 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + person2: + age: 45 + immigration_status: TPS + nc_lieap_is_daas_disabled: true + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NC + output: + nc_lieap_household_size: 1 + nc_lieap_income_limit: 20_345 diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap.py b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap.py new file mode 100644 index 00000000000..bb25e4367e5 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap.py @@ -0,0 +1,52 @@ +from policyengine_us.model_api import * + + +class nc_lieap(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "North Carolina LIEAP regular heating assistance" + documentation = ( + "Verified for FY2026 and FY2027. The year is the heating season ending " + "in that year, so 2026 is federal fiscal year 2026, with applications " + "from December 2025 through March 2026. Earlier years use model " + "parameter backfilling and are unverified historical estimates. The " + "manual publishes income limits for 1 to 26 eligible members and " + "payment bands for 1 to 15 members (130% limit) or 1 to 11 members " + "(150% limit); for larger households the limits and bands are " + "extrapolated with the same poverty guideline formula." + ) + defined_for = "nc_lieap_eligible" + reference = ( + # Section 300.09 (page 10), Section 300.12 A (page 18) and the Section + # 300.12 B payment charts (pages 19-20). + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=18", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nc.ncdhhs.lieap.payment + size = spm_unit("nc_lieap_household_size", period) + income = spm_unit("nc_lieap_income", period) / MONTHS_IN_YEAR + limit = spm_unit("nc_lieap_income_limit", period) / MONTHS_IN_YEAR + lower_limit = np.floor(limit * p.lower_income_share + 0.5) + amount = where( + income <= lower_limit, p.lower_income.calc(size), p.higher_income.calc(size) + ) + fuel = spm_unit("heating_type", period) + types = fuel.possible_values + solid_fuel = (fuel == types.COAL) | (fuel == types.WOOD) + # Every income-eligible coal or wood household receives the flat amount + # (EP-300 pages 10, 18 and 20). Section 300.12 A names 130% as the income + # limit for all households, so its coal/wood sentence is read as restating + # income eligibility; Section 300.09 sets that limit at 150% for the + # special population, which therefore qualifies up to its own limit. + # A subsidy can leave an account credit; no current-bill cap is imposed. + # NONE means the home has no heat. UNSPECIFIED is paid only when heat is + # included in rent: EP-300.08 item 4 makes that public housing household + # fully vulnerable, and the payment does not depend on its fuel. + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + no_schedule = (fuel == types.NONE) | ( + (fuel == types.UNSPECIFIED) & ~heat_in_rent + ) + return where(no_schedule, 0, where(solid_fuel, p.coal_wood, amount)) diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_bank_account_exclusions.py b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_bank_account_exclusions.py new file mode 100644 index 00000000000..d0afc1bb64d --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_bank_account_exclusions.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class nc_lieap_bank_account_exclusions(Variable): + value_type = float + entity = Person + definition_period = YEAR + quantity_type = STOCK + unit = USD + label = "North Carolina LIEAP excluded bank-account funds" + documentation = ( + "The portion of reported bank balances excluded under EP-300.11: " + "outstanding withdrawals and funds still in the accounts that were " + "counted as income in the LIEAP application. Report the combined " + "exclusion without double counting overlapping amounts. These offsets " + "cannot reduce cash or lump sums held outside bank accounts." + ) + reference = ( + # Section 300.11 (pages 17-18); the bank-account offsets are on page 18. + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=18", + ) diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_earned_income.py b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_earned_income.py new file mode 100644 index 00000000000..d59a205ca22 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_earned_income.py @@ -0,0 +1,49 @@ +from policyengine_us.model_api import * + + +class nc_lieap_earned_income(Variable): + value_type = float + entity = Person + definition_period = YEAR + unit = USD + label = "North Carolina LIEAP earned income per person" + defined_for = StateCode.NC + reference = ( + # Section 300.09 A (pages 10-11), Section 300.09 B.3 (pages 12-13) and + # Section 300.10 (pages 15-17). + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10", + # FNS 350.01 D, high school student earnings (page 2). + "https://policies.ncdhhs.gov/wp-content/uploads/fns-350-whose-income-is-counted.pdf#page=2", + # FNS 300.02 sources of income chart: college work study (page 5), + # rental income (pages 17-18), student earned income (page 21), wages + # and work study (page 26). + "https://policies.ncdhhs.gov/wp-content/uploads/fns-300-sources-of-income.pdf#page=5", + # FNS 315.09, college work-study (page 8). + "https://policies.ncdhhs.gov/wp-content/uploads/fns-315-special-budgeting-income.pdf#page=8", + # FY2026 state plan, item 1.9 countable income checklist (page 6). + "https://liheapch.acf.gov/docs/2026/state-plans/NC_Plan_2026.pdf#page=6", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.states.nc.ncdhhs.lieap + # Section 300.09 A takes countable income types from the FNS manual. + # FNS 350.01 D.1 excludes earned income of K-12 students aged 17 or + # younger who do not head the unit; FNS 300.02 and 315.09 exclude Title IV + # work-study pay. The state plan's section 1.9 checklist marks child + # earnings and work-study income as countable without those exceptions. + # NOTE: the federal variable does not test head-of-unit status and + # excludes all earnings of a federal work-study participant. + countable = person("snap_countable_earner", period.first_month) + # Existing net business and farm income approximates receipts less allowed + # costs. No second business-expense deduction or new input is introduced. + work = 0 + for source in p.earned_income_sources: + work = work + max_(person(source, period), 0) + # Section 300.09 B.3 includes rental income in the work deduction. The + # FNS 300.02 chart treats rent as unearned unless a member manages the + # property 20 hours or more a week, so the earner exclusions above do + # not apply to it and it is counted in full. + rental = 0 + for source in p.rental_income_sources: + rental = rental + max_(person(source, period), 0) + return work * countable + rental diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_eligible.py b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_eligible.py new file mode 100644 index 00000000000..e92ad8d6291 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_eligible.py @@ -0,0 +1,82 @@ +from policyengine_us.model_api import * + + +class nc_lieap_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "North Carolina LIEAP regular heating eligibility" + defined_for = StateCode.NC + reference = ( + # Section 300.08 (page 9), Section 300.10 A.2.b (pages 15-16) and Section + # 300.11 (pages 17-18). + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=9", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nc.ncdhhs.lieap + person = spm_unit.members + size = spm_unit("nc_lieap_household_size", period) + limit = np.floor( + spm_unit("nc_lieap_income_limit", period) / MONTHS_IN_YEAR + 0.5 + ) + income = spm_unit("nc_lieap_income", period) / MONTHS_IN_YEAR + # Non-qualified members' income must first pass an unprorated gross test + # using eligible household size (300.10 A.2.b), before the net proration. + nonqualified = spm_unit.any(~person("is_citizen_or_legal_immigrant", period)) + gross = add(spm_unit, period, ["nc_lieap_gross_income_person", "tanf"]) + gross_eligible = ~nonqualified | (gross / MONTHS_IN_YEAR <= limit) + # EP-300.08 accepts the applicant's statement of vulnerability. Public + # heat-in-rent housing requires excess heating charges paid within the + # prior 12 months at the current address, not merely a positive fuel bill. + # Section 8 remains a private arrangement. Institutional status and + # the agency's vendor/account checks are outside this calculation. + heat = spm_unit("has_heating_expense", period) + included_rent = spm_unit("heat_expense_included_in_rent", period) + public_housing = spm_unit.household("is_in_public_housing", period) + excess_paid = spm_unit("nc_lieap_has_paid_excess_heating_costs", period) + vulnerable = where(included_rent, public_housing & excess_paid, heat) + resource_eligible = True + if p.resource_test: + # The special population differs from the income limit's on purpose. + # Section 300.09 (page 10) gives the 150% income limit to households + # with a member aged 60 or over or a disabled person "receiving + # services through the Division of Aging and Adult Services", so + # nc_lieap_income_limit tests age or the DAAS input only. Section + # 300.11 (page 17) gives the higher resource limit to households + # "with member aged 60 or older or disabled", with no DAAS + # condition, so a generally disabled member also qualifies here. + # The manual does not say whether an ineligible alien can confer + # either limit. The income limit looks only at members counted in + # the eligible household size, the unit the Section 300.09 tables + # are keyed to. The resource test looks at every member because + # Section 300.11 (page 18) counts ineligible aliens' assets in the + # household's total resources. + special = spm_unit.any( + (person("age", period) >= p.elderly_age) + | person("is_usda_disabled", period) + | person("nc_lieap_is_daas_disabled", period) + ) + resource_limit = where(special, p.special_resource_limit, p.resource_limit) + # EP-300.11 deducts outstanding withdrawals and already-counted income + # only from bank accounts; those offsets cannot reduce nonbank cash. + # Include ineligible members' assets without income-style proration. + countable_banks = max_( + person("bank_account_assets", period) + - max_(person("nc_lieap_bank_account_exclusions", period), 0), + 0, + ) + nonbank = max_(person("nc_lieap_nonbank_resources", period), 0) + resource_eligible = ( + spm_unit.sum(countable_banks + nonbank) <= resource_limit + ) + # The plan marks SNAP categorical eligibility but does not define a waiver + # of the manual's financial tests. Automatic reenrollment/prior-year awards, + # tribal administration, and discretionary supplements are not modeled. + return ( + (size > 0) + & (income <= limit) + & gross_eligible + & vulnerable + & resource_eligible + ) diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_gross_income_person.py b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_gross_income_person.py new file mode 100644 index 00000000000..45b24214010 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_gross_income_person.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class nc_lieap_gross_income_person(Variable): + value_type = float + entity = Person + definition_period = YEAR + unit = USD + label = "North Carolina LIEAP gross income per person" + defined_for = StateCode.NC + reference = ( + # Section 300.09 (pages 10-13) and Section 300.10 (pages 15-17). + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.states.nc.ncdhhs.lieap + # Section 300.09 A takes the types of income to count from the FNS + # manual, not its base periods or net-income deductions. The unearned + # sources are listed for LIEAP itself, so a change to the federal SNAP + # list does not change LIEAP income. Rental income, which LIEAP counts + # as earned income, is in nc_lieap_earned_income only. + unearned = max_(add(person, period, p.unearned_income_sources), 0) + return person("nc_lieap_earned_income", period) + unearned diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_has_paid_excess_heating_costs.py b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_has_paid_excess_heating_costs.py new file mode 100644 index 00000000000..64d0550d3a5 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_has_paid_excess_heating_costs.py @@ -0,0 +1,18 @@ +from policyengine_us.model_api import * + + +class nc_lieap_has_paid_excess_heating_costs(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Paid excess heating charges for North Carolina LIEAP" + documentation = ( + "Whether the household paid excess heating-utility charges in the " + "12 months before application at its current address. This is the " + "public-housing exception when heating utilities are included in rent " + "under EP-300.08(4). An unpaid charge or a payment at a previous address " + "does not meet this condition." + ) + reference = ( + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=9" + ) diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_household_size.py b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_household_size.py new file mode 100644 index 00000000000..fb043f0619b --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_household_size.py @@ -0,0 +1,34 @@ +from policyengine_us.model_api import * + + +class nc_lieap_household_size(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "North Carolina LIEAP eligible household size" + documentation = ( + "Counts the SPM unit members who are citizens or hold a qualified " + "immigration status. SPM units approximate the residents who share " + "heat (EP-150.01). EP-175.01 requires at least one citizen or eligible " + "alien, and the EP-175.05 chart lists permanent residents, refugees, " + "asylees, parolees, conditional entrants and aliens whose deportation " + "is withheld as eligible for Energy Programs and states no waiting " + "period. EP-300.10 excludes ineligible aliens from the size and specifies " + "their income treatment. The qualified-status variable cannot resolve " + "every EP-175 document or PRUCOL category, qualified-but-ineligible " + "members, or the optional inclusion of foster children, so those " + "cases remain partial." + ) + defined_for = StateCode.NC + reference = ( + # EP-150.01, household composition (single-page document). + "https://policies.ncdhhs.gov/wp-content/uploads/eps150.pdf", + # EP-175.01 (page 1) and the EP-175.05 alien status chart (pages 4-6). + "https://policies.ncdhhs.gov/wp-content/uploads/eps175.pdf#page=4", + # Section 300.10 A (pages 15-16). + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=15", + ) + # EP-175.05 (page 4) lists these statuses as eligible with no waiting period, + # so the SNAP immigration variable, which applies SNAP's own restrictions, + # is not used here. + adds = ["is_citizen_or_legal_immigrant"] diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_income.py b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_income.py new file mode 100644 index 00000000000..55086366df2 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_income.py @@ -0,0 +1,62 @@ +from policyengine_us.model_api import * + + +class nc_lieap_income(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "North Carolina LIEAP countable household income" + defined_for = StateCode.NC + reference = ( + # Section 300.09 income and deductions (pages 10-13) and Section 300.10 + # households with an ineligible alien (pages 15-17). + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10", + ) + + def formula(spm_unit, period, parameters): + # The FY2026 state plan (item 1.8, page 5) reports gross income, while + # Section 300.09 B (pages 11-13) nets out the deductions below. The model + # follows the manual. + p = parameters(period).gov.states.nc.ncdhhs.lieap + person = spm_unit.members + included = person("is_citizen_or_legal_immigrant", period) + size = spm_unit("nc_lieap_household_size", period) + fraction = size / max_(spm_unit("spm_unit_size", period), 1) + share = where(included, 1, spm_unit.project(fraction)) + earned = person("nc_lieap_earned_income", period) * share + monthly = earned / MONTHS_IN_YEAR + table_deduction = where( + monthly > p.earned_income_deduction_threshold, + monthly * p.earned_income_deduction_rate, + p.earned_income_deduction.calc(monthly, right=True), + ) + # The lowest band's standard deduction can exceed very small monthly + # earnings. Cap the deduction at the earnings so that it cannot offset + # unearned income. + work_deduction = min_(table_deduction, monthly) * MONTHS_IN_YEAR + specified = (person("age", period) >= p.elderly_age) | person( + "is_usda_disabled", period + ) + medical = specified * included * p.medical_deduction * MONTHS_IN_YEAR + support = max_(person("child_support_expense", period), 0) * share + care = max_(person("care_expenses", period), 0) * share + person_income = person("nc_lieap_gross_income_person", period) * share + # Work First Family Assistance is unearned income in the FNS 300.02 chart + # (page 26) and is checked in the plan's item 1.9 list. + tanf = spm_unit("tanf", period) + # Childcare expenses already exclude modeled subsidies. The payer and + # additional transport costs cannot be identified, so childcare paid by an + # excluded member is not prorated. Adult care uses its reported person. + # Section 300.09 B.1.b (page 11) verifies childcare costs for each member + # with earned income, while B.1.a allows out-of-pocket childcare without + # that condition. The care deductions are not tied to earnings here. + childcare = max_(spm_unit("childcare_expenses", period), 0) + # Annual income approximates the prior application month; terminated-income + # exceptions and room/board transfers within the unit are not modeled. + return max_( + spm_unit.sum(person_income - work_deduction - medical - support - care) + + tanf + - childcare, + 0, + ) diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_income_limit.py b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_income_limit.py new file mode 100644 index 00000000000..63b81ee4b12 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_income_limit.py @@ -0,0 +1,37 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class nc_lieap_income_limit(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "North Carolina LIEAP annual income limit before monthly rounding" + defined_for = StateCode.NC + reference = ( + # Section 300.09 (page 10), its maximum countable income tables (pages + # 14-15) and the Section 300.12 B payment charts (pages 19-20). + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nc.ncdhhs.lieap + person = spm_unit.members + size = spm_unit("nc_lieap_household_size", period) + included = person("is_citizen_or_legal_immigrant", period) + special = spm_unit.any( + ( + (person("age", period) >= p.elderly_age) + | person("nc_lieap_is_daas_disabled", period) + ) + & included + ) + rate = where(special, p.special_income_limit, p.income_limit) + state_group = spm_unit.household("state_group_str", period) + amount = fpg( + max_(size, 1), state_group, period, parameters, year_lag=p.fpg_year_lag + ) + # Keep the unrounded amount: the published eligibility and payment-band + # tables each round their own monthly threshold to the nearest dollar. + return where(size > 0, amount * rate, 0) diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_is_daas_disabled.py b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_is_daas_disabled.py new file mode 100644 index 00000000000..f0906b29831 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_is_daas_disabled.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class nc_lieap_is_daas_disabled(Variable): + value_type = bool + entity = Person + definition_period = YEAR + label = "Meets North Carolina LIEAP DAAS disability criteria" + documentation = ( + "Whether the person receives Division of Aging and Adult Services " + "services and meets EP-300.02's disability-benefit definition: receiving " + "SSI, Social Security disability, or Veterans Administration disability. " + "Both service receipt and the disability-benefit condition are required. " + "This reported fact is not inferred from generic disability or Medicaid." + ) + reference = ( + # Section 300.02 A (pages 1-2) and Section 300.09 (page 10). + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=1", + ) diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_nonbank_resources.py b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_nonbank_resources.py new file mode 100644 index 00000000000..17cc677588f --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_nonbank_resources.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class nc_lieap_nonbank_resources(Variable): + value_type = float + entity = Person + definition_period = YEAR + quantity_type = STOCK + unit = USD + label = "North Carolina LIEAP cash and lump sums outside bank accounts" + documentation = ( + "Cash on hand and retained lump-sum payments at application that are " + "not included in bank_account_assets. Count each amount once: a lump " + "sum held as cash is part of this total, not an additional amount. " + "Include resources of ineligible household members." + ) + reference = ( + # Section 300.11 (pages 17-18); the counted resources are listed on + # page 18. + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=18", + )