From 6611388ab4270621873af6a5fb9f2bbd5feb59cf Mon Sep 17 00:00:00 2001 From: Ziming Date: Wed, 30 Sep 2026 15:28:48 -0400 Subject: [PATCH 1/7] Add North Carolina LIEAP regular heating assistance --- changelog.d/nc-lieap.added.md | 1 + .../ncdhhs/lieap/earned_income_deduction.yaml | 38 ++++++ .../lieap/earned_income_deduction_rate.yaml | 9 ++ .../earned_income_deduction_threshold.yaml | 9 ++ .../states/nc/ncdhhs/lieap/elderly_age.yaml | 9 ++ .../states/nc/ncdhhs/lieap/fpg_year_lag.yaml | 9 ++ .../states/nc/ncdhhs/lieap/income_limit.yaml | 9 ++ .../nc/ncdhhs/lieap/medical_deduction.yaml | 9 ++ .../nc/ncdhhs/lieap/payment/coal_wood.yaml | 9 ++ .../ncdhhs/lieap/payment/higher_income.yaml | 18 +++ .../nc/ncdhhs/lieap/payment/lower_income.yaml | 18 +++ .../lieap/payment/lower_income_share.yaml | 9 ++ .../nc/ncdhhs/lieap/resource_limit.yaml | 9 ++ .../states/nc/ncdhhs/lieap/resource_test.yaml | 10 ++ .../nc/ncdhhs/lieap/special_income_limit.yaml | 9 ++ .../ncdhhs/lieap/special_resource_limit.yaml | 9 ++ policyengine_us/programs.yaml | 9 +- .../states/nc/ncdhhs/lieap/integration.yaml | 41 ++++++ .../gov/states/nc/ncdhhs/lieap/nc_lieap.yaml | 72 +++++++++++ .../nc/ncdhhs/lieap/nc_lieap_eligible.yaml | 120 +++++++++++++++++ .../nc/ncdhhs/lieap/nc_lieap_income.yaml | 121 ++++++++++++++++++ .../gov/states/nc/ncdhhs/lieap/nc_lieap.py | 41 ++++++ .../nc/ncdhhs/lieap/nc_lieap_earned_income.py | 21 +++ .../nc/ncdhhs/lieap/nc_lieap_eligible.py | 57 +++++++++ .../lieap/nc_lieap_gross_income_person.py | 20 +++ .../ncdhhs/lieap/nc_lieap_household_size.py | 16 +++ .../states/nc/ncdhhs/lieap/nc_lieap_income.py | 50 ++++++++ .../nc/ncdhhs/lieap/nc_lieap_income_limit.py | 30 +++++ 28 files changed, 781 insertions(+), 1 deletion(-) create mode 100644 changelog.d/nc-lieap.added.md create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_deduction.yaml create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_deduction_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_deduction_threshold.yaml create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/elderly_age.yaml create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/fpg_year_lag.yaml create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/income_limit.yaml create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/medical_deduction.yaml create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/coal_wood.yaml create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/higher_income.yaml create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/lower_income.yaml create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/lower_income_share.yaml create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_limit.yaml create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_test.yaml create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/special_income_limit.yaml create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/special_resource_limit.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/integration.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_income.yaml create mode 100644 policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap.py create mode 100644 policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_earned_income.py create mode 100644 policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_eligible.py create mode 100644 policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_gross_income_person.py create mode 100644 policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_household_size.py create mode 100644 policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_income.py create mode 100644 policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_income_limit.py diff --git a/changelog.d/nc-lieap.added.md b/changelog.d/nc-lieap.added.md new file mode 100644 index 00000000000..d5e437a58d1 --- /dev/null +++ b/changelog.d/nc-lieap.added.md @@ -0,0 +1 @@ +Add partial North Carolina LIEAP regular heating assistance from FY2026 using existing inputs. 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..cafb4b31d50 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_deduction.yaml @@ -0,0 +1,38 @@ +description: North Carolina allows these monthly work-related deductions by countable earnings 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 + label: North Carolina LIEAP monthly earned income deduction + reference: + - title: North Carolina Energy Programs Manual, Section 300.09 B.3 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=12,13 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..724969374a3 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_deduction_rate.yaml @@ -0,0 +1,9 @@ +description: North Carolina deducts this share of monthly earnings above the fixed deduction table under the Low Income Energy Assistance Program. +values: + 2025-10-01: 0.2 +metadata: + unit: /1 + label: North Carolina LIEAP earned income deduction rate + reference: + - title: North Carolina Energy Programs Manual, Section 300 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=12,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..8631ab40b70 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_deduction_threshold.yaml @@ -0,0 +1,9 @@ +description: North Carolina uses a proportional deduction above this monthly earned income under the Low Income Energy Assistance Program. +values: + 2025-10-01: 400 +metadata: + unit: currency-USD + label: North Carolina LIEAP earned income deduction threshold + reference: + - title: North Carolina Energy Programs Manual, Section 300 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=12,13 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..d660a98cc83 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/elderly_age.yaml @@ -0,0 +1,9 @@ +description: North Carolina treats a person at least this age as elderly under the Low Income Energy Assistance Program. +values: + 2025-10-01: 60 +metadata: + unit: year + label: North Carolina LIEAP elderly age + reference: + - title: North Carolina Energy Programs Manual, Section 300 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=1,10,13 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..23428ca16f9 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/fpg_year_lag.yaml @@ -0,0 +1,9 @@ +description: North Carolina uses poverty guidelines from this many years before the heating season ending year under the Low Income Energy Assistance Program. +values: + 2025-10-01: 1 +metadata: + unit: year + label: North Carolina LIEAP fpg year lag + reference: + - title: North Carolina Energy Programs Manual, Section 300 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=14,15 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..5ed9d8acfac --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/income_limit.yaml @@ -0,0 +1,9 @@ +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 + label: North Carolina LIEAP income limit + reference: + - title: North Carolina Energy Programs Manual, Section 300 + 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..21cf81bab74 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/medical_deduction.yaml @@ -0,0 +1,9 @@ +description: North Carolina allows this 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 + label: North Carolina LIEAP medical deduction + reference: + - title: North Carolina Energy Programs Manual, Section 300 + 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..dffc6154088 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/coal_wood.yaml @@ -0,0 +1,9 @@ +description: North Carolina pays this fixed regular heating subsidy to households heating with coal or wood under the Low Income Energy Assistance Program. +values: + 2025-10-01: 300 +metadata: + unit: currency-USD + label: North Carolina LIEAP coal wood + reference: + - title: North Carolina Energy Programs Manual, Section 300 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=18,20 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..cf67aff8bab --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/higher_income.yaml @@ -0,0 +1,18 @@ +description: North Carolina pays this regular heating subsidy 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 + label: North Carolina LIEAP higher income heating payment + reference: + - title: North Carolina FY2026 heating benefit matrix + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NC_BenefitMatrix_Heat-Cool_2026.pdf#page=1,2,3 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..62860eba385 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/lower_income.yaml @@ -0,0 +1,18 @@ +description: North Carolina pays this regular heating subsidy 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 + label: North Carolina LIEAP lower income heating payment + reference: + - title: North Carolina FY2026 heating benefit matrix + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NC_BenefitMatrix_Heat-Cool_2026.pdf#page=1,2,3 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..97d41979543 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/payment/lower_income_share.yaml @@ -0,0 +1,9 @@ +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 + label: North Carolina LIEAP lower income share + reference: + - title: North Carolina Energy Programs Manual, Section 300 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=19,20 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..4bca421cb46 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_limit.yaml @@ -0,0 +1,9 @@ +description: North Carolina limits non-special household countable resources to this amount under the Low Income Energy Assistance Program. +values: + 2025-10-01: 3000 +metadata: + unit: currency-USD + label: North Carolina LIEAP resource limit + reference: + - title: North Carolina Energy Programs Manual, Section 300 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=17,18 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..e00810a1287 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_test.yaml @@ -0,0 +1,10 @@ +description: North Carolina applies a resource test under the Low Income Energy Assistance Program, except during the expressly waived fiscal year. +values: + 2025-07-01: false + 2026-07-01: true +metadata: + unit: bool + label: North Carolina LIEAP resource test applies + reference: + - title: North Carolina Energy Programs Manual, Section 300.11, FY2025-2026 waiver + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=17,18 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..df68d302b55 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/special_income_limit.yaml @@ -0,0 +1,9 @@ +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 + label: North Carolina LIEAP special income limit + reference: + - title: North Carolina Energy Programs Manual, Section 300 + 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..da29cd7fac0 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/special_resource_limit.yaml @@ -0,0 +1,9 @@ +description: North Carolina limits elderly or disabled household countable resources to this amount under the Low Income Energy Assistance Program. +values: + 2025-10-01: 4500 +metadata: + unit: currency-USD + label: North Carolina LIEAP special resource limit + reference: + - title: North Carolina Energy Programs Manual, Section 300 + href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=17,18 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 81c10bc47b6..b01c8fa4cfe 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, TX + coverage: OR, DC, Riverside County, MA, IL, TX, NC state_implementations: - state: OR status: in_progress @@ -823,6 +823,13 @@ programs: name: Illinois LIHEAP full_name: Illinois Low Income Home Energy Assistance Program variable: il_liheap + - 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 + notes: Regular heating from FY2026. Includes income deductions, eligible-member proration, heating payments, and the dated resource waiver. DAAS service receipt, detailed immigration/document categories, foster-child election, childcare payer/transport costs, categorical reenrollment, tribal administration, and discretionary supplements remain incomplete. No new inputs. - state: TX status: partial name: Texas CEAP 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..8a20fc50b8d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/integration.yaml @@ -0,0 +1,41 @@ +# Sources: EP-300 Sections 300.08-300.12 and the FY2026 benefit matrix. +- name: Housing vulnerability, state filtering, and uncapped payments + period: 2_026 + 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] + output: + nc_lieap: [400, 0, 400, 0, 0] +- name: All members excluded means no payment + period: 2_026 + 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 +- name: Years before encoded coverage have no formula + period: 2_022 + input: + state_code: NC + heating_type: NATURAL_GAS + gas_expense: 1_000 + output: + nc_lieap: 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..3944ede8639 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap.yaml @@ -0,0 +1,72 @@ +# Sources: EP-300 Sections 300.08-300.12 and the FY2026 benefit matrix. +- name: Regular heating payments at the published lower-band maximum + period: 2_026 + 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: Regular heating payments at the next income band + period: 2_026 + 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] +- name: Elderly heating payments at the published lower-band maximum + period: 2_026 + 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: Elderly heating payments at the next income band + period: 2_026 + 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: Coal and wood use a fixed award and retain the 130 percent ceiling + period: 2_026 + 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, 0] +- name: Known non-solid heat sources share the schedule; no heat and unknown fuel return zero + period: 2_026 + input: + state_code: [NC, NC, NC, NC, NC, NC, NC, NC] + age: [40, 40, 40, 40, 40, 40, 40, 40] + nc_lieap_eligible: [true, true, true, true, true, true, true, true] + nc_lieap_income: [0, 0, 0, 0, 0, 0, 0, 0] + heating_type: [NATURAL_GAS, FUEL_OIL, KEROSENE, PROPANE, SOLAR, OTHER, NONE, UNSPECIFIED] + output: + nc_lieap: [400, 400, 400, 400, 400, 400, 0, 0] 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..1092854a9d2 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_eligible.yaml @@ -0,0 +1,120 @@ +# Sources: EP-300 Sections 300.08-300.12 and the FY2026 benefit matrix. +- name: Published regular monthly limits, at the boundary + period: 2_026 + 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_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: Published regular monthly limits, one dollar above + period: 2_026 + 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_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: Published elderly monthly limits, at the boundary + period: 2_026 + 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_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: Published elderly monthly limits, one dollar above + period: 2_026 + 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_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: DAAS service receipt cannot be inferred from generic disability + period: 2_026 + input: + state_code: NC + age: 59 + is_disabled: true + nc_lieap_income: 22_200 + nc_lieap_gross_income_person: 0 + tanf: 0 + heating_expense: 1_000 + output: + nc_lieap_eligible: false +- name: FY2026 resource waiver applies to large bank balances + period: 2_026 + input: + state_code: NC + age: 40 + bank_account_assets: 100_000 + nc_lieap_income: 0 + nc_lieap_gross_income_person: 0 + tanf: 0 + heating_expense: 1_000 + output: + nc_lieap_eligible: true +- name: Resource waiver expires before FY2027 + period: 2_027 + 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_expense: [1_000, 1_000, 1_000, 1_000] + output: + nc_lieap_eligible: [true, false, true, false] +- name: FY2027 regular and special limits follow the October 2026 amendment + period: 2_027 + 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_expense: [1_000, 1_000, 1_000, 1_000] + output: + nc_lieap_eligible: [true, false, true, false] 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..33ea59dfd57 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_income.yaml @@ -0,0 +1,121 @@ +# Sources: EP-300 Sections 300.08-300.12 and the FY2026 benefit matrix. +- name: Work deductions use closed upper bands then twenty percent + period: 2_026 + 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: Net self-employment and rental income receive the work deduction once + period: 2_026 + input: + state_code: NC + age: 40 + self_employment_income: 6_000 + rental_income: 6_000 + ssi: 0 + social_security: 0 + tanf: 0 + childcare_expenses: 0 + output: + nc_lieap_earned_income: 12_000 + nc_lieap_gross_income_person: 12_000 + nc_lieap_income: 9_600 +- name: Elderly medical allowance, actual support, and unreimbursed care reduce income + period: 2_026 + 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: Child K12 earnings and business losses do not offset adult income + period: 2_026 + 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] + ssi: [0, 0] + social_security: [0, 0] + tanf: [0, 0] + childcare_expenses: [0, 0] + output: + nc_lieap_income: [0, 9_600] +- name: Excluded-member wages are prorated before the work deduction, within the manual gross test + period: 2_026 + input: + people: + adult: + age: 40 + employment_income: 3_600 + immigration_status: UNDOCUMENTED + ssi: 0 + social_security: 0 + member1: + age: 30 + ssi: 0 + social_security: 0 + member2: + age: 30 + ssi: 0 + social_security: 0 + spm_units: + unit: + members: [adult, member1, member2] + tanf: 0 + childcare_expenses: 0 + heating_expense: 1_000 + households: + home: + members: [adult, member1, member2] + state_code: NC + output: + nc_lieap_household_size: 2 + nc_lieap_income: 1_920 + nc_lieap_eligible: true +- name: Excluded-member wages are prorated before the work deduction, above the manual gross test + period: 2_026 + input: + people: + adult: + age: 40 + employment_income: 60_000 + immigration_status: UNDOCUMENTED + ssi: 0 + social_security: 0 + member1: + age: 30 + ssi: 0 + social_security: 0 + member2: + age: 30 + ssi: 0 + social_security: 0 + spm_units: + unit: + members: [adult, member1, member2] + tanf: 0 + childcare_expenses: 0 + heating_expense: 1_000 + households: + home: + members: [adult, member1, member2] + state_code: NC + output: + nc_lieap_household_size: 2 + nc_lieap_income: 32_000 + nc_lieap_eligible: false 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..5190565c3ef --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap.py @@ -0,0 +1,41 @@ +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" + defined_for = "nc_lieap_eligible" + reference = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=18,19,20" + + def formula_2026(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) + # Section 300.12 expressly keeps coal/wood at the regular 130% limit even + # for a special-population household. Derive that limit using the same FPG. + policy = parameters(period).gov.states.nc.ncdhhs.lieap + person = spm_unit.members + included = person("is_citizen_or_legal_immigrant", period) + elderly = spm_unit.any((person("age", period) >= policy.elderly_age) & included) + rate = where(elderly, policy.special_income_limit, policy.income_limit) + solid_limit = np.floor(limit * policy.income_limit / rate + 0.5) + solid_amount = where(income <= solid_limit, p.coal_wood, 0) + # A subsidy can leave an account credit; no current-bill cap is imposed. + # NONE/UNSPECIFIED do not establish a heating arrangement. Earlier years + # have no verified formula; this is an encoding date, not program inception. + return where( + (fuel == types.NONE) | (fuel == types.UNSPECIFIED), + 0, + where(solid_fuel, solid_amount, amount), + ) 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..a7b95a1a242 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_earned_income.py @@ -0,0 +1,21 @@ +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 = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,11,12,13,15,16,17" + + def formula_2026(person, period, parameters): + countable = person("snap_countable_earner", period.first_month) + # Existing net self-employment income approximates receipts less allowed + # costs. No second business-expense deduction or new input is introduced. + earnings = max_(person("employment_income", period), 0) + max_( + person("self_employment_income", period), 0 + ) + # Section 300.09 B.3 includes rental income in the work deduction. + return earnings * countable + max_(person("rental_income", period), 0) 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..48b9329d26d --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_eligible.py @@ -0,0 +1,57 @@ +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 = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=9,15,16,17,18" + + def formula_2026(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)) + sources = parameters(period).gov.usda.snap.income.sources.unearned_spm_unit + gross = add(spm_unit, period, ["nc_lieap_gross_income_person"]) + add( + spm_unit, period, sources + ) + gross_eligible = ~nonqualified | (gross / MONTHS_IN_YEAR <= limit) + # Positive heat costs approximate a separate bill. For public housing with + # heat in rent, a positive reported heat bill approximates an excess charge. + # Private heat-in-rent households do not qualify. Institutions, utility + # account ownership, and the exact public-housing excess history are gaps. + heat = spm_unit("heating_expense", period) > 0 + included_rent = spm_unit("heat_expense_included_in_rent", period) + public_housing = spm_unit.household("is_in_public_housing", period) + vulnerable = heat & (~included_rent | public_housing) + resource_eligible = True + if p.resource_test: + special = spm_unit.any( + (person("age", period) >= p.elderly_age) + | person("is_usda_disabled", period) + ) + resource_limit = where(special, p.special_resource_limit, p.resource_limit) + # Bank balances approximate the countable cash/checking/savings total; + # cash on hand and offsets for income already counted are not available. + resource_eligible = ( + add(spm_unit, period, ["bank_account_assets"]) <= 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..14fe2c9d16b --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_gross_income_person.py @@ -0,0 +1,20 @@ +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 = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,11,12,13,15,16,17" + + def formula_2026(person, period, parameters): + p = parameters(period).gov.usda.snap.income.sources + # Section 300.09 incorporates FNS income types, not its base periods or + # net-income deductions. Rental income is already in LIEAP earned income. + sources = [source for source in p.unearned if source != "rental_income"] + return person("nc_lieap_earned_income", period) + max_( + add(person, period, sources), 0 + ) 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..1689e5cd6e0 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_household_size.py @@ -0,0 +1,16 @@ +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" + defined_for = StateCode.NC + reference = "https://policies.ncdhhs.gov/wp-content/uploads/eps150.pdf" + + def formula_2026(spm_unit, period, parameters): + # SPM units approximate residents sharing heat (EP-150). The generic + # status input cannot resolve all EP-175 document/PRUCOL categories or + # the optional inclusion of foster children. Those cases remain partial. + return add(spm_unit, period, ["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..724158d1a64 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_income.py @@ -0,0 +1,50 @@ +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 = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,11,12,13,15,16,17" + + def formula_2026(spm_unit, period, parameters): + 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 + work_deduction = ( + where( + monthly > p.earned_income_deduction_threshold, + monthly * p.earned_income_deduction_rate, + p.earned_income_deduction.calc(monthly, right=True), + ) + * 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 + unit_sources = parameters(period).gov.usda.snap.income.sources.unearned_spm_unit + unit_income = add(spm_unit, period, unit_sources) + # 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. + 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) + + unit_income + - 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..d0a9118a36e --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_income_limit.py @@ -0,0 +1,30 @@ +from policyengine_us.model_api import * + + +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 = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,14,15,19,20" + + def formula_2026(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) + elderly = spm_unit.any((person("age", period) >= p.elderly_age) & included) + # The 150% disability pathway also requires DAAS service receipt, which + # existing inputs do not identify. Do not infer it from generic disability. + rate = where(elderly, p.special_income_limit, p.income_limit) + fpg_year = period.start.year - int(p.fpg_year_lag) + fpg = parameters(f"{fpg_year}-01-01").gov.hhs.fpg + state_group = spm_unit.household("state_group_str", period) + amount = fpg.first_person[state_group] + fpg.additional_person[ + state_group + ] * max_(size - 1, 0) + # 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) From cf003ab49bfc65897190eb29f9736c4cf5315430 Mon Sep 17 00:00:00 2001 From: Ziming Date: Wed, 30 Sep 2026 17:30:49 -0400 Subject: [PATCH 2/7] Complete North Carolina LIEAP housing resource and DAAS pathways --- changelog.d/nc-lieap.added.md | 2 +- policyengine_us/programs.yaml | 3 +- .../states/nc/ncdhhs/lieap/integration.yaml | 16 +++- .../gov/states/nc/ncdhhs/lieap/nc_lieap.yaml | 15 ++++ .../nc/ncdhhs/lieap/nc_lieap_eligible.yaml | 77 +++++++++++++++++++ .../ncdhhs/lieap/nc_lieap_income_limit.yaml | 12 +++ .../variables/gov/hhs/tax_unit_fpg.py | 11 ++- .../gov/states/nc/ncdhhs/lieap/nc_lieap.py | 20 +++-- .../lieap/nc_lieap_bank_account_exclusions.py | 20 +++++ .../nc/ncdhhs/lieap/nc_lieap_earned_income.py | 2 +- .../nc/ncdhhs/lieap/nc_lieap_eligible.py | 30 +++++--- .../lieap/nc_lieap_gross_income_person.py | 2 +- .../nc_lieap_has_paid_excess_heating_costs.py | 18 +++++ .../ncdhhs/lieap/nc_lieap_household_size.py | 13 +++- .../states/nc/ncdhhs/lieap/nc_lieap_income.py | 2 +- .../nc/ncdhhs/lieap/nc_lieap_income_limit.py | 23 +++--- .../ncdhhs/lieap/nc_lieap_is_daas_disabled.py | 18 +++++ .../lieap/nc_lieap_nonbank_resources.py | 19 +++++ 18 files changed, 263 insertions(+), 40 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_income_limit.yaml create mode 100644 policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_bank_account_exclusions.py create mode 100644 policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_has_paid_excess_heating_costs.py create mode 100644 policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_is_daas_disabled.py create mode 100644 policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_nonbank_resources.py diff --git a/changelog.d/nc-lieap.added.md b/changelog.d/nc-lieap.added.md index d5e437a58d1..0c56b9f85a3 100644 --- a/changelog.d/nc-lieap.added.md +++ b/changelog.d/nc-lieap.added.md @@ -1 +1 @@ -Add partial North Carolina LIEAP regular heating assistance from FY2026 using existing inputs. +Add partial North Carolina LIEAP regular heating assistance verified for FY2026, including reported public-housing excess payments, countable resources, and the DAAS disability pathway. diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index b01c8fa4cfe..23276eee206 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -829,7 +829,8 @@ programs: full_name: North Carolina Low Income Energy Assistance Program variable: nc_lieap parameter_prefix: gov.states.nc.ncdhhs.lieap - notes: Regular heating from FY2026. Includes income deductions, eligible-member proration, heating payments, and the dated resource waiver. DAAS service receipt, detailed immigration/document categories, foster-child election, childcare payer/transport costs, categorical reenrollment, tribal administration, and discretionary supplements remain incomplete. No new inputs. + verified_years: "2026" + notes: Regular heating verified for FY2026. Includes income deductions, eligible-member proration, heating payments, and the dated resource waiver. Reported inputs establish public-housing excess heating payments in the prior 12 months at the current address, qualifying disability with DAAS services, nonbank cash and lump sums, and excluded bank funds. The resource calculation includes ineligible members and prevents bank exclusions from reducing nonbank resources. Detailed immigration/document categories, foster-child election, childcare payer/transport costs, institutional status, vendor/account checks, categorical reenrollment, tribal administration, and discretionary supplements remain incomplete. Earlier results use parameter backfilling and are unverified historical estimates. - state: TX status: partial name: Texas CEAP 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 index 8a20fc50b8d..0a9828785fe 100644 --- 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 @@ -13,6 +13,7 @@ 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: All members excluded means no payment @@ -31,11 +32,22 @@ output: nc_lieap_household_size: 0 nc_lieap: 0 -- name: Years before encoded coverage have no formula +# Backfilling checks the model convention, not verified historical NC policy. +- name: Parameter backfill uses the 2021 guideline for a 2022 estimate period: 2_022 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: 0 + nc_lieap: 400 + nc_lieap_household_size: 1 + nc_lieap_income_limit: 16_744 + nc_lieap_eligible: true 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 index 3944ede8639..450d575d6fe 100644 --- 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 @@ -70,3 +70,18 @@ heating_type: [NATURAL_GAS, FUEL_OIL, KEROSENE, PROPANE, SOLAR, OTHER, NONE, UNSPECIFIED] output: nc_lieap: [400, 400, 400, 400, 400, 400, 0, 0] + +- name: DAAS disability uses special payment bands but retains the coal and wood ceiling + # Special lower band ends at $978/month; solid fuel still has the $1,695 + # regular income ceiling even when the household qualifies for 150% FPG. + 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, 0] 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 index 1092854a9d2..134dbc9f0f7 100644 --- 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 @@ -89,6 +89,7 @@ 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 @@ -118,3 +119,79 @@ heating_expense: [1_000, 1_000, 1_000, 1_000] output: nc_lieap_eligible: [true, false, true, false] + +- name: Public housing requires paid excess heating charges when heat is in rent + # EP-300.08: 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_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: Countable resources include nonbank cash and net bank balances + # EP-300.11: $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_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: Excluded members retain their resources in the household resource test + # EP-300.11 expressly counts ineligible aliens' resources: $2,000 + $1,001. + 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_expense: 100 + households: + household: + members: [person1, person2] + state_code: NC + output: + nc_lieap_eligible: false + +- name: DAAS-qualified disability receives the higher resource limit + 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_expense: [100, 100] + bank_account_assets: [4_500, 4_501] + output: + nc_lieap_eligible: [true, false] 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..f59f15c84f4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_income_limit.yaml @@ -0,0 +1,12 @@ +# EP-300.02 and 300.09: age 60+ or qualified disability plus DAAS services. +- name: DAAS disability qualifies for the special income limit independently of age + period: 2026 + input: + state_code: [NC, NC, NC, NC] + age: [40, 40, 60, 40] + is_disabled: [true, true, false, true] + is_citizen_or_legal_immigrant: [true, true, true, false] + nc_lieap_household_size: [1, 1, 1, 1] + nc_lieap_is_daas_disabled: [true, false, false, true] + output: + nc_lieap_income_limit: [23_475, 20_345, 23_475, 20_345] diff --git a/policyengine_us/variables/gov/hhs/tax_unit_fpg.py b/policyengine_us/variables/gov/hhs/tax_unit_fpg.py index f492918fcbb..51714218342 100644 --- a/policyengine_us/variables/gov/hhs/tax_unit_fpg.py +++ b/policyengine_us/variables/gov/hhs/tax_unit_fpg.py @@ -1,8 +1,15 @@ from policyengine_us.model_api import * +from policyengine_core.periods import instant -def fpg(unit_size, state_group, period, parameters): - p_fpg = parameters(period).gov.hhs.fpg +def fpg(unit_size, state_group, period, parameters, year_lag=0): + """Return the annual guideline for the supplied size and guideline year. + + Lag only the parameter date, preserving its month and day. Callers supply + the current household size and apply any caps, policy rates, or rounding. + """ + guideline_date = instant(period).offset(-int(year_lag), "year") + p_fpg = parameters(guideline_date).gov.hhs.fpg p1 = p_fpg.first_person[state_group] pn = p_fpg.additional_person[state_group] return p1 + pn * (unit_size - 1) 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 index 5190565c3ef..9207a12f6b8 100644 --- a/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap.py +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap.py @@ -1,4 +1,5 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg class nc_lieap(Variable): @@ -7,10 +8,14 @@ class nc_lieap(Variable): definition_period = YEAR unit = USD label = "North Carolina LIEAP regular heating assistance" + documentation = ( + "Verified for FY2026. Earlier years use model parameter backfilling " + "and are unverified historical estimates." + ) defined_for = "nc_lieap_eligible" reference = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=18,19,20" - def formula_2026(spm_unit, period, parameters): + 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 @@ -25,15 +30,14 @@ def formula_2026(spm_unit, period, parameters): # Section 300.12 expressly keeps coal/wood at the regular 130% limit even # for a special-population household. Derive that limit using the same FPG. policy = parameters(period).gov.states.nc.ncdhhs.lieap - person = spm_unit.members - included = person("is_citizen_or_legal_immigrant", period) - elderly = spm_unit.any((person("age", period) >= policy.elderly_age) & included) - rate = where(elderly, policy.special_income_limit, policy.income_limit) - solid_limit = np.floor(limit * policy.income_limit / rate + 0.5) + state_group = spm_unit.household("state_group_str", period) + guideline = fpg( + max_(size, 1), state_group, period, parameters, year_lag=policy.fpg_year_lag + ) + solid_limit = np.floor(guideline * policy.income_limit / MONTHS_IN_YEAR + 0.5) solid_amount = where(income <= solid_limit, p.coal_wood, 0) # A subsidy can leave an account credit; no current-bill cap is imposed. - # NONE/UNSPECIFIED do not establish a heating arrangement. Earlier years - # have no verified formula; this is an encoding date, not program inception. + # NONE/UNSPECIFIED do not establish a heating arrangement. return where( (fuel == types.NONE) | (fuel == types.UNSPECIFIED), 0, 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..dd99a86827c --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_bank_account_exclusions.py @@ -0,0 +1,20 @@ +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 = ( + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=17,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 index a7b95a1a242..354a4788d29 100644 --- 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 @@ -10,7 +10,7 @@ class nc_lieap_earned_income(Variable): defined_for = StateCode.NC reference = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,11,12,13,15,16,17" - def formula_2026(person, period, parameters): + def formula(person, period, parameters): countable = person("snap_countable_earner", period.first_month) # Existing net self-employment income approximates receipts less allowed # costs. No second business-expense deduction or new input is introduced. 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 index 48b9329d26d..5b2d6e00fa5 100644 --- 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 @@ -9,7 +9,7 @@ class nc_lieap_eligible(Variable): defined_for = StateCode.NC reference = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=9,15,16,17,18" - def formula_2026(spm_unit, period, parameters): + 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) @@ -25,25 +25,35 @@ def formula_2026(spm_unit, period, parameters): spm_unit, period, sources ) gross_eligible = ~nonqualified | (gross / MONTHS_IN_YEAR <= limit) - # Positive heat costs approximate a separate bill. For public housing with - # heat in rent, a positive reported heat bill approximates an excess charge. - # Private heat-in-rent households do not qualify. Institutions, utility - # account ownership, and the exact public-housing excess history are gaps. - heat = spm_unit("heating_expense", period) > 0 + # 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) - vulnerable = heat & (~included_rent | public_housing) + 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: 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) - # Bank balances approximate the countable cash/checking/savings total; - # cash on hand and offsets for income already counted are not available. + # 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 = ( - add(spm_unit, period, ["bank_account_assets"]) <= resource_limit + 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, 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 index 14fe2c9d16b..f9455f90a55 100644 --- 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 @@ -10,7 +10,7 @@ class nc_lieap_gross_income_person(Variable): defined_for = StateCode.NC reference = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,11,12,13,15,16,17" - def formula_2026(person, period, parameters): + def formula(person, period, parameters): p = parameters(period).gov.usda.snap.income.sources # Section 300.09 incorporates FNS income types, not its base periods or # net-income deductions. Rental income is already in LIEAP earned income. 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 index 1689e5cd6e0..56aadc212be 100644 --- 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 @@ -7,10 +7,17 @@ class nc_lieap_household_size(Variable): definition_period = YEAR label = "North Carolina LIEAP eligible household size" defined_for = StateCode.NC - reference = "https://policies.ncdhhs.gov/wp-content/uploads/eps150.pdf" + reference = ( + "https://policies.ncdhhs.gov/wp-content/uploads/eps150.pdf", + "https://policies.ncdhhs.gov/wp-content/uploads/eps175.pdf#page=1,4,5,6", + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=15,16", + ) - def formula_2026(spm_unit, period, parameters): + def formula(spm_unit, period, parameters): # SPM units approximate residents sharing heat (EP-150). The generic - # status input cannot resolve all EP-175 document/PRUCOL categories or + # EP-175.01 requires one citizen/eligible alien; EP-300.10 excludes + # ineligible members from size and specifies their income treatment. + # The existing qualified-status variable cannot resolve all EP-175 + # document/PRUCOL categories, qualified-but-ineligible members, or # the optional inclusion of foster children. Those cases remain partial. return add(spm_unit, period, ["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 index 724158d1a64..fc0ba313078 100644 --- 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 @@ -10,7 +10,7 @@ class nc_lieap_income(Variable): defined_for = StateCode.NC reference = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,11,12,13,15,16,17" - def formula_2026(spm_unit, period, parameters): + def formula(spm_unit, period, parameters): p = parameters(period).gov.states.nc.ncdhhs.lieap person = spm_unit.members included = person("is_citizen_or_legal_immigrant", period) 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 index d0a9118a36e..9f8b6f89836 100644 --- 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 @@ -1,4 +1,5 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg class nc_lieap_income_limit(Variable): @@ -10,21 +11,23 @@ class nc_lieap_income_limit(Variable): defined_for = StateCode.NC reference = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,14,15,19,20" - def formula_2026(spm_unit, period, parameters): + 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) - elderly = spm_unit.any((person("age", period) >= p.elderly_age) & included) - # The 150% disability pathway also requires DAAS service receipt, which - # existing inputs do not identify. Do not infer it from generic disability. - rate = where(elderly, p.special_income_limit, p.income_limit) - fpg_year = period.start.year - int(p.fpg_year_lag) - fpg = parameters(f"{fpg_year}-01-01").gov.hhs.fpg + 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.first_person[state_group] + fpg.additional_person[ - state_group - ] * max_(size - 1, 0) + 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..8f702f14618 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_is_daas_disabled.py @@ -0,0 +1,18 @@ +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 = ( + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=1,2,10" + ) 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..f8a20d58fbc --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_nonbank_resources.py @@ -0,0 +1,19 @@ +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 = ( + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=17,18" + ) From 8015df15f14720478ba04d448be878d80d573911 Mon Sep 17 00:00:00 2001 From: Ziming Date: Wed, 30 Sep 2026 23:05:52 -0400 Subject: [PATCH 3/7] Fix issues from review: North Carolina LIEAP coal/wood payment, metadata and tests - Pay the flat coal/wood amount to every income-eligible household, so the special population is paid up to its 150% limit (EP-300 300.09, 300.12) - Cite FNS manual sections 300, 315 and 350 for the student-earner and work-study exclusions and record the state plan checklist difference - Complete parameter metadata: period, subsection reference titles, labels - Add tests isolating the gross test, end-to-end households, the FY2027 resource test and coal/wood boundaries; send heating_type explicitly in every eligibility or payment case Co-Authored-By: Claude Fable 5.1 --- .../ncdhhs/lieap/earned_income_deduction.yaml | 1 + .../lieap/earned_income_deduction_rate.yaml | 3 +- .../earned_income_deduction_threshold.yaml | 3 +- .../states/nc/ncdhhs/lieap/elderly_age.yaml | 3 +- .../states/nc/ncdhhs/lieap/fpg_year_lag.yaml | 7 +- .../states/nc/ncdhhs/lieap/income_limit.yaml | 3 +- .../nc/ncdhhs/lieap/medical_deduction.yaml | 3 +- .../nc/ncdhhs/lieap/payment/coal_wood.yaml | 5 +- .../ncdhhs/lieap/payment/higher_income.yaml | 1 + .../nc/ncdhhs/lieap/payment/lower_income.yaml | 1 + .../lieap/payment/lower_income_share.yaml | 5 +- .../nc/ncdhhs/lieap/resource_limit.yaml | 5 +- .../states/nc/ncdhhs/lieap/resource_test.yaml | 1 + .../nc/ncdhhs/lieap/special_income_limit.yaml | 3 +- .../ncdhhs/lieap/special_resource_limit.yaml | 5 +- .../states/nc/ncdhhs/lieap/integration.yaml | 265 ++++++++++++++++++ .../gov/states/nc/ncdhhs/lieap/nc_lieap.yaml | 47 +++- .../nc/ncdhhs/lieap/nc_lieap_eligible.yaml | 69 +++++ .../nc/ncdhhs/lieap/nc_lieap_income.yaml | 2 + .../ncdhhs/lieap/nc_lieap_income_limit.yaml | 43 ++- .../gov/states/nc/ncdhhs/lieap/nc_lieap.py | 19 +- .../nc/ncdhhs/lieap/nc_lieap_earned_income.py | 15 +- 22 files changed, 466 insertions(+), 43 deletions(-) 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 index cafb4b31d50..293bbc336d1 100644 --- 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 @@ -32,6 +32,7 @@ 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 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 index 724969374a3..d9f4b0ad6d5 100644 --- 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 @@ -3,7 +3,8 @@ 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 + - title: North Carolina Energy Programs Manual, Section 300.09 B.3 href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=12,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 index 8631ab40b70..0e72ac410e4 100644 --- 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 @@ -3,7 +3,8 @@ 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 + - title: North Carolina Energy Programs Manual, Section 300.09 B.3 href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=12,13 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 index d660a98cc83..2249cd68ac7 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/elderly_age.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/elderly_age.yaml @@ -3,7 +3,8 @@ values: 2025-10-01: 60 metadata: unit: year + period: year label: North Carolina LIEAP elderly age reference: - - title: North Carolina Energy Programs Manual, Section 300 + - title: North Carolina Energy Programs Manual, Sections 300.02 A.1, 300.09 and 300.09 B.4.a href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=1,10,13 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 index 23428ca16f9..df31e02f139 100644 --- 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 @@ -1,9 +1,10 @@ -description: North Carolina uses poverty guidelines from this many years before the heating season ending year under the Low Income Energy Assistance Program. +description: North Carolina uses poverty guidelines from this many years before the heating season ending year, as inferred from the published income limit tables, under the Low Income Energy Assistance Program. values: 2025-10-01: 1 metadata: unit: year - label: North Carolina LIEAP fpg year lag + period: year + label: North Carolina LIEAP federal poverty guideline year lag reference: - - title: North Carolina Energy Programs Manual, Section 300 + - title: North Carolina Energy Programs Manual, Section 300.09, maximum countable income tables href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=14,15 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 index 5ed9d8acfac..57bb7aa9f61 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/income_limit.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/income_limit.yaml @@ -3,7 +3,8 @@ 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 + - title: North Carolina Energy Programs Manual, Section 300.09 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 index 21cf81bab74..3e40300f353 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/medical_deduction.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/medical_deduction.yaml @@ -3,7 +3,8 @@ 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 + - title: North Carolina Energy Programs Manual, Section 300.09 B.4.a 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 index dffc6154088..b4d126d35aa 100644 --- 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 @@ -3,7 +3,8 @@ values: 2025-10-01: 300 metadata: unit: currency-USD - label: North Carolina LIEAP coal wood + period: year + label: North Carolina LIEAP coal or wood heating payment reference: - - title: North Carolina Energy Programs Manual, Section 300 + - title: North Carolina Energy Programs Manual, Sections 300.12 A and 300.12 B href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=18,20 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 index cf67aff8bab..64891be2d2f 100644 --- 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 @@ -12,6 +12,7 @@ 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 FY2026 heating benefit matrix 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 index 62860eba385..7972819a6a2 100644 --- 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 @@ -12,6 +12,7 @@ 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 FY2026 heating benefit matrix 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 index 97d41979543..d1ea1bfa43d 100644 --- 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 @@ -3,7 +3,8 @@ values: 2025-10-01: 0.5 metadata: unit: /1 - label: North Carolina LIEAP lower income share + period: year + label: North Carolina LIEAP lower payment band share of the income limit reference: - - title: North Carolina Energy Programs Manual, Section 300 + - title: North Carolina Energy Programs Manual, Section 300.12 B href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=19,20 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 index 4bca421cb46..360bb1dc8cb 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_limit.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_limit.yaml @@ -1,9 +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: 3000 + 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 + - title: North Carolina Energy Programs Manual, Section 300.11 href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=17,18 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 index e00810a1287..f581d1f66af 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_test.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_test.yaml @@ -4,6 +4,7 @@ values: 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 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 index df68d302b55..2384a8c6172 100644 --- 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 @@ -3,7 +3,8 @@ 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 + - title: North Carolina Energy Programs Manual, Section 300.09 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 index da29cd7fac0..bd21e4f97fe 100644 --- 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 @@ -1,9 +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: 4500 + 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 + - title: North Carolina Energy Programs Manual, Section 300.11 href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=17,18 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 index 0a9828785fe..cf82f7f8624 100644 --- 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 @@ -51,3 +51,268 @@ 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 and payment bands: EP-300 pages 14, 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 is eligible but gets no 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 payment needs a known heating fuel: with + # heating_type UNSPECIFIED the household is eligible and nc_lieap is $0. + # 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. + 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: 0 + +- name: Case 5, the same public housing family with electric heat reported gets the lower-band payment. + # Same household and arithmetic as Case 4; only heating_type differs. + # 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: 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. + # The resource waiver ends June 30, 2026 (EP-300.11, page 17): $3,000, or + # $4,500 with a member aged 60 or older or disabled. FY2027 limits use the + # 2026 guideline of $15,960 (October 2026 reissue, pages 14, 19, 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 index 450d575d6fe..597bac851ce 100644 --- 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 @@ -49,7 +49,10 @@ ELECTRICITY, ELECTRICITY, ELECTRICITY] output: nc_lieap: [300, 300, 300, 400, 400, 400, 400, 400, 400, 400, 400] -- name: Coal and wood use a fixed award and retain the 130 percent ceiling +- name: Coal and wood use a fixed award for every income-eligible household + # 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: 2_026 input: state_code: [NC, NC, NC, NC] @@ -59,7 +62,7 @@ nc_lieap_income: [0, 0, 20_340, 20_352] heating_type: [COAL, WOOD, WOOD, COAL] output: - nc_lieap: [300, 300, 300, 0] + nc_lieap: [300, 300, 300, 300] - name: Known non-solid heat sources share the schedule; no heat and unknown fuel return zero period: 2_026 input: @@ -71,9 +74,11 @@ output: nc_lieap: [400, 400, 400, 400, 400, 400, 0, 0] -- name: DAAS disability uses special payment bands but retains the coal and wood ceiling - # Special lower band ends at $978/month; solid fuel still has the $1,695 - # regular income ceiling even when the household qualifies for 150% FPG. +- name: DAAS disability uses special payment bands and the fixed coal and wood award 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] @@ -84,4 +89,34 @@ 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, 0] + 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, not forced: the payment formula no longer + # tests income, so the 130 or 150 percent limit comes only from + # nc_lieap_eligible. 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] 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 index 134dbc9f0f7..f93e525d945 100644 --- 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 @@ -12,6 +12,10 @@ 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: @@ -30,6 +34,10 @@ 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: @@ -48,6 +56,10 @@ 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: @@ -66,6 +78,10 @@ 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: @@ -80,6 +96,7 @@ 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 @@ -93,6 +110,7 @@ nc_lieap_income: 0 nc_lieap_gross_income_person: 0 tanf: 0 + heating_type: ELECTRICITY heating_expense: 1_000 output: nc_lieap_eligible: true @@ -105,6 +123,7 @@ 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] @@ -116,6 +135,7 @@ 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] @@ -130,6 +150,7 @@ 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] @@ -146,6 +167,7 @@ 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] @@ -173,6 +195,7 @@ members: [person1, person2] nc_lieap_income: 0 tanf: 0 + heating_type: ELECTRICITY heating_expense: 100 households: household: @@ -191,7 +214,53 @@ 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 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 index 33ea59dfd57..9cc132d9227 100644 --- 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 @@ -78,6 +78,7 @@ members: [adult, member1, member2] tanf: 0 childcare_expenses: 0 + heating_type: ELECTRICITY heating_expense: 1_000 households: home: @@ -110,6 +111,7 @@ members: [adult, member1, member2] tanf: 0 childcare_expenses: 0 + heating_type: ELECTRICITY heating_expense: 1_000 households: home: 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 index f59f15c84f4..804fb307396 100644 --- 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 @@ -2,11 +2,40 @@ - name: DAAS disability qualifies for the special income limit independently of age period: 2026 input: - state_code: [NC, NC, NC, NC] - age: [40, 40, 60, 40] - is_disabled: [true, true, false, true] - is_citizen_or_legal_immigrant: [true, true, true, false] - nc_lieap_household_size: [1, 1, 1, 1] - nc_lieap_is_daas_disabled: [true, false, false, true] + 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, 20_345] + 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. + # Replaces the former fourth household above, which forced a size of 1 for + # a lone excluded member. Here 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 index 9207a12f6b8..16604e60225 100644 --- a/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap.py +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap.py @@ -1,5 +1,4 @@ from policyengine_us.model_api import * -from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg class nc_lieap(Variable): @@ -13,7 +12,7 @@ class nc_lieap(Variable): "and are unverified historical estimates." ) defined_for = "nc_lieap_eligible" - reference = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=18,19,20" + reference = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,18,19,20" def formula(spm_unit, period, parameters): p = parameters(period).gov.states.nc.ncdhhs.lieap.payment @@ -27,19 +26,15 @@ def formula(spm_unit, period, parameters): fuel = spm_unit("heating_type", period) types = fuel.possible_values solid_fuel = (fuel == types.COAL) | (fuel == types.WOOD) - # Section 300.12 expressly keeps coal/wood at the regular 130% limit even - # for a special-population household. Derive that limit using the same FPG. - policy = parameters(period).gov.states.nc.ncdhhs.lieap - state_group = spm_unit.household("state_group_str", period) - guideline = fpg( - max_(size, 1), state_group, period, parameters, year_lag=policy.fpg_year_lag - ) - solid_limit = np.floor(guideline * policy.income_limit / MONTHS_IN_YEAR + 0.5) - solid_amount = where(income <= solid_limit, p.coal_wood, 0) + # 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/UNSPECIFIED do not establish a heating arrangement. return where( (fuel == types.NONE) | (fuel == types.UNSPECIFIED), 0, - where(solid_fuel, solid_amount, amount), + where(solid_fuel, p.coal_wood, amount), ) 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 index 354a4788d29..7337dc59f0f 100644 --- 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 @@ -8,9 +8,22 @@ class nc_lieap_earned_income(Variable): unit = USD label = "North Carolina LIEAP earned income per person" defined_for = StateCode.NC - reference = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,11,12,13,15,16,17" + reference = ( + "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,11,12,13,15,16,17", + "https://policies.ncdhhs.gov/wp-content/uploads/fns-350-whose-income-is-counted.pdf#page=2", + "https://policies.ncdhhs.gov/wp-content/uploads/fns-300-sources-of-income.pdf#page=5,21,26", + "https://policies.ncdhhs.gov/wp-content/uploads/fns-315-special-budgeting-income.pdf#page=8", + "https://liheapch.acf.gov/docs/2026/state-plans/NC_Plan_2026.pdf#page=6", + ) def formula(person, period, parameters): + # 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 self-employment income approximates receipts less allowed # costs. No second business-expense deduction or new input is introduced. From 3ea11fa8b47499cf6e4b87494312fb8570dbb473 Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 1 Oct 2026 01:26:17 -0400 Subject: [PATCH 4/7] Count all existing business and farm income in North Carolina LIEAP --- .../nc/ncdhhs/lieap/nc_lieap_income.yaml | 34 ++++++++++++++++--- .../nc/ncdhhs/lieap/nc_lieap_earned_income.py | 9 +++-- 2 files changed, 36 insertions(+), 7 deletions(-) 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 index 9cc132d9227..8c3c1bd50e5 100644 --- 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 @@ -12,21 +12,25 @@ 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: Net self-employment and rental income receive the work deduction once +- name: Net business, farm and rental income receive the work deduction once period: 2_026 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: - nc_lieap_earned_income: 12_000 - nc_lieap_gross_income_person: 12_000 - nc_lieap_income: 9_600 + # 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: Elderly medical allowance, actual support, and unreimbursed care reduce income period: 2_026 input: @@ -49,6 +53,8 @@ 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] @@ -121,3 +127,23 @@ nc_lieap_household_size: 2 nc_lieap_income: 32_000 nc_lieap_eligible: false + +- name: Service business and farm profits can 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] 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 index 7337dc59f0f..f088fb196c4 100644 --- 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 @@ -25,10 +25,13 @@ def formula(person, period, parameters): # 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 self-employment income approximates receipts less allowed + # Existing net business and farm income approximates receipts less allowed # costs. No second business-expense deduction or new input is introduced. - earnings = max_(person("employment_income", period), 0) + max_( - person("self_employment_income", period), 0 + earnings = ( + max_(person("employment_income", period), 0) + + max_(person("self_employment_income", period), 0) + + max_(person("sstb_self_employment_income", period), 0) + + max_(person("farm_operations_income", period), 0) ) # Section 300.09 B.3 includes rental income in the work deduction. return earnings * countable + max_(person("rental_income", period), 0) From 895d89f452150a59f5ef58cb788e7df335078a34 Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 1 Oct 2026 17:43:27 -0400 Subject: [PATCH 5/7] Shorten the North Carolina LIEAP registry note Co-Authored-By: Claude Fable 5.1 --- policyengine_us/programs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 23276eee206..cf08a922912 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -830,7 +830,7 @@ programs: variable: nc_lieap parameter_prefix: gov.states.nc.ncdhhs.lieap verified_years: "2026" - notes: Regular heating verified for FY2026. Includes income deductions, eligible-member proration, heating payments, and the dated resource waiver. Reported inputs establish public-housing excess heating payments in the prior 12 months at the current address, qualifying disability with DAAS services, nonbank cash and lump sums, and excluded bank funds. The resource calculation includes ineligible members and prevents bank exclusions from reducing nonbank resources. Detailed immigration/document categories, foster-child election, childcare payer/transport costs, institutional status, vendor/account checks, categorical reenrollment, tribal administration, and discretionary supplements remain incomplete. Earlier results use parameter backfilling and are unverified historical estimates. + notes: FY2026 regular heating only, including the dated resource waiver and the FY2027 thresholds. Crisis and cooling assistance are out of scope. - state: TX status: partial name: Texas CEAP From 586fafeb251f785d012a7b285e88e8628c7284ba Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 2 Oct 2026 12:35:30 -0400 Subject: [PATCH 6/7] Address North Carolina LIEAP review Pay the size and income amount for an unspecified fuel when heat is included in rent. Move the earned income sources into a list parameter and stop filtering rental income out of the SNAP list by name. Cap the work deduction at earnings. Use single-page references with sections and pages in the titles, cite the current manual edition for FY2027, fix test periods and names, make household size a plain sum, rewrite parameter descriptions, and add rounding-edge, resource, extrapolation and household cases. Record verified years 2026-2027 and place North Carolina alphabetically in the registry. Co-Authored-By: Claude Fable 5.1 --- changelog.d/nc-lieap.added.md | 2 +- .../ncdhhs/lieap/earned_income_deduction.yaml | 6 +- .../lieap/earned_income_deduction_rate.yaml | 6 +- .../earned_income_deduction_threshold.yaml | 6 +- .../ncdhhs/lieap/earned_income_sources.yaml | 18 +++ .../states/nc/ncdhhs/lieap/elderly_age.yaml | 6 +- .../states/nc/ncdhhs/lieap/fpg_year_lag.yaml | 14 +- .../states/nc/ncdhhs/lieap/income_limit.yaml | 2 +- .../nc/ncdhhs/lieap/medical_deduction.yaml | 4 +- .../nc/ncdhhs/lieap/payment/coal_wood.yaml | 6 +- .../ncdhhs/lieap/payment/higher_income.yaml | 8 +- .../nc/ncdhhs/lieap/payment/lower_income.yaml | 8 +- .../lieap/payment/lower_income_share.yaml | 4 +- .../nc/ncdhhs/lieap/resource_limit.yaml | 4 +- .../states/nc/ncdhhs/lieap/resource_test.yaml | 16 +- .../nc/ncdhhs/lieap/special_income_limit.yaml | 2 +- .../ncdhhs/lieap/special_resource_limit.yaml | 4 +- policyengine_us/programs.yaml | 6 +- .../states/nc/ncdhhs/lieap/integration.yaml | 51 ++++-- .../gov/states/nc/ncdhhs/lieap/nc_lieap.yaml | 141 +++++++++++++--- .../nc/ncdhhs/lieap/nc_lieap_eligible.yaml | 132 ++++++++++++--- .../ncdhhs/lieap/nc_lieap_household_size.yaml | 27 ++++ .../nc/ncdhhs/lieap/nc_lieap_income.yaml | 153 ++++++++++++++---- .../ncdhhs/lieap/nc_lieap_income_limit.yaml | 12 +- .../gov/states/nc/ncdhhs/lieap/nc_lieap.py | 28 +++- .../lieap/nc_lieap_bank_account_exclusions.py | 3 +- .../nc/ncdhhs/lieap/nc_lieap_earned_income.py | 32 ++-- .../nc/ncdhhs/lieap/nc_lieap_eligible.py | 24 ++- .../lieap/nc_lieap_gross_income_person.py | 13 +- .../ncdhhs/lieap/nc_lieap_household_size.py | 33 ++-- .../states/nc/ncdhhs/lieap/nc_lieap_income.py | 27 +++- .../nc/ncdhhs/lieap/nc_lieap_income_limit.py | 6 +- .../ncdhhs/lieap/nc_lieap_is_daas_disabled.py | 3 +- .../lieap/nc_lieap_nonbank_resources.py | 4 +- 34 files changed, 628 insertions(+), 183 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_sources.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/nc_lieap_household_size.yaml diff --git a/changelog.d/nc-lieap.added.md b/changelog.d/nc-lieap.added.md index 0c56b9f85a3..3ce01f7ddc4 100644 --- a/changelog.d/nc-lieap.added.md +++ b/changelog.d/nc-lieap.added.md @@ -1 +1 @@ -Add partial North Carolina LIEAP regular heating assistance verified for FY2026, including reported public-housing excess payments, countable resources, and the DAAS disability pathway. +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 index 293bbc336d1..f1424527b8b 100644 --- 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 @@ -1,4 +1,4 @@ -description: North Carolina allows these monthly work-related deductions by countable earnings under the Low Income Energy Assistance Program. +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 @@ -35,5 +35,5 @@ metadata: period: month label: North Carolina LIEAP monthly earned income deduction reference: - - title: North Carolina Energy Programs Manual, Section 300.09 B.3 - href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=12,13 + - 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 index d9f4b0ad6d5..7f27f1405db 100644 --- 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 @@ -1,4 +1,4 @@ -description: North Carolina deducts this share of monthly earnings above the fixed deduction table under the Low Income Energy Assistance Program. +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: @@ -6,5 +6,5 @@ metadata: period: year label: North Carolina LIEAP earned income deduction rate reference: - - title: North Carolina Energy Programs Manual, Section 300.09 B.3 - href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=12,13 + - 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 index 0e72ac410e4..e5ca44f42c1 100644 --- 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 @@ -1,4 +1,4 @@ -description: North Carolina uses a proportional deduction above this monthly earned income under the Low Income Energy Assistance Program. +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: @@ -6,5 +6,5 @@ metadata: period: month label: North Carolina LIEAP earned income deduction threshold reference: - - title: North Carolina Energy Programs Manual, Section 300.09 B.3 - href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=12,13 + - 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..a7250174311 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/earned_income_sources.yaml @@ -0,0 +1,18 @@ +description: North Carolina counts these earned income sources under the Low Income Energy Assistance Program. +values: + 2025-10-01: + - employment_income + - self_employment_income + - sstb_self_employment_income + - farm_operations_income + # Section 300.09 B.3 gives rental income the earned income deduction. + - rental_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 index 2249cd68ac7..63f32e7657b 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/elderly_age.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/elderly_age.yaml @@ -1,4 +1,4 @@ -description: North Carolina treats a person at least this age as elderly under the Low Income Energy Assistance Program. +description: North Carolina sets this age as the elderly threshold under the Low Income Energy Assistance Program. values: 2025-10-01: 60 metadata: @@ -6,5 +6,5 @@ metadata: period: year label: North Carolina LIEAP elderly age reference: - - title: North Carolina Energy Programs Manual, Sections 300.02 A.1, 300.09 and 300.09 B.4.a - href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=1,10,13 + - 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 index df31e02f139..23798e7d1bf 100644 --- 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 @@ -1,4 +1,8 @@ -description: North Carolina uses poverty guidelines from this many years before the heating season ending year, as inferred from the published income limit tables, under the Low Income Energy Assistance Program. +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: @@ -6,5 +10,9 @@ metadata: period: year label: North Carolina LIEAP federal poverty guideline year lag reference: - - title: North Carolina Energy Programs Manual, Section 300.09, maximum countable income tables - href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=14,15 + - 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 index 57bb7aa9f61..6e6f96ab7e8 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/income_limit.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/income_limit.yaml @@ -6,5 +6,5 @@ metadata: period: year label: North Carolina LIEAP income limit reference: - - title: North Carolina Energy Programs Manual, Section 300.09 + - 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 index 3e40300f353..cd3262ac461 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/medical_deduction.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/medical_deduction.yaml @@ -1,4 +1,4 @@ -description: North Carolina allows this monthly standard medical deduction for each eligible specified person under the Low Income Energy Assistance Program. +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: @@ -6,5 +6,5 @@ metadata: period: month label: North Carolina LIEAP medical deduction reference: - - title: North Carolina Energy Programs Manual, Section 300.09 B.4.a + - 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 index b4d126d35aa..c0c7c598054 100644 --- 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 @@ -1,4 +1,4 @@ -description: North Carolina pays this fixed regular heating subsidy to households heating with coal or wood under the Low Income Energy Assistance Program. +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: @@ -6,5 +6,5 @@ metadata: period: year label: North Carolina LIEAP coal or wood heating payment reference: - - title: North Carolina Energy Programs Manual, Sections 300.12 A and 300.12 B - href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=18,20 + - 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 index 64891be2d2f..8ec5443af89 100644 --- 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 @@ -1,4 +1,4 @@ -description: North Carolina pays this regular heating subsidy to higher income households by eligible household size under the Low Income Energy Assistance Program. +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 @@ -15,5 +15,7 @@ metadata: period: year label: North Carolina LIEAP higher income heating payment reference: - - title: North Carolina FY2026 heating benefit matrix - href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NC_BenefitMatrix_Heat-Cool_2026.pdf#page=1,2,3 + - 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 index 7972819a6a2..d6361502580 100644 --- 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 @@ -1,4 +1,4 @@ -description: North Carolina pays this regular heating subsidy to lower income households by eligible household size under the Low Income Energy Assistance Program. +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 @@ -15,5 +15,7 @@ metadata: period: year label: North Carolina LIEAP lower income heating payment reference: - - title: North Carolina FY2026 heating benefit matrix - href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NC_BenefitMatrix_Heat-Cool_2026.pdf#page=1,2,3 + - 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 index d1ea1bfa43d..7af7cfaf478 100644 --- 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 @@ -6,5 +6,5 @@ metadata: 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 - href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=19,20 + - 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/resource_limit.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_limit.yaml index 360bb1dc8cb..77b037e80cc 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_limit.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_limit.yaml @@ -6,5 +6,5 @@ metadata: period: year label: North Carolina LIEAP resource limit reference: - - title: North Carolina Energy Programs Manual, Section 300.11 - href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=17,18 + - 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 index f581d1f66af..ca9df12cf7a 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_test.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/resource_test.yaml @@ -1,4 +1,14 @@ -description: North Carolina applies a resource test under the Low Income Energy Assistance Program, except during the expressly waived fiscal year. +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 @@ -7,5 +17,5 @@ metadata: period: year label: North Carolina LIEAP resource test applies reference: - - title: North Carolina Energy Programs Manual, Section 300.11, FY2025-2026 waiver - href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=17,18 + - 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 index 2384a8c6172..65babe7945c 100644 --- 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 @@ -6,5 +6,5 @@ metadata: period: year label: North Carolina LIEAP special income limit reference: - - title: North Carolina Energy Programs Manual, Section 300.09 + - 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 index bd21e4f97fe..d7768c87bb5 100644 --- 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 @@ -6,5 +6,5 @@ metadata: period: year label: North Carolina LIEAP special resource limit reference: - - title: North Carolina Energy Programs Manual, Section 300.11 - href: https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=17,18 + - 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/programs.yaml b/policyengine_us/programs.yaml index cf08a922912..5a790da38d2 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, TX, NC + coverage: OR, DC, Riverside County, MA, IL, NC, TX state_implementations: - state: OR status: in_progress @@ -829,8 +829,8 @@ programs: full_name: North Carolina Low Income Energy Assistance Program variable: nc_lieap parameter_prefix: gov.states.nc.ncdhhs.lieap - verified_years: "2026" - notes: FY2026 regular heating only, including the dated resource waiver and the FY2027 thresholds. Crisis and cooling assistance are out of scope. + 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: TX status: partial name: Texas CEAP 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 index cf82f7f8624..e1995f37483 100644 --- 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 @@ -1,6 +1,16 @@ # Sources: EP-300 Sections 300.08-300.12 and the FY2026 benefit matrix. -- name: Housing vulnerability, state filtering, and uncapped payments - period: 2_026 +# 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] @@ -16,8 +26,10 @@ nc_lieap_has_paid_excess_heating_costs: [false, false, true, false, false] output: nc_lieap: [400, 0, 400, 0, 0] -- name: All members excluded means no payment - period: 2_026 +- 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 @@ -33,8 +45,10 @@ nc_lieap_household_size: 0 nc_lieap: 0 # Backfilling checks the model convention, not verified historical NC policy. -- name: Parameter backfill uses the 2021 guideline for a 2022 estimate - period: 2_022 +- 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 @@ -55,19 +69,22 @@ # 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 and payment bands: EP-300 pages 14, 19, 20. +# 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 is eligible but gets no payment. +- 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 payment needs a known heating fuel: with - # heating_type UNSPECIFIED the household is eligible and nc_lieap is $0. - # Case 5 is the same household with the fuel reported. + # 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: @@ -102,12 +119,10 @@ nc_lieap_income_limit: 34_645 has_heating_expense: false nc_lieap_eligible: true - nc_lieap: 0 + nc_lieap: 400 -- name: Case 5, the same public housing family with electric heat reported gets the lower-band payment. +- 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. - # 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: @@ -280,9 +295,11 @@ nc_lieap: 400 - name: Case 9, FY2027 one-person households under and over the resource limits. - # The resource waiver ends June 30, 2026 (EP-300.11, page 17): $3,000, or + # 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 (October 2026 reissue, pages 14, 19, 20). + # 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 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 index 597bac851ce..a197ab4f764 100644 --- 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 @@ -1,6 +1,10 @@ # Sources: EP-300 Sections 300.08-300.12 and the FY2026 benefit matrix. -- name: Regular heating payments at the published lower-band maximum - period: 2_026 +- 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] @@ -12,8 +16,11 @@ 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: Regular heating payments at the next income band - period: 2_026 +- 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] @@ -25,8 +32,21 @@ 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] -- name: Elderly heating payments at the published lower-band maximum - period: 2_026 +# 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] @@ -37,8 +57,11 @@ ELECTRICITY, ELECTRICITY, ELECTRICITY] output: nc_lieap: [400, 400, 400, 500, 500, 500, 500, 500, 500, 500, 500] -- name: Elderly heating payments at the next income band - period: 2_026 +- 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] @@ -49,11 +72,11 @@ ELECTRICITY, ELECTRICITY, ELECTRICITY] output: nc_lieap: [300, 300, 300, 400, 400, 400, 400, 400, 400, 400, 400] -- name: Coal and wood use a fixed award for every income-eligible household +- 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: 2_026 + period: 2026 input: state_code: [NC, NC, NC, NC] age: [40, 60, 60, 60] @@ -63,18 +86,24 @@ heating_type: [COAL, WOOD, WOOD, COAL] output: nc_lieap: [300, 300, 300, 300] -- name: Known non-solid heat sources share the schedule; no heat and unknown fuel return zero - period: 2_026 +- 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] - age: [40, 40, 40, 40, 40, 40, 40, 40] - nc_lieap_eligible: [true, true, true, true, true, true, true, true] - nc_lieap_income: [0, 0, 0, 0, 0, 0, 0, 0] - heating_type: [NATURAL_GAS, FUEL_OIL, KEROSENE, PROPANE, SOLAR, OTHER, NONE, UNSPECIFIED] + 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] + nc_lieap: [400, 400, 400, 400, 400, 400, 0, 0, 400] -- name: DAAS disability uses special payment bands and the fixed coal and wood award up to 150 percent +- 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 @@ -92,9 +121,9 @@ 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, not forced: the payment formula no longer - # tests income, so the 130 or 150 percent limit comes only from - # nc_lieap_eligible. One-person households on the 2025 guideline of $15,650. + # 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). @@ -120,3 +149,71 @@ 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 index f93e525d945..2c83a98fd80 100644 --- 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 @@ -1,6 +1,11 @@ # Sources: EP-300 Sections 300.08-300.12 and the FY2026 benefit matrix. -- name: Published regular monthly limits, at the boundary - period: 2_026 +# 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] @@ -21,8 +26,10 @@ 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: Published regular monthly limits, one dollar above - period: 2_026 +- 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] @@ -43,8 +50,11 @@ 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: Published elderly monthly limits, at the boundary - period: 2_026 +- 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] @@ -65,8 +75,10 @@ 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: Published elderly monthly limits, one dollar above - period: 2_026 +- 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] @@ -87,8 +99,11 @@ 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: DAAS service receipt cannot be inferred from generic disability - period: 2_026 +- 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 @@ -100,8 +115,10 @@ heating_expense: 1_000 output: nc_lieap_eligible: false -- name: FY2026 resource waiver applies to large bank balances - period: 2_026 +- 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 @@ -114,8 +131,10 @@ heating_expense: 1_000 output: nc_lieap_eligible: true -- name: Resource waiver expires before FY2027 - period: 2_027 +- 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] @@ -127,8 +146,12 @@ heating_expense: [1_000, 1_000, 1_000, 1_000] output: nc_lieap_eligible: [true, false, true, false] -- name: FY2027 regular and special limits follow the October 2026 amendment - period: 2_027 +- 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] @@ -140,8 +163,8 @@ output: nc_lieap_eligible: [true, false, true, false] -- name: Public housing requires paid excess heating charges when heat is in rent - # EP-300.08: a bill alone is insufficient; a qualifying past payment suffices +- 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: @@ -158,8 +181,9 @@ output: nc_lieap_eligible: [false, true, true, false, true, true] -- name: Countable resources include nonbank cash and net bank balances - # EP-300.11: $3,000 ordinary limit; bank exclusions do not offset cash. +- 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] @@ -175,8 +199,9 @@ output: nc_lieap_eligible: [true, false, true, false, false, true] -- name: Excluded members retain their resources in the household resource test - # EP-300.11 expressly counts ineligible aliens' resources: $2,000 + $1,001. +- 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: @@ -204,7 +229,8 @@ output: nc_lieap_eligible: false -- name: DAAS-qualified disability receives the higher resource limit +- 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] @@ -264,3 +290,63 @@ 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 index 8c3c1bd50e5..3595e8fca75 100644 --- 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 @@ -1,6 +1,13 @@ # Sources: EP-300 Sections 300.08-300.12 and the FY2026 benefit matrix. -- name: Work deductions use closed upper bands then twenty percent - period: 2_026 +- 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] @@ -12,8 +19,8 @@ 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: Net business, farm and rental income receive the work deduction once - period: 2_026 +- name: Case 2, business, farm and rental income get the work deduction once. + period: 2026 input: state_code: NC age: 40 @@ -31,8 +38,13 @@ nc_lieap_earned_income: 18_000 nc_lieap_gross_income_person: 18_000 nc_lieap_income: 14_400 -- name: Elderly medical allowance, actual support, and unreimbursed care reduce income - period: 2_026 +- 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 @@ -45,8 +57,12 @@ care_expenses: 600 output: nc_lieap_income: 5_580 -- name: Child K12 earnings and business losses do not offset adult income - period: 2_026 +- 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] @@ -61,74 +77,81 @@ childcare_expenses: [0, 0] output: nc_lieap_income: [0, 9_600] -- name: Excluded-member wages are prorated before the work deduction, within the manual gross test - period: 2_026 +- 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: - adult: + person1: age: 40 employment_income: 3_600 immigration_status: UNDOCUMENTED ssi: 0 social_security: 0 - member1: + person2: age: 30 ssi: 0 social_security: 0 - member2: + person3: age: 30 ssi: 0 social_security: 0 spm_units: - unit: - members: [adult, member1, member2] + spm_unit: + members: [person1, person2, person3] tanf: 0 childcare_expenses: 0 heating_type: ELECTRICITY heating_expense: 1_000 households: - home: - members: [adult, member1, member2] + household: + members: [person1, person2, person3] state_code: NC output: nc_lieap_household_size: 2 nc_lieap_income: 1_920 nc_lieap_eligible: true -- name: Excluded-member wages are prorated before the work deduction, above the manual gross test - period: 2_026 +- 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: - adult: + person1: age: 40 employment_income: 60_000 immigration_status: UNDOCUMENTED ssi: 0 social_security: 0 - member1: + person2: age: 30 ssi: 0 social_security: 0 - member2: + person3: age: 30 ssi: 0 social_security: 0 spm_units: - unit: - members: [adult, member1, member2] + spm_unit: + members: [person1, person2, person3] tanf: 0 childcare_expenses: 0 heating_type: ELECTRICITY heating_expense: 1_000 households: - home: - members: [adult, member1, member2] + household: + members: [person1, person2, person3] state_code: NC output: nc_lieap_household_size: 2 nc_lieap_income: 32_000 nc_lieap_eligible: false -- name: Service business and farm profits can exceed the income limit after the work deduction +- name: Case 7, service business and farm profits exceed the income limit after the work deduction. period: 2026 input: state_code: [NC, NC] @@ -147,3 +170,79 @@ 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] 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 index 804fb307396..4c341da21e9 100644 --- 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 @@ -1,5 +1,8 @@ # EP-300.02 and 300.09: age 60+ or qualified disability plus DAAS services. -- name: DAAS disability qualifies for the special income limit independently of age +- 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] @@ -12,10 +15,9 @@ 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. - # Replaces the former fourth household above, which forced a size of 1 for - # a lone excluded member. Here 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 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). 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 index 16604e60225..bb25e4367e5 100644 --- a/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap.py +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap.py @@ -8,11 +8,21 @@ class nc_lieap(Variable): unit = USD label = "North Carolina LIEAP regular heating assistance" documentation = ( - "Verified for FY2026. Earlier years use model parameter backfilling " - "and are unverified historical estimates." + "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 = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,18,19,20" + 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 @@ -32,9 +42,11 @@ def formula(spm_unit, period, parameters): # 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/UNSPECIFIED do not establish a heating arrangement. - return where( - (fuel == types.NONE) | (fuel == types.UNSPECIFIED), - 0, - where(solid_fuel, p.coal_wood, amount), + # 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 index dd99a86827c..d0afc1bb64d 100644 --- 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 @@ -16,5 +16,6 @@ class nc_lieap_bank_account_exclusions(Variable): "cannot reduce cash or lump sums held outside bank accounts." ) reference = ( - "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=17,18" + # 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 index f088fb196c4..02e1973524d 100644 --- 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 @@ -9,14 +9,23 @@ class nc_lieap_earned_income(Variable): label = "North Carolina LIEAP earned income per person" defined_for = StateCode.NC reference = ( - "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,11,12,13,15,16,17", + # 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", - "https://policies.ncdhhs.gov/wp-content/uploads/fns-300-sources-of-income.pdf#page=5,21,26", + # FNS 300.02 sources of income chart: college work study (page 5), + # 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 + snap_unearned = parameters(period).gov.usda.snap.income.sources.unearned # 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 @@ -27,11 +36,14 @@ def formula(person, period, parameters): 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. - earnings = ( - max_(person("employment_income", period), 0) - + max_(person("self_employment_income", period), 0) - + max_(person("sstb_self_employment_income", period), 0) - + max_(person("farm_operations_income", period), 0) - ) - # Section 300.09 B.3 includes rental income in the work deduction. - return earnings * countable + max_(person("rental_income", period), 0) + total = 0 + for source in p.earned_income_sources: + amount = max_(person(source, period), 0) + # Section 300.09 B.3 includes rental income in the work deduction. + # It is on the SNAP unearned list, so the earner exclusions above + # do not apply to it and it is counted in full. + if source in snap_unearned: + total = total + amount + else: + total = total + amount * countable + return total 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 index 5b2d6e00fa5..d71dee61b8b 100644 --- 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 @@ -7,7 +7,11 @@ class nc_lieap_eligible(Variable): definition_period = YEAR label = "North Carolina LIEAP regular heating eligibility" defined_for = StateCode.NC - reference = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=9,15,16,17,18" + 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 @@ -21,9 +25,7 @@ def formula(spm_unit, period, parameters): # using eligible household size (300.10 A.2.b), before the net proration. nonqualified = spm_unit.any(~person("is_citizen_or_legal_immigrant", period)) sources = parameters(period).gov.usda.snap.income.sources.unearned_spm_unit - gross = add(spm_unit, period, ["nc_lieap_gross_income_person"]) + add( - spm_unit, period, sources - ) + gross = add(spm_unit, period, ["nc_lieap_gross_income_person", *sources]) 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 @@ -37,6 +39,20 @@ def formula(spm_unit, period, parameters): 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) 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 index f9455f90a55..fbdb8227e87 100644 --- 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 @@ -8,13 +8,20 @@ class nc_lieap_gross_income_person(Variable): unit = USD label = "North Carolina LIEAP gross income per person" defined_for = StateCode.NC - reference = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,11,12,13,15,16,17" + 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.usda.snap.income.sources + earned_sources = parameters( + period + ).gov.states.nc.ncdhhs.lieap.earned_income_sources # Section 300.09 incorporates FNS income types, not its base periods or - # net-income deductions. Rental income is already in LIEAP earned income. - sources = [source for source in p.unearned if source != "rental_income"] + # net-income deductions. A SNAP unearned source that LIEAP counts as + # earned income (rental income) is already in nc_lieap_earned_income. + sources = [source for source in p.unearned if source not in earned_sources] return person("nc_lieap_earned_income", period) + max_( add(person, period, sources), 0 ) 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 index 56aadc212be..fb043f0619b 100644 --- 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 @@ -6,18 +6,29 @@ class nc_lieap_household_size(Variable): 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", - "https://policies.ncdhhs.gov/wp-content/uploads/eps175.pdf#page=1,4,5,6", - "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=15,16", + # 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", ) - - def formula(spm_unit, period, parameters): - # SPM units approximate residents sharing heat (EP-150). The generic - # EP-175.01 requires one citizen/eligible alien; EP-300.10 excludes - # ineligible members from size and specifies their income treatment. - # The existing qualified-status variable cannot resolve all EP-175 - # document/PRUCOL categories, qualified-but-ineligible members, or - # the optional inclusion of foster children. Those cases remain partial. - return add(spm_unit, period, ["is_citizen_or_legal_immigrant"]) + # 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 index fc0ba313078..41b23f4d39b 100644 --- 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 @@ -8,9 +8,16 @@ class nc_lieap_income(Variable): unit = USD label = "North Carolina LIEAP countable household income" defined_for = StateCode.NC - reference = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,11,12,13,15,16,17" + 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) @@ -19,14 +26,15 @@ def formula(spm_unit, period, parameters): share = where(included, 1, spm_unit.project(fraction)) earned = person("nc_lieap_earned_income", period) * share monthly = earned / MONTHS_IN_YEAR - work_deduction = ( - where( - monthly > p.earned_income_deduction_threshold, - monthly * p.earned_income_deduction_rate, - p.earned_income_deduction.calc(monthly, right=True), - ) - * 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 ) @@ -39,6 +47,9 @@ def formula(spm_unit, period, parameters): # 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. 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 index 9f8b6f89836..63b81ee4b12 100644 --- 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 @@ -9,7 +9,11 @@ class nc_lieap_income_limit(Variable): unit = USD label = "North Carolina LIEAP annual income limit before monthly rounding" defined_for = StateCode.NC - reference = "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=10,14,15,19,20" + 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 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 index 8f702f14618..f0906b29831 100644 --- 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 @@ -14,5 +14,6 @@ class nc_lieap_is_daas_disabled(Variable): "This reported fact is not inferred from generic disability or Medicaid." ) reference = ( - "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=1,2,10" + # 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 index f8a20d58fbc..17cc677588f 100644 --- 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 @@ -15,5 +15,7 @@ class nc_lieap_nonbank_resources(Variable): "Include resources of ineligible household members." ) reference = ( - "https://policies.ncdhhs.gov/wp-content/uploads/EP-300-5.1.2026.pdf#page=17,18" + # 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", ) From d76e933755ba24239fa5ce03aaa51151de6f8bc9 Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 5 Oct 2026 15:29:06 -0400 Subject: [PATCH 7/7] List North Carolina LIEAP income sources in its own parameters North Carolina LIEAP read the federal SNAP unearned list and removed the sources it counts as earned by matching names. PR 9672 renamed the SNAP rental entry, so merging main would have counted rental income twice. - Add unearned_income_sources and rental_income_sources lists and stop reading gov.usda.snap.income.sources; each unearned source cites its row in the FNS 300.02 chart and its box in the state plan's item 1.9 list - Keep rental income as earned income (EP-300.09 B.3) and count Form 4835 farm rent with it; the student and work-study exclusions apply to wage, business and farm earnings only - Read tanf directly in the income and gross tests - Add cases for farm rent, a K-12 student's rental income and the unearned sources Co-Authored-By: Claude Fable 5.1 --- .../ncdhhs/lieap/earned_income_sources.yaml | 4 +- .../ncdhhs/lieap/rental_income_sources.yaml | 19 +++++ .../ncdhhs/lieap/unearned_income_sources.yaml | 55 +++++++++++++ .../nc/ncdhhs/lieap/nc_lieap_income.yaml | 77 +++++++++++++++++++ .../nc/ncdhhs/lieap/nc_lieap_earned_income.py | 24 +++--- .../nc/ncdhhs/lieap/nc_lieap_eligible.py | 3 +- .../lieap/nc_lieap_gross_income_person.py | 19 ++--- .../states/nc/ncdhhs/lieap/nc_lieap_income.py | 7 +- 8 files changed, 177 insertions(+), 31 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/rental_income_sources.yaml create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/unearned_income_sources.yaml 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 index a7250174311..f14deea0503 100644 --- 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 @@ -1,12 +1,10 @@ -description: North Carolina counts these earned income sources under the Low Income Energy Assistance Program. +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 - # Section 300.09 B.3 gives rental income the earned income deduction. - - rental_income metadata: unit: list period: year 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/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/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 index 3595e8fca75..e6a07b8b9a1 100644 --- 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 @@ -246,3 +246,80 @@ 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/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 index 02e1973524d..d59a205ca22 100644 --- 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 @@ -15,7 +15,8 @@ class nc_lieap_earned_income(Variable): # 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), - # student earned income (page 21), wages and work study (page 26). + # 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", @@ -25,7 +26,6 @@ class nc_lieap_earned_income(Variable): def formula(person, period, parameters): p = parameters(period).gov.states.nc.ncdhhs.lieap - snap_unearned = parameters(period).gov.usda.snap.income.sources.unearned # 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 @@ -36,14 +36,14 @@ def formula(person, period, parameters): 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. - total = 0 + work = 0 for source in p.earned_income_sources: - amount = max_(person(source, period), 0) - # Section 300.09 B.3 includes rental income in the work deduction. - # It is on the SNAP unearned list, so the earner exclusions above - # do not apply to it and it is counted in full. - if source in snap_unearned: - total = total + amount - else: - total = total + amount * countable - return total + 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 index d71dee61b8b..e92ad8d6291 100644 --- 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 @@ -24,8 +24,7 @@ def formula(spm_unit, period, parameters): # 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)) - sources = parameters(period).gov.usda.snap.income.sources.unearned_spm_unit - gross = add(spm_unit, period, ["nc_lieap_gross_income_person", *sources]) + 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 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 index fbdb8227e87..45b24214010 100644 --- 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 @@ -14,14 +14,11 @@ class nc_lieap_gross_income_person(Variable): ) def formula(person, period, parameters): - p = parameters(period).gov.usda.snap.income.sources - earned_sources = parameters( - period - ).gov.states.nc.ncdhhs.lieap.earned_income_sources - # Section 300.09 incorporates FNS income types, not its base periods or - # net-income deductions. A SNAP unearned source that LIEAP counts as - # earned income (rental income) is already in nc_lieap_earned_income. - sources = [source for source in p.unearned if source not in earned_sources] - return person("nc_lieap_earned_income", period) + max_( - add(person, period, sources), 0 - ) + 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_income.py b/policyengine_us/variables/gov/states/nc/ncdhhs/lieap/nc_lieap_income.py index 41b23f4d39b..55086366df2 100644 --- 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 @@ -42,8 +42,9 @@ def formula(spm_unit, period, parameters): 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 - unit_sources = parameters(period).gov.usda.snap.income.sources.unearned_spm_unit - unit_income = add(spm_unit, period, unit_sources) + # 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. @@ -55,7 +56,7 @@ def formula(spm_unit, period, parameters): # exceptions and room/board transfers within the unit are not modeled. return max_( spm_unit.sum(person_income - work_deduction - medical - support - care) - + unit_income + + tanf - childcare, 0, )