From 32b9eb419889768a00559c69b0bcfdf57c487410 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 11 Nov 2026 19:19:34 -0500 Subject: [PATCH 1/4] Add North Dakota CCAP income and co-payment waivers Co-Authored-By: Claude Opus 5.5 --- changelog.d/nd-ccap-waivers.added.md | 1 + policyengine_us/programs.yaml | 2 +- .../core/test_nd_ccap_waivers_invariants.py | 217 ++++++ .../nd_ccap_categorical_copay_waiver.yaml | 44 ++ .../nd/dhs/ccap/copay/nd_ccap_copay.yaml | 730 +++++++++++++++++- .../eligibility/nd_ccap_income_eligible.yaml | 184 ++++- .../nd_ccap_workforce_benefit_eligible.yaml | 56 ++ .../dhs/ccap/copay/nd_ccap_arsen_enrolled.py | 19 + .../copay/nd_ccap_categorical_copay_waiver.py | 25 + .../states/nd/dhs/ccap/copay/nd_ccap_copay.py | 25 +- .../ccap/copay/nd_ccap_crossroads_enrolled.py | 15 + ...d_ccap_has_disqualified_alien_caretaker.py | 15 + ...d_ccap_has_ineligible_kinship_caretaker.py | 16 + ...gible_non_legally_responsible_caretaker.py | 17 + .../copay/nd_ccap_has_tanf_eligible_child.py | 15 + .../copay/nd_ccap_tanf_copay_exception.py | 50 ++ .../copay/nd_ccap_tanf_diversion_enrolled.py | 19 + .../eligibility/nd_ccap_income_eligible.py | 15 +- .../nd_ccap_workforce_benefit_eligible.py | 15 + ..._ccap_workforce_benefit_eligible_parent.py | 37 + 20 files changed, 1502 insertions(+), 15 deletions(-) create mode 100644 changelog.d/nd-ccap-waivers.added.md create mode 100644 policyengine_us/tests/core/test_nd_ccap_waivers_invariants.py create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_categorical_copay_waiver.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.yaml create mode 100644 policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_arsen_enrolled.py create mode 100644 policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_categorical_copay_waiver.py create mode 100644 policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_crossroads_enrolled.py create mode 100644 policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_disqualified_alien_caretaker.py create mode 100644 policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_ineligible_kinship_caretaker.py create mode 100644 policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_ineligible_non_legally_responsible_caretaker.py create mode 100644 policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_tanf_eligible_child.py create mode 100644 policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.py create mode 100644 policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_diversion_enrolled.py create mode 100644 policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.py create mode 100644 policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible_parent.py diff --git a/changelog.d/nd-ccap-waivers.added.md b/changelog.d/nd-ccap-waivers.added.md new file mode 100644 index 00000000000..76d625f3cdf --- /dev/null +++ b/changelog.d/nd-ccap-waivers.added.md @@ -0,0 +1 @@ +Added North Dakota CCAP Workforce Benefit, program co-payment waivers, and TANF co-payment exceptions. diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 0f74596fd68..a365d06fae9 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -965,7 +965,7 @@ programs: full_name: North Dakota Child Care Assistance Program variable: nd_ccap parameter_prefix: gov.states.nd.dhs.ccap - notes: Models the initial (75% of SMI) and continuing (85% of SMI) income limits, the SMI-band co-payment schedule with the 30%-of-SMI and TANF waivers, and the federal family-share ceiling. Not modeled - the CCAP Workforce Benefit income-limit and co-payment waiver for early-childhood workforce families, and the Diversion and Crossroads co-payment waivers + notes: Models the initial (75% of SMI) and continuing (85% of SMI) income limits, the SMI-band co-payment schedule with the 30%-of-SMI and TANF waivers, and the federal family-share ceiling. Also models the Workforce Benefit income-limit and co-payment waiver, Crossroads, Diversion and ARSEN co-payment waivers, and TANF and Diversion co-payment exceptions. - state: NM status: complete name: New Mexico CCAP diff --git a/policyengine_us/tests/core/test_nd_ccap_waivers_invariants.py b/policyengine_us/tests/core/test_nd_ccap_waivers_invariants.py new file mode 100644 index 00000000000..8232e89dc00 --- /dev/null +++ b/policyengine_us/tests/core/test_nd_ccap_waivers_invariants.py @@ -0,0 +1,217 @@ +import numpy as np +import pytest +from hypothesis import example, given, settings, strategies as st + +from policyengine_us import Simulation + + +CASE_COUNT = 12 +WAIVER_INPUTS = ( + "nd_ccap_crossroads_enrolled", + "nd_ccap_tanf_diversion_enrolled", + "nd_ccap_arsen_enrolled", +) +CARETAKER_INPUTS = ( + "nd_ccap_has_disqualified_alien_caretaker", + "nd_ccap_has_ineligible_non_legally_responsible_caretaker", + "nd_ccap_has_ineligible_kinship_caretaker", +) + + +@pytest.fixture(scope="module") +def simulation(): + situation = { + entity: {} + for entity in ( + "people", + "tax_units", + "spm_units", + "families", + "marital_units", + "households", + ) + } + for index in range(CASE_COUNT): + parent = f"parent_{index}" + child = f"child_{index}" + situation["people"][parent] = {} + situation["people"][child] = {} + members = [parent, child] + for entity in ("tax_units", "spm_units", "families", "households"): + situation[entity][f"unit_{index}"] = {"members": members} + for person in members: + situation["marital_units"][person] = {"members": [person]} + return Simulation(situation=situation) + + +@settings(max_examples=8, deadline=None, derandomize=True) +@example( + period="2026-10", + income=4_500, + monthly_smi=10_000, + tanf=False, + enrolled=False, + child_ssi=True, + caretakers=(False, False, False), + tanf_eligible_child=False, +) +@example( + period="2026-10", + income=5_000, + monthly_smi=10_000, + tanf=True, + enrolled=False, + child_ssi=True, + caretakers=(True, True, True), + tanf_eligible_child=False, +) +@example( + period="2026-09", + income=50_000, + monthly_smi=10_000, + tanf=True, + enrolled=True, + child_ssi=True, + caretakers=(False, False, True), + tanf_eligible_child=True, +) +@example( + period="2024-07", + income=-1_000, + monthly_smi=10_000, + tanf=False, + enrolled=False, + child_ssi=False, + caretakers=(False, False, False), + tanf_eligible_child=False, +) +@given( + period=st.sampled_from(("2024-07", "2025-10", "2026-09", "2026-10")), + income=st.integers(min_value=-100_000, max_value=1_000_000_000), + monthly_smi=st.integers(min_value=0, max_value=25_000), + tanf=st.booleans(), + enrolled=st.booleans(), + child_ssi=st.booleans(), + caretakers=st.tuples(st.booleans(), st.booleans(), st.booleans()), + tanf_eligible_child=st.booleans(), +) +def test_nd_ccap_waivers_preserve_bounds_and_default_behavior( + simulation, + period, + income, + monthly_smi, + tanf, + enrolled, + child_ssi, + caretakers, + tanf_eligible_child, +): + year = period[:4] + simulation.drop_computed_arrays() + simulation.set_input("state_code", year, ["ND"] * CASE_COUNT) + simulation.set_input("age", year, [30, 3] * CASE_COUNT) + simulation.set_input("is_disabled", year, [False] * (2 * CASE_COUNT)) + simulation.set_input("hhs_smi", year, [monthly_smi * 12] * CASE_COUNT) + simulation.set_input("nd_ccap_countable_income", period, [income] * CASE_COUNT) + # Isolate the co-payment calculation from the other eligibility tests. + simulation.set_input("nd_ccap_eligible", period, [True] * CASE_COUNT) + simulation.set_input("is_tanf_enrolled", period, [tanf] * 10 + [True, True]) + simulation.set_input( + "is_nd_ccap_enrolled", + period, + [enrolled] * 4 + [False, True] + [enrolled] * 6, + ) + + workforce = np.zeros(2 * CASE_COUNT, dtype=bool) + workforce[[8, 10, 18]] = True + simulation.set_input("nd_ccap_workforce_benefit_eligible_parent", period, workforce) + for variable, waived_case in zip(WAIVER_INPUTS, (6, 7, 8)): + values = np.zeros(CASE_COUNT, dtype=bool) + values[[waived_case, 9]] = True + simulation.set_input(variable, period, values) + + for variable, caretaker in zip(CARETAKER_INPUTS, caretakers): + values = [False] * 3 + [caretaker] * 7 + [False, False] + if variable == "nd_ccap_has_ineligible_kinship_caretaker": + values[-2:] = [True, True] + simulation.set_input(variable, period, values) + simulation.set_input( + "nd_ccap_has_tanf_eligible_child", + period, + [False] * 3 + [tanf_eligible_child] * 7 + [False, True], + ) + + ssi = np.zeros(2 * CASE_COUNT) + ssi[3] = 1 + ssi[4] = 1 + for index in range(3, 10): + ssi[2 * index + 1] = int(child_ssi) + simulation.set_input("ssi", period, ssi) + + inputs = ( + "nd_ccap_workforce_benefit_eligible_parent", + *WAIVER_INPUTS, + *CARETAKER_INPUTS, + "nd_ccap_has_tanf_eligible_child", + ) + for variable in inputs: + assert simulation.tax_benefit_system.variables[variable].default_value is False + + countable_income = simulation.calculate("nd_ccap_countable_income", period) + smi = simulation.calculate("hhs_smi", period) + positive_income = np.maximum(countable_income, 0) + income_to_smi = np.divide( + positive_income, + smi, + out=np.zeros_like(positive_income), + where=smi > 0, + ) + p = simulation.tax_benefit_system.parameters(period).gov.states.nd.dhs.ccap + unwaived = np.minimum( + p.copay.rate.calc(income_to_smi) * smi, + p.copay.max_rate * positive_income, + ) + copay = simulation.calculate("nd_ccap_copay", period) + assert np.all(copay >= 0) + assert np.all(copay <= unwaived + 0.01) + np.testing.assert_allclose(copay[[4, 5, 6, 8, 9]], 0, atol=0.01) + assert np.all(copay[4:10] <= copay[3] + 0.01) + kinship_exception = caretakers[2] and not ( + period >= "2026-10" and tanf_eligible_child + ) + diversion_exception = ( + caretakers[0] + or caretakers[1] + or kinship_exception + or (period >= "2026-10" and child_ssi) + ) + expected_diversion_copay = ( + unwaived[7] + if diversion_exception and income_to_smi[7] > p.copay.waiver_smi_threshold + else 0 + ) + np.testing.assert_allclose(copay[7], expected_diversion_copay, atol=0.01) + assert copay[11] <= copay[10] + 0.01 + if period >= "2026-10": + assert copay[11] == 0 + else: + assert copay[11] == copay[10] + + # Preserve main's calculation unless a TANF child receives SSI from October 2026. + expected = np.where( + (income_to_smi[:3] <= p.copay.waiver_smi_threshold) | tanf, + 0, + unwaived[:3], + ) + if period >= "2026-10" and tanf: + expected[1] = ( + 0 if income_to_smi[1] <= p.copay.waiver_smi_threshold else unwaived[1] + ) + np.testing.assert_allclose(copay[:3], expected, atol=0.01) + + income_eligible = simulation.calculate("nd_ccap_income_eligible", period) + rate = p.income.continuing_smi_rate if enrolled else p.income.initial_smi_rate + np.testing.assert_array_equal( + income_eligible[:3], countable_income[:3] <= smi[:3] * rate + ) + assert np.all(income_eligible[4:6]) diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_categorical_copay_waiver.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_categorical_copay_waiver.yaml new file mode 100644 index 00000000000..670b23d7c9d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_categorical_copay_waiver.yaml @@ -0,0 +1,44 @@ +# Tests for nd_ccap_categorical_copay_waiver (SPMUnit, MONTH, bool). +# North Dakota added the ARSEN waiver on April 1, 2021. + +# North Dakota starts the ARSEN categorical waiver on April 1, 2021. +- name: North Dakota withholds the ARSEN categorical waiver before April 2021 + period: 2021-03 + input: + people: + parent: + age: 30 + child: + age: 3 + spm_units: + family: + members: [parent, child] + is_tanf_enrolled: false + nd_ccap_arsen_enrolled: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_categorical_copay_waiver: false + +# North Dakota starts the ARSEN categorical waiver on April 1, 2021. +- name: North Dakota starts the ARSEN categorical waiver in April 2021 + period: 2021-04 + input: + people: + parent: + age: 30 + child: + age: 3 + spm_units: + family: + members: [parent, child] + is_tanf_enrolled: false + nd_ccap_arsen_enrolled: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_categorical_copay_waiver: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml index 6b5973c305d..a4fbeb0c53b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml @@ -1,4 +1,5 @@ # Tests for nd_ccap_copay (SPMUnit, MONTH, USD). +# The original worksheet cases below check SMI bands and the TANF waiver. # The family monthly co-payment is a flat amount per SMI band from the HHS # "Individual Worksheet for Calculating Co-pay": copay = share(income/SMI) x # monthly SMI, where the band share = band rate (2% for 30-50%, 4% for 50-60%, @@ -6,7 +7,7 @@ # TANF recipients (400-28-90-10/-20). The rate structure was validated against # the worksheet for household sizes 1-8 on 2026-06-26. # -# Period is 2025-01: at that period PolicyEngine's hhs_smi equals the real +# The original worksheet cases use 2025-01, when hhs_smi equals the real # published FFY2025 SMI exactly (the uprating projection only applies after the # last published value, 2025-10-01), so these cases use real-world SMI rather # than the uprated calendar-2026 projection. @@ -380,3 +381,730 @@ output: # share 0.0474 * 8,616.44 = 408.42 nd_ccap_copay: 408.42 + +# The additional waiver cases set annual SMI directly to 120,000. +# Monthly SMI = 120,000 / 12 = 10,000. +# At monthly income 4,500, the worksheet applies the 44-46 percent SMI band: +# ordinary copay = 2 percent * 44 percent * 10,000 = 88. +# Set eligibility directly in these copay cases to isolate waiver decisions. + +# Monthly SMI = 120,000 / 12 = 10,000; income / SMI = 0.45. +# Copay = 0.02 * 0.44 * 10,000 = 88. +- name: North Dakota charges the worksheet copay when new inputs retain their defaults + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: false + nd_ccap_eligible: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_workforce_benefit_eligible: false + nd_ccap_copay: 88 + +# The ordinary copay = 0.02 * 0.44 * 10,000 = 88; North Dakota waives it. +- name: North Dakota waives the copay for a Workforce Benefit family + period: 2024-07 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + nd_ccap_workforce_benefit_eligible_parent: true + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2024: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: false + nd_ccap_eligible: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_workforce_benefit_eligible: true + nd_ccap_copay: 0 + +# The ordinary copay = 0.02 * 0.44 * 10,000 = 88; North Dakota waives it. +- name: North Dakota waives the copay for a family receiving Crossroads + period: 2024-07 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2024: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: false + nd_ccap_eligible: true + nd_ccap_crossroads_enrolled: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 0 + +# The ordinary copay = 0.02 * 0.44 * 10,000 = 88; North Dakota waives it. +- name: North Dakota waives the copay for a family receiving Diversion + period: 2024-07 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2024: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: false + nd_ccap_eligible: true + nd_ccap_tanf_diversion_enrolled: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 0 + +# The ordinary copay = 0.02 * 0.44 * 10,000 = 88; North Dakota waives it. +- name: North Dakota waives the copay for a family receiving ARSEN + period: 2024-07 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2024: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: false + nd_ccap_eligible: true + nd_ccap_arsen_enrolled: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 0 + +# The ordinary copay = 0.02 * 0.44 * 10,000 = 88; North Dakota waives it. +- name: North Dakota retains the TANF copay waiver without an exception + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 0 + +# The child receives 100 in monthly SSI. +# The ordinary copay = 0.02 * 0.44 * 10,000 = 88. +- name: North Dakota retains the TANF waiver for a child receiving SSI before October 2026 + period: 2026-09 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 100 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 0 + +# The child receives 100 in monthly SSI. +# The ordinary copay = 0.02 * 0.44 * 10,000 = 88. +- name: North Dakota charges the TANF copay for a child receiving SSI from October 2026 + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 100 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 88 + +# The parent receives 100 in monthly SSI; the child receives zero. +# North Dakota waives the ordinary copay of 88. +- name: North Dakota retains the TANF copay waiver when only an adult receives SSI + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 100 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 0 + +# Copay = 0.02 * 0.44 * 10,000 = 88. +- name: North Dakota charges the TANF copay for a disqualified alien caretaker + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + nd_ccap_has_disqualified_alien_caretaker: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 88 + +# Copay = 0.02 * 0.44 * 10,000 = 88. +- name: North Dakota charges the TANF copay for an ineligible non-legally responsible caretaker + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + nd_ccap_has_ineligible_non_legally_responsible_caretaker: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 88 + +# Copay = 0.02 * 0.44 * 10,000 = 88. +- name: North Dakota charges the TANF copay for an ineligible kinship caretaker with non-TANF children + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + nd_ccap_has_ineligible_kinship_caretaker: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 88 + +# A TANF-eligible non-kinship child preserves the waiver of the ordinary copay of 88. +- name: North Dakota preserves the kinship waiver when a non-kinship child qualifies for TANF + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + nd_ccap_has_ineligible_kinship_caretaker: true + nd_ccap_has_tanf_eligible_child: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 0 + +# The TANF-eligible-child exception applies to kinship caretakers. +# Copay = 0.02 * 0.44 * 10,000 = 88. +- name: North Dakota charges an alien caretaker despite a TANF-eligible child + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + nd_ccap_has_disqualified_alien_caretaker: true + nd_ccap_has_tanf_eligible_child: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 88 + +# Monthly SMI = 120,000 / 12 = 10,000; 3,000 / 10,000 = 0.30. +# North Dakota waives the copay at the 30 percent boundary. +- name: North Dakota waives the copay at 30 percent of SMI despite TANF exceptions + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 100 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 3000 + is_tanf_enrolled: true + nd_ccap_eligible: true + nd_ccap_has_disqualified_alien_caretaker: true + nd_ccap_has_ineligible_non_legally_responsible_caretaker: true + nd_ccap_has_ineligible_kinship_caretaker: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 0 + +# Crossroads, ARSEN and Workforce families retain independent waivers. +# North Dakota charges the Diversion family for a disqualified alien caretaker. +# The Diversion copay = 0.02 * 0.44 * 10,000 = 88. +- name: North Dakota applies TANF exceptions to Diversion and retains independent program waivers + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + crossroads_parent: + age: 30 + ssi: 0 + crossroads_child: + age: 3 + ssi: 100 + diversion_parent: + age: 30 + ssi: 0 + diversion_child: + age: 3 + ssi: 0 + arsen_parent: + age: 30 + ssi: 0 + arsen_child: + age: 3 + ssi: 0 + workforce_parent: + age: 30 + ssi: 0 + nd_ccap_workforce_benefit_eligible_parent: true + workforce_child: + age: 3 + ssi: 0 + spm_units: + crossroads: + members: [crossroads_parent, crossroads_child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + nd_ccap_crossroads_enrolled: true + diversion: + members: [diversion_parent, diversion_child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: false + nd_ccap_eligible: true + nd_ccap_tanf_diversion_enrolled: true + nd_ccap_has_disqualified_alien_caretaker: true + arsen: + members: [arsen_parent, arsen_child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + nd_ccap_arsen_enrolled: true + nd_ccap_has_ineligible_non_legally_responsible_caretaker: true + workforce: + members: [workforce_parent, workforce_child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + nd_ccap_has_ineligible_kinship_caretaker: true + households: + crossroads: + members: [crossroads_parent, crossroads_child] + state_code: ND + diversion: + members: [diversion_parent, diversion_child] + state_code: ND + arsen: + members: [arsen_parent, arsen_child] + state_code: ND + workforce: + members: [workforce_parent, workforce_child] + state_code: ND + output: + nd_ccap_copay: [0, 88, 0, 0] + +# Each ordinary copay = 0.02 * 0.44 * 10,000 = 88. +# North Dakota waives only the first and third families; the second has a child receiving SSI. +- name: North Dakota keeps Workforce and child SSI decisions within each family + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + workforce_parent: + age: 30 + ssi: 0 + nd_ccap_workforce_benefit_eligible_parent: true + workforce_child: + age: 3 + ssi: 0 + tanf_ssi_parent: + age: 30 + ssi: 0 + tanf_ssi_child: + age: 3 + ssi: 100 + tanf_plain_parent: + age: 30 + ssi: 0 + tanf_plain_child: + age: 3 + ssi: 0 + spm_units: + workforce: + members: [workforce_parent, workforce_child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: false + nd_ccap_eligible: true + tanf_ssi: + members: [tanf_ssi_parent, tanf_ssi_child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + tanf_plain: + members: [tanf_plain_parent, tanf_plain_child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + households: + workforce: + members: [workforce_parent, workforce_child] + state_code: ND + tanf_ssi: + members: [tanf_ssi_parent, tanf_ssi_child] + state_code: ND + tanf_plain: + members: [tanf_plain_parent, tanf_plain_child] + state_code: ND + output: + nd_ccap_workforce_benefit_eligible: [true, false, false] + nd_ccap_copay: [0, 88, 0] + +# The disabled child qualifies under the existing CCAP child age limit and receives 100 in SSI. +# Copay = 0.02 * 0.44 * 10,000 = 88. +- name: North Dakota charges the TANF copay for an eligible disabled child aged 18 receiving SSI + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 18 + ssi: 100 + is_disabled: true + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 88 + +# Before October 2026, a TANF-eligible child does not preserve the kinship waiver. +# Copay = 0.02 * 0.44 * 10,000 = 88. +- name: North Dakota charges the kinship caretaker before the October 2026 TANF-child exception + period: 2026-09 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + nd_ccap_has_ineligible_kinship_caretaker: true + nd_ccap_has_tanf_eligible_child: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 88 + +# The child receives 100 in monthly SSI at age 17 and does not have a disability. +# Copay = 0.02 * 0.44 * 10,000 = 88. +- name: North Dakota charges the TANF copay for a child aged 17 receiving SSI + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 17 + ssi: 100 + is_disabled: false + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 88 + +# The family receives Diversion and no TANF; the child receives 100 in monthly SSI. +# The ordinary copay = 0.02 * 0.44 * 10,000 = 88. +# North Dakota retains the Diversion waiver before October 1, 2026. +- name: North Dakota retains the Diversion waiver for a child receiving SSI before October 2026 + period: 2026-09 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 100 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: false + nd_ccap_eligible: true + nd_ccap_tanf_diversion_enrolled: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 0 + +# The family receives Diversion and no TANF; the child receives 100 in monthly SSI. +# The ordinary copay = 0.02 * 0.44 * 10,000 = 88. +# North Dakota applies the child SSI exception from October 1, 2026. +- name: North Dakota charges a Diversion family for a child receiving SSI from October 2026 + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 100 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: false + nd_ccap_eligible: true + nd_ccap_tanf_diversion_enrolled: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_copay: 88 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.yaml index 640bd12232a..d5a3403468b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.yaml @@ -1,9 +1,10 @@ # Tests for nd_ccap_income_eligible (SPMUnit, MONTH, bool). +# The original threshold cases below isolate the two-tier income comparison. # Initial applicants (is_nd_ccap_enrolled = false) are tested against 75% of # the monthly state median income; enrolled recipients (is_nd_ccap_enrolled = # true) are tested against 85% (400-28-25-15). Countable income is set # directly to isolate the two-tier comparison. -# 2-person ND monthly SMI for 2026 = $7,361.62/mo: +# The original threshold cases use 2026-01 and two-person SMI of $7,361.62/mo: # 75% = $5,521.22/mo, 85% = $6,257.38/mo. # nd_ccap_countable_income is a MONTH variable, so values are monthly. @@ -146,3 +147,184 @@ state_code: ND output: nd_ccap_income_eligible: false + +# The additional Workforce Benefit cases set annual SMI to 120,000. +# Monthly SMI = 10,000; application threshold = 0.75 * 10,000 = 7,500; +# continuing threshold = 0.85 * 10,000 = 8,500. +# Qualifying Workforce families receive an income exclusion and copay waiver. + +# Monthly SMI = 120,000 / 12 = 10,000; earnings = 240,000 / 12 = 20,000. +# Earnings exceed 75 percent SMI (7,500); North Dakota waives the income limit and copay. +- name: North Dakota waives the income limit for a Workforce Benefit applicant + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + nd_ccap_workforce_benefit_eligible_parent: true + employment_income: + 2026: 240000 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + is_ccdf_asset_eligible: true + meets_ccdf_activity_test: + 2026: true + is_tanf_enrolled: false + is_nd_ccap_enrolled: false + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_workforce_benefit_eligible: true + nd_ccap_countable_income: 20000 + nd_ccap_income_eligible: true + nd_ccap_eligible: true + nd_ccap_copay: 0 + +# Monthly SMI = 120,000 / 12 = 10,000; income 20,000 exceeds 7,500. +- name: North Dakota applies the income limit to an ordinary applicant + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 20000 + is_tanf_enrolled: false + is_nd_ccap_enrolled: false + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_workforce_benefit_eligible: false + nd_ccap_income_eligible: false + +# Monthly SMI = 120,000 / 12 = 10,000; earnings = 240,000 / 12 = 20,000. +# Earnings exceed 85 percent SMI (8,500); North Dakota waives the income limit and copay. +- name: North Dakota waives the income limit for an enrolled Workforce Benefit family + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + nd_ccap_workforce_benefit_eligible_parent: true + employment_income: + 2026: 240000 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + is_ccdf_asset_eligible: true + meets_ccdf_activity_test: + 2026: true + is_tanf_enrolled: false + is_nd_ccap_enrolled: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_workforce_benefit_eligible: true + nd_ccap_countable_income: 20000 + nd_ccap_income_eligible: true + nd_ccap_eligible: true + nd_ccap_copay: 0 + +# Monthly SMI = 120,000 / 12 = 10,000; income 20,000 exceeds 8,500. +- name: North Dakota applies the income limit to an ordinary enrolled family + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 20000 + is_tanf_enrolled: false + is_nd_ccap_enrolled: true + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_workforce_benefit_eligible: false + nd_ccap_income_eligible: false + +# Monthly SMI = 120,000 / 12 = 10,000; income 20,000 exceeds 75 percent SMI (7,500). +# North Dakota waives the income limit for the Workforce Benefit family. +- name: North Dakota keeps the Workforce income waiver within the qualifying family + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + workforce_parent: + age: 30 + ssi: 0 + nd_ccap_workforce_benefit_eligible_parent: true + workforce_child: + age: 3 + ssi: 0 + ordinary_parent: + age: 30 + ssi: 0 + ordinary_child: + age: 3 + ssi: 0 + spm_units: + workforce: + members: [workforce_parent, workforce_child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 20000 + is_tanf_enrolled: false + is_nd_ccap_enrolled: false + ordinary: + members: [ordinary_parent, ordinary_child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 20000 + is_tanf_enrolled: false + is_nd_ccap_enrolled: false + households: + workforce: + members: [workforce_parent, workforce_child] + state_code: ND + ordinary: + members: [ordinary_parent, ordinary_child] + state_code: ND + output: + nd_ccap_workforce_benefit_eligible: [true, false] + nd_ccap_income_eligible: [true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.yaml new file mode 100644 index 00000000000..e4b69fd3650 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.yaml @@ -0,0 +1,56 @@ +# Tests for nd_ccap_workforce_benefit_eligible (SPMUnit, MONTH, bool). +# North Dakota started the Workforce Benefit on June 1, 2024. + +# A qualifying parent receives the Workforce Benefit from June 1, 2024. +- name: North Dakota withholds the Workforce Benefit before June 2024 + period: 2024-05 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + nd_ccap_workforce_benefit_eligible_parent: true + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2024: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: false + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_workforce_benefit_eligible: false + +# A qualifying parent receives the Workforce Benefit from June 1, 2024. +- name: North Dakota starts the Workforce Benefit in June 2024 + period: 2024-06 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + ssi: 0 + nd_ccap_workforce_benefit_eligible_parent: true + child: + age: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2024: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: false + households: + household: + members: [parent, child] + state_code: ND + output: + nd_ccap_workforce_benefit_eligible: true diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_arsen_enrolled.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_arsen_enrolled.py new file mode 100644 index 00000000000..4f6fb276a6f --- /dev/null +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_arsen_enrolled.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class nd_ccap_arsen_enrolled(Variable): + value_type = bool + entity = SPMUnit + definition_period = MONTH + default_value = False + label = "North Dakota CCAP family participates in ARSEN" + documentation = ( + "Records whether this family participates in the Alternative Response " + "for Substance Exposed Newborns program in the month. " + "North Dakota waives its co-payment from April 1, 2021, under CCAP " + "manual 400-28-90-20 and 400-28-55-30." + ) + reference = ( + "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_90_20.htm", + "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_55_30.htm", + ) diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_categorical_copay_waiver.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_categorical_copay_waiver.py new file mode 100644 index 00000000000..7c8fb5007a0 --- /dev/null +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_categorical_copay_waiver.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class nd_ccap_categorical_copay_waiver(Variable): + value_type = bool + entity = SPMUnit + definition_period = MONTH + label = "North Dakota CCAP family qualifies for a program co-payment waiver" + reference = ( + "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_90_20.htm", + "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_45_05.htm", + ) + + def formula_2011_10_01(spm_unit, period, parameters): + crossroads = spm_unit("nd_ccap_crossroads_enrolled", period) + diversion = spm_unit("nd_ccap_tanf_diversion_enrolled", period) + exception = spm_unit("nd_ccap_tanf_copay_exception", period) + return crossroads | (diversion & ~exception) + + def formula_2021_04_01(spm_unit, period, parameters): + crossroads = spm_unit("nd_ccap_crossroads_enrolled", period) + diversion = spm_unit("nd_ccap_tanf_diversion_enrolled", period) + arsen = spm_unit("nd_ccap_arsen_enrolled", period) + exception = spm_unit("nd_ccap_tanf_copay_exception", period) + return crossroads | (diversion & ~exception) | arsen diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.py index ecf4ca3cf01..a440328ecb6 100644 --- a/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.py +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.py @@ -8,7 +8,10 @@ class nd_ccap_copay(Variable): label = "North Dakota CCAP family co-payment" definition_period = MONTH defined_for = "nd_ccap_eligible" - reference = "https://www.nd.gov/dhs/policymanuals/40028/40028.htm" + reference = ( + "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_90_20.htm", + "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_20_40.htm", + ) def formula(spm_unit, period, parameters): p = parameters(period).gov.states.nd.dhs.ccap.copay @@ -28,10 +31,20 @@ def formula(spm_unit, period, parameters): # never exceeds max_rate of countable income. This does not bind under # the current schedule (the band rate tops out at 6%). copay = min_(copay, p.max_rate * countable_income) - # The co-payment is waived for families at or below 30% of the state - # median income and for TANF recipients (400-28-90-20). Diversion and - # Crossroads recipients are also waived but are not tracked at the - # moment. + # North Dakota waives co-payments for low-income families, qualifying + # TANF families, Crossroads, Diversion, ARSEN and Workforce Benefit + # families. TANF exceptions also apply to Diversion, while Crossroads, + # ARSEN and Workforce Benefit families keep their independent waivers + # (400-28-90-20 and 400-28-20-40). is_tanf_enrolled = spm_unit("is_tanf_enrolled", period) - waived = (income_to_smi <= p.waiver_smi_threshold) | is_tanf_enrolled + tanf_exception = spm_unit("nd_ccap_tanf_copay_exception", period) + tanf_waiver = is_tanf_enrolled & ~tanf_exception + program_waiver = spm_unit("nd_ccap_categorical_copay_waiver", period) + workforce = spm_unit("nd_ccap_workforce_benefit_eligible", period) + waived = ( + (income_to_smi <= p.waiver_smi_threshold) + | tanf_waiver + | program_waiver + | workforce + ) return where(waived, 0, copay) diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_crossroads_enrolled.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_crossroads_enrolled.py new file mode 100644 index 00000000000..6ad18c46ee2 --- /dev/null +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_crossroads_enrolled.py @@ -0,0 +1,15 @@ +from policyengine_us.model_api import * + + +class nd_ccap_crossroads_enrolled(Variable): + value_type = bool + entity = SPMUnit + definition_period = MONTH + default_value = False + label = "North Dakota CCAP family receives Crossroads services" + documentation = ( + "Records whether this family receives Crossroads Program services in the " + "month under CCAP manual 400-28-90-20. " + "North Dakota waived these families' co-payments by October 1, 2011." + ) + reference = "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_90_20.htm" diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_disqualified_alien_caretaker.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_disqualified_alien_caretaker.py new file mode 100644 index 00000000000..90b4df747d9 --- /dev/null +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_disqualified_alien_caretaker.py @@ -0,0 +1,15 @@ +from policyengine_us.model_api import * + + +class nd_ccap_has_disqualified_alien_caretaker(Variable): + value_type = bool + entity = SPMUnit + definition_period = MONTH + default_value = False + label = "North Dakota CCAP family has a disqualified alien TANF caretaker" + documentation = ( + "Records whether a disqualified alien caretaker needs child care for " + "an activity in this TANF or Diversion family. " + "North Dakota applies the co-payment under CCAP manual 400-28-90-20." + ) + reference = "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_90_20.htm" diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_ineligible_kinship_caretaker.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_ineligible_kinship_caretaker.py new file mode 100644 index 00000000000..6f934a43a28 --- /dev/null +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_ineligible_kinship_caretaker.py @@ -0,0 +1,16 @@ +from policyengine_us.model_api import * + + +class nd_ccap_has_ineligible_kinship_caretaker(Variable): + value_type = bool + entity = SPMUnit + definition_period = MONTH + default_value = False + label = "North Dakota CCAP family has an ineligible kinship caretaker with non-kinship children" + documentation = ( + "Records whether an ineligible Kinship Care caretaker needs child care " + "for non-kinship or non-TANF children in this family. " + "North Dakota applies the co-payment under CCAP manual 400-28-90-20, " + "except when some children qualify for TANF." + ) + reference = "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_90_20.htm" diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_ineligible_non_legally_responsible_caretaker.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_ineligible_non_legally_responsible_caretaker.py new file mode 100644 index 00000000000..0aa0011407b --- /dev/null +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_ineligible_non_legally_responsible_caretaker.py @@ -0,0 +1,17 @@ +from policyengine_us.model_api import * + + +class nd_ccap_has_ineligible_non_legally_responsible_caretaker(Variable): + value_type = bool + entity = SPMUnit + definition_period = MONTH + default_value = False + label = ( + "North Dakota CCAP family has an ineligible non-legally responsible caretaker" + ) + documentation = ( + "Records whether an ineligible non-legally responsible caretaker needs " + "child care for an activity in this TANF or Diversion family. " + "North Dakota applies the co-payment under CCAP manual 400-28-90-20." + ) + reference = "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_90_20.htm" diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_tanf_eligible_child.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_tanf_eligible_child.py new file mode 100644 index 00000000000..bc28390d2ae --- /dev/null +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_has_tanf_eligible_child.py @@ -0,0 +1,15 @@ +from policyengine_us.model_api import * + + +class nd_ccap_has_tanf_eligible_child(Variable): + value_type = bool + entity = SPMUnit + definition_period = MONTH + default_value = False + label = "North Dakota CCAP family has a TANF-eligible child" + documentation = ( + "Records whether this family includes a child who qualifies for TANF. " + "North Dakota preserves the ineligible Kinship Care caretaker's " + "co-payment waiver in this situation under CCAP manual 400-28-90-20." + ) + reference = "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_90_20.htm" diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.py new file mode 100644 index 00000000000..7e0a7f715d2 --- /dev/null +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.py @@ -0,0 +1,50 @@ +from policyengine_us.model_api import * + + +class nd_ccap_tanf_copay_exception(Variable): + value_type = bool + entity = SPMUnit + definition_period = MONTH + label = "North Dakota CCAP family has an exception to the TANF or Diversion co-payment waiver" + reference = ( + "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_90_20.htm", + "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_45_05.htm", + ) + + def formula_2011_10_01(spm_unit, period, parameters): + alien = spm_unit("nd_ccap_has_disqualified_alien_caretaker", period) + non_legally_responsible = spm_unit( + "nd_ccap_has_ineligible_non_legally_responsible_caretaker", period + ) + return alien | non_legally_responsible + + def formula_2021_04_01(spm_unit, period, parameters): + alien = spm_unit("nd_ccap_has_disqualified_alien_caretaker", period) + non_legally_responsible = spm_unit( + "nd_ccap_has_ineligible_non_legally_responsible_caretaker", period + ) + kinship = spm_unit("nd_ccap_has_ineligible_kinship_caretaker", period) + return alien | non_legally_responsible | kinship + + def formula_2026_10_01(spm_unit, period, parameters): + person = spm_unit.members + p = parameters(period).gov.states.nd.dhs.ccap.eligibility + # Include children under 18 and North Dakota's older disabled children, + # without imposing the separate CCAP immigration test on SSI receipt. + child = person("is_child", period.this_year) | ( + person("is_disabled", period.this_year) + & (person("age", period.this_year) < p.disabled_child_age_limit) + ) + child_receives_ssi = spm_unit.any(child & (person("ssi", period) > 0)) + alien = spm_unit("nd_ccap_has_disqualified_alien_caretaker", period) + non_legally_responsible = spm_unit( + "nd_ccap_has_ineligible_non_legally_responsible_caretaker", period + ) + kinship = spm_unit("nd_ccap_has_ineligible_kinship_caretaker", period) + tanf_child = spm_unit("nd_ccap_has_tanf_eligible_child", period) + return ( + child_receives_ssi + | alien + | non_legally_responsible + | (kinship & ~tanf_child) + ) diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_diversion_enrolled.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_diversion_enrolled.py new file mode 100644 index 00000000000..3280fb370e4 --- /dev/null +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_diversion_enrolled.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class nd_ccap_tanf_diversion_enrolled(Variable): + value_type = bool + entity = SPMUnit + definition_period = MONTH + default_value = False + label = "North Dakota CCAP family receives TANF Diversion" + documentation = ( + "Records whether this family receives TANF Diversion in the month under " + "CCAP manual 400-28-90-20. " + "North Dakota waived these families' co-payments by October 1, 2011, " + "subject to the TANF-related exceptions in 400-28-45-05." + ) + reference = ( + "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_90_20.htm", + "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_45_05.htm", + ) diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.py index a2235d409be..103f67f2d09 100644 --- a/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.py +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.py @@ -7,7 +7,10 @@ class nd_ccap_income_eligible(Variable): label = "North Dakota CCAP income eligible" definition_period = MONTH defined_for = StateCode.ND - reference = "https://www.nd.gov/dhs/policymanuals/40028/40028.htm" + reference = ( + "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_25_15.htm", + "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_20_40.htm", + ) def formula(spm_unit, period, parameters): p = parameters(period).gov.states.nd.dhs.ccap.income @@ -22,8 +25,8 @@ def formula(spm_unit, period, parameters): initial_limit = monthly_smi * p.initial_smi_rate continuing_limit = monthly_smi * p.continuing_smi_rate income_limit = where(enrolled, continuing_limit, initial_limit) - # The CCAP Workforce Benefit waives the income limit (and copay) for - # eligible early-childhood workforce families. We don't track which - # families qualify for the Workforce Benefit at the moment, so this - # waiver is not modeled. - return countable_income <= income_limit + # North Dakota excludes all income while a caretaker qualifies for the + # Workforce Benefit, including at review (400-28-20-40). After that + # qualification ends, enrolled families face the ordinary 85% limit. + workforce = spm_unit("nd_ccap_workforce_benefit_eligible", period) + return (countable_income <= income_limit) | workforce diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.py new file mode 100644 index 00000000000..4b4246c4a0b --- /dev/null +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.py @@ -0,0 +1,15 @@ +from policyengine_us.model_api import * + + +class nd_ccap_workforce_benefit_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = MONTH + label = "North Dakota CCAP family meets the Workforce Benefit employment test" + defined_for = StateCode.ND + reference = "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_20_40.htm" + + def formula_2024_06_01(spm_unit, period, parameters): + return spm_unit.any( + spm_unit.members("nd_ccap_workforce_benefit_eligible_parent", period) + ) diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible_parent.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible_parent.py new file mode 100644 index 00000000000..fda6a39838e --- /dev/null +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible_parent.py @@ -0,0 +1,37 @@ +from policyengine_us.model_api import * + + +class nd_ccap_workforce_benefit_eligible_parent(Variable): + value_type = bool + entity = Person + definition_period = MONTH + default_value = False + label = "North Dakota CCAP caretaker meets the Workforce Benefit employment test" + documentation = ( + "Records whether this caretaker meets the Workforce Benefit employment " + "conditions in CCAP manual 400-28-20-40 for the month. " + "From June 1, 2024, an employee must work at least 25 hours weekly in a " + "North Dakota licensed, tribally licensed or registered child care program. " + "The employer verifies employment on SFN 354. " + "Approved-relative, self-declared and out-of-state providers do not qualify. " + "Owners qualify only when their children attend another licensed program. " + "Employees could not care for their own children before September 1, 2024. " + "From May 1, 2025, the provider must operate at least 25 hours weekly, " + "and maternity leave can excuse reduced employee hours. " + "From April 1, 2026, employees and providers average 25 hours weekly. " + "Employer-confirmed vacation, temporary closure and temporary sick leave " + "can excuse reduced hours for up to one month. " + "Maternity and extended sick leave require an anticipated return date. " + "A job change or questionable hours at review require a new SFN 354. " + "Keep this input true through the month employment or the license ends, " + "or hours fall below the minimum without an allowed exception. " + "Ordinary income testing and co-payments resume the following month. " + "Other family eligibility conditions still apply." + ) + reference = ( + "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_20_40.htm", + "https://www.nd.gov/dhs/policymanuals/40028/Content/ML/2024/CCAP%20ML%203819%20Effective%20June.1.2024.pdf", + "https://www.nd.gov/dhs/policymanuals/40028/Content/ML/2024/CCAP%20ML%203862%20Effective%20September.1.2024.pdf", + "https://www.nd.gov/dhs/policymanuals/40028/Content/ML/2025/CCAP%20ML%203909%20Effective%20May.1.2025.pdf", + "https://www.nd.gov/dhs/policymanuals/40028/Content/ML/2026/CCAP%20Release%2026.2%20Effective%20April.1.2026.pdf", + ) From 50c610d3d472fff6059ff144a47a7c293d7d0160 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 11 Nov 2026 20:03:04 -0500 Subject: [PATCH 2/4] Use annual periods in North Dakota CCAP waiver fixtures Co-Authored-By: Claude Opus 5.5 --- .../nd_ccap_categorical_copay_waiver.yaml | 18 +- .../nd/dhs/ccap/copay/nd_ccap_copay.yaml | 249 ++++++++++++------ .../eligibility/nd_ccap_income_eligible.yaml | 60 +++-- .../nd_ccap_workforce_benefit_eligible.yaml | 18 +- 4 files changed, 230 insertions(+), 115 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_categorical_copay_waiver.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_categorical_copay_waiver.yaml index 670b23d7c9d..6fd6276c906 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_categorical_copay_waiver.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_categorical_copay_waiver.yaml @@ -7,9 +7,11 @@ input: people: parent: - age: 30 + age: + 2021: 30 child: - age: 3 + age: + 2021: 3 spm_units: family: members: [parent, child] @@ -18,7 +20,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2021: ND output: nd_ccap_categorical_copay_waiver: false @@ -28,9 +31,11 @@ input: people: parent: - age: 30 + age: + 2021: 30 child: - age: 3 + age: + 2021: 3 spm_units: family: members: [parent, child] @@ -39,6 +44,7 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2021: ND output: nd_ccap_categorical_copay_waiver: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml index a4fbeb0c53b..d58ae7c6c90 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml @@ -396,10 +396,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: family: @@ -412,7 +414,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_workforce_benefit_eligible: false nd_ccap_copay: 88 @@ -424,11 +427,13 @@ input: people: parent: - age: 30 + age: + 2024: 30 ssi: 0 nd_ccap_workforce_benefit_eligible_parent: true child: - age: 3 + age: + 2024: 3 ssi: 0 spm_units: family: @@ -441,7 +446,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2024: ND output: nd_ccap_workforce_benefit_eligible: true nd_ccap_copay: 0 @@ -453,10 +459,12 @@ input: people: parent: - age: 30 + age: + 2024: 30 ssi: 0 child: - age: 3 + age: + 2024: 3 ssi: 0 spm_units: family: @@ -470,7 +478,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2024: ND output: nd_ccap_copay: 0 @@ -481,10 +490,12 @@ input: people: parent: - age: 30 + age: + 2024: 30 ssi: 0 child: - age: 3 + age: + 2024: 3 ssi: 0 spm_units: family: @@ -498,7 +509,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2024: ND output: nd_ccap_copay: 0 @@ -509,10 +521,12 @@ input: people: parent: - age: 30 + age: + 2024: 30 ssi: 0 child: - age: 3 + age: + 2024: 3 ssi: 0 spm_units: family: @@ -526,7 +540,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2024: ND output: nd_ccap_copay: 0 @@ -537,10 +552,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: family: @@ -553,7 +570,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: 0 @@ -565,10 +583,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 3 + age: + 2026: 3 ssi: 100 spm_units: family: @@ -581,7 +601,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: 0 @@ -593,10 +614,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 3 + age: + 2026: 3 ssi: 100 spm_units: family: @@ -609,7 +632,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: 88 @@ -621,10 +645,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 100 child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: family: @@ -637,7 +663,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: 0 @@ -648,10 +675,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: family: @@ -665,7 +694,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: 88 @@ -676,10 +706,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: family: @@ -693,7 +725,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: 88 @@ -704,10 +737,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: family: @@ -721,7 +756,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: 88 @@ -732,10 +768,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: family: @@ -750,7 +788,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: 0 @@ -762,10 +801,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: family: @@ -780,7 +821,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: 88 @@ -792,10 +834,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 3 + age: + 2026: 3 ssi: 100 spm_units: family: @@ -811,7 +855,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: 0 @@ -824,29 +869,37 @@ input: people: crossroads_parent: - age: 30 + age: + 2026: 30 ssi: 0 crossroads_child: - age: 3 + age: + 2026: 3 ssi: 100 diversion_parent: - age: 30 + age: + 2026: 30 ssi: 0 diversion_child: - age: 3 + age: + 2026: 3 ssi: 0 arsen_parent: - age: 30 + age: + 2026: 30 ssi: 0 arsen_child: - age: 3 + age: + 2026: 3 ssi: 0 workforce_parent: - age: 30 + age: + 2026: 30 ssi: 0 nd_ccap_workforce_benefit_eligible_parent: true workforce_child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: crossroads: @@ -886,16 +939,20 @@ households: crossroads: members: [crossroads_parent, crossroads_child] - state_code: ND + state_code: + 2026: ND diversion: members: [diversion_parent, diversion_child] - state_code: ND + state_code: + 2026: ND arsen: members: [arsen_parent, arsen_child] - state_code: ND + state_code: + 2026: ND workforce: members: [workforce_parent, workforce_child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: [0, 88, 0, 0] @@ -907,23 +964,29 @@ input: people: workforce_parent: - age: 30 + age: + 2026: 30 ssi: 0 nd_ccap_workforce_benefit_eligible_parent: true workforce_child: - age: 3 + age: + 2026: 3 ssi: 0 tanf_ssi_parent: - age: 30 + age: + 2026: 30 ssi: 0 tanf_ssi_child: - age: 3 + age: + 2026: 3 ssi: 100 tanf_plain_parent: - age: 30 + age: + 2026: 30 ssi: 0 tanf_plain_child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: workforce: @@ -950,13 +1013,16 @@ households: workforce: members: [workforce_parent, workforce_child] - state_code: ND + state_code: + 2026: ND tanf_ssi: members: [tanf_ssi_parent, tanf_ssi_child] - state_code: ND + state_code: + 2026: ND tanf_plain: members: [tanf_plain_parent, tanf_plain_child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_workforce_benefit_eligible: [true, false, false] nd_ccap_copay: [0, 88, 0] @@ -969,12 +1035,15 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 18 + age: + 2026: 18 ssi: 100 - is_disabled: true + is_disabled: + 2026: true spm_units: family: members: [parent, child] @@ -986,7 +1055,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: 88 @@ -998,10 +1068,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: family: @@ -1016,7 +1088,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: 88 @@ -1028,12 +1101,15 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 17 + age: + 2026: 17 ssi: 100 - is_disabled: false + is_disabled: + 2026: false spm_units: family: members: [parent, child] @@ -1045,7 +1121,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: 88 @@ -1058,10 +1135,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 3 + age: + 2026: 3 ssi: 100 spm_units: family: @@ -1075,7 +1154,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: 0 @@ -1088,10 +1168,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 3 + age: + 2026: 3 ssi: 100 spm_units: family: @@ -1105,6 +1187,7 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_copay: 88 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.yaml index d5a3403468b..4307ad8692c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.yaml @@ -161,20 +161,23 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 nd_ccap_workforce_benefit_eligible_parent: true employment_income: 2026: 240000 child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: family: members: [parent, child] hhs_smi: 2026: 120000 - is_ccdf_asset_eligible: true + is_ccdf_asset_eligible: + 2026: true meets_ccdf_activity_test: 2026: true is_tanf_enrolled: false @@ -182,7 +185,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_workforce_benefit_eligible: true nd_ccap_countable_income: 20000 @@ -197,10 +201,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: family: @@ -213,7 +219,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_workforce_benefit_eligible: false nd_ccap_income_eligible: false @@ -226,20 +233,23 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 nd_ccap_workforce_benefit_eligible_parent: true employment_income: 2026: 240000 child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: family: members: [parent, child] hhs_smi: 2026: 120000 - is_ccdf_asset_eligible: true + is_ccdf_asset_eligible: + 2026: true meets_ccdf_activity_test: 2026: true is_tanf_enrolled: false @@ -247,7 +257,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_workforce_benefit_eligible: true nd_ccap_countable_income: 20000 @@ -262,10 +273,12 @@ input: people: parent: - age: 30 + age: + 2026: 30 ssi: 0 child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: family: @@ -278,7 +291,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_workforce_benefit_eligible: false nd_ccap_income_eligible: false @@ -291,17 +305,21 @@ input: people: workforce_parent: - age: 30 + age: + 2026: 30 ssi: 0 nd_ccap_workforce_benefit_eligible_parent: true workforce_child: - age: 3 + age: + 2026: 3 ssi: 0 ordinary_parent: - age: 30 + age: + 2026: 30 ssi: 0 ordinary_child: - age: 3 + age: + 2026: 3 ssi: 0 spm_units: workforce: @@ -321,10 +339,12 @@ households: workforce: members: [workforce_parent, workforce_child] - state_code: ND + state_code: + 2026: ND ordinary: members: [ordinary_parent, ordinary_child] - state_code: ND + state_code: + 2026: ND output: nd_ccap_workforce_benefit_eligible: [true, false] nd_ccap_income_eligible: [true, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.yaml index e4b69fd3650..1ddc4f36153 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.yaml @@ -8,11 +8,13 @@ input: people: parent: - age: 30 + age: + 2024: 30 ssi: 0 nd_ccap_workforce_benefit_eligible_parent: true child: - age: 3 + age: + 2024: 3 ssi: 0 spm_units: family: @@ -24,7 +26,8 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2024: ND output: nd_ccap_workforce_benefit_eligible: false @@ -35,11 +38,13 @@ input: people: parent: - age: 30 + age: + 2024: 30 ssi: 0 nd_ccap_workforce_benefit_eligible_parent: true child: - age: 3 + age: + 2024: 3 ssi: 0 spm_units: family: @@ -51,6 +56,7 @@ households: household: members: [parent, child] - state_code: ND + state_code: + 2024: ND output: nd_ccap_workforce_benefit_eligible: true From 10ea78a604d74508bf1f5577b62b40951b4b2629 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 13 Nov 2026 18:10:21 -0500 Subject: [PATCH 3/4] Charge North Dakota CCAP co-pay for SSI caretakers and exempt Workforce second caretakers From October 2011 through September 2026, North Dakota charged the TANF and Diversion co-pay for child care that followed from the activity of a caretaker who received SSI. Release 26.3 repealed that rule on October 1, 2026. From June 2024 through December 2025, North Dakota approved Workforce Benefit households whose second caretaker had no allowable activity (ML 3819, 3862 and 3909). Release 26.1 ended that exemption in January 2026, and a dated parameter sets both dates. The Workforce test now counts only the tax unit head and spouse. From October 2026, only an SSI child in care triggers the TANF co-pay. One in-care variable replaces two copies of that test. Co-Authored-By: Claude Opus 5.5 --- .../second_caretaker_activity_exempt.yaml | 26 +++ policyengine_us/programs.yaml | 2 +- .../core/test_nd_ccap_waivers_invariants.py | 24 ++- .../nd/dhs/ccap/copay/nd_ccap_copay.yaml | 185 +++++++++++++++++- .../copay/nd_ccap_tanf_copay_exception.yaml | 111 +++++++++++ .../nd_ccap_activity_eligible.yaml | 107 ++++++++++ .../ccap/eligibility/nd_ccap_eligible.yaml | 60 ++++++ .../eligibility/nd_ccap_income_eligible.yaml | 5 + .../nd_ccap_workforce_benefit_eligible.yaml | 28 +++ .../copay/nd_ccap_tanf_copay_exception.py | 24 ++- .../eligibility/nd_ccap_activity_eligible.py | 16 +- .../nd_ccap_workforce_benefit_eligible.py | 10 +- .../gov/states/nd/dhs/ccap/nd_ccap.py | 13 +- .../nd/dhs/ccap/nd_ccap_child_in_care.py | 21 ++ .../nd/dhs/ccap/rates/nd_ccap_base_subsidy.py | 13 +- 15 files changed, 607 insertions(+), 38 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/nd/dhs/ccap/workforce/second_caretaker_activity_exempt.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.yaml create mode 100644 policyengine_us/variables/gov/states/nd/dhs/ccap/nd_ccap_child_in_care.py diff --git a/policyengine_us/parameters/gov/states/nd/dhs/ccap/workforce/second_caretaker_activity_exempt.yaml b/policyengine_us/parameters/gov/states/nd/dhs/ccap/workforce/second_caretaker_activity_exempt.yaml new file mode 100644 index 00000000000..61173d75a47 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nd/dhs/ccap/workforce/second_caretaker_activity_exempt.yaml @@ -0,0 +1,26 @@ +description: North Dakota exempts the second caretaker in a Workforce Benefit household from the allowable-activity requirement under the Child Care Assistance Program if this is true. + +# ML 3819 (June 1, 2024) says the second caretaker "does not need to be in an +# allowable activity"; ML 3862 and ML 3909 keep that text. Release 26.1 +# (January 1, 2026) requires the second caretaker's activity. The Workforce +# Benefit starts June 1, 2024, so the earlier false value has no effect. +values: + 0000-01-01: false + 2024-06-01: true + 2026-01-01: false + +metadata: + unit: bool + period: year + label: North Dakota CCAP Workforce Benefit second caretaker activity exemption + reference: + - title: North Dakota CCAP Manual Letter 3819, Basic Eligibility Alignment 400-28-20-40 (eff. June 1, 2024) + href: https://www.nd.gov/dhs/policymanuals/40028/Content/ML/2024/CCAP%20ML%203819%20Effective%20June.1.2024.pdf#page=2 + - title: North Dakota CCAP Manual Letter 3862, Basic Eligibility Alignment 400-28-20-40 (eff. September 1, 2024) + href: https://www.nd.gov/dhs/policymanuals/40028/Content/ML/2024/CCAP%20ML%203862%20Effective%20September.1.2024.pdf#page=2 + - title: North Dakota CCAP Manual Letter 3909, Basic Eligibility Alignment 400-28-20-40 (eff. May 1, 2025) + href: https://www.nd.gov/dhs/policymanuals/40028/Content/ML/2025/CCAP%20ML%203909%20Effective%20May.1.2025.pdf#page=4 + - title: "North Dakota CCAP Release 26.1, Basic Eligibility Alignment 400-28-20-40 (eff. January 1, 2026) #page 1-2" + href: https://www.nd.gov/dhs/policymanuals/40028/Content/ML/2026/CCAP%20Release%2026.1%20Effective%20January.1.2026.pdf#page=1 + - title: North Dakota CCAP Policy Manual, Basic Eligibility Alignment 400-28-20-40 + href: https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_20_40.htm diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index a365d06fae9..e22b00d146b 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -965,7 +965,7 @@ programs: full_name: North Dakota Child Care Assistance Program variable: nd_ccap parameter_prefix: gov.states.nd.dhs.ccap - notes: Models the initial (75% of SMI) and continuing (85% of SMI) income limits, the SMI-band co-payment schedule with the 30%-of-SMI and TANF waivers, and the federal family-share ceiling. Also models the Workforce Benefit income-limit and co-payment waiver, Crossroads, Diversion and ARSEN co-payment waivers, and TANF and Diversion co-payment exceptions. + notes: Models the initial (75% of SMI) and continuing (85% of SMI) income limits, the SMI-band co-payment schedule with the 30%-of-SMI and TANF waivers, and the federal family-share ceiling. Also models the Workforce Benefit income-limit and co-payment waiver, Crossroads, Diversion and ARSEN co-payment waivers, and TANF and Diversion co-payment exceptions. From October 2026, when a child who receives SSI in a TANF or Diversion family attends care, the model charges one family co-payment that also covers that child's siblings in care. Not modeled - the income-requirement exemption for TANF and Diversion families, the co-payment waiver they keep after TANF or Diversion closes, Workforce Benefit eligibility for households outside North Dakota, and provider registration fees - state: NM status: complete name: New Mexico CCAP diff --git a/policyengine_us/tests/core/test_nd_ccap_waivers_invariants.py b/policyengine_us/tests/core/test_nd_ccap_waivers_invariants.py index 8232e89dc00..d46ecb61ad6 100644 --- a/policyengine_us/tests/core/test_nd_ccap_waivers_invariants.py +++ b/policyengine_us/tests/core/test_nd_ccap_waivers_invariants.py @@ -111,6 +111,8 @@ def test_nd_ccap_waivers_preserve_bounds_and_default_behavior( simulation.set_input("state_code", year, ["ND"] * CASE_COUNT) simulation.set_input("age", year, [30, 3] * CASE_COUNT) simulation.set_input("is_disabled", year, [False] * (2 * CASE_COUNT)) + # Every child attends care, so a child's SSI can trigger the exception. + simulation.set_input("childcare_hours_per_week", year, [0, 40] * CASE_COUNT) simulation.set_input("hhs_smi", year, [monthly_smi * 12] * CASE_COUNT) simulation.set_input("nd_ccap_countable_income", period, [income] * CASE_COUNT) # Isolate the co-payment calculation from the other eligibility tests. @@ -197,15 +199,20 @@ def test_nd_ccap_waivers_preserve_bounds_and_default_behavior( else: assert copay[11] == copay[10] - # Preserve main's calculation unless a TANF child receives SSI from October 2026. + # Preserve main's calculation, except that SSI can end the TANF waiver: + # a caretaker's SSI (unit 2) before October 2026, and a child's SSI + # (unit 1) from then on. expected = np.where( (income_to_smi[:3] <= p.copay.waiver_smi_threshold) | tanf, 0, unwaived[:3], ) - if period >= "2026-10" and tanf: - expected[1] = ( - 0 if income_to_smi[1] <= p.copay.waiver_smi_threshold else unwaived[1] + ssi_exception = 1 if period >= "2026-10" else 2 + if tanf: + expected[ssi_exception] = ( + 0 + if income_to_smi[ssi_exception] <= p.copay.waiver_smi_threshold + else unwaived[ssi_exception] ) np.testing.assert_allclose(copay[:3], expected, atol=0.01) @@ -215,3 +222,12 @@ def test_nd_ccap_waivers_preserve_bounds_and_default_behavior( income_eligible[:3], countable_income[:3] <= smi[:3] * rate ) assert np.all(income_eligible[4:6]) + + # Units 4, 5 and 9 have a Workforce Benefit caretaker and no other modeled + # activity. North Dakota waives the second caretaker's activity test + # through December 2025 (ML 3819) and restores it in January 2026. + activity = simulation.calculate("nd_ccap_activity_eligible", period) + if period < "2026-01": + assert np.all(activity[[4, 5, 9]]) + else: + np.testing.assert_array_equal(activity[[4, 5, 9]], activity[[0, 0, 0]]) diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml index d58ae7c6c90..06691cccd44 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml @@ -590,6 +590,8 @@ age: 2026: 3 ssi: 100 + childcare_hours_per_week: + 2026: 40 spm_units: family: members: [parent, child] @@ -606,7 +608,7 @@ output: nd_ccap_copay: 0 -# The child receives 100 in monthly SSI. +# The child attends care 40 hours a week and receives 100 in monthly SSI. # The ordinary copay = 0.02 * 0.44 * 10,000 = 88. - name: North Dakota charges the TANF copay for a child receiving SSI from October 2026 period: 2026-10 @@ -621,6 +623,8 @@ age: 2026: 3 ssi: 100 + childcare_hours_per_week: + 2026: 40 spm_units: family: members: [parent, child] @@ -841,6 +845,8 @@ age: 2026: 3 ssi: 100 + childcare_hours_per_week: + 2026: 40 spm_units: family: members: [parent, child] @@ -876,6 +882,8 @@ age: 2026: 3 ssi: 100 + childcare_hours_per_week: + 2026: 40 diversion_parent: age: 2026: 30 @@ -936,6 +944,15 @@ is_tanf_enrolled: true nd_ccap_eligible: true nd_ccap_has_ineligible_kinship_caretaker: true + tax_units: + crossroads: + members: [crossroads_parent, crossroads_child] + diversion: + members: [diversion_parent, diversion_child] + arsen: + members: [arsen_parent, arsen_child] + workforce: + members: [workforce_parent, workforce_child] households: crossroads: members: [crossroads_parent, crossroads_child] @@ -980,6 +997,8 @@ age: 2026: 3 ssi: 100 + childcare_hours_per_week: + 2026: 40 tanf_plain_parent: age: 2026: 30 @@ -1010,6 +1029,13 @@ nd_ccap_countable_income: 4500 is_tanf_enrolled: true nd_ccap_eligible: true + tax_units: + workforce: + members: [workforce_parent, workforce_child] + tanf_ssi: + members: [tanf_ssi_parent, tanf_ssi_child] + tanf_plain: + members: [tanf_plain_parent, tanf_plain_child] households: workforce: members: [workforce_parent, workforce_child] @@ -1042,6 +1068,8 @@ age: 2026: 18 ssi: 100 + childcare_hours_per_week: + 2026: 40 is_disabled: 2026: true spm_units: @@ -1108,6 +1136,8 @@ age: 2026: 17 ssi: 100 + childcare_hours_per_week: + 2026: 40 is_disabled: 2026: false spm_units: @@ -1142,6 +1172,8 @@ age: 2026: 3 ssi: 100 + childcare_hours_per_week: + 2026: 40 spm_units: family: members: [parent, child] @@ -1175,6 +1207,77 @@ age: 2026: 3 ssi: 100 + childcare_hours_per_week: + 2026: 40 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: false + nd_ccap_eligible: true + nd_ccap_tanf_diversion_enrolled: true + households: + household: + members: [parent, child] + state_code: + 2026: ND + output: + nd_ccap_copay: 88 + +# Monthly SMI = 120,000 / 12 = 10,000; 4,500 / 10,000 = 0.45, the 44-46% band. +# Until October 2026, North Dakota charges the co-pay for care that follows +# from the activity of a TANF caretaker who receives SSI (400-28-90-20). +# Copay = min(0.02 * 0.44 * 10,000, 0.07 * 4,500) = min(88, 315) = 88. +- name: North Dakota charges the TANF copay when a caretaker receives SSI before October 2026 + period: 2026-09 + absolute_error_margin: 0.01 + input: + people: + parent: + age: + 2026: 30 + ssi: 100 + child: + age: + 2026: 3 + ssi: 0 + childcare_hours_per_week: + 2026: 40 + spm_units: + family: + members: [parent, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + households: + household: + members: [parent, child] + state_code: + 2026: ND + output: + nd_ccap_copay: 88 + +# The Diversion family loses its waiver to the same caretaker-SSI exception. +# Copay = 0.02 * 0.44 * 10,000 = 88. +- name: North Dakota charges the Diversion copay when a caretaker receives SSI before October 2026 + period: 2026-09 + absolute_error_margin: 0.01 + input: + people: + parent: + age: + 2026: 30 + ssi: 100 + child: + age: + 2026: 3 + ssi: 0 + childcare_hours_per_week: + 2026: 40 spm_units: family: members: [parent, child] @@ -1191,3 +1294,83 @@ 2026: ND output: nd_ccap_copay: 88 + +# The 10-year-old receives SSI and attends no child care; the 3-year-old attends +# care and receives no SSI. Release 26.3 charges the co-pay for "child care for +# an SSI child" alone, so the TANF waiver covers the 88 worksheet copay. +- name: North Dakota keeps the TANF waiver when the SSI child attends no child care + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: + 2026: 30 + ssi: 0 + ssi_child: + age: + 2026: 10 + ssi: 100 + child: + age: + 2026: 3 + ssi: 0 + childcare_hours_per_week: + 2026: 40 + spm_units: + family: + members: [parent, ssi_child, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + households: + household: + members: [parent, ssi_child, child] + state_code: + 2026: ND + output: + nd_ccap_copay: 0 + +# North Dakota's incapacity age limit is under 19, so a disabled 19-year-old +# who receives SSI and attends care does not trigger the child-SSI exception. +# The TANF waiver covers the 88 worksheet copay. +- name: North Dakota keeps the TANF waiver for a disabled 19-year-old receiving SSI + period: 2026-10 + absolute_error_margin: 0.01 + input: + people: + parent: + age: + 2026: 45 + ssi: 0 + disabled_adult_child: + age: + 2026: 19 + is_disabled: + 2026: true + ssi: 100 + childcare_hours_per_week: + 2026: 20 + child: + age: + 2026: 3 + ssi: 0 + childcare_hours_per_week: + 2026: 40 + spm_units: + family: + members: [parent, disabled_adult_child, child] + hhs_smi: + 2026: 120000 + nd_ccap_countable_income: 4500 + is_tanf_enrolled: true + nd_ccap_eligible: true + households: + household: + members: [parent, disabled_adult_child, child] + state_code: + 2026: ND + output: + nd_ccap_copay: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.yaml new file mode 100644 index 00000000000..20e24f36fab --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.yaml @@ -0,0 +1,111 @@ +# Tests for nd_ccap_tanf_copay_exception (SPMUnit, MONTH, bool). +# From October 2011 through September 2026, North Dakota charged the co-pay +# when child care followed from the activity of a TANF caretaker "In receipt +# of SSI" (400-28-90-20, ML 3278 and ML 3605 versions). Release 26.3 repealed +# that exception on October 1, 2026, and moved the SSI test to the child. + +- name: North Dakota applies the caretaker-SSI exception from October 2011 + period: 2020-01 + input: + people: + parent: + age: + 2020: 30 + ssi: 100 + child: + age: + 2020: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: + 2020: ND + output: + nd_ccap_tanf_copay_exception: true + +- name: North Dakota applies the caretaker-SSI exception through September 2026 + period: 2026-09 + input: + people: + parent: + age: + 2026: 30 + ssi: 100 + child: + age: + 2026: 3 + ssi: 0 + spm_units: + family: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: + 2026: ND + output: + nd_ccap_tanf_copay_exception: true + +- name: North Dakota ends the caretaker-SSI exception in October 2026 + period: 2026-10 + input: + people: + parent: + age: + 2026: 30 + ssi: 100 + child: + age: + 2026: 3 + ssi: 0 + childcare_hours_per_week: + 2026: 40 + spm_units: + family: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: + 2026: ND + output: + nd_ccap_tanf_copay_exception: false + +# The 18-year-old receives SSI but is neither the tax unit head nor spouse, +# so the model does not treat them as a caretaker. +- name: North Dakota applies the caretaker-SSI exception to caretakers alone + period: 2025-01 + input: + people: + parent: + age: + 2025: 40 + is_tax_unit_head_or_spouse: + 2025: true + ssi: 0 + adult_child: + age: + 2025: 18 + is_tax_unit_head_or_spouse: + 2025: false + ssi: 100 + child: + age: + 2025: 3 + is_tax_unit_head_or_spouse: + 2025: false + ssi: 0 + spm_units: + family: + members: [parent, adult_child, child] + households: + household: + members: [parent, adult_child, child] + state_code: + 2025: ND + output: + nd_ccap_tanf_copay_exception: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.yaml index fec407a9059..777f4a2aaeb 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.yaml @@ -132,3 +132,110 @@ state_code: ND output: nd_ccap_activity_eligible: true + +# ML 3819 Example 1: one caretaker works 30 hours a week at a North Dakota +# licensed program and qualifies for the Workforce Benefit; the other has no +# allowable activity. North Dakota approved CCAP from June 2024 through +# December 2025. +- name: North Dakota exempts the second caretaker of a Workforce Benefit household in 2025 + period: 2025-01 + input: + people: + person1: + age: 32 + employment_income: 24_000 + is_tax_unit_head_or_spouse: true + nd_ccap_workforce_benefit_eligible_parent: true + person2: + age: 30 # no activity + is_tax_unit_head_or_spouse: true + person3: + age: 3 + is_tax_unit_head_or_spouse: false + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: ND + output: + nd_ccap_activity_eligible: true + +# Release 26.1 requires the second caretaker's activity from January 2026. +- name: North Dakota requires the second caretaker's activity in a Workforce Benefit household from 2026 + period: 2026-01 + input: + people: + person1: + age: 32 + employment_income: 24_000 + is_tax_unit_head_or_spouse: true + nd_ccap_workforce_benefit_eligible_parent: true + person2: + age: 30 # no activity + is_tax_unit_head_or_spouse: true + person3: + age: 3 + is_tax_unit_head_or_spouse: false + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: ND + output: + nd_ccap_activity_eligible: false + +# The Workforce Benefit starts June 1, 2024. +- name: North Dakota requires the second caretaker's activity before the Workforce Benefit starts + period: 2024-05 + input: + people: + person1: + age: 32 + employment_income: 24_000 + is_tax_unit_head_or_spouse: true + nd_ccap_workforce_benefit_eligible_parent: true + person2: + age: 30 # no activity + is_tax_unit_head_or_spouse: true + person3: + age: 3 + is_tax_unit_head_or_spouse: false + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: ND + output: + nd_ccap_activity_eligible: false + +# Without a Workforce Benefit caretaker, North Dakota still requires the +# second caretaker's activity in 2025. +- name: North Dakota requires the second caretaker's activity outside the Workforce Benefit in 2025 + period: 2025-01 + input: + people: + person1: + age: 32 + employment_income: 24_000 + is_tax_unit_head_or_spouse: true + person2: + age: 30 # no activity + is_tax_unit_head_or_spouse: true + person3: + age: 3 + is_tax_unit_head_or_spouse: false + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: ND + output: + nd_ccap_activity_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_eligible.yaml index 0e17b2d81b1..8a337dcda64 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_eligible.yaml @@ -129,3 +129,63 @@ output: # defined_for = StateCode.ND zeroes eligibility outside North Dakota. nd_ccap_eligible: false + +# ML 3819 Example 1 with the income and activity tests computed. Workforce +# Benefit qualification excludes all income, and the second caretaker needs no +# activity from June 2024 through December 2025. +- name: North Dakota approves a Workforce Benefit household with an inactive second caretaker in 2025 + period: 2025-01 + input: + people: + person1: + age: 32 + employment_income: 240_000 + is_tax_unit_head_or_spouse: true + nd_ccap_workforce_benefit_eligible_parent: true + person2: + age: 30 # no activity + is_tax_unit_head_or_spouse: true + person3: + age: 3 + is_tax_unit_head_or_spouse: false + spm_units: + spm_unit: + members: [person1, person2, person3] + is_ccdf_asset_eligible: true + households: + household: + members: [person1, person2, person3] + state_code: ND + output: + nd_ccap_income_eligible: true + nd_ccap_activity_eligible: true + nd_ccap_eligible: true + +# Release 26.1 denies the same household from January 2026. +- name: North Dakota denies a Workforce Benefit household with an inactive second caretaker from 2026 + period: 2026-01 + input: + people: + person1: + age: 32 + employment_income: 240_000 + is_tax_unit_head_or_spouse: true + nd_ccap_workforce_benefit_eligible_parent: true + person2: + age: 30 # no activity + is_tax_unit_head_or_spouse: true + person3: + age: 3 + is_tax_unit_head_or_spouse: false + spm_units: + spm_unit: + members: [person1, person2, person3] + is_ccdf_asset_eligible: true + households: + household: + members: [person1, person2, person3] + state_code: ND + output: + nd_ccap_income_eligible: true + nd_ccap_activity_eligible: false + nd_ccap_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.yaml index 4307ad8692c..dada9b1cc37 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_income_eligible.yaml @@ -336,6 +336,11 @@ nd_ccap_countable_income: 20000 is_tanf_enrolled: false is_nd_ccap_enrolled: false + tax_units: + workforce: + members: [workforce_parent, workforce_child] + ordinary: + members: [ordinary_parent, ordinary_child] households: workforce: members: [workforce_parent, workforce_child] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.yaml index 1ddc4f36153..a83c33aef3c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.yaml @@ -60,3 +60,31 @@ 2024: ND output: nd_ccap_workforce_benefit_eligible: true + +# North Dakota requires a caretaker in qualifying child care employment +# (400-28-20-40). A 16-year-old sibling's job leaves the family ineligible. +- name: North Dakota withholds the Workforce Benefit when only a non-caretaker qualifies + period: 2025-01 + input: + people: + parent: + age: + 2025: 30 + nd_ccap_workforce_benefit_eligible_parent: false + teen: + age: + 2025: 16 + nd_ccap_workforce_benefit_eligible_parent: true + child: + age: + 2025: 3 + spm_units: + family: + members: [parent, teen, child] + households: + household: + members: [parent, teen, child] + state_code: + 2025: ND + output: + nd_ccap_workforce_benefit_eligible: false diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.py index 7e0a7f715d2..6f16a9a8a21 100644 --- a/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.py +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.py @@ -9,22 +9,35 @@ class nd_ccap_tanf_copay_exception(Variable): reference = ( "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_90_20.htm", "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_45_05.htm", + "https://www.nd.gov/dhs/policymanuals/40028/Content/Archive%20Documents/2011%20-%203278%2C%203295%2C%203327/400_28_90_20%20ML3295.htm", + "https://www.nd.gov/dhs/policymanuals/40028/Content/Archive%20Documents/2023/ML%203732/400_28_90_20.htm", + # PDF pages 3-4 + "https://www.nd.gov/dhs/policymanuals/40028/Content/ML/2026/CCAP%20Release%2026.3%20Effective%20October.1.2026.pdf#page=3", ) def formula_2011_10_01(spm_unit, period, parameters): + # Until October 2026, North Dakota charged the co-pay when child care + # resulted from the activity of a TANF caretaker who received SSI + # (400-28-90-20 and the repealed 400-28-45-15). + person = spm_unit.members + caretaker = person("is_tax_unit_head_or_spouse", period.this_year) + caretaker_ssi = spm_unit.any(caretaker & (person("ssi", period) > 0)) alien = spm_unit("nd_ccap_has_disqualified_alien_caretaker", period) non_legally_responsible = spm_unit( "nd_ccap_has_ineligible_non_legally_responsible_caretaker", period ) - return alien | non_legally_responsible + return caretaker_ssi | alien | non_legally_responsible def formula_2021_04_01(spm_unit, period, parameters): + person = spm_unit.members + caretaker = person("is_tax_unit_head_or_spouse", period.this_year) + caretaker_ssi = spm_unit.any(caretaker & (person("ssi", period) > 0)) alien = spm_unit("nd_ccap_has_disqualified_alien_caretaker", period) non_legally_responsible = spm_unit( "nd_ccap_has_ineligible_non_legally_responsible_caretaker", period ) kinship = spm_unit("nd_ccap_has_ineligible_kinship_caretaker", period) - return alien | non_legally_responsible | kinship + return caretaker_ssi | alien | non_legally_responsible | kinship def formula_2026_10_01(spm_unit, period, parameters): person = spm_unit.members @@ -35,7 +48,12 @@ def formula_2026_10_01(spm_unit, period, parameters): person("is_disabled", period.this_year) & (person("age", period.this_year) < p.disabled_child_age_limit) ) - child_receives_ssi = spm_unit.any(child & (person("ssi", period) > 0)) + # The co-pay applies to "child care for an SSI child" (400-28-45-05), + # so an SSI child outside care leaves the waiver in place. When an + # SSI child is in care, the model charges the whole family + # co-payment, including for a sibling whose care stays waived. + in_care = person("nd_ccap_child_in_care", period) + child_receives_ssi = spm_unit.any(child & in_care & (person("ssi", period) > 0)) alien = spm_unit("nd_ccap_has_disqualified_alien_caretaker", period) non_legally_responsible = spm_unit( "nd_ccap_has_ineligible_non_legally_responsible_caretaker", period diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.py index 080c50a68d8..3b4b359b115 100644 --- a/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.py +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.py @@ -7,7 +7,11 @@ class nd_ccap_activity_eligible(Variable): label = "North Dakota CCAP activity eligible" definition_period = MONTH defined_for = StateCode.ND - reference = "https://www.nd.gov/dhs/policymanuals/40028/40028.htm" + reference = ( + "https://www.nd.gov/dhs/policymanuals/40028/40028.htm", + "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_20_40.htm", + "https://www.nd.gov/dhs/policymanuals/40028/Content/ML/2024/CCAP%20ML%203819%20Effective%20June.1.2024.pdf#page=2", + ) def formula(spm_unit, period, parameters): # All caretakers (head and spouse) must be in an allowable activity. @@ -24,4 +28,12 @@ def formula(spm_unit, period, parameters): # individually modeled (job search, education or training programs, # SNAP E&T, or a temporary leave from work or school; 400-28-55-05). meets_ccdf = spm_unit("meets_ccdf_activity_test", period.this_year) - return modeled_eligible | meets_ccdf + # From June 2024 through December 2025, North Dakota approved a + # Workforce Benefit household whose second caretaker had no allowable + # activity (400-28-20-40, ML 3819 Example 1). The qualifying caretaker's + # own employment meets the activity test. Release 26.1 requires the + # second caretaker's activity from January 2026. + p = parameters(period).gov.states.nd.dhs.ccap.workforce + workforce = spm_unit("nd_ccap_workforce_benefit_eligible", period) + workforce_exempt = p.second_caretaker_activity_exempt & workforce + return modeled_eligible | meets_ccdf | workforce_exempt diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.py index 4b4246c4a0b..9ce5008f342 100644 --- a/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.py +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_workforce_benefit_eligible.py @@ -10,6 +10,10 @@ class nd_ccap_workforce_benefit_eligible(Variable): reference = "https://www.nd.gov/dhs/policymanuals/40028/Content/400_28_20_40.htm" def formula_2024_06_01(spm_unit, period, parameters): - return spm_unit.any( - spm_unit.members("nd_ccap_workforce_benefit_eligible_parent", period) - ) + # North Dakota requires at least one caretaker in qualifying child care + # employment (400-28-20-40); the model treats the tax unit head and + # spouse as the caretakers, as the activity test does. + person = spm_unit.members + caretaker = person("is_tax_unit_head_or_spouse", period.this_year) + qualifies = person("nd_ccap_workforce_benefit_eligible_parent", period) + return spm_unit.any(caretaker & qualifies) diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/nd_ccap.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/nd_ccap.py index f56dbe8f398..92679c3de3b 100644 --- a/policyengine_us/variables/gov/states/nd/dhs/ccap/nd_ccap.py +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/nd_ccap.py @@ -18,18 +18,7 @@ def formula(spm_unit, period, parameters): # co-payment (400-28-100-30). base_subsidy = spm_unit("nd_ccap_base_subsidy", period) person = spm_unit.members - # Reported care days or hours establish participation independently - # of the missing-hours pricing fallback. - weekly_hours = person("childcare_hours_per_week", period.this_year) - daily_hours = person("childcare_hours_per_day", period.this_year) - monthly_days = person("childcare_attending_days_per_month", period.this_year) - weekly_days = person("childcare_days_per_week", period.this_year) - in_care = ( - (weekly_hours > 0) - | (daily_hours > 0) - | (monthly_days > 0) - | (weekly_days > 0) - ) + in_care = person("nd_ccap_child_in_care", period) qris_step_bonus = spm_unit.sum( person("nd_ccap_qris_step_bonus", period) * in_care ) diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/nd_ccap_child_in_care.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/nd_ccap_child_in_care.py new file mode 100644 index 00000000000..69202c9a390 --- /dev/null +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/nd_ccap_child_in_care.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class nd_ccap_child_in_care(Variable): + value_type = bool + entity = Person + label = "North Dakota CCAP person attends child care" + definition_period = MONTH + defined_for = StateCode.ND + reference = "https://www.nd.gov/dhs/policymanuals/40028/40028.htm" + + def formula(person, period, parameters): + # Reported care days or hours establish participation independently + # of the missing-hours pricing fallback. + year = period.this_year + return ( + (person("childcare_hours_per_week", year) > 0) + | (person("childcare_hours_per_day", year) > 0) + | (person("childcare_attending_days_per_month", year) > 0) + | (person("childcare_days_per_week", year) > 0) + ) diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/rates/nd_ccap_base_subsidy.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/rates/nd_ccap_base_subsidy.py index d3082cf417c..3a02a714f9c 100644 --- a/policyengine_us/variables/gov/states/nd/dhs/ccap/rates/nd_ccap_base_subsidy.py +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/rates/nd_ccap_base_subsidy.py @@ -24,18 +24,7 @@ def formula(spm_unit, period, parameters): # payment outside the monthly care subsidy, and we don't track whether a # provider charges a registration fee or its amount at the moment. person = spm_unit.members - # Reported care days or hours establish participation independently - # of the missing-hours pricing fallback. - weekly_hours = person("childcare_hours_per_week", period.this_year) - daily_hours = person("childcare_hours_per_day", period.this_year) - monthly_days = person("childcare_attending_days_per_month", period.this_year) - weekly_days = person("childcare_days_per_week", period.this_year) - in_care = ( - (weekly_hours > 0) - | (daily_hours > 0) - | (monthly_days > 0) - | (weekly_days > 0) - ) + in_care = person("nd_ccap_child_in_care", period) maximum_monthly_rate = spm_unit.sum( person("nd_ccap_state_max_rate", period) * in_care ) From 6e175fe8024f3bc1e4e5fd2ef6716275a38f601b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 13 Nov 2026 18:11:50 -0500 Subject: [PATCH 4/4] Make North Dakota CCAP comments name the acting agency Co-Authored-By: Claude Opus 5.5 --- .../workforce/second_caretaker_activity_exempt.yaml | 9 +++++---- .../gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml | 10 +++++----- .../dhs/ccap/copay/nd_ccap_tanf_copay_exception.yaml | 5 +++-- .../ccap/eligibility/nd_ccap_activity_eligible.yaml | 5 +++-- .../nd/dhs/ccap/eligibility/nd_ccap_eligible.yaml | 8 ++++---- .../nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.py | 8 ++++---- .../dhs/ccap/eligibility/nd_ccap_activity_eligible.py | 4 ++-- 7 files changed, 26 insertions(+), 23 deletions(-) diff --git a/policyengine_us/parameters/gov/states/nd/dhs/ccap/workforce/second_caretaker_activity_exempt.yaml b/policyengine_us/parameters/gov/states/nd/dhs/ccap/workforce/second_caretaker_activity_exempt.yaml index 61173d75a47..16685643a50 100644 --- a/policyengine_us/parameters/gov/states/nd/dhs/ccap/workforce/second_caretaker_activity_exempt.yaml +++ b/policyengine_us/parameters/gov/states/nd/dhs/ccap/workforce/second_caretaker_activity_exempt.yaml @@ -1,9 +1,10 @@ description: North Dakota exempts the second caretaker in a Workforce Benefit household from the allowable-activity requirement under the Child Care Assistance Program if this is true. -# ML 3819 (June 1, 2024) says the second caretaker "does not need to be in an -# allowable activity"; ML 3862 and ML 3909 keep that text. Release 26.1 -# (January 1, 2026) requires the second caretaker's activity. The Workforce -# Benefit starts June 1, 2024, so the earlier false value has no effect. +# From June 1, 2024 (ML 3819), North Dakota exempted the second caretaker of a +# Workforce Benefit household from the activity requirement, and it kept that +# rule in ML 3862 and ML 3909. From January 1, 2026 (Release 26.1), North +# Dakota requires the second caretaker's activity. North Dakota started the +# Workforce Benefit on June 1, 2024, so the earlier false value has no effect. values: 0000-01-01: false 2024-06-01: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml index 06691cccd44..8350fbe8b1f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_copay.yaml @@ -1296,8 +1296,8 @@ nd_ccap_copay: 88 # The 10-year-old receives SSI and attends no child care; the 3-year-old attends -# care and receives no SSI. Release 26.3 charges the co-pay for "child care for -# an SSI child" alone, so the TANF waiver covers the 88 worksheet copay. +# care and receives no SSI. North Dakota charges the co-pay for "child care for +# an SSI child" (400-28-45-05), so the TANF waiver covers the 88 worksheet copay. - name: North Dakota keeps the TANF waiver when the SSI child attends no child care period: 2026-10 absolute_error_margin: 0.01 @@ -1333,9 +1333,9 @@ output: nd_ccap_copay: 0 -# North Dakota's incapacity age limit is under 19, so a disabled 19-year-old -# who receives SSI and attends care does not trigger the child-SSI exception. -# The TANF waiver covers the 88 worksheet copay. +# The model applies North Dakota's under-19 incapacity age limit to annual age, +# so a disabled 19-year-old who receives SSI and attends care does not trigger +# the child-SSI exception. The TANF waiver covers the 88 worksheet copay. - name: North Dakota keeps the TANF waiver for a disabled 19-year-old receiving SSI period: 2026-10 absolute_error_margin: 0.01 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.yaml index 20e24f36fab..105e5ae01ee 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.yaml @@ -1,8 +1,9 @@ # Tests for nd_ccap_tanf_copay_exception (SPMUnit, MONTH, bool). # From October 2011 through September 2026, North Dakota charged the co-pay # when child care followed from the activity of a TANF caretaker "In receipt -# of SSI" (400-28-90-20, ML 3278 and ML 3605 versions). Release 26.3 repealed -# that exception on October 1, 2026, and moved the SSI test to the child. +# of SSI" (400-28-90-20, ML 3278 and ML 3605 versions). North Dakota repealed +# that exception on October 1, 2026 (Release 26.3) and moved the SSI test to +# the child. - name: North Dakota applies the caretaker-SSI exception from October 2011 period: 2020-01 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.yaml index 777f4a2aaeb..69d85376e31 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.yaml @@ -162,7 +162,8 @@ output: nd_ccap_activity_eligible: true -# Release 26.1 requires the second caretaker's activity from January 2026. +# North Dakota requires the second caretaker's activity from January 2026 +# (Release 26.1). - name: North Dakota requires the second caretaker's activity in a Workforce Benefit household from 2026 period: 2026-01 input: @@ -188,7 +189,7 @@ output: nd_ccap_activity_eligible: false -# The Workforce Benefit starts June 1, 2024. +# North Dakota started the Workforce Benefit on June 1, 2024. - name: North Dakota requires the second caretaker's activity before the Workforce Benefit starts period: 2024-05 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_eligible.yaml index 8a337dcda64..ede593746a0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/dhs/ccap/eligibility/nd_ccap_eligible.yaml @@ -130,9 +130,9 @@ # defined_for = StateCode.ND zeroes eligibility outside North Dakota. nd_ccap_eligible: false -# ML 3819 Example 1 with the income and activity tests computed. Workforce -# Benefit qualification excludes all income, and the second caretaker needs no -# activity from June 2024 through December 2025. +# ML 3819 Example 1 with the income and activity tests computed. North Dakota +# excludes all income of a Workforce Benefit family and, from June 2024 through +# December 2025, required no activity of its second caretaker. - name: North Dakota approves a Workforce Benefit household with an inactive second caretaker in 2025 period: 2025-01 input: @@ -161,7 +161,7 @@ nd_ccap_activity_eligible: true nd_ccap_eligible: true -# Release 26.1 denies the same household from January 2026. +# North Dakota denies the same household from January 2026 (Release 26.1). - name: North Dakota denies a Workforce Benefit household with an inactive second caretaker from 2026 period: 2026-01 input: diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.py index 6f16a9a8a21..e6f782609ed 100644 --- a/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.py +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/copay/nd_ccap_tanf_copay_exception.py @@ -48,10 +48,10 @@ def formula_2026_10_01(spm_unit, period, parameters): person("is_disabled", period.this_year) & (person("age", period.this_year) < p.disabled_child_age_limit) ) - # The co-pay applies to "child care for an SSI child" (400-28-45-05), - # so an SSI child outside care leaves the waiver in place. When an - # SSI child is in care, the model charges the whole family - # co-payment, including for a sibling whose care stays waived. + # North Dakota charges the co-pay for "child care for an SSI child" + # (400-28-45-05), so the waiver holds when the SSI child attends no + # care. When an SSI child attends care, the model charges the whole + # family co-payment, including for a sibling whose care stays waived. in_care = person("nd_ccap_child_in_care", period) child_receives_ssi = spm_unit.any(child & in_care & (person("ssi", period) > 0)) alien = spm_unit("nd_ccap_has_disqualified_alien_caretaker", period) diff --git a/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.py b/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.py index 3b4b359b115..16d25e64ab2 100644 --- a/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.py +++ b/policyengine_us/variables/gov/states/nd/dhs/ccap/eligibility/nd_ccap_activity_eligible.py @@ -31,8 +31,8 @@ def formula(spm_unit, period, parameters): # From June 2024 through December 2025, North Dakota approved a # Workforce Benefit household whose second caretaker had no allowable # activity (400-28-20-40, ML 3819 Example 1). The qualifying caretaker's - # own employment meets the activity test. Release 26.1 requires the - # second caretaker's activity from January 2026. + # own employment meets the activity test. North Dakota requires the + # second caretaker's activity from January 2026 (Release 26.1). p = parameters(period).gov.states.nd.dhs.ccap.workforce workforce = spm_unit("nd_ccap_workforce_benefit_eligible", period) workforce_exempt = p.second_caretaker_activity_exempt & workforce