From 332a92274b86e5616089532611f50ea5b86c9606 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Fri, 6 Nov 2026 17:44:28 -0500 Subject: [PATCH 1/2] Apply North Carolina Work First's $3,000 resource limit 10A NCAC 71W .0407 and the Work First Manual (section 115) make a unit ineligible when its countable reserve exceeds $3,000. Cash, bank accounts, stocks, bonds and mutual funds count; vehicles, real property and retirement accounts do not. nc_tanf_eligible now requires nc_tanf_resources_eligible. Fixes #10685 Co-Authored-By: Claude Opus 5.5 --- ...fix-10685-nc-work-first-resources.fixed.md | 1 + .../nc/ncdhhs/tanf/resources/limit.yaml | 16 ++++++++ .../states/nc/ncdhhs/tanf/integration.yaml | 40 +++++++++++++++++++ .../tanf/nc_tanf_countable_resources.yaml | 23 +++++++++++ .../nc/ncdhhs/tanf/nc_tanf_eligible.yaml | 10 +++++ .../tanf/nc_tanf_resources_eligible.yaml | 15 +++++++ .../tanf/nc_tanf_countable_resources.py | 21 ++++++++++ .../states/nc/ncdhhs/tanf/nc_tanf_eligible.py | 3 +- .../ncdhhs/tanf/nc_tanf_resources_eligible.py | 18 +++++++++ 9 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 changelog.d/fix-10685-nc-work-first-resources.fixed.md create mode 100644 policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/resources/limit.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_resources_eligible.yaml create mode 100644 policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.py create mode 100644 policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_resources_eligible.py diff --git a/changelog.d/fix-10685-nc-work-first-resources.fixed.md b/changelog.d/fix-10685-nc-work-first-resources.fixed.md new file mode 100644 index 00000000000..30bb7a0c94f --- /dev/null +++ b/changelog.d/fix-10685-nc-work-first-resources.fixed.md @@ -0,0 +1 @@ +North Carolina Work First applies its $3,000 resource limit (10A NCAC 71W .0407). diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/resources/limit.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/resources/limit.yaml new file mode 100644 index 00000000000..a44b82cc124 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/resources/limit.yaml @@ -0,0 +1,16 @@ +description: North Carolina limits the Temporary Assistance for Needy Families program to units with countable resources up to this amount. + +values: + # The earliest source found is the Work First Manual's 2009 resources section; the value is + # backdated to the program's start, as the other North Carolina TANF parameters are. + 1997-01-01: 3_000 + +metadata: + unit: currency-USD + period: year + label: North Carolina TANF resource limit + reference: + - title: 10A NCAC 71W .0407 - Reserve + href: https://regulations.justia.com/states/north-carolina/title-10a/chapter-71/subchapter-w/section-0400/section-71w-0407 + - title: Work First Manual, section 115 - Resources (I.A. Resource Limit) + href: https://policies.ncdhhs.gov/wp-content/uploads/wf115.pdf#page=1 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 e69796b5b0d..a8c4ff92d6f 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 @@ -502,3 +502,43 @@ 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 + +- name: One adult and one child with no income and $3,000 in the bank receive the full payment. + period: 2024 + input: + people: + person1: + age: 30 + bank_account_assets: 3_000 + person2: + age: 17 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NC + output: + # $3,000 is at the limit (10A NCAC 71W .0407), so the unit stays eligible. + nc_tanf: (472 - 0) * 0.5 * 12 + +- name: One adult and one child with no income and $5,000 in stocks receive nothing. + period: 2024 + input: + people: + person1: + age: 30 + stock_assets: 5_000 + person2: + age: 17 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NC + output: + # $5,000 of countable resources exceeds the $3,000 limit. + nc_tanf: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.yaml new file mode 100644 index 00000000000..2367a9b9920 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.yaml @@ -0,0 +1,23 @@ +- name: Bank, stock and bond assets count; vehicle value does not. + period: 2025 + input: + people: + parent: + age: 30 + bank_account_assets: 1_000 + stock_assets: 1_500 + bond_assets: 700 + child: + age: 5 + spm_units: + spm_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + household_vehicles_value: 20_000 + output: + # 1,000 + 1,500 + 700 = 3,200; the vehicle is excluded. + nc_tanf_countable_resources: 3_200 + nc_tanf_resources_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_eligible.yaml index f3556562872..8bdc990f8fe 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_eligible.yaml @@ -24,3 +24,13 @@ nc_tanf_income_eligible: true output: nc_tanf_eligible: false + +- name: Demographic and income eligible, but resources above the limit. + period: 2024 + input: + state_code: NC + nc_demographic_tanf_eligible: true + nc_tanf_income_eligible: true + nc_tanf_resources_eligible: false + output: + nc_tanf_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_resources_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_resources_eligible.yaml new file mode 100644 index 00000000000..bd219361921 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_resources_eligible.yaml @@ -0,0 +1,15 @@ +- name: Countable resources at the $3,000 limit are allowed. + period: 2025 + input: + state_code: NC + nc_tanf_countable_resources: 3_000 + output: + nc_tanf_resources_eligible: true + +- name: Countable resources above the $3,000 limit make the unit ineligible. + period: 2025 + input: + state_code: NC + nc_tanf_countable_resources: 3_001 + output: + nc_tanf_resources_eligible: false diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.py b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.py new file mode 100644 index 00000000000..7c0af25d0a9 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class nc_tanf_countable_resources(Variable): + value_type = float + entity = SPMUnit + label = "North Carolina TANF countable resources" + unit = USD + definition_period = YEAR + quantity_type = STOCK + reference = ( + "https://regulations.justia.com/states/north-carolina/title-10a/chapter-71/subchapter-w/section-0400/section-71w-0407", + "https://policies.ncdhhs.gov/wp-content/uploads/wf115.pdf#page=2", + ) + defined_for = StateCode.NC + + def formula(spm_unit, period, parameters): + # Work First counts cash, checking and savings accounts, stocks, bonds and + # mutual fund shares; motor vehicles, real property and retirement accounts + # do not count (Work First Manual, section 115, II). + return spm_unit("spm_unit_cash_assets", period.this_year) diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_eligible.py b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_eligible.py index 362d75a2852..786a1a04aae 100644 --- a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_eligible.py +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_eligible.py @@ -11,5 +11,6 @@ class nc_tanf_eligible(Variable): def formula(spm_unit, period, parameters): is_demographically_eligible = spm_unit("nc_demographic_tanf_eligible", period) is_income_eligible = spm_unit("nc_tanf_income_eligible", period) + is_resources_eligible = spm_unit("nc_tanf_resources_eligible", period) - return is_income_eligible & is_demographically_eligible + return is_income_eligible & is_demographically_eligible & is_resources_eligible diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_resources_eligible.py b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_resources_eligible.py new file mode 100644 index 00000000000..3d5c0e972a5 --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_resources_eligible.py @@ -0,0 +1,18 @@ +from policyengine_us.model_api import * + + +class nc_tanf_resources_eligible(Variable): + value_type = bool + entity = SPMUnit + label = "North Carolina TANF resources eligible" + definition_period = YEAR + reference = ( + "https://regulations.justia.com/states/north-carolina/title-10a/chapter-71/subchapter-w/section-0400/section-71w-0407", + "https://policies.ncdhhs.gov/wp-content/uploads/wf115.pdf#page=1", + ) + defined_for = StateCode.NC + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nc.ncdhhs.tanf.resources + countable_resources = spm_unit("nc_tanf_countable_resources", period) + return countable_resources <= p.limit From db0ee98db096cd85375e4a7aa375c7b8b9c0e5cb Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Sat, 7 Nov 2026 10:34:42 -0500 Subject: [PATCH 2/2] Exclude SSI recipients' resources from the NC Work First reserve test 10A NCAC 71W .0407(b) counts a parent's resources only if the parent does not receive SSI, and a child's only if the child is in the payment. Subtract SSI recipients' person-level cash assets from spm_unit_cash_assets, reading the components from that variable's adds, as MO and SC TANF do. Cite .0407(b), (d) and (e) from the OAH Subchapter 71W PDF (pages 6-7), add the PDF page comment for the Work First Manual 115 chart (pages 2-4), add the FFY 2026-2028 State Plan Financial Rules 1-2 reference to the limit, and name the checking-balance and non-parent-adult simplifications. Add variable-level and integration tests for SSI-recipient parent and child households. Co-Authored-By: Claude Opus 5.5 --- .../nc/ncdhhs/tanf/resources/limit.yaml | 2 + .../states/nc/ncdhhs/tanf/integration.yaml | 55 ++++++++++++++ .../tanf/nc_tanf_countable_resources.yaml | 73 +++++++++++++++++++ .../tanf/nc_tanf_countable_resources.py | 35 ++++++++- 4 files changed, 161 insertions(+), 4 deletions(-) diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/resources/limit.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/resources/limit.yaml index a44b82cc124..4a2c7c1dc26 100644 --- a/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/resources/limit.yaml +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/tanf/resources/limit.yaml @@ -14,3 +14,5 @@ metadata: href: https://regulations.justia.com/states/north-carolina/title-10a/chapter-71/subchapter-w/section-0400/section-71w-0407 - title: Work First Manual, section 115 - Resources (I.A. Resource Limit) href: https://policies.ncdhhs.gov/wp-content/uploads/wf115.pdf#page=1 + - title: North Carolina TANF State Plan FFY 2026-2028, Financial Rules 1-2 + 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 a8c4ff92d6f..a1dddd6e38a 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 @@ -542,3 +542,58 @@ output: # $5,000 of countable resources exceeds the $3,000 limit. nc_tanf: 0 + +- name: An SSI-recipient parent with $4,000 in the bank and a child aged 10 receive $181 per month. + period: 2025 + input: + people: + parent: + age: 35 + ssi: 11_000 + bank_account_assets: 4_000 + child: + age: 10 + spm_units: + spm_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + # The SSI parent is not in the household size, and their income and + # resources do not count (10A NCAC 71W .0407(b)(1)). + nc_tanf_household_size: 1 + nc_tanf_countable_gross_unearned_income: 0 + nc_tanf_countable_resources: 0 + nc_tanf_resources_eligible: true + # Need standard for 1: 362 a month; 0.5 * (362 - 0) = 181 a month + nc_tanf: 181 * 12 # 2_172 + +- name: A parent, an SSI-recipient child aged 8 with $4,000 in the bank, and a child aged 5 receive $236 per month. + period: 2025 + input: + people: + parent: + age: 35 + child1: + age: 8 + ssi: 11_000 + bank_account_assets: 4_000 + child2: + age: 5 + spm_units: + spm_unit: + members: [parent, child1, child2] + households: + household: + members: [parent, child1, child2] + state_code: NC + output: + # The SSI child is not in the household size, and their resources do not + # count (10A NCAC 71W .0407(b)(2)). + nc_tanf_household_size: 2 + nc_tanf_countable_resources: 0 + nc_tanf_resources_eligible: true + # Need standard for 2: 472 a month; 0.5 * (472 - 0) = 236 a month + nc_tanf: 236 * 12 # 2_832 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.yaml index 2367a9b9920..17ea6a968ec 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.yaml @@ -21,3 +21,76 @@ # 1,000 + 1,500 + 700 = 3,200; the vehicle is excluded. nc_tanf_countable_resources: 3_200 nc_tanf_resources_eligible: false + +- name: An SSI-recipient parent's $4,000 bank balance does not count. + period: 2025 + input: + people: + parent: + age: 35 + ssi: 11_000 + bank_account_assets: 4_000 + child: + age: 10 + spm_units: + spm_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + # 10A NCAC 71W .0407(b)(1): a parent's resources count unless the parent + # receives SSI. 4,000 - 4,000 = 0. + nc_tanf_countable_resources: 0 + nc_tanf_resources_eligible: true + +- name: An SSI-recipient child's $4,000 bank balance does not count. + period: 2025 + input: + people: + parent: + age: 35 + child1: + age: 8 + ssi: 11_000 + bank_account_assets: 4_000 + child2: + age: 5 + spm_units: + spm_unit: + members: [parent, child1, child2] + households: + household: + members: [parent, child1, child2] + state_code: NC + output: + # 10A NCAC 71W .0407(b)(2): a child's resources count only if the child + # is in the payment, and an SSI recipient is not. 4,000 - 4,000 = 0. + nc_tanf_countable_resources: 0 + nc_tanf_resources_eligible: true + +- name: Only the SSI recipient's share is removed when other members also hold assets. + period: 2025 + input: + people: + parent: + age: 35 + receives_ssi: true + bank_account_assets: 4_000 + stock_assets: 2_000 + child: + age: 10 + bank_account_assets: 3_500 + spm_units: + spm_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + # Total 4,000 + 2,000 + 3,500 = 9,500; the SSI parent's 6,000 is removed. + # 9,500 - 6,000 = 3,500, which exceeds the 3,000 limit. + nc_tanf_countable_resources: 3_500 + nc_tanf_resources_eligible: false diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.py b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.py index 7c0af25d0a9..a175ee809bd 100644 --- a/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.py +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/tanf/nc_tanf_countable_resources.py @@ -10,12 +10,39 @@ class nc_tanf_countable_resources(Variable): quantity_type = STOCK reference = ( "https://regulations.justia.com/states/north-carolina/title-10a/chapter-71/subchapter-w/section-0400/section-71w-0407", + # PDF pages 6-7 + "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=6", + # PDF pages 2-4 "https://policies.ncdhhs.gov/wp-content/uploads/wf115.pdf#page=2", ) defined_for = StateCode.NC def formula(spm_unit, period, parameters): - # Work First counts cash, checking and savings accounts, stocks, bonds and - # mutual fund shares; motor vehicles, real property and retirement accounts - # do not count (Work First Manual, section 115, II). - return spm_unit("spm_unit_cash_assets", period.this_year) + # 10A NCAC 71W .0407(d)-(e) and Work First Manual section 115, II: + # cash, checking and savings accounts, stocks, bonds and mutual fund + # shares count; motor vehicles, real property and retirement funds do + # not. Simplification: .0407(d)(3) and (e)(5) exclude the part of a + # checking account that is the month's income deposited for the + # family's needs, but bank_account_assets has no such split, so the + # full balance counts. + total = spm_unit("spm_unit_cash_assets", period) + # .0407(b)(1)-(2): a parent's resources count unless the parent + # receives SSI, and a child's count only if the child is in the + # payment, which SSI recipients are not (as in nc_tanf_household_size + # and the NC TANF income variables). The excluded members' share is + # subtracted from the unit aggregate, so households that supply only + # spm_unit_cash_assets keep the aggregate behavior. + # Limitation: .0407(b) does not list non-parent adults (for example a + # grandparent caretaker in a child-only case, an adult sibling or an + # unmarried partner), but their resources count here, as their income + # does in the NC TANF income variables. + person = spm_unit.members + is_ssi_recipient = (person("ssi", period) > 0) | person("receives_ssi", period) + # Read the components from spm_unit_cash_assets.adds, so the + # subtraction removes exactly the excluded members' share. + components = spm_unit.simulation.tax_benefit_system.get_variable( + "spm_unit_cash_assets" + ).adds + person_assets = add(person, period, components) + excluded_assets = spm_unit.sum(person_assets * is_ssi_recipient) + return max_(total - excluded_assets, 0)