From f1800bd15ca9c185417270307f71cf287c411ac2 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Fri, 16 Oct 2026 16:32:00 -0400 Subject: [PATCH 1/3] Apply North Carolina Work First's 27.5% earnings exclusion and $25 minimum payment North Carolina excludes 27.5% of gross earned income and does not count children's earnings (TANF State Plan, Financial Rules 3 and 6), and pays a family when the payment, 50% of the difference between countable income and the need standard, is $25 or more (Financial Rules 8). PE counted all earnings and required $25 of the reduced need standard per household member. Co-Authored-By: Claude Opus 5.5 --- .../fix-10210-nc-work-first-earnings.fixed.md | 1 + ...erage_reduced_need_standard_threshold.yaml | 4 + .../income/earned_income_exclusion_rate.yaml | 16 ++++ .../states/nc/ncdhhs/tanf/integration.yaml | 90 ++++++++++++++++--- .../tanf/nc_tanf_countable_earned_income.yaml | 54 +++++++++-- .../ncdhhs/tanf/nc_tanf_income_eligible.yaml | 44 +++++++++ .../edge_cases/state/nc/tanf.yaml | 45 +++++----- .../income/nc_tanf_countable_earned_income.py | 14 ++- .../nc/ncdhhs/tanf/nc_tanf_income_eligible.py | 23 ++--- 9 files changed, 237 insertions(+), 54 deletions(-) create mode 100644 changelog.d/fix-10210-nc-work-first-earnings.fixed.md create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/income/earned_income_exclusion_rate.yaml diff --git a/changelog.d/fix-10210-nc-work-first-earnings.fixed.md b/changelog.d/fix-10210-nc-work-first-earnings.fixed.md new file mode 100644 index 00000000000..45eb1b6e5de --- /dev/null +++ b/changelog.d/fix-10210-nc-work-first-earnings.fixed.md @@ -0,0 +1 @@ +Exclude 27.5% of adults' gross earnings and children's earnings from North Carolina Work First countable income, and require a payment of at least $25 a month instead of $25 per household member. diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/eligibility/average_reduced_need_standard_threshold.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/eligibility/average_reduced_need_standard_threshold.yaml index 622ca223070..29e2b2bd507 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/eligibility/average_reduced_need_standard_threshold.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/eligibility/average_reduced_need_standard_threshold.yaml @@ -10,3 +10,7 @@ metadata: reference: - title: WORK FIRST | Income and Budgeting (Change No. 04-2023, January 1, 2024) href: https://policies.ncdhhs.gov/wp-content/uploads/wf-114_1-2-2024.pdf#page=5 + - title: North Carolina TANF State Plan 2022-2025, Financial Rules 8 + href: https://www.ncdhhs.gov/2022-2025-tanf-state-plan-final-signedpdf/open#page=18 + - title: North Carolina TANF State Plan FFY 2026-2028, Financial Rules 8 + href: https://www.ncdhhs.gov/efs-wf-06-2025a2/open#page=19 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/income/earned_income_exclusion_rate.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/income/earned_income_exclusion_rate.yaml new file mode 100644 index 00000000000..13891c4d5e4 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/income/earned_income_exclusion_rate.yaml @@ -0,0 +1,16 @@ +description: North Carolina excludes this share of gross earned income when computing countable income under the Temporary Assistance for Needy Families program. + +values: + 1997-01-01: 0.275 + +metadata: + unit: /1 + period: year + label: North Carolina TANF earned income exclusion rate + reference: + - title: North Carolina TANF State Plan 2016-2019, Financial Rules 6 + href: https://www.ncdhhs.gov/documents/files/dss/dcdl/economicservices/efs-wf-29-2015a/download#page=34 + - title: North Carolina TANF State Plan 2022-2025, Financial Rules 6 + href: https://www.ncdhhs.gov/2022-2025-tanf-state-plan-final-signedpdf/open#page=17 + - title: North Carolina TANF State Plan FFY 2026-2028, Financial Rules 6 + href: https://www.ncdhhs.gov/efs-wf-06-2025a2/open#page=18 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/integration.yaml index 74811deca3b..c610254283a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/integration.yaml @@ -50,7 +50,7 @@ output: nc_tanf: 0 -- name: One adult and one child household with $422 monthly income receives $300 per year +- name: One adult and one child household with $422 monthly income receives $996.30 per year period: 2024 input: people: @@ -68,7 +68,8 @@ members: [adult1, child1] state_code: NC output: - nc_tanf: 300 + # Countable earnings: 422 * (1 - 0.275) = 305.95; 0.5 * (472 - 305.95) = 83.025 a month + nc_tanf: 83.025 * 12 # 996.30 - name: One adult and two children household with one SSI recipient receives $236 per month period: 2024 @@ -93,7 +94,7 @@ output: nc_tanf: 236 * 12 -- name: One adult and two children household with $400 monthly income and one SSI recipient receives $36 per month +- name: One adult and two children household with $400 monthly income and one SSI recipient receives $91 per month period: 2024 input: people: @@ -114,9 +115,10 @@ members: [adult1, child1, child2] state_code: NC output: - nc_tanf: 36 * 12 + # Countable earnings: 400 * (1 - 0.275) = 290; 0.5 * (472 - 290) = 91 a month + nc_tanf: 91 * 12 -- name: One adult and two children household with $400 monthly income and two SSI recipient receives $0 per month +- name: One adult and two children household with $400 monthly income and two SSI recipient receives $36 per month period: 2024 input: people: @@ -138,9 +140,10 @@ members: [adult1, child1, child2] state_code: NC output: - nc_tanf: 0 + # Household size 1. Countable earnings: 400 * (1 - 0.275) = 290; 0.5 * (362 - 290) = 36 a month + nc_tanf: 36 * 12 -- name: One adult and two children household with $330 monthly income and two SSI recipient receives $16 per month +- name: One adult and two children household with $330 monthly income and two SSI recipient receives $61.375 per month period: 2024 input: people: @@ -162,9 +165,10 @@ members: [adult1, child1, child2] state_code: NC output: - nc_tanf: 16 * 12 + # Countable earnings: 330 * (1 - 0.275) = 239.25; 0.5 * (362 - 239.25) = 61.375 a month + nc_tanf: 61.375 * 12 # 736.50 -- name: One adult and one child household with income $430 monthly income receives $0 per year +- name: One adult and one child household with income $430 monthly income receives $961.50 per year period: 2024 input: people: @@ -182,7 +186,8 @@ members: [adult1, child1] state_code: NC output: - nc_tanf: 0 + # Countable earnings: 430 * (1 - 0.275) = 311.75; 0.5 * (472 - 311.75) = 80.125 a month + nc_tanf: 80.125 * 12 # 961.50 - name: 14 people household including a child with no income receives $6252 per year period: 2024 @@ -252,7 +257,7 @@ output: nc_tanf: (1042 - 0) * 0.5 * 12 -- name: 14 people household including a child with $692 monthly income receives $300 per year +- name: 14 people household including a child with $692 monthly income receives $3,241.80 per year period: 2024 input: people: @@ -318,9 +323,10 @@ members: [adult1, adult2, adult3, adult4, adult5, adult6, adult7, adult8, adult9, adult10, adult11, adult12, adult13, child1] state_code: NC output: - nc_tanf: (1042 - 692) * 0.5 * 12 + # Countable earnings: 692 * (1 - 0.275) = 501.70; 0.5 * (1_042 - 501.70) = 270.15 a month + nc_tanf: 270.15 * 12 # 3_241.80 -- name: 14 people household including a child with $1000 monthly income receives $0 per year +- name: 14 people household including a child with $1000 monthly income receives $1,902 per year period: 2024 input: people: @@ -386,4 +392,60 @@ members: [adult1, adult2, adult3, adult4, adult5, adult6, adult7, adult8, adult9, adult10, adult11, adult12, adult13, child1] state_code: NC output: - nc_tanf: 0 + # Countable earnings: 1_000 * (1 - 0.275) = 725; 0.5 * (1_042 - 725) = 158.50 a month + nc_tanf: 158.5 * 12 # 1_902 + +- name: Single parent with five children and $598 monthly earnings receives $1,586.70 per year. + period: 2026 + input: + people: + parent: + age: 30 + employment_income: 7_176 + child1: + age: 15 + child2: + age: 12 + child3: + age: 9 + child4: + age: 6 + child5: + age: 3 + spm_units: + spm_unit: + members: [parent, child1, child2, child3, child4, child5] + households: + household: + members: [parent, child1, child2, child3, child4, child5] + state_code: NC + output: + # Countable earnings: 7_176 * (1 - 0.275) = 5_202.60 (433.55 a month) + nc_tanf_countable_earned_income: 5_202.6 + # Need standard for 6: 698 * 12 = 8_376; 8_376 - 5_202.60 = 3_173.40 + nc_tanf_reduced_need_standard: 3_173.4 + nc_tanf_income_eligible: true + # 0.5 * 3_173.40 = 1_586.70 (132.225 a month) + nc_tanf: 1_586.7 + +- name: Parent with two children and $600 monthly earnings receives $54.50 per month. + period: 2026 + input: + people: + parent: + age: 30 + employment_income: 7_200 + child1: + age: 8 + child2: + age: 4 + spm_units: + spm_unit: + members: [parent, child1, child2] + households: + household: + members: [parent, child1, child2] + state_code: NC + output: + # Countable earnings: 600 * (1 - 0.275) = 435 a month; 0.5 * (544 - 435) = 54.50 + nc_tanf: 54.5 * 12 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_earned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_earned_income.yaml index 58a21f01e9f..c7233233309 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_earned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_earned_income.yaml @@ -7,29 +7,71 @@ output: nc_tanf_countable_earned_income: 0 -- name: Earned employee income $500, self-employment income $500, $1000 per year +- name: Earned employee income $500, self-employment income $500, $725 per year period: 2024 input: state_code: NC employment_income: 500 self_employment_income: 500 output: - nc_tanf_countable_earned_income: 1_000 + nc_tanf_countable_earned_income: 725 # 1_000 * (1 - 0.275) -- name: Earned employee income $500, no self-employment income, $500 per year +- name: Earned employee income $500, no self-employment income, $362.50 per year period: 2024 input: state_code: NC employment_income: 500 self_employment_income: 0 output: - nc_tanf_countable_earned_income: 500 + nc_tanf_countable_earned_income: 362.5 # 500 * (1 - 0.275) -- name: Earned employee income $0, self-employment income $500, $500 per year +- name: Earned employee income $0, self-employment income $500, $362.50 per year period: 2024 input: state_code: NC employment_income: 0 self_employment_income: 500 output: - nc_tanf_countable_earned_income: 500 + nc_tanf_countable_earned_income: 362.5 # 500 * (1 - 0.275) + +- name: A child's earnings are not counted. + period: 2024 + input: + people: + parent: + age: 30 + employment_income: 6_000 + child: + age: 16 + employment_income: 3_000 + spm_units: + spm_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + # Only the parent's earnings count: 6_000 * (1 - 0.275) = 4_350 + nc_tanf_countable_earned_income: 4_350 + +- name: Earnings of an 18-year-old member are counted. + period: 2024 + input: + people: + parent: + age: 40 + employment_income: 0 + adult_child: + age: 18 + employment_income: 3_000 + spm_units: + spm_unit: + members: [parent, adult_child] + households: + household: + members: [parent, adult_child] + state_code: NC + output: + # Age 18 is not below the age limit of 18: 3_000 * (1 - 0.275) = 2_175 + nc_tanf_countable_earned_income: 2_175 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_income_eligible.yaml index 386d05f8755..caf235b166e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_income_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_income_eligible.yaml @@ -45,3 +45,47 @@ nc_demographic_tanf_eligible: true output: nc_tanf_income_eligible: false + +- name: Six-person household with a $50 payment is eligible; the $25 minimum is not divided by household size. + period: 2024 + input: + state_code: NC + nc_tanf_reduced_need_standard: 100 * 12 + nc_tanf_household_size: 6 + nc_demographic_tanf_eligible: true + output: + # Payment: 0.5 * 100 = 50 a month >= 25 + nc_tanf_income_eligible: true + +- name: One-person household with a $20 payment is ineligible. + period: 2024 + input: + state_code: NC + nc_tanf_reduced_need_standard: 40 * 12 + nc_tanf_household_size: 1 + nc_demographic_tanf_eligible: true + output: + # Payment: 0.5 * 40 = 20 a month < 25 + nc_tanf_income_eligible: false + +- name: Payment of exactly $25 is eligible. + period: 2024 + input: + state_code: NC + nc_tanf_reduced_need_standard: 50 * 12 + nc_tanf_household_size: 2 + nc_demographic_tanf_eligible: true + output: + # Payment: 0.5 * 50 = 25 a month >= 25 + nc_tanf_income_eligible: true + +- name: Payment of $24.50 is ineligible. + period: 2024 + input: + state_code: NC + nc_tanf_reduced_need_standard: 49 * 12 + nc_tanf_household_size: 2 + nc_demographic_tanf_eligible: true + output: + # Payment: 0.5 * 49 = 24.50 a month < 25 + nc_tanf_income_eligible: false diff --git a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/state/nc/tanf.yaml b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/state/nc/tanf.yaml index 13044985842..32d3ed6136b 100644 --- a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/state/nc/tanf.yaml +++ b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/state/nc/tanf.yaml @@ -1,7 +1,9 @@ # NC TANF (Work First, nc_tanf) — need standard (need_standard/main.yaml, monthly): # size 2 = 472, size 3 = 544, size 4 = 594. Benefit = 50% of (need standard - # countable earned + unearned income) (benefit/payment_percentage.yaml). -# Income-eligible only if reduced need standard per person >= 25/mo = 300/yr +# Countable earned income excludes 27.5% of adults' gross earnings +# (income/earned_income_exclusion_rate.yaml). Income-eligible only if the +# payment is >= 25/mo = 300/yr # (eligibility/average_reduced_need_standard_threshold.yaml). Demographic # eligibility requires a child under 18 (eligibility/age_limit.yaml). - name: tanf_max_benefit_youngest_child_age_17 (nc) @@ -267,11 +269,11 @@ output: spm_units: spm_unit: - # Earned income 3_000 (no disregard in NC countable earned income): - # reduced need standard = 6_528 - 3_000 = 3_528 (per person 1_176 >= 300). - # nc_tanf = 3_528 x 0.5 = 1_764. - nc_tanf: 1_764 - tanf: 1_764 + # Countable earned income = 3_000 x (1 - 0.275) = 2_175; + # reduced need standard = 6_528 - 2_175 = 4_353. + # nc_tanf = 4_353 x 0.5 = 2_176.50 (>= 300). + nc_tanf: 2_176.5 + tanf: 2_176.5 - name: tanf_self_employment_income_counted_as_earned (nc) period: 2026 absolute_error_margin: 0.1 @@ -322,11 +324,11 @@ spm_units: spm_unit: # Self-employment income is in the NC earned-income source list - # (income/earned.yaml). Size-2 need standard = 472 x 12 = 5_664; - # reduced = 5_664 - 2_400 = 3_264 (per person 1_632 >= 300). - # nc_tanf = 3_264 x 0.5 = 1_632. - nc_tanf: 1_632 - tanf: 1_632 + # (income/earned.yaml). Countable = 2_400 x (1 - 0.275) = 1_740. + # Size-2 need standard = 472 x 12 = 5_664; reduced = 5_664 - 1_740 = 3_924. + # nc_tanf = 3_924 x 0.5 = 1_962 (>= 300). + nc_tanf: 1_962 + tanf: 1_962 - name: tanf_income_at_min_benefit_boundary_eligible (nc) period: 2026 absolute_error_margin: 0.1 @@ -339,7 +341,7 @@ is_tax_unit_dependent: false is_disabled: false immigration_status_str: CITIZEN - employment_income_before_lsr: 5_628 + employment_income_before_lsr: 8_176 spouse: age: 28 is_tax_unit_head: false @@ -386,11 +388,11 @@ output: spm_units: spm_unit: - # Exact eligibility cliff for size 3: reduced = 6_528 - 5_628 = 900; - # per person 900 / 3 = 300 >= 300 (25/mo x 12) -> still eligible. - # nc_tanf = 900 x 0.5 = 450. Binds the >= comparison. - nc_tanf: 450 - tanf: 450 + # Eligibility cliff for size 3: countable = 8_176 x (1 - 0.275) = 5_927.60; + # reduced = 6_528 - 5_927.60 = 600.40; payment = 600.40 x 0.5 = 300.20 + # >= 300 (25/mo x 12) -> still eligible. nc_tanf = 300.20. + nc_tanf: 300.2 + tanf: 300.2 - name: tanf_income_just_above_min_benefit_boundary (nc) period: 2026 absolute_error_margin: 0.1 @@ -403,7 +405,7 @@ is_tax_unit_dependent: false is_disabled: false immigration_status_str: CITIZEN - employment_income_before_lsr: 5_629 + employment_income_before_lsr: 8_177 spouse: age: 28 is_tax_unit_head: false @@ -450,9 +452,8 @@ output: spm_units: spm_unit: - # One dollar past the cliff: reduced = 6_528 - 5_629 = 899; - # per person 899 / 3 = 299.67 < 300 -> income-ineligible -> 0. - # (The prior file's names claimed a 5_687 threshold; the actual - # cliff sits between 5_628 and 5_629.) + # One dollar past the cliff: countable = 8_177 x (1 - 0.275) = 5_928.325; + # reduced = 6_528 - 5_928.325 = 599.675; payment = 299.84 < 300 + # -> income-ineligible -> 0. The cliff sits between 8_176 and 8_177. nc_tanf: 0 tanf: 0 diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_earned_income.py b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_earned_income.py index ebff65fa7a6..25f6d5d4a19 100644 --- a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_earned_income.py +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_earned_income.py @@ -8,5 +8,17 @@ class nc_tanf_countable_earned_income(Variable): unit = USD definition_period = YEAR defined_for = StateCode.NC + reference = ( + "https://www.ncdhhs.gov/efs-wf-06-2025a2/open#page=18", + "https://policies.ncdhhs.gov/wp-content/uploads/wf-114_1-2-2024.pdf#page=2", + ) - adds = "gov.states.nc.ncdhhs.tanf.income.earned" + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nc.ncdhhs.tanf + person = spm_unit.members + gross_earned = add(person, period, p.income.earned) + # State Plan Financial Rules 3: the earnings of children are not counted. + is_child = person("age", period) < p.eligibility.age_limit + adult_earned = spm_unit.sum(where(is_child, 0, gross_earned)) + # State Plan Financial Rules 6: 27.5% of gross earned income is excluded. + return adult_earned * (1 - p.income.earned_income_exclusion_rate) diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_income_eligible.py b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_income_eligible.py index 423f44ef818..9608b9fbecc 100644 --- a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_income_eligible.py +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_income_eligible.py @@ -7,20 +7,21 @@ class nc_tanf_income_eligible(Variable): label = "North Carolina TANF income eligible" definition_period = YEAR defined_for = "nc_demographic_tanf_eligible" + reference = ( + "https://www.ncdhhs.gov/efs-wf-06-2025a2/open#page=19", + "https://policies.ncdhhs.gov/wp-content/uploads/wf-114_1-2-2024.pdf#page=5", + ) def formula(spm_unit, period, parameters): - p = parameters(period).gov.states.nc.ncdhhs.tanf.eligibility + p = parameters(period).gov.states.nc.ncdhhs.tanf household_size = spm_unit("nc_tanf_household_size", period) reduced_need_standard = spm_unit("nc_tanf_reduced_need_standard", period) - - need_standard_fraction = np.divide( - reduced_need_standard, - household_size, - out=np.zeros_like(reduced_need_standard, dtype=float), - where=household_size != 0, - ) - difference_threshold = ( - p.average_reduced_need_standard_threshold * MONTHS_IN_YEAR + # State Plan Financial Rules 8: a family remains eligible if the + # payment (50% of the difference between countable income and the + # need standard) is $25 or more. + payment = reduced_need_standard * p.benefit.payment_percentage + minimum_payment = ( + p.eligibility.average_reduced_need_standard_threshold * MONTHS_IN_YEAR ) - return need_standard_fraction >= difference_threshold + return (household_size > 0) & (payment >= minimum_payment) From 589543dd03d344d8128622e0212527e0b331ed64 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Tue, 27 Oct 2026 11:00:50 -0400 Subject: [PATCH 2/3] Exclude SSI recipients' income from North Carolina Work First State Plan Financial Rules 3 and 4, 10A NCAC 71W .0408(a)(1) and the WF-114 income chart exclude all income of SSI recipients. The household size already drops them; the earned and unearned income variables now drop their income too, with the same ssi/receives_ssi check. Rename the 18-year-old earnings test so that it describes the PolicyEngine approximation, add SSI cases, cite 10A NCAC 71W .0601(a) and the 2019-2022 State Plan, and fix the 2016-2019 payment anchor. Co-Authored-By: Claude Opus 5.5 --- .../fix-10210-nc-work-first-earnings.fixed.md | 2 +- .../tanf/benefit/payment_percentage.yaml | 2 +- ...erage_reduced_need_standard_threshold.yaml | 2 ++ .../income/earned_income_exclusion_rate.yaml | 2 ++ .../states/nc/ncdhhs/tanf/integration.yaml | 27 ++++++++++++++++ .../tanf/nc_tanf_countable_earned_income.yaml | 32 +++++++++++++++++-- ..._tanf_countable_gross_unearned_income.yaml | 28 ++++++++++++++++ .../income/nc_tanf_countable_earned_income.py | 10 ++++-- ...nc_tanf_countable_gross_unearned_income.py | 21 +++++++++--- 9 files changed, 115 insertions(+), 11 deletions(-) diff --git a/changelog.d/fix-10210-nc-work-first-earnings.fixed.md b/changelog.d/fix-10210-nc-work-first-earnings.fixed.md index 45eb1b6e5de..27cb56caff3 100644 --- a/changelog.d/fix-10210-nc-work-first-earnings.fixed.md +++ b/changelog.d/fix-10210-nc-work-first-earnings.fixed.md @@ -1 +1 @@ -Exclude 27.5% of adults' gross earnings and children's earnings from North Carolina Work First countable income, and require a payment of at least $25 a month instead of $25 per household member. +Exclude children's earnings, SSI recipients' income and 27.5% of other gross earnings from North Carolina Work First countable income, and require a payment of at least $25 a month instead of $25 per household member. diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/benefit/payment_percentage.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/benefit/payment_percentage.yaml index 32b5eee1764..477f4c07836 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/benefit/payment_percentage.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/benefit/payment_percentage.yaml @@ -11,6 +11,6 @@ metadata: - title: WORK FIRST | Income and Budgeting (Change No. 04-2023, January 1, 2024) href: https://policies.ncdhhs.gov/wp-content/uploads/wf-114_1-2-2024.pdf#page=5 - title: North Carolina TANF State Plan 2016-2019 - href: https://www.ncdhhs.gov/documents/files/dss/dcdl/economicservices/efs-wf-29-2015a/download#page=35 + href: https://www.ncdhhs.gov/documents/files/dss/dcdl/economicservices/efs-wf-29-2015a/download#page=34 - title: North Carolina TANF State Plan 2019-2022 href: https://www.ncdhhs.gov/documents/files/dss/2019-2022-tanf-state-plan-draft/download#page=18 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/eligibility/average_reduced_need_standard_threshold.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/eligibility/average_reduced_need_standard_threshold.yaml index 29e2b2bd507..3bfeab95c2b 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/eligibility/average_reduced_need_standard_threshold.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/eligibility/average_reduced_need_standard_threshold.yaml @@ -14,3 +14,5 @@ metadata: href: https://www.ncdhhs.gov/2022-2025-tanf-state-plan-final-signedpdf/open#page=18 - title: North Carolina TANF State Plan FFY 2026-2028, Financial Rules 8 href: https://www.ncdhhs.gov/efs-wf-06-2025a2/open#page=19 + - title: 10A NCAC 71W .0601(a), Issuance of Funds + href: http://reports.oah.state.nc.us/ncac/title%2010a%20-%20health%20and%20human%20services/chapter%2071%20-%20adult%20and%20family%20support/subchapter%20w/subchapter%20w%20rules.pdf#page=10 diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/income/earned_income_exclusion_rate.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/income/earned_income_exclusion_rate.yaml index 13891c4d5e4..6649db4180f 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/income/earned_income_exclusion_rate.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/income/earned_income_exclusion_rate.yaml @@ -10,6 +10,8 @@ metadata: reference: - title: North Carolina TANF State Plan 2016-2019, Financial Rules 6 href: https://www.ncdhhs.gov/documents/files/dss/dcdl/economicservices/efs-wf-29-2015a/download#page=34 + - title: North Carolina TANF State Plan 2019-2022, Financial Rules 6 + href: https://www.ncdhhs.gov/documents/files/dss/publications/acf-approved-2019-2022-tanf-state-plan/download#page=16 - title: North Carolina TANF State Plan 2022-2025, Financial Rules 6 href: https://www.ncdhhs.gov/2022-2025-tanf-state-plan-final-signedpdf/open#page=17 - title: North Carolina TANF State Plan FFY 2026-2028, Financial Rules 6 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/integration.yaml index c610254283a..b27993e71cc 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/integration.yaml @@ -449,3 +449,30 @@ output: # Countable earnings: 600 * (1 - 0.275) = 435 a month; 0.5 * (544 - 435) = 54.50 nc_tanf: 54.5 * 12 + +- name: Two parents and a child, where the parent with $400 monthly earnings receives SSI, receive $236 per month. + period: 2024 + input: + people: + parent_a: + age: 35 + employment_income: 400 * 12 + receives_ssi: true + parent_b: + age: 35 + child: + age: 5 + spm_units: + spm_unit: + members: [parent_a, parent_b, child] + households: + household: + members: [parent_a, parent_b, child] + state_code: NC + output: + # The SSI recipient is not in the household size, and none of their + # income is counted (State Plan Financial Rules 3). + nc_tanf_household_size: 2 + nc_tanf_countable_earned_income: 0 + # Need standard for 2: 472 a month; 0.5 * (472 - 0) = 236 a month + nc_tanf: 236 * 12 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_earned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_earned_income.yaml index c7233233309..af40e8da3db 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_earned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_earned_income.yaml @@ -55,7 +55,7 @@ # Only the parent's earnings count: 6_000 * (1 - 0.275) = 4_350 nc_tanf_countable_earned_income: 4_350 -- name: Earnings of an 18-year-old member are counted. +- name: Earnings of an 18-year-old not in high school are counted (PolicyEngine counts every non-SSI member who is not a child). period: 2024 input: people: @@ -73,5 +73,33 @@ members: [parent, adult_child] state_code: NC output: - # Age 18 is not below the age limit of 18: 3_000 * (1 - 0.275) = 2_175 + # Age 18 is not below the age limit of 18, so PolicyEngine counts the + # earnings: 3_000 * (1 - 0.275) = 2_175. This approximates the rule, which + # counts parents' income (10A NCAC 71W .0408(a)(1)) and keeps an 18-year-old + # in high school as a child (State Plan Non-Financial Eligibility Rules 1). nc_tanf_countable_earned_income: 2_175 + +- name: An SSI recipient's earnings are not counted, and the other parent's earnings are. + period: 2024 + input: + people: + parent_a: + age: 35 + employment_income: 4_800 + receives_ssi: true + parent_b: + age: 35 + employment_income: 2_000 + child: + age: 5 + spm_units: + spm_unit: + members: [parent_a, parent_b, child] + households: + household: + members: [parent_a, parent_b, child] + state_code: NC + output: + # State Plan Financial Rules 3: the income of SSI recipients is not counted. + # Only parent B's earnings count: 2_000 * (1 - 0.275) = 1_450 + nc_tanf_countable_earned_income: 1_450 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_gross_unearned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_gross_unearned_income.yaml index 966688e984f..9ef7e16f95a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_gross_unearned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_gross_unearned_income.yaml @@ -106,3 +106,31 @@ receives_tanf: true output: nc_tanf_countable_gross_unearned_income: 5_000 + +- name: An SSI recipient's unearned income and child support are not counted. + period: 2024 + input: + people: + parent: + age: 35 + social_security: 6_000 + child_support_received: 1_000 + receives_ssi: true + other_parent: + age: 35 + pension_income: 1_200 + child_support_received: 500 + child: + age: 5 + spm_units: + spm_unit: + members: [parent, other_parent, child] + is_tanf_enrolled: false + households: + household: + members: [parent, other_parent, child] + state_code: NC + output: + # State Plan Financial Rules 3 and 4: no income of SSI recipients is counted. + # Only the other parent's income counts: 1_200 + 500 = 1_700 + nc_tanf_countable_gross_unearned_income: 1_700 diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_earned_income.py b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_earned_income.py index 25f6d5d4a19..b472e3990d6 100644 --- a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_earned_income.py +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_earned_income.py @@ -11,14 +11,18 @@ class nc_tanf_countable_earned_income(Variable): reference = ( "https://www.ncdhhs.gov/efs-wf-06-2025a2/open#page=18", "https://policies.ncdhhs.gov/wp-content/uploads/wf-114_1-2-2024.pdf#page=2", + "http://reports.oah.state.nc.us/ncac/title%2010a%20-%20health%20and%20human%20services/chapter%2071%20-%20adult%20and%20family%20support/subchapter%20w/subchapter%20w%20rules.pdf#page=7", ) def formula(spm_unit, period, parameters): p = parameters(period).gov.states.nc.ncdhhs.tanf person = spm_unit.members gross_earned = add(person, period, p.income.earned) - # State Plan Financial Rules 3: the earnings of children are not counted. + # State Plan Financial Rules 3: all income is counted except the + # earnings of children and the income of SSI recipients. is_child = person("age", period) < p.eligibility.age_limit - adult_earned = spm_unit.sum(where(is_child, 0, gross_earned)) + is_ssi_recipient = (person("ssi", period) > 0) | person("receives_ssi", period) + counted = ~is_child & ~is_ssi_recipient + countable_earned = spm_unit.sum(where(counted, gross_earned, 0)) # State Plan Financial Rules 6: 27.5% of gross earned income is excluded. - return adult_earned * (1 - p.income.earned_income_exclusion_rate) + return countable_earned * (1 - p.income.earned_income_exclusion_rate) diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_gross_unearned_income.py b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_gross_unearned_income.py index dd687f5c329..697394e293b 100644 --- a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_gross_unearned_income.py +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_gross_unearned_income.py @@ -8,12 +8,25 @@ class nc_tanf_countable_gross_unearned_income(Variable): unit = USD definition_period = YEAR defined_for = StateCode.NC + reference = ( + "https://www.ncdhhs.gov/efs-wf-06-2025a2/open#page=18", + "https://policies.ncdhhs.gov/wp-content/uploads/wf-114_1-2-2024.pdf#page=2", + "http://reports.oah.state.nc.us/ncac/title%2010a%20-%20health%20and%20human%20services/chapter%2071%20-%20adult%20and%20family%20support/subchapter%20w/subchapter%20w%20rules.pdf#page=7", + ) def formula(spm_unit, period, parameters): p = parameters(period).gov.states.nc.ncdhhs.tanf.income + person = spm_unit.members + # State Plan Financial Rules 3 and 4: no income of SSI recipients + # is counted. + is_ssi_recipient = (person("ssi", period) > 0) | person("receives_ssi", period) # Sum unearned sources, plus child support if not currently enrolled. - gross_unearned = add(spm_unit, period, p.unearned) - child_support = add(spm_unit, period, ["child_support_received"]) + gross_unearned = add(person, period, p.unearned) + child_support = person("child_support_received", period) + countable_unearned = spm_unit.sum(where(is_ssi_recipient, 0, gross_unearned)) + countable_child_support = spm_unit.sum( + where(is_ssi_recipient, 0, child_support) + ) tanf_enrolled = spm_unit("is_tanf_enrolled", period.first_month) - additional_amount = where(tanf_enrolled, 0, child_support) - return gross_unearned + additional_amount + additional_amount = where(tanf_enrolled, 0, countable_child_support) + return countable_unearned + additional_amount From c711e7034b83a761cb524568b2253827ab47cb59 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Tue, 27 Oct 2026 13:09:47 -0400 Subject: [PATCH 3/3] Count child support an SSI-recipient parent receives for a child WF-114 section XII and 10A NCAC 71W .0408(a)(2) count child support as the dependent child's income. The SSI exclusion now removes only the SSI recipient's own unearned income, and child support only when an SSI-recipient child receives it. The unearned test now expects 2,700, and new cases pin the SSI-parent and SSI-child child-support paths. Also cite the ACF-approved 2019-2022 State Plan in payment_percentage. Co-Authored-By: Claude Opus 5.5 --- .../tanf/benefit/payment_percentage.yaml | 2 +- .../states/nc/ncdhhs/tanf/integration.yaml | 26 +++++++++++++ ..._tanf_countable_gross_unearned_income.yaml | 37 +++++++++++++++++-- ...nc_tanf_countable_gross_unearned_income.py | 16 +++++--- 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/benefit/payment_percentage.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/benefit/payment_percentage.yaml index 477f4c07836..2240f39a13a 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/benefit/payment_percentage.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/benefit/payment_percentage.yaml @@ -13,4 +13,4 @@ metadata: - title: North Carolina TANF State Plan 2016-2019 href: https://www.ncdhhs.gov/documents/files/dss/dcdl/economicservices/efs-wf-29-2015a/download#page=34 - title: North Carolina TANF State Plan 2019-2022 - href: https://www.ncdhhs.gov/documents/files/dss/2019-2022-tanf-state-plan-draft/download#page=18 + href: https://www.ncdhhs.gov/documents/files/dss/publications/acf-approved-2019-2022-tanf-state-plan/download#page=16 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/integration.yaml index b27993e71cc..e69796b5b0d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/integration.yaml @@ -476,3 +476,29 @@ nc_tanf_countable_earned_income: 0 # Need standard for 2: 472 a month; 0.5 * (472 - 0) = 236 a month nc_tanf: 236 * 12 + +- name: A parent who receives SSI and $300 monthly child support for a child aged 5 receives $31 per month. + period: 2024 + input: + people: + parent: + age: 35 + receives_ssi: true + child_support_received: 300 * 12 + child: + age: 5 + spm_units: + spm_unit: + members: [parent, child] + is_tanf_enrolled: false + households: + household: + members: [parent, child] + state_code: NC + output: + # The SSI parent is not in the household size. WF-114 section XII: child + # support is the dependent child's income, so the 300 a month counts. + nc_tanf_household_size: 1 + nc_tanf_countable_gross_unearned_income: 3_600 + # Need standard for 1: 362 a month; 0.5 * (362 - 300) = 31 a month + nc_tanf: 31 * 12 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_gross_unearned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_gross_unearned_income.yaml index 9ef7e16f95a..cf6d11ee2ed 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_gross_unearned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_gross_unearned_income.yaml @@ -107,7 +107,7 @@ output: nc_tanf_countable_gross_unearned_income: 5_000 -- name: An SSI recipient's unearned income and child support are not counted. +- name: An SSI recipient's unearned income is not counted; child support they receive for a child in the unit is. period: 2024 input: people: @@ -131,6 +131,35 @@ members: [parent, other_parent, child] state_code: NC output: - # State Plan Financial Rules 3 and 4: no income of SSI recipients is counted. - # Only the other parent's income counts: 1_200 + 500 = 1_700 - nc_tanf_countable_gross_unearned_income: 1_700 + # State Plan Financial Rules 3 and 4: the SSI parent's Social Security is + # not counted. WF-114 section XII: child support is the dependent child's + # income, so the SSI parent's 1_000 counts. 1_200 + 500 + 1_000 = 2_700 + nc_tanf_countable_gross_unearned_income: 2_700 + +- name: Child support an SSI-recipient child receives is not counted. + period: 2024 + input: + people: + parent: + age: 35 + pension_income: 1_200 + ssi_child: + age: 8 + child_support_received: 2_400 + receives_ssi: true + child: + age: 5 + child_support_received: 600 + spm_units: + spm_unit: + members: [parent, ssi_child, child] + is_tanf_enrolled: false + households: + household: + members: [parent, ssi_child, child] + state_code: NC + output: + # The SSI child's support is that child's own income, and no income of SSI + # recipients is counted (State Plan Financial Rules 3 and 4). + # 1_200 + 600 = 1_800 + nc_tanf_countable_gross_unearned_income: 1_800 diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_gross_unearned_income.py b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_gross_unearned_income.py index 697394e293b..7465cb0f1ed 100644 --- a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_gross_unearned_income.py +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/income/nc_tanf_countable_gross_unearned_income.py @@ -10,23 +10,29 @@ class nc_tanf_countable_gross_unearned_income(Variable): defined_for = StateCode.NC reference = ( "https://www.ncdhhs.gov/efs-wf-06-2025a2/open#page=18", + # PDF pages 2, 45 "https://policies.ncdhhs.gov/wp-content/uploads/wf-114_1-2-2024.pdf#page=2", "http://reports.oah.state.nc.us/ncac/title%2010a%20-%20health%20and%20human%20services/chapter%2071%20-%20adult%20and%20family%20support/subchapter%20w/subchapter%20w%20rules.pdf#page=7", ) def formula(spm_unit, period, parameters): - p = parameters(period).gov.states.nc.ncdhhs.tanf.income + p = parameters(period).gov.states.nc.ncdhhs.tanf person = spm_unit.members # State Plan Financial Rules 3 and 4: no income of SSI recipients # is counted. is_ssi_recipient = (person("ssi", period) > 0) | person("receives_ssi", period) - # Sum unearned sources, plus child support if not currently enrolled. - gross_unearned = add(person, period, p.unearned) - child_support = person("child_support_received", period) + gross_unearned = add(person, period, p.income.unearned) countable_unearned = spm_unit.sum(where(is_ssi_recipient, 0, gross_unearned)) + # WF-114 section XII: child support is unearned income of the + # dependent child, so support an SSI-recipient parent receives for a + # child in the unit counts. Only an SSI-recipient child's own child + # support is excluded. + is_child = person("age", period) < p.eligibility.age_limit + child_support = person("child_support_received", period) countable_child_support = spm_unit.sum( - where(is_ssi_recipient, 0, child_support) + where(is_ssi_recipient & is_child, 0, child_support) ) + # Child support counts only if the unit is not currently enrolled. tanf_enrolled = spm_unit("is_tanf_enrolled", period.first_month) additional_amount = where(tanf_enrolled, 0, countable_child_support) return countable_unearned + additional_amount