diff --git a/changelog.d/nc-scca-need-test.fixed.md b/changelog.d/nc-scca-need-test.fixed.md new file mode 100644 index 00000000000..765cb44d8ab --- /dev/null +++ b/changelog.d/nc-scca-need-test.fixed.md @@ -0,0 +1 @@ +Require a need for care in North Carolina's Subsidized Child Care Assistance program under 10A NCAC 10 .0905. diff --git a/policyengine_us/parameters/gov/states/nc/ncdhhs/scca/need/earned_income_sources.yaml b/policyengine_us/parameters/gov/states/nc/ncdhhs/scca/need/earned_income_sources.yaml new file mode 100644 index 00000000000..609d98530e3 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nc/ncdhhs/scca/need/earned_income_sources.yaml @@ -0,0 +1,15 @@ +description: North Carolina counts these income sources as employment or self-employment under the Subsidized Child Care Assistance need for care. +values: + 2019-03-01: + - employment_income + - self_employment_income + - sstb_self_employment_income + - farm_operations_income + +metadata: + unit: list + period: year + label: North Carolina SCCA need for care earned income sources + reference: + - title: Subsidized Child Care Assistance Program Policy Manual, Chapter 5, section III.B, child care to support employment + href: https://policies.ncdhhs.gov/wp-content/uploads/Chapter-5-CN-25-3-Establishing-Need-and-Plan-of-Care.pdf#page=5 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/court_supervision.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/court_supervision.yaml index b5c222070f9..69ee7263f13 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/court_supervision.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/court_supervision.yaml @@ -112,6 +112,7 @@ spm_units: spm_unit: members: [person1, person2, person3] + nc_scca_need_eligible: true nc_scca_countable_income: 2_150 spm_unit_pre_subsidy_childcare_expenses: 120_000 households: diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/entry/nc_scca_entry_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/entry/nc_scca_entry_eligible.yaml index 359214ba0ea..61e73f04544 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/entry/nc_scca_entry_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/entry/nc_scca_entry_eligible.yaml @@ -9,6 +9,7 @@ spm_units: spm_unit: members: [person1, child1] + nc_scca_need_eligible: true households: household: members: [person1, child1] @@ -47,6 +48,7 @@ spm_units: spm_unit: members: [person1, child1, child2] + nc_scca_need_eligible: true households: household: members: [person1, child1, child2] @@ -71,3 +73,22 @@ state_code: SC output: nc_scca_entry_eligible: false + +- name: Case 5, an eligible child without a need for care, not entry eligible. + period: 2024-01 + input: + people: + person1: + nc_scca_child_eligible: false + child1: + nc_scca_child_eligible: true + spm_units: + spm_unit: + members: [person1, child1] + nc_scca_need_eligible: false + households: + household: + members: [person1, child1] + state_code: NC + output: + nc_scca_entry_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/entry/nc_scca_need_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/entry/nc_scca_need_eligible.yaml new file mode 100644 index 00000000000..62a43b9b002 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/entry/nc_scca_need_eligible.yaml @@ -0,0 +1,495 @@ +# 10A NCAC 10 .0905 and Subsidized Child Care Assistance Program Policy Manual, Chapter 5. +- name: Case 1, single parent with wages has a need for care. + period: 2026-01 + input: + people: + parent: + age: 30 + employment_income: 20_000 + child: + age: 3 + spm_units: + spm_unit: + members: [parent, child] + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + nc_scca_need_eligible: true + +- name: Case 2, single parent with no activity has no need for care. + period: 2026-01 + input: + people: + parent: + age: 30 + child: + age: 3 + spm_units: + spm_unit: + members: [parent, child] + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + nc_scca_need_eligible: false + +- name: Case 3, part-time wages below minimum wage still count, since Chapter 5 III.B sets no floor. + period: 2026-01 + input: + people: + parent: + age: 30 + employment_income: 2_000 + weekly_hours_worked_before_lsr: 40 + weeks_worked: 52 + child: + age: 3 + spm_units: + spm_unit: + members: [parent, child] + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + nc_scca_need_eligible: true + +- name: Case 4, self-employment income meets Chapter 5 III.B.4. + period: 2026-01 + input: + people: + parent: + age: 30 + self_employment_income: 15_000 + child: + age: 3 + spm_units: + spm_unit: + members: [parent, child] + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + nc_scca_need_eligible: true + +- name: Case 5, a business loss with no other activity does not. + period: 2026-01 + input: + people: + parent: + age: 30 + self_employment_income: -5_000 + child: + age: 3 + spm_units: + spm_unit: + members: [parent, child] + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + nc_scca_need_eligible: false + +- name: Case 6, a business loss does not erase wages. + period: 2026-01 + input: + people: + parent: + age: 30 + employment_income: 20_000 + self_employment_income: -30_000 + child: + age: 3 + spm_units: + spm_unit: + members: [parent, child] + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + nc_scca_need_eligible: true + +- name: Case 7, a full-time college student has a need under .0905(f). + period: 2026-01 + input: + people: + parent: + age: 30 + is_full_time_college_student: true + child: + age: 3 + spm_units: + spm_unit: + members: [parent, child] + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + nc_scca_need_eligible: true + +- name: Case 8, two parents who both work have a need under Chapter 5 III.A. + period: 2026-01 + input: + people: + parent1: + age: 30 + employment_income: 20_000 + parent2: + age: 30 + employment_income: 20_000 + child: + age: 3 + spm_units: + spm_unit: + members: [parent1, parent2, child] + tax_units: + tax_unit: + members: [parent1, parent2, child] + households: + household: + members: [parent1, parent2, child] + state_code: NC + output: + nc_scca_need_eligible: true + +- name: Case 9, one parent working and the other in school have a need under Chapter 5 III.A. + period: 2026-01 + input: + people: + parent1: + age: 30 + employment_income: 20_000 + parent2: + age: 30 + is_full_time_college_student: true + child: + age: 3 + spm_units: + spm_unit: + members: [parent1, parent2, child] + tax_units: + tax_unit: + members: [parent1, parent2, child] + households: + household: + members: [parent1, parent2, child] + state_code: NC + output: + nc_scca_need_eligible: true + +- name: Case 10, one parent working and the other at home and able to provide care have no need. + period: 2026-01 + input: + people: + parent1: + age: 30 + employment_income: 20_000 + parent2: + age: 30 + child: + age: 3 + spm_units: + spm_unit: + members: [parent1, parent2, child] + tax_units: + tax_unit: + members: [parent1, parent2, child] + households: + household: + members: [parent1, parent2, child] + state_code: NC + output: + nc_scca_need_eligible: false + +- name: Case 11, one parent working and the other disabled have a need under Chapter 5 III.A. + period: 2026-01 + input: + people: + parent1: + age: 30 + employment_income: 20_000 + parent2: + age: 30 + is_disabled: true + child: + age: 3 + spm_units: + spm_unit: + members: [parent1, parent2, child] + tax_units: + tax_unit: + members: [parent1, parent2, child] + households: + household: + members: [parent1, parent2, child] + state_code: NC + output: + nc_scca_need_eligible: true + +- name: Case 12, a single parent unable to work or provide care has a need under .0905(b). + period: 2026-01 + input: + people: + parent: + age: 30 + is_disabled: true + child: + age: 3 + spm_units: + spm_unit: + members: [parent, child] + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + nc_scca_need_eligible: true + +- name: Case 13, an incarcerated parent is out of the household, so the working parent has a need. + period: 2026-01 + input: + people: + parent1: + age: 30 + employment_income: 20_000 + parent2: + age: 30 + is_incarcerated: true + child: + age: 3 + spm_units: + spm_unit: + members: [parent1, parent2, child] + tax_units: + tax_unit: + members: [parent1, parent2, child] + households: + household: + members: [parent1, parent2, child] + state_code: NC + output: + nc_scca_need_eligible: true + +- name: Case 14, a retired grandparent who files separately does not block the working parent's need. + period: 2026-01 + input: + people: + grandparent: + age: 70 + parent: + age: 30 + employment_income: 20_000 + child: + age: 3 + spm_units: + spm_unit: + members: [grandparent, parent, child] + tax_units: + grandparent_tax_unit: + members: [grandparent] + parent_tax_unit: + members: [parent, child] + households: + household: + members: [grandparent, parent, child] + state_code: NC + output: + nc_scca_need_eligible: true + +- name: Case 15, a working grandparent who files separately does not supply the parent's need. + period: 2026-01 + input: + people: + grandparent: + age: 60 + employment_income: 50_000 + parent: + age: 30 + child: + age: 3 + spm_units: + spm_unit: + members: [grandparent, parent, child] + tax_units: + grandparent_tax_unit: + members: [grandparent] + parent_tax_unit: + members: [parent, child] + households: + household: + members: [grandparent, parent, child] + state_code: NC + output: + nc_scca_need_eligible: false + +- name: Case 16, Work First enrollment meets Chapter 5 III.B.5. + period: 2026-01 + input: + people: + parent: + age: 30 + child: + age: 3 + spm_units: + spm_unit: + members: [parent, child] + is_tanf_enrolled: true + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + nc_scca_need_eligible: true + +- name: Case 17, a disabled eligible child has a developmental need under Chapter 5 III.D. + period: 2026-01 + input: + people: + parent: + age: 30 + child: + age: 3 + is_disabled: true + spm_units: + spm_unit: + members: [parent, child] + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + nc_scca_need_eligible: true + +- name: Case 18, an eligible child receiving Child Protective Services has a need under Chapter 5 III.E. + period: 2026-01 + input: + people: + parent: + age: 30 + child: + age: 3 + receives_or_needs_protective_services: true + spm_units: + spm_unit: + members: [parent, child] + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + nc_scca_need_eligible: true + +- name: Case 19, the activity input covers job search and other needs the model does not track. + period: 2026-01 + input: + people: + parent: + age: 30 + child: + age: 3 + spm_units: + spm_unit: + members: [parent, child] + meets_ccdf_activity_test: true + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + output: + nc_scca_need_eligible: true + +- name: Case 20, a working parent with an eligible child receives the subsidy through the full pipeline. + period: 2026-01 + input: + people: + parent: + age: 30 + employment_income: 30_000 + child: + age: 3 + spm_units: + spm_unit: + members: [parent, child] + spm_unit_pre_subsidy_childcare_expenses: 12_000 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + county_str: WAKE_COUNTY_NC + output: + nc_scca_need_eligible: true + nc_scca_entry_eligible: true + # $1,000 a month less the 10% fee on $2,500, which the $1,256 market rate leaves uncapped. + nc_scca: 750 + +- name: Case 21, the same family with the same income from a pension receives nothing. + period: 2026-01 + input: + people: + parent: + age: 30 + pension_income: 30_000 + child: + age: 3 + spm_units: + spm_unit: + members: [parent, child] + spm_unit_pre_subsidy_childcare_expenses: 12_000 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NC + county_str: WAKE_COUNTY_NC + output: + nc_scca_need_eligible: false + nc_scca_entry_eligible: false + nc_scca: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/integration.yaml index f72d862bdd9..1269041a0d1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/integration.yaml @@ -122,6 +122,7 @@ spm_unit: nc_scca_countable_income: 5_199 members: [person1, person2, child1, child2] + nc_scca_need_eligible: true spm_unit_pre_subsidy_childcare_expenses: 120_000 households: household: @@ -154,6 +155,7 @@ spm_unit: nc_scca_countable_income: 5_199 members: [person1, person2, child1, child2] + nc_scca_need_eligible: true spm_unit_pre_subsidy_childcare_expenses: 120_000 households: household: diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/nc_scca_child_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/nc_scca_child_eligible.yaml index 5ba72c53b0b..4e001ab3603 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/nc_scca_child_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/nc_scca_child_eligible.yaml @@ -38,6 +38,7 @@ spm_units: spm_unit: members: [person1, child1, child2] + nc_scca_need_eligible: true # 2026 FPG for 3 = 27_320/yr = 2_276.67/mo. # 133% = 3_027.97 < 4_500 (age 7 fails); 200% = 4_553.33 > 4_500 (age 4 passes). nc_scca_countable_income: 4_500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/nc_scca_maximum_payment.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/nc_scca_maximum_payment.yaml index 0efd7c92c10..8c030c1badb 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/nc_scca_maximum_payment.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/scca/nc_scca_maximum_payment.yaml @@ -20,6 +20,7 @@ spm_unit: nc_scca_countable_income: 51_99 members: [person1, person2, child1, child2] + nc_scca_need_eligible: true households: household: members: [person1, person2, child1, child2] @@ -96,6 +97,7 @@ # 2026 FPG for 3 = 2_276.67/mo; 133% = 3_027.97, 200% = 4_553.33. nc_scca_countable_income: 4_500 members: [person1, child1, child2] + nc_scca_need_eligible: true households: household: members: [person1, child1, child2] @@ -132,6 +134,7 @@ # 2026 FPG for 4 = 2_750/mo; 133% = 3_657.50, 200% = 5_500. nc_scca_countable_income: 4_125 members: [person1, child1, child2, child3] + nc_scca_need_eligible: true households: household: members: [person1, child1, child2, child3] diff --git a/policyengine_us/tests/test_nc_scca_need_properties.py b/policyengine_us/tests/test_nc_scca_need_properties.py new file mode 100644 index 00000000000..7c4f023da1b --- /dev/null +++ b/policyengine_us/tests/test_nc_scca_need_properties.py @@ -0,0 +1,168 @@ +"""Invariants of North Carolina's need for child care. + +10A NCAC 10 .0905 and Chapter 5 of the Subsidized Child Care Assistance Program +Policy Manual say which families have a need. + +1. Without the other paths, a family has a need when each parent or responsible + adult works, studies, or can't work and provide care (.0905(a), (b) and (f); + Chapter 5 III.A). Any positive earnings count. +2. No family without a need receives a subsidy: nc_scca_entry_eligible implies + nc_scca_need_eligible, and nc_scca is zero without it. +3. Giving an adult earnings never removes the need. +4. An adult who files separately and claims no eligible child doesn't change the + answer, whatever that adult earns. +5. The other paths each give a need to a family with no activity (Chapter 5 III.B.5, + III.D and III.E, and the activity input). + +The YAML cases pin single families. These generate earnings and family makeups, +which YAML can't express. Tests of the need itself fix the child as eligible, so +an income above the program's limit can't empty the test. +""" + +import pytest +from hypothesis import example, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation + +YEAR = 2026 +MONTH = "2026-01" + +adults = st.fixed_dictionaries( + { + "wages": st.integers(min_value=0, max_value=60_000), + "business": st.integers(min_value=-30_000, max_value=60_000), + "student": st.booleans(), + "disabled": st.booleans(), + } +) + +IDLE = {"wages": 0, "business": 0, "student": False, "disabled": False} +WORKING = {"wages": 20_000, "business": 0, "student": False, "disabled": False} +ELIGIBLE_CHILD = {"nc_scca_child_eligible": {MONTH: True}} + + +def person_input(adult): + return { + "age": {YEAR: 30}, + "employment_income": {YEAR: adult["wages"]}, + "self_employment_income": {YEAR: adult["business"]}, + "is_full_time_college_student": {YEAR: adult["student"]}, + "is_disabled": {YEAR: adult["disabled"]}, + } + + +def simulate(adults, spm_unit=None, other_adult=None, child=ELIGIBLE_CHILD): + names = [f"adult{index}" for index in range(len(adults))] + people = {name: person_input(item) for name, item in zip(names, adults)} + people["child"] = {"age": {YEAR: 3}, **child} + family = names + ["child"] + tax_units = {"claiming": {"members": family}} + members = list(family) + if other_adult is not None: + people["other_adult"] = {"age": {YEAR: 60}, **other_adult} + tax_units["other"] = {"members": ["other_adult"]} + members.append("other_adult") + situation = { + "people": people, + "tax_units": tax_units, + "spm_units": { + "spm_unit": { + "members": members, + "spm_unit_pre_subsidy_childcare_expenses": {YEAR: 12_000}, + **(spm_unit or {}), + } + }, + "families": {"family": {"members": members}}, + "marital_units": {name: {"members": [name]} for name in people}, + "households": { + "household": { + "members": members, + "state_name": {YEAR: "NC"}, + "county_str": {YEAR: "WAKE_COUNTY_NC"}, + } + }, + } + return Simulation(situation=situation) + + +def has_need(simulation): + return bool(simulation.calculate("nc_scca_need_eligible", MONTH)[0]) + + +def adult_has_need(adult): + earns = adult["wages"] > 0 or adult["business"] > 0 + return earns or adult["student"] or adult["disabled"] + + +@settings(max_examples=25, deadline=None) +@given(family=st.lists(adults, min_size=1, max_size=2)) +# One adult with no activity, alone and beside a working adult; a single adult who +# can't work or provide care; and a business loss beside wages. +@example(family=[IDLE]) +@example(family=[WORKING, IDLE]) +@example(family=[{**IDLE, "disabled": True}]) +@example(family=[{**WORKING, "business": -30_000}]) +def test_need_follows_the_rule(family): + simulation = simulate(family) + assert has_need(simulation) == all(adult_has_need(item) for item in family) + + +@settings(max_examples=15, deadline=None) +@given(family=st.lists(adults, min_size=1, max_size=2)) +def test_no_family_without_a_need_receives_a_subsidy(family): + # This test runs the child's own age and income tests. + simulation = simulate(family, child={}) + entry_eligible = bool(simulation.calculate("nc_scca_entry_eligible", MONTH)[0]) + subsidy = float(simulation.calculate("nc_scca", MONTH)[0]) + if entry_eligible: + assert has_need(simulation) + if not entry_eligible: + assert subsidy == 0 + if not has_need(simulation): + assert not entry_eligible + assert subsidy >= 0 + + +@settings(max_examples=15, deadline=None) +@given( + family=st.lists(adults, min_size=1, max_size=2), + raise_by=st.integers(min_value=1, max_value=40_000), +) +def test_earnings_never_remove_the_need(family, raise_by): + before = has_need(simulate(family)) + raised = [{**family[0], "wages": family[0]["wages"] + raise_by}] + family[1:] + assert has_need(simulate(raised)) >= before + + +@settings(max_examples=15, deadline=None) +@given( + family=st.lists(adults, min_size=1, max_size=2), + other_earnings=st.integers(min_value=0, max_value=60_000), +) +def test_a_separate_filer_does_not_change_the_answer(family, other_earnings): + alone = has_need(simulate(family)) + beside = simulate(family, other_adult={"employment_income": {YEAR: other_earnings}}) + assert has_need(beside) == alone + + +def test_a_family_with_no_activity_has_no_need_without_another_path(): + assert not has_need(simulate([IDLE])) + + +@pytest.mark.parametrize( + "path", + [ + {"spm_unit": {"is_tanf_enrolled": {MONTH: True}}}, + {"spm_unit": {"meets_ccdf_activity_test": {YEAR: True}}}, + {"child": {**ELIGIBLE_CHILD, "is_disabled": {YEAR: True}}}, + { + "child": { + **ELIGIBLE_CHILD, + "receives_or_needs_protective_services": {YEAR: True}, + } + }, + ], +) +def test_each_other_path_gives_a_family_with_no_activity_a_need(path): + assert has_need(simulate([IDLE], **path)) diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/scca/entry/nc_scca_entry_eligible.py b/policyengine_us/variables/gov/states/nc/ncdhhs/scca/entry/nc_scca_entry_eligible.py index 10d84548f59..d7b34f38117 100644 --- a/policyengine_us/variables/gov/states/nc/ncdhhs/scca/entry/nc_scca_entry_eligible.py +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/scca/entry/nc_scca_entry_eligible.py @@ -7,9 +7,13 @@ class nc_scca_entry_eligible(Variable): label = ( "North Carolina entry eligibility for Subsidized Child Care Assistance Program" ) - reference = "https://policies.ncdhhs.gov/wp-content/uploads/FINAL-Chapter-7-Family-definition-and-determining-income-eligibility-08-05-24.pdf#page=2" + reference = ( + "https://policies.ncdhhs.gov/wp-content/uploads/FINAL-Chapter-7-Family-definition-and-determining-income-eligibility-08-05-24.pdf#page=2", + "https://policies.ncdhhs.gov/wp-content/uploads/Chapter-5-CN-25-3-Establishing-Need-and-Plan-of-Care.pdf#page=2", + ) definition_period = MONTH defined_for = StateCode.NC def formula(spm_unit, period, parameters): - return add(spm_unit, period, ["nc_scca_child_eligible"]) > 0 + has_eligible_child = add(spm_unit, period, ["nc_scca_child_eligible"]) > 0 + return has_eligible_child & spm_unit("nc_scca_need_eligible", period) diff --git a/policyengine_us/variables/gov/states/nc/ncdhhs/scca/entry/nc_scca_need_eligible.py b/policyengine_us/variables/gov/states/nc/ncdhhs/scca/entry/nc_scca_need_eligible.py new file mode 100644 index 00000000000..9e2954e073a --- /dev/null +++ b/policyengine_us/variables/gov/states/nc/ncdhhs/scca/entry/nc_scca_need_eligible.py @@ -0,0 +1,76 @@ +from policyengine_us.model_api import * + + +class nc_scca_need_eligible(Variable): + value_type = bool + entity = SPMUnit + label = "Meets the North Carolina Subsidized Child Care Assistance need for care" + definition_period = MONTH + defined_for = StateCode.NC + reference = ( + # 10A NCAC 10 .0905: employment, training, and recipients unable to work + # or train and incapable of providing care. + "http://reports.oah.state.nc.us/ncac/title%2010a%20-%20health%20and%20human%20services/chapter%2010%20-%20subsidized%20child%20care/10a%20ncac%2010%20.0905.pdf", + # Policy Manual Chapter 5, section II: the five need categories. + "https://policies.ncdhhs.gov/wp-content/uploads/Chapter-5-CN-25-3-Establishing-Need-and-Plan-of-Care.pdf#page=2", + # Section III.A: both parents or responsible adults must have a need. + "https://policies.ncdhhs.gov/wp-content/uploads/Chapter-5-CN-25-3-Establishing-Need-and-Plan-of-Care.pdf#page=4", + # Section III.B.5: Work First. + "https://policies.ncdhhs.gov/wp-content/uploads/Chapter-5-CN-25-3-Establishing-Need-and-Plan-of-Care.pdf#page=8", + # Section III.C: education. + "https://policies.ncdhhs.gov/wp-content/uploads/Chapter-5-CN-25-3-Establishing-Need-and-Plan-of-Care.pdf#page=9", + # Section III.D: developmental needs of the child. + "https://policies.ncdhhs.gov/wp-content/uploads/Chapter-5-CN-25-3-Establishing-Need-and-Plan-of-Care.pdf#page=12", + # Section III.E: Child Protective Services. + "https://policies.ncdhhs.gov/wp-content/uploads/Chapter-5-CN-25-3-Establishing-Need-and-Plan-of-Care.pdf#page=14", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nc.ncdhhs.scca.need + person = spm_unit.members + year = period.this_year + # Section III.B sets no minimum hours for employment, and a self-employed + # person short of minimum wage gets fewer authorized hours under III.B.4, + # still with a need for care. Any positive earnings count. + employed = np.any( + [person(source, year) > 0 for source in p.earned_income_sources], + axis=0, + ) + # .0905(f) covers high school, basic education and post-secondary or skills + # training. The model can't see the 20-month limit or graduate study. + student = person("is_full_time_student", year) + # .0905(b) pays for recipients unable to work or train and incapable of + # providing care, including a recipient with no other adult at home. + # Disability stands in for the agency's determination. + unable_to_provide_care = person("is_disabled", year) | person( + "is_incapable_of_self_care", year + ) + has_need = employed | student | unable_to_provide_care + # Sections III and III.A require a need of both parents or responsible + # adults in the household. Under section III, another adult in the home + # blocks care only when willing and able to provide it, which the model + # can't see, so the filers who claim an eligible child stand in for the + # responsible adults. An incarcerated adult isn't in the household. + eligible_child = person("nc_scca_child_eligible", period) + claims_eligible_child = person.tax_unit.sum(eligible_child) > 0 + responsible_adult = ( + person("is_tax_unit_head_or_spouse", year) + & claims_eligible_child + & ~person("is_incarcerated", period) + ) + adults_have_need = spm_unit.sum(responsible_adult & ~has_need) == 0 + # Section III.B.5 serves Work First participants through their approved + # employment and training plan. Enrollment stands in for that plan. + work_first = spm_unit("is_tanf_enrolled", period) + # Section III.D serves a child whose development is delayed or at risk of + # delay, and section III.E a child receiving Child Protective Services. + # Disability stands in for the documented delay. + child_need = person("is_disabled", year) | person( + "receives_or_needs_protective_services", year + ) + has_child_need = spm_unit.any(eligible_child & child_need) + # This input covers job search under .0905(d), absences under .0905(c) and + # (e), part-time study, an elderly recipient under .0905(b)(6), for which + # the rule sets no age, and Child Welfare Services under section III.F. + other_need = spm_unit("meets_ccdf_activity_test", year) + return adults_have_need | work_first | has_child_need | other_need