diff --git a/changelog.d/nv-ccap.added.md b/changelog.d/nv-ccap.added.md new file mode 100644 index 00000000000..422e90b0348 --- /dev/null +++ b/changelog.d/nv-ccap.added.md @@ -0,0 +1 @@ +Add Nevada Child Care and Development Program (CCDP). diff --git a/policyengine_us/parameters/gov/hhs/ccdf/child_care_subsidy_programs.yaml b/policyengine_us/parameters/gov/hhs/ccdf/child_care_subsidy_programs.yaml index 748e5f5bffc..39f81e9e718 100644 --- a/policyengine_us/parameters/gov/hhs/ccdf/child_care_subsidy_programs.yaml +++ b/policyengine_us/parameters/gov/hhs/ccdf/child_care_subsidy_programs.yaml @@ -22,6 +22,7 @@ values: - nh_child_care_subsidies # New Hampshire Child Care Scholarship Program - pa_child_care_subsidies # Pennsylvania Child Care Works - nj_child_care_subsidies # New Jersey Child Care Assistance Program + - nv_child_care_subsidies # Nevada Child Care and Development Program (CCDP) - ri_child_care_subsidies # Rhode Island Child Care Assistance Program - sc_child_care_subsidies # South Carolina Child Care Scholarship Program - va_child_care_subsidies # Virginia Child Care Subsidy Program diff --git a/policyengine_us/parameters/gov/states/nv/dwss/ccdp/age_group/months.yaml b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/age_group/months.yaml new file mode 100644 index 00000000000..c3f3e8b9c5b --- /dev/null +++ b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/age_group/months.yaml @@ -0,0 +1,35 @@ +description: Nevada defines these child age group boundaries in months under the Child Care and Development Program. + +metadata: + type: single_amount + threshold_unit: month + amount_unit: /1 + period: year + label: Nevada CCDP child age group by months + reference: + - title: Nevada CCDF State Plan FFY 2025-2027, Section 4.3.1 — Base Payment Rate age categories (pp. 54-56) + href: https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/ACF-118_CCDF_FFY_2025-2027_For_Nevada__3.pdf#page=54 + +# Maps a child's age in months to a rate-table age group index: +# 0 = INFANT, 1 = TODDLER, 2 = PRESCHOOL, 3 = SCHOOL_AGE. +# The State Plan rate table lists representative ages (Infant 6 months, Toddler +# 18 months, Preschool 4 years, School-Age 6 years) but does not publish exact +# month cutoffs. These boundaries follow the sibling CCAP convention pending +# confirmation against an operational rate sheet (PR Verification TODO). +brackets: + - threshold: + 2024-10-01: 0 + amount: + 2024-10-01: 0 + - threshold: + 2024-10-01: 25 + amount: + 2024-10-01: 1 + - threshold: + 2024-10-01: 37 + amount: + 2024-10-01: 2 + - threshold: + 2024-10-01: 60 + amount: + 2024-10-01: 3 diff --git a/policyengine_us/parameters/gov/states/nv/dwss/ccdp/billing/monthly_billed_days.yaml b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/billing/monthly_billed_days.yaml new file mode 100644 index 00000000000..e6dd0232730 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/billing/monthly_billed_days.yaml @@ -0,0 +1,17 @@ +description: Nevada bills child care for this number of days per month when converting daily rates to a monthly maximum under the Child Care and Development Program. +values: + 2024-10-01: 21.67 + +metadata: + unit: day + period: year + label: Nevada CCDP monthly billed days + reference: + - title: Nevada Child Care Policy Manual, MS 163 — Payment of Subsidy + href: https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/Child_Care_Manual_July_2024.pdf#page=29 + +# Full-time-month proxy (260 weekday days per year / 12 = 21.67) used to convert +# the per-day base rate to a monthly maximum. Nevada does not publish an explicit +# daily-to-monthly billing convention, so this is a conservative full-time proxy +# applied when a household's actual attended days per month are not available +# (PR Verification TODO — confirm against an operational rate sheet). diff --git a/policyengine_us/parameters/gov/states/nv/dwss/ccdp/copay/amount.yaml b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/copay/amount.yaml new file mode 100644 index 00000000000..33ab64cb29a --- /dev/null +++ b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/copay/amount.yaml @@ -0,0 +1,34 @@ +description: Nevada sets the monthly family copayment at this amount based on income as a share of state median income under the Child Care and Development Program. + +metadata: + type: single_amount + threshold_unit: /1 + amount_unit: currency-USD + period: month + label: Nevada CCDP monthly family copayment + reference: + - title: Nevada CCDF State Plan FFY 2025-2027, Section 2.5.5.v — Flat rate copayments during graduated phase-out + href: https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/ACF-118_CCDF_FFY_2025-2027_For_Nevada__3.pdf#page=36 + - title: Nevada DWSS Policy Transmittal CC PT 06-25 (Oct 2, 2025) — Annual Income Changes + href: https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/CC_PT_06-25_ANNUAL_INCOME_CHANGES_10.02.2025.pdf#page=1 + +# The flat $0/$90/$150 copay and two-tier copay structure took effect +# Oct 1, 2024 (State Plan Section 2.4.v); CC PT 06-25 is the Oct 1, 2025 annual +# income-chart refresh that restates the same percentages and bands. +# Income up to 32.99% SMI -> $0; 33%-42% SMI -> $90; 43%-49% SMI -> $150. +# The plan states "33% SMI to 42%" -> $90 and "43% SMI to 49%" -> $150, so the +# 0.33 and 0.43 thresholds belong to the higher band (PolicyEngine's at-or-above +# default); no boundary epsilon is needed. +brackets: + - threshold: + 2024-10-01: 0 + amount: + 2024-10-01: 0 + - threshold: + 2024-10-01: 0.33 + amount: + 2024-10-01: 90 + - threshold: + 2024-10-01: 0.43 + amount: + 2024-10-01: 150 diff --git a/policyengine_us/parameters/gov/states/nv/dwss/ccdp/eligibility/child_age_limit.yaml b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/eligibility/child_age_limit.yaml new file mode 100644 index 00000000000..6e6baf344b5 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/eligibility/child_age_limit.yaml @@ -0,0 +1,11 @@ +description: Nevada limits child eligibility to under this age under the Child Care and Development Program. +values: + 2024-01-01: 13 + +metadata: + unit: year + period: year + label: Nevada CCDP child age limit + reference: + - title: Nevada Child Care Policy Manual, MS 210 — Age + href: https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/Child_Care_Manual_July_2024.pdf#page=36 diff --git a/policyengine_us/parameters/gov/states/nv/dwss/ccdp/eligibility/special_needs_child_age_limit.yaml b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/eligibility/special_needs_child_age_limit.yaml new file mode 100644 index 00000000000..fd3790bd315 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/eligibility/special_needs_child_age_limit.yaml @@ -0,0 +1,11 @@ +description: Nevada limits special needs child eligibility to under this age under the Child Care and Development Program. +values: + 2024-01-01: 19 + +metadata: + unit: year + period: year + label: Nevada CCDP special needs child age limit + reference: + - title: Nevada Child Care Policy Manual, MS 211 — Child with Special Needs + href: https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/Child_Care_Manual_July_2024.pdf#page=37 diff --git a/policyengine_us/parameters/gov/states/nv/dwss/ccdp/income/countable_income/sources.yaml b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/income/countable_income/sources.yaml new file mode 100644 index 00000000000..84f6879e04d --- /dev/null +++ b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/income/countable_income/sources.yaml @@ -0,0 +1,42 @@ +description: Nevada counts these income sources under the Child Care and Development Program. +values: + 2024-01-01: + # Earned income (MS 306.48 Wages/Salaries/Commissions, 306.7 Contractual + # Earnings, 306.36 Seasonal Employment — all countable). employment_income + # already includes tips (306.48) and bonuses/commissions, so tip_income is + # not listed separately to avoid double-counting. + - employment_income + - self_employment_income + - farm_operations_income + # Unearned income (countable per MS 306). + - social_security # MS 306.34 RSDI umbrella (Retirement, Survivors, Disability) + - unemployment_compensation # MS 306.45 + - workers_compensation # MS 306.49 + - pension_income # MS 306.29 + - military_retirement_pay # MS 306.29 pensions; PolicyEngine records this separately from pension_income + - alimony_income # MS 306.2 + - child_support_received # MS 306.6 + - dividend_income # MS 306.10 + - rental_income # MS 306.30 Property Income (Rental/Lease) + # MS 306 income types we don't model at the moment: + # - MS 306.40 Supplemental Security Income (SSI) is EXEMPT in Nevada (differs + # from many states); ssi is therefore NOT listed. + # - MS 306.41/306.42 TANF, Tribal TANF, Kinship Care, and General Assistance + # are countable, but TANF is excluded here to avoid a circular dependency + # (nv_ccdp_countable_income -> tanf -> ... -> childcare_expenses -> + # child_care_subsidies -> nv_ccdp). The is_tanf_enrolled hook handles the + # NEON pathway and copay waiver instead. + # - Interest income is not enumerated as a countable type in MS 306, so + # interest_income is omitted (Dividends are listed at 306.10, interest is not). + # - MS 306.15 Gambling Winnings, 306.20 In-Kind Income, 306.35 Royalties, + # 306.44 Trust Funds, and 306.46 Veterans Administration Benefits (coded + # "Maybe") have no corresponding PolicyEngine variable, or are not + # reliably countable, at the moment. + +metadata: + unit: list + period: year + label: Nevada CCDP countable income sources + reference: + - title: Nevada Child Care Policy Manual, MS 306 — Types of Income + href: https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/Child_Care_Manual_July_2024.pdf#page=55 diff --git a/policyengine_us/parameters/gov/states/nv/dwss/ccdp/income/smi_intake.yaml b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/income/smi_intake.yaml new file mode 100644 index 00000000000..7bf3c1831ff --- /dev/null +++ b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/income/smi_intake.yaml @@ -0,0 +1,17 @@ +description: Nevada limits new applicants to income at or below this share of state median income under the Child Care and Development Program. +values: + 2024-10-01: 0.41 + +metadata: + unit: /1 + period: year + label: Nevada CCDP intake income limit share of state median income + reference: + - title: Nevada DWSS Policy Transmittal CC PT 06-25 (Oct 2, 2025) — Annual Income Changes + href: https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/CC_PT_06-25_ANNUAL_INCOME_CHANGES_10.02.2025.pdf#page=1 + - title: Nevada CCDF State Plan FFY 2025-2027, Section 2.2.4 — Initial eligibility income limits + href: https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/ACF-118_CCDF_FFY_2025-2027_For_Nevada__3.pdf#page=22 + +# The flat $0/$90/$150 copay and two-tier 41%/49% SMI eligibility took effect +# Oct 1, 2024 (State Plan Section 2.4.v); CC PT 06-25 is the Oct 1, 2025 annual +# income-chart refresh that restates the same percentages and bands. diff --git a/policyengine_us/parameters/gov/states/nv/dwss/ccdp/income/smi_renewal.yaml b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/income/smi_renewal.yaml new file mode 100644 index 00000000000..db8109ba9f4 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/income/smi_renewal.yaml @@ -0,0 +1,17 @@ +description: Nevada limits renewing households to income at or below this share of state median income under the Child Care and Development Program. +values: + 2024-10-01: 0.49 + +metadata: + unit: /1 + period: year + label: Nevada CCDP renewal income limit share of state median income + reference: + - title: Nevada DWSS Policy Transmittal CC PT 06-25 (Oct 2, 2025) — Annual Income Changes + href: https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/CC_PT_06-25_ANNUAL_INCOME_CHANGES_10.02.2025.pdf#page=1 + - title: Nevada CCDF State Plan FFY 2025-2027, Section 2.5.5 — Graduated phase-out of assistance at redetermination + href: https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/ACF-118_CCDF_FFY_2025-2027_For_Nevada__3.pdf#page=36 + +# The flat $0/$90/$150 copay and two-tier 41%/49% SMI eligibility took effect +# Oct 1, 2024 (State Plan Section 2.4.v); CC PT 06-25 is the Oct 1, 2025 annual +# income-chart refresh that restates the same percentages and bands. diff --git a/policyengine_us/parameters/gov/states/nv/dwss/ccdp/rates/center.yaml b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/rates/center.yaml new file mode 100644 index 00000000000..b168ba245df --- /dev/null +++ b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/rates/center.yaml @@ -0,0 +1,32 @@ +description: Nevada provides these daily base reimbursement rates for child care centers under the Child Care and Development Program. +metadata: + period: day + unit: currency-USD + label: Nevada CCDP child care center daily rates + breakdown: + - nv_ccdp_region + - nv_ccdp_age_group + reference: + - title: Nevada CCDF State Plan FFY 2025-2027, Section 4.3.1, Table 1 and Table 2 — Base Payment Rate (pp. 54-56) + href: https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/ACF-118_CCDF_FFY_2025-2027_For_Nevada__3.pdf#page=54 + +# Table 1 (most populous region, Clark County) and Table 2 (lowest-percentile +# region, all other counties); base payment rate per day. +MOST_POPULOUS: + INFANT: + 2024-10-01: 62.50 + TODDLER: + 2024-10-01: 57 + PRESCHOOL: + 2024-10-01: 51.50 + SCHOOL_AGE: + 2024-10-01: 48 +LOWEST_PERCENTILE: + INFANT: + 2024-10-01: 53.50 + TODDLER: + 2024-10-01: 52.50 + PRESCHOOL: + 2024-10-01: 47.50 + SCHOOL_AGE: + 2024-10-01: 42.50 diff --git a/policyengine_us/parameters/gov/states/nv/dwss/ccdp/rates/fcc.yaml b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/rates/fcc.yaml new file mode 100644 index 00000000000..33915080569 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nv/dwss/ccdp/rates/fcc.yaml @@ -0,0 +1,32 @@ +description: Nevada provides these daily base reimbursement rates for family child care providers under the Child Care and Development Program. +metadata: + period: day + unit: currency-USD + label: Nevada CCDP family child care daily rates + breakdown: + - nv_ccdp_region + - nv_ccdp_age_group + reference: + - title: Nevada CCDF State Plan FFY 2025-2027, Section 4.3.1, Table 1 and Table 2 — Base Payment Rate (pp. 54-56) + href: https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/ACF-118_CCDF_FFY_2025-2027_For_Nevada__3.pdf#page=54 + +# Table 1 (most populous region, Clark County) and Table 2 (lowest-percentile +# region, all other counties); base payment rate per day. +MOST_POPULOUS: + INFANT: + 2024-10-01: 45 + TODDLER: + 2024-10-01: 45 + PRESCHOOL: + 2024-10-01: 46.50 + SCHOOL_AGE: + 2024-10-01: 46 +LOWEST_PERCENTILE: + INFANT: + 2024-10-01: 40.50 + TODDLER: + 2024-10-01: 40 + PRESCHOOL: + 2024-10-01: 40 + SCHOOL_AGE: + 2024-10-01: 40 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 83ef1f1f1a0..1ee8fdaf74e 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -460,7 +460,7 @@ programs: category: Benefits agency: HHS status: partial - coverage: AK, AR, AL, CA, CO, CT, DE, DC, FL, HI, IA, ID, IL, IN, KS, KY, MA, MD, ME, NC, NH, NJ, PA, RI, SC, TX, VA, VT, WA, WV + coverage: AK, AR, AL, CA, CO, CT, DE, DC, FL, HI, IA, ID, IL, IN, KS, KY, MA, MD, ME, NC, NH, NJ, NV, PA, RI, SC, TX, VA, VT, WA, WV state_implementations: - state: AK status: complete @@ -569,6 +569,12 @@ programs: status: in_progress name: New Jersey CCAP full_name: New Jersey Child Care Assistance Program + - state: NV + status: complete + name: CCDP + full_name: Nevada Child Care and Development Program + variable: nv_ccdp + parameter_prefix: gov.states.nv.dwss.ccdp - state: DC status: complete name: DC CCSP diff --git a/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/copay/nv_ccdp_copay.yaml b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/copay/nv_ccdp_copay.yaml new file mode 100644 index 00000000000..5b3c32716b8 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/copay/nv_ccdp_copay.yaml @@ -0,0 +1,357 @@ +# nv_ccdp_copay: SPMUnit, float (USD), MONTH +# CC PT 06-25 flat copay by income as a share of SMI: +# <= 32.99% SMI -> $0; 33%-42% SMI -> $90; 43%-49% SMI -> $150. +# Waived for TANF/NEON (is_tanf_enrolled), CPS/foster placements, and homeless. +# Family of 3, period 2025-01 -> monthly SMI = 6_721.82. + +- name: Income below 33% SMI yields a zero copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 24_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 2_000/mo / 6_721.82 = 0.2975 < 0.33 -> $0 + nv_ccdp_copay: 0 + +- name: Income in the 33-42% SMI band yields a $90 copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 30_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 2_500/mo / 6_721.82 = 0.3719 in [0.33, 0.43) -> $90 + nv_ccdp_copay: 90 + +- name: Income in the 43-49% SMI band yields a $150 copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 40_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 3_333/mo / 6_721.82 = 0.4959 >= 0.43 -> $150 + nv_ccdp_copay: 150 + +- name: TANF/NEON enrollment waives the copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 40_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + is_tanf_enrolled: true + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # Would be $150 band, but TANF enrollment waives -> $0 + nv_ccdp_copay: 0 + +- name: A homeless household has the copay waived. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 40_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NV + is_homeless: true + output: + # Would be $150 band, but homelessness waives -> $0 + nv_ccdp_copay: 0 + +- name: A foster/protective-care placement waives the copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 40_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + is_in_foster_care: true + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # Eligible child in foster care -> protective-care waiver -> $0 + nv_ccdp_copay: 0 + +# Edge cases: copay-band cut-points. The scale is at-or-above (single_amount), +# so the 0.33 and 0.43 thresholds belong to the HIGHER band: +# ratio < 0.33 -> $0; 0.33 <= ratio < 0.43 -> $90; ratio >= 0.43 -> $150. +# Family of 3, period 2025-01: monthly SMI = 6_721.82. + +- name: Income just below the 33% SMI cut-point yields a zero copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 26_400 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 2_200/mo / 6_721.82 = 0.3273 < 0.33 -> $0 + nv_ccdp_copay: 0 + +- name: Income just above the 33% SMI cut-point yields a $90 copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 26_700 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 2_225/mo / 6_721.82 = 0.3310 >= 0.33 -> $90 + nv_ccdp_copay: 90 + +- name: Income just below the 43% SMI cut-point stays in the $90 band. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 34_500 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 2_875/mo / 6_721.82 = 0.4277 in [0.33, 0.43) -> $90 + nv_ccdp_copay: 90 + +- name: Income just above the 43% SMI cut-point yields a $150 copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 34_800 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 2_900/mo / 6_721.82 = 0.4314 >= 0.43 -> $150 + nv_ccdp_copay: 150 + +- name: Zero income yields a zero copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # ratio 0 < 0.33 -> $0 + nv_ccdp_copay: 0 + +- name: Negative self-employment income floors to the zero-copay band. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + self_employment_income: -120_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # countable income floors at 0, so the SMI ratio is 0 -> $0 (no negative band) + nv_ccdp_copay: 0 + +# Protective-services copay waiver (the other half of the OR alongside +# is_in_foster_care in nv_ccdp_copay.py). The waiver only fires when the +# protective-care child is also an eligible child (nv_ccdp_eligible_child). + +- name: A child receiving/needing protective services waives the copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 40_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + receives_or_needs_protective_services: true + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # Would be $150 band, but the eligible child needs protective services -> $0 + nv_ccdp_copay: 0 + +- name: Protective services for a non-eligible child does not waive the copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 40_000 + person2: + age: 30 + person3: + # Age 15 with no developmental delay -> not an nv_ccdp_eligible_child, + # so the protective-services flag cannot trigger the copay waiver. + age: 15 + immigration_status: CITIZEN + receives_or_needs_protective_services: true + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 3_333/mo / 6_721.82 = 0.4959 >= 0.43 -> $150; no eligible child in + # protective care, so the waiver does not apply. + nv_ccdp_copay: 150 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_activity_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_activity_eligible.yaml new file mode 100644 index 00000000000..7d860eb927a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_activity_eligible.yaml @@ -0,0 +1,92 @@ +# nv_ccdp_activity_eligible: SPMUnit, bool, MONTH +# OR of: meets_ccdf_activity_test (SPMUnit/YEAR), is_tanf_enrolled (SPMUnit), +# is_homeless (Household). Each disjunct is tested in isolation so an OR-to-AND +# flip is caught. + +- name: No qualifying activity is not activity-eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 30 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + meets_ccdf_activity_test: false + is_tanf_enrolled: false + households: + household: + members: [person1, person2] + state_code: NV + is_homeless: false + output: + nv_ccdp_activity_eligible: false + +- name: Meeting the CCDF activity test is activity-eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 30 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + meets_ccdf_activity_test: true + is_tanf_enrolled: false + households: + household: + members: [person1, person2] + state_code: NV + is_homeless: false + output: + nv_ccdp_activity_eligible: true + +- name: TANF enrollment (NEON pathway) is activity-eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 30 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + meets_ccdf_activity_test: false + is_tanf_enrolled: true + households: + household: + members: [person1, person2] + state_code: NV + is_homeless: false + output: + nv_ccdp_activity_eligible: true + +- name: Homelessness (Homeless Self-Sufficiency Plan) is activity-eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 30 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + meets_ccdf_activity_test: false + is_tanf_enrolled: false + households: + household: + members: [person1, person2] + state_code: NV + is_homeless: true + output: + nv_ccdp_activity_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_eligible.yaml new file mode 100644 index 00000000000..9fa1e9e6127 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_eligible.yaml @@ -0,0 +1,219 @@ +# nv_ccdp_eligible: SPMUnit, bool, MONTH +# AND of: has eligible child, income-eligible, CCDF asset-eligible, activity-eligible. +# MS 218 residency enforced by defined_for = StateCode.NV. +# Family of 3, period 2025-01; intake monthly SMI limit = 2_755.95. +# Each AND component is failed in isolation to catch an AND-to-OR flip. + +- name: All four conditions met -> eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + employment_income: 24_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + nv_ccdp_eligible: true + +- name: No eligible child -> ineligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + employment_income: 24_000 + person2: + age: 30 + person3: + age: 14 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # Only child is 14 (>= 13, no special need) -> no eligible child + nv_ccdp_eligible: false + +- name: Income over the intake limit -> ineligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + employment_income: 50_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 50_000 / 12 = 4_167/mo > 2_755.95 intake limit -> income-ineligible + nv_ccdp_eligible: false + +- name: No qualifying activity -> ineligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + employment_income: 24_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: false + is_tanf_enrolled: false + nv_ccdp_enrolled: false + households: + household: + members: [person1, person2, person3] + state_code: NV + is_homeless: false + output: + # No approved activity -> activity-ineligible + nv_ccdp_eligible: false + +- name: Assets over the CCDF ceiling -> ineligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + employment_income: 24_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + spm_unit_assets: 2_000_000 + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 2_000_000 > 1_000_000 CCDF asset ceiling -> asset-ineligible + nv_ccdp_eligible: false + +# Edge cases: income just at the intake boundary, special-needs child pathway, +# and an immigration-ineligible-only child. + +- name: Income just at the 41% intake limit -> eligible (inclusive boundary). + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + employment_income: 33_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 33_000 / 12 = 2_750/mo <= 2_755.95 intake limit -> income-eligible -> eligible + nv_ccdp_eligible: true + +- name: Special-needs child age 13 keeps the unit eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 40 + employment_income: 24_000 + person2: + age: 38 + person3: + age: 13 + immigration_status: CITIZEN + has_developmental_delay: true + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 13 < 19 special-needs limit -> eligible child present -> unit eligible + nv_ccdp_eligible: true + +- name: Only child fails the immigration test -> ineligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + employment_income: 24_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: UNDOCUMENTED + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # Child fails is_ccdf_immigration_eligible_child -> no eligible child -> ineligible + nv_ccdp_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_eligible_child.yaml b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_eligible_child.yaml new file mode 100644 index 00000000000..c2d36d44ab3 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_eligible_child.yaml @@ -0,0 +1,173 @@ +# nv_ccdp_eligible_child: Person, bool, MONTH +# MS 210: child under 13. MS 211: special-needs child under 19. +# A special need is a developmental delay OR any disability (is_disabled). +# MS 214: child must be a U.S. citizen or eligible non-citizen +# (is_ccdf_immigration_eligible_child). + +- name: Citizen child under 13 is an eligible child. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 5 + immigration_status: CITIZEN + households: + household: + members: [person] + state_code: NV + output: + # age 5 < 13 and citizen -> eligible + nv_ccdp_eligible_child: true + +- name: Citizen child at age 13 is not eligible (not special needs). + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 13 + immigration_status: CITIZEN + households: + household: + members: [person] + state_code: NV + output: + # age 13 not < 13 and no developmental delay -> ineligible + nv_ccdp_eligible_child: false + +- name: Special-needs child age 13 is eligible (under 19 limit). + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 13 + immigration_status: CITIZEN + has_developmental_delay: true + households: + household: + members: [person] + state_code: NV + output: + # developmental delay -> uses 19 limit; 13 < 19 -> eligible + nv_ccdp_eligible_child: true + +- name: Disabled child age 13 (no developmental delay) is eligible (under 19 limit). + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 13 + immigration_status: CITIZEN + is_disabled: true + households: + household: + members: [person] + state_code: NV + output: + # MS 211 special need via is_disabled -> uses 19 limit; 13 < 19 -> eligible + nv_ccdp_eligible_child: true + +- name: Special-needs child at age 19 is not eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 19 + immigration_status: CITIZEN + has_developmental_delay: true + households: + household: + members: [person] + state_code: NV + output: + # 19 not < 19 -> ineligible even with developmental delay + nv_ccdp_eligible_child: false + +- name: Young child failing the immigration test is not an eligible child. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 5 + immigration_status: UNDOCUMENTED + households: + household: + members: [person] + state_code: NV + output: + # age passes but immigration check fails -> ineligible + nv_ccdp_eligible_child: false + +# Edge cases: age limits (under 13 general, under 19 special needs). +# The comparison is `age < limit` (strict), and `age` is a float. + +- name: Citizen child just under 13 (age 12.9) is an eligible child. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 12.9 + immigration_status: CITIZEN + households: + household: + members: [person] + state_code: NV + output: + # 12.9 < 13 -> eligible + nv_ccdp_eligible_child: true + +- name: Newborn citizen (age 0) is an eligible child. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 0 + immigration_status: CITIZEN + households: + household: + members: [person] + state_code: NV + output: + # 0 < 13 -> eligible + nv_ccdp_eligible_child: true + +- name: Special-needs citizen child just under 19 (age 18.9) is eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 18.9 + immigration_status: CITIZEN + has_developmental_delay: true + households: + household: + members: [person] + state_code: NV + output: + # developmental delay -> 19 limit; 18.9 < 19 -> eligible + nv_ccdp_eligible_child: true + +- name: Special-needs citizen child over 19 (age 20) is not eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 20 + immigration_status: CITIZEN + has_developmental_delay: true + households: + household: + members: [person] + state_code: NV + output: + # 20 not < 19 -> ineligible even with developmental delay + nv_ccdp_eligible_child: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_income_eligible.yaml new file mode 100644 index 00000000000..332d78ad21a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_income_eligible.yaml @@ -0,0 +1,310 @@ +# nv_ccdp_income_eligible: SPMUnit, bool, MONTH +# New applicants (not enrolled): monthly countable income <= 41% SMI (intake). +# Enrolled families: monthly countable income <= 49% SMI (renewal/redetermination). +# The 85% SMI federal mid-certification protection (45 CFR 98.21) is not modeled +# because we don't track certification periods at the moment. +# Family of 3, period 2025-01 -> monthly SMI = 6_721.82: +# intake limit = 0.41 * 6_721.82 = 2_755.95/mo (annual 33_071) +# renewal limit = 0.49 * 6_721.82 = 3_293.69/mo (annual 39_524) + +- name: New applicant below the 41% intake limit is income-eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + employment_income: 30_000 + person2: + age: 30 + person3: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3] + nv_ccdp_enrolled: false + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 30_000 / 12 = 2_500/mo <= 2_755.95 intake limit -> eligible + nv_ccdp_income_eligible: true + +- name: New applicant above the 41% intake limit is not income-eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + employment_income: 40_000 + person2: + age: 30 + person3: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3] + nv_ccdp_enrolled: false + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 40_000 / 12 = 3_333/mo > 2_755.95 intake limit -> ineligible + nv_ccdp_income_eligible: false + +- name: Enrolled family between intake and renewal limits stays income-eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + employment_income: 38_000 + person2: + age: 30 + person3: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3] + nv_ccdp_enrolled: true + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 38_000 / 12 = 3_166.67/mo > 2_755.95 intake but <= 3_293.69 renewal + # ceiling -> enrolled family is still eligible (would fail as a new applicant) + nv_ccdp_income_eligible: true + +- name: Enrolled family above the 49% renewal ceiling is not income-eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + employment_income: 42_000 + person2: + age: 30 + person3: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3] + nv_ccdp_enrolled: true + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 42_000 / 12 = 3_500/mo > 3_293.69 renewal ceiling -> ineligible + nv_ccdp_income_eligible: false + +# Edge cases: intake (41% SMI) and renewal (49% SMI) boundaries. +# The comparison is `monthly countable income <= limit` (inclusive <=). +# Family of 3, period 2025-01: monthly SMI = 6_721.82. +# intake limit = 0.41 * 6_721.82 = 2_755.9462/mo +# renewal limit = 0.49 * 6_721.82 = 3_293.6918/mo + +- name: New applicant just below the 41% intake limit is income-eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + employment_income: 33_000 + person2: + age: 30 + person3: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3] + nv_ccdp_enrolled: false + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 33_000 / 12 = 2_750.00/mo <= 2_755.9462 intake limit -> eligible + nv_ccdp_income_eligible: true + +- name: New applicant just above the 41% intake limit is not income-eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + employment_income: 33_120 + person2: + age: 30 + person3: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3] + nv_ccdp_enrolled: false + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 33_120 / 12 = 2_760.00/mo > 2_755.9462 intake limit -> ineligible + nv_ccdp_income_eligible: false + +- name: Enrolled family just below the 49% renewal ceiling stays income-eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + employment_income: 39_480 + person2: + age: 30 + person3: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3] + nv_ccdp_enrolled: true + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 39_480 / 12 = 3_290.00/mo <= 3_293.6918 renewal ceiling -> eligible + nv_ccdp_income_eligible: true + +- name: Enrolled family just above the 49% renewal ceiling is not income-eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + employment_income: 39_540 + person2: + age: 30 + person3: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3] + nv_ccdp_enrolled: true + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 39_540 / 12 = 3_295.00/mo > 3_293.6918 renewal ceiling -> ineligible + nv_ccdp_income_eligible: false + +- name: Zero income is income-eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + person2: + age: 30 + person3: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3] + nv_ccdp_enrolled: false + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 0 <= 2_755.9462 intake limit -> eligible + nv_ccdp_income_eligible: true + +- name: Negative self-employment income with no deductions is income-eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 32 + self_employment_income: -120_000 + person2: + age: 30 + person3: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3] + nv_ccdp_enrolled: false + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # countable income floors at 0: max(-120_000 - 0, 0) = 0 <= intake limit -> eligible + nv_ccdp_income_eligible: true + +- name: Maximum-size family (8) below the intake limit is income-eligible. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 40 + employment_income: 50_000 + person2: + age: 38 + person3: + age: 12 + person4: + age: 10 + person5: + age: 8 + person6: + age: 6 + person7: + age: 4 + person8: + age: 2 + spm_units: + spm_unit: + members: + [ + person1, + person2, + person3, + person4, + person5, + person6, + person7, + person8, + ] + nv_ccdp_enrolled: false + households: + household: + members: + [ + person1, + person2, + person3, + person4, + person5, + person6, + person7, + person8, + ] + state_code: NV + output: + # Family of 8: monthly SMI = 96_026 * 1.38 / 12 = 11_042.99. + # intake limit = 0.41 * 11_042.99 = 4_527.63/mo. + # 50_000 / 12 = 4_166.67/mo <= 4_527.63 -> eligible. + nv_ccdp_income_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/income/nv_ccdp_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/income/nv_ccdp_countable_income.yaml new file mode 100644 index 00000000000..81dd5fa8876 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/income/nv_ccdp_countable_income.yaml @@ -0,0 +1,157 @@ +# nv_ccdp_countable_income: SPMUnit, float (USD), YEAR +# MS 306 gross from the source list minus MS 302 child support paid, floored at 0. +# SSI is EXEMPT in Nevada and is NOT in the source list. + +- name: Sums earned and unearned countable income. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 24_000 + social_security: 6_000 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NV + output: + # 24_000 + 6_000 = 30_000; no child support paid + nv_ccdp_countable_income: 30_000 + +- name: Deducts court-ordered child support paid. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 30_000 + child_support_expense: 4_000 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NV + output: + # 30_000 gross - 4_000 child support paid = 26_000 + nv_ccdp_countable_income: 26_000 + +- name: SSI is excluded from countable income in Nevada. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 12_000 + ssi: 9_000 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NV + output: + # Only employment_income counts; SSI is exempt -> 12_000 + nv_ccdp_countable_income: 12_000 + +- name: Countable income floors at zero. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 3_000 + child_support_expense: 5_000 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NV + output: + # max(3_000 - 5_000, 0) = 0 + nv_ccdp_countable_income: 0 + +# Edge cases: zero income, maximum income, and negative income with no deductions. + +- name: Zero income yields zero countable income. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NV + output: + # no income sources -> 0 + nv_ccdp_countable_income: 0 + +- name: Negative self-employment income with no deductions floors at zero. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + self_employment_income: -50_000 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NV + output: + # max(-50_000 - 0, 0) = 0 (no child support paid) + nv_ccdp_countable_income: 0 + +- name: Maximum income sums all countable sources. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 500_000 + social_security: 40_000 + rental_income: 60_000 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NV + output: + # 500_000 + 40_000 + 60_000 = 600_000; no child support paid + nv_ccdp_countable_income: 600_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/integration.yaml new file mode 100644 index 00000000000..ae95a41f584 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/integration.yaml @@ -0,0 +1,257 @@ +# Nevada CCDP end-to-end integration tests. +# Period 2025-01 uses the 2024-10-01 SMI table; NV 4-person base = 96_026. +# Family of 3 -> monthly SMI = 96_026 * 0.84 / 12 = 6_721.82. +# intake limit (not enrolled): 0.41 * 6_721.82 = 2_755.95/mo +# renewal limit (enrolled): 0.49 * 6_721.82 = 3_293.69/mo +# Copay bands: < 0.33 -> $0; 0.33-0.42 -> $90; 0.43-0.49 -> $150. +# Monthly max rate = daily rate * 21.67 billed days. +# Dimensions exercised: both regions, both provider types, all four age +# groups, all three copay tiers, and both enrollment states. + +- name: Case 1, Clark County center infant, intake, zero copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 24_000 + person2: + age: 30 + person3: + age: 1 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + spm_unit_pre_subsidy_childcare_expenses: 30_000 + households: + household: + members: [person1, person2, person3] + state_code: NV + county: CLARK_COUNTY_NV + output: + nv_ccdp_eligible_child: [false, false, true] + nv_ccdp_age_group: [SCHOOL_AGE, SCHOOL_AGE, INFANT] + nv_ccdp_region: [MOST_POPULOUS, MOST_POPULOUS, MOST_POPULOUS] + # 24_000 / 12 = 2_000/mo -> ratio 0.2975 < 0.33 -> $0 copay + nv_ccdp_income_eligible: true + nv_ccdp_eligible: true + nv_ccdp_copay: 0 + # Clark center infant rate $62.50; max rate = 62.50 * 21.67 = 1_354.38. + # expense per child = 30_000 / 12 = 2_500 (does not bind). + # benefit = max(min(2_500, 1_354.38) - 0, 0) = 1_354.38. + nv_ccdp: 1_354.38 + +- name: Case 2, Washoe County family child care preschool, intake, $90 copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 30_000 + person2: + age: 30 + person3: + age: 4 + immigration_status: CITIZEN + nv_ccdp_provider_type: FCC + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + spm_unit_pre_subsidy_childcare_expenses: 30_000 + households: + household: + members: [person1, person2, person3] + state_code: NV + county: WASHOE_COUNTY_NV + output: + nv_ccdp_eligible_child: [false, false, true] + nv_ccdp_age_group: [SCHOOL_AGE, SCHOOL_AGE, PRESCHOOL] + nv_ccdp_region: [LOWEST_PERCENTILE, LOWEST_PERCENTILE, LOWEST_PERCENTILE] + nv_ccdp_eligible: true + # 30_000 / 12 = 2_500/mo -> ratio 0.3719 in [0.33, 0.43) -> $90 copay + nv_ccdp_copay: 90 + # Lowest-percentile FCC preschool rate $40; max rate = 40 * 21.67 = 866.80. + # benefit = max(866.80 - 90, 0) = 776.80. + nv_ccdp: 776.80 + +- name: Case 3, enrolled (renewal) Clark center school age, $150 copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 36_000 + person2: + age: 30 + person3: + age: 6 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: true + spm_unit_pre_subsidy_childcare_expenses: 30_000 + households: + household: + members: [person1, person2, person3] + state_code: NV + county: CLARK_COUNTY_NV + output: + nv_ccdp_eligible_child: [false, false, true] + nv_ccdp_age_group: [SCHOOL_AGE, SCHOOL_AGE, SCHOOL_AGE] + # 36_000 / 12 = 3_000/mo. Enrolled -> uses 49% renewal ceiling (3_293.69), + # so 3_000 <= 3_293.69 -> eligible. + nv_ccdp_income_eligible: true + nv_ccdp_eligible: true + # ratio 0.4463 in [0.43, 0.49) -> $150 copay + nv_ccdp_copay: 150 + # Clark center school-age rate $48; max rate = 48 * 21.67 = 1_040.16. + # benefit = max(1_040.16 - 150, 0) = 890.16. + nv_ccdp: 890.16 + +- name: Case 4, homeless family, Washoe FCC toddler, copay waived, charge caps benefit. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 30_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + nv_ccdp_provider_type: FCC + spm_units: + spm_unit: + members: [person1, person2, person3] + nv_ccdp_enrolled: false + spm_unit_pre_subsidy_childcare_expenses: 7_200 + households: + household: + members: [person1, person2, person3] + state_code: NV + county: WASHOE_COUNTY_NV + is_homeless: true + output: + nv_ccdp_age_group: [SCHOOL_AGE, SCHOOL_AGE, TODDLER] + nv_ccdp_region: [LOWEST_PERCENTILE, LOWEST_PERCENTILE, LOWEST_PERCENTILE] + # Homelessness satisfies the activity test and waives the copay. + nv_ccdp_activity_eligible: true + nv_ccdp_eligible: true + nv_ccdp_copay: 0 + # Lowest-percentile FCC toddler rate $40; max rate = 40 * 21.67 = 866.80. + # expense per child = 7_200 / 12 = 600 (binds, below 866.80). + # benefit = max(min(600, 866.80) - 0, 0) = 600. + nv_ccdp: 600 + +- name: Case 5, zero income eligible family receives full subsidy. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + person2: + age: 30 + person3: + age: 1 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + spm_unit_pre_subsidy_childcare_expenses: 30_000 + households: + household: + members: [person1, person2, person3] + state_code: NV + county: CLARK_COUNTY_NV + output: + # Zero countable income -> income-eligible, $0 copay band. + nv_ccdp_countable_income: 0 + nv_ccdp_income_eligible: true + nv_ccdp_eligible: true + nv_ccdp_copay: 0 + # Clark center infant max rate = 62.50 * 21.67 = 1_354.38. + nv_ccdp: 1_354.38 + +- name: Case 6, income just under the intake limit, $90 copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 33_000 + person2: + age: 30 + person3: + age: 4 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + spm_unit_pre_subsidy_childcare_expenses: 30_000 + households: + household: + members: [person1, person2, person3] + state_code: NV + county: CLARK_COUNTY_NV + output: + # 33_000 / 12 = 2_750/mo <= 2_755.95 intake limit -> eligible (boundary). + nv_ccdp_income_eligible: true + nv_ccdp_eligible: true + # ratio 0.4091 in [0.33, 0.43) -> $90 copay + nv_ccdp_copay: 90 + # Clark center preschool rate $51.50; max rate = 51.50 * 21.67 = 1_116.01. + # benefit = max(1_116.01 - 90, 0) = 1_026.01. + nv_ccdp: 1_026.01 + +- name: Case 7, degenerate family with no eligible child gets zero benefit. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 24_000 + person2: + age: 30 + person3: + age: 14 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + spm_unit_pre_subsidy_childcare_expenses: 30_000 + households: + household: + members: [person1, person2, person3] + state_code: NV + county: CLARK_COUNTY_NV + output: + # Only child is 14 (>= 13, no special need) -> no eligible child. + nv_ccdp_eligible_child: [false, false, false] + nv_ccdp_eligible: false + nv_ccdp: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp.yaml b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp.yaml new file mode 100644 index 00000000000..5ba7ba1ca08 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp.yaml @@ -0,0 +1,227 @@ +# nv_ccdp: SPMUnit, float (USD), MONTH +# MS 163: per child = min(monthly pre-subsidy expense, daily rate * 21.67 +# billed days) summed over eligible children, minus family copay, floored at 0. +# defined_for = nv_ccdp_eligible. +# Family of 3, period 2025-01, Clark County (MOST_POPULOUS), CENTER, child age 3 +# (TODDLER) -> daily rate $57; monthly max rate = 57 * 21.67 = 1_235.19. + +- name: Provider rate binds, zero copay. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 24_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + spm_unit_pre_subsidy_childcare_expenses: 30_000 + households: + household: + members: [person1, person2, person3] + state_code: NV + county: CLARK_COUNTY_NV + output: + # monthly per-child expense = 30_000 / 12 = 2_500 (does not bind). + # max rate = 57 * 21.67 = 1_235.19; copay $0 (2_000/mo < 33% SMI). + # benefit = max(min(2_500, 1_235.19) - 0, 0) = 1_235.19. + nv_ccdp: 1_235.19 + +- name: Provider charge caps the reimbursement. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 24_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + spm_unit_pre_subsidy_childcare_expenses: 8_400 + households: + household: + members: [person1, person2, person3] + state_code: NV + county: CLARK_COUNTY_NV + output: + # monthly per-child expense = 8_400 / 12 = 700 (binds, below 1_235.19). + # copay $0; benefit = max(min(700, 1_235.19) - 0, 0) = 700. + nv_ccdp: 700 + +- name: Copay reduces the benefit. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 30_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + spm_unit_pre_subsidy_childcare_expenses: 30_000 + households: + household: + members: [person1, person2, person3] + state_code: NV + county: CLARK_COUNTY_NV + output: + # max rate = 1_235.19; copay $90 (2_500/mo in 33-42% band). + # benefit = max(1_235.19 - 90, 0) = 1_145.19. + nv_ccdp: 1_145.19 + +- name: Ineligible family (income too high) gets zero benefit. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 80_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + spm_unit_pre_subsidy_childcare_expenses: 30_000 + households: + household: + members: [person1, person2, person3] + state_code: NV + county: CLARK_COUNTY_NV + output: + # 80_000 / 12 = 6_667/mo > 2_755.95 intake limit -> ineligible -> $0 + nv_ccdp: 0 + +# Edge cases: multiple eligible children, FCC provider, and negative income. + +- name: Two eligible children sum their per-child reimbursements. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 35 + employment_income: 30_000 + person2: + age: 32 + person3: + age: 1 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + person4: + age: 4 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + spm_unit_pre_subsidy_childcare_expenses: 60_000 + households: + household: + members: [person1, person2, person3, person4] + state_code: NV + county: CLARK_COUNTY_NV + output: + # Family of 4: monthly SMI = 8_002.17; 30_000/12 = 2_500/mo -> ratio 0.3124 -> $0 copay. + # Expenses split evenly across the 2 children: 60_000 / 2 = 30_000/yr = 2_500/mo each. + # Clark center INFANT max = 62.50 * 21.67 = 1_354.38 (expense 2_500 does not bind). + # Clark center PRESCHOOL max = 51.50 * 21.67 = 1_116.005 (expense 2_500 does not bind). + # benefit = 1_354.375 + 1_116.005 - 0 = 2_470.38 (float32 sum). + nv_ccdp: 2_470.38 + +- name: Family child care provider, Washoe County, infant. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 24_000 + person2: + age: 30 + person3: + age: 1 + immigration_status: CITIZEN + nv_ccdp_provider_type: FCC + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + spm_unit_pre_subsidy_childcare_expenses: 30_000 + households: + household: + members: [person1, person2, person3] + state_code: NV + county: WASHOE_COUNTY_NV + output: + # Lowest-percentile FCC infant rate $40.50; max rate = 40.50 * 21.67 = 877.635. + # expense per child = 30_000 / 12 = 2_500 (does not bind); copay $0. + # benefit = max(min(2_500, 877.635) - 0, 0) = 877.635 ~= 877.64. + nv_ccdp: 877.64 + +- name: Negative self-employment income does not inflate the benefit. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + self_employment_income: -120_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + spm_unit_pre_subsidy_childcare_expenses: 30_000 + households: + household: + members: [person1, person2, person3] + state_code: NV + county: CLARK_COUNTY_NV + output: + # Countable income floors at 0 -> income-eligible, $0 copay (no negative band). + # Clark center TODDLER max = 57 * 21.67 = 1_235.19; expense 2_500/mo does not bind. + # benefit = max(min(2_500, 1_235.19) - 0, 0) = 1_235.19 (not inflated by the loss). + nv_ccdp: 1_235.19 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_age_group.yaml b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_age_group.yaml new file mode 100644 index 00000000000..e3ccef257b4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_age_group.yaml @@ -0,0 +1,157 @@ +# nv_ccdp_age_group: Person, Enum (NVCCDPAgeGroup), MONTH +# age_group/months.yaml brackets (age in months = age_years * 12): +# < 25 -> INFANT, 25-36 -> TODDLER, 37-59 -> PRESCHOOL, >= 60 -> SCHOOL_AGE + +- name: Infant at 12 months (age 1). + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 1 + households: + household: + members: [person] + state_code: NV + output: + # 1 * 12 = 12 months < 25 -> INFANT + nv_ccdp_age_group: INFANT + +- name: Infant at exactly 24 months (age 2, below 25-month toddler cutoff). + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 2 + households: + household: + members: [person] + state_code: NV + output: + # 2 * 12 = 24 months < 25 -> INFANT + nv_ccdp_age_group: INFANT + +- name: Toddler at 36 months (age 3). + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 3 + households: + household: + members: [person] + state_code: NV + output: + # 3 * 12 = 36 months in [25, 37) -> TODDLER + nv_ccdp_age_group: TODDLER + +- name: Preschool at 48 months (age 4). + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 4 + households: + household: + members: [person] + state_code: NV + output: + # 4 * 12 = 48 months in [37, 60) -> PRESCHOOL + nv_ccdp_age_group: PRESCHOOL + +- name: School age at 72 months (age 6). + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 6 + households: + household: + members: [person] + state_code: NV + output: + # 6 * 12 = 72 months >= 60 -> SCHOOL_AGE + nv_ccdp_age_group: SCHOOL_AGE + +# Edge cases: months-scale cut-points (25, 37, 60). The scale is at-or-above, +# so each threshold belongs to the upper band. `age` is a float, so ages are +# chosen to land just above each cutoff in months (age * 12). + +- name: Toddler just above the 25-month cutoff (age 2.1 = 25.2 months). + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 2.1 + households: + household: + members: [person] + state_code: NV + output: + # 2.1 * 12 = 25.2 months >= 25 -> TODDLER + nv_ccdp_age_group: TODDLER + +- name: Preschool just above the 37-month cutoff (age 3.1 = 37.2 months). + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 3.1 + households: + household: + members: [person] + state_code: NV + output: + # 3.1 * 12 = 37.2 months in [37, 60) -> PRESCHOOL + nv_ccdp_age_group: PRESCHOOL + +- name: Preschool just below the 60-month cutoff (age 4.9 = 58.8 months). + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 4.9 + households: + household: + members: [person] + state_code: NV + output: + # 4.9 * 12 = 58.8 months < 60 -> PRESCHOOL + nv_ccdp_age_group: PRESCHOOL + +- name: School age at exactly the 60-month cutoff (age 5 = 60 months). + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 5 + households: + household: + members: [person] + state_code: NV + output: + # 5 * 12 = 60 months >= 60 -> SCHOOL_AGE + nv_ccdp_age_group: SCHOOL_AGE + +- name: Newborn at age 0 is an infant. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 0 + households: + household: + members: [person] + state_code: NV + output: + # 0 months < 25 -> INFANT + nv_ccdp_age_group: INFANT diff --git a/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_enrolled.yaml b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_enrolled.yaml new file mode 100644 index 00000000000..a87e752a97e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_enrolled.yaml @@ -0,0 +1,37 @@ +# nv_ccdp_enrolled: SPMUnit, bool, MONTH — input variable. +# No formula; confirm the boolean input parses and defaults to false. + +- name: Enrollment defaults to false (new applicant). + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 3 + spm_units: + spm_unit: + members: [person] + households: + household: + members: [person] + state_code: NV + output: + nv_ccdp_enrolled: false + +- name: Enrolled flag is read from input. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 3 + spm_units: + spm_unit: + members: [person] + nv_ccdp_enrolled: true + households: + household: + members: [person] + state_code: NV + output: + nv_ccdp_enrolled: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_provider_rate.yaml b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_provider_rate.yaml new file mode 100644 index 00000000000..51cd47bbddf --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_provider_rate.yaml @@ -0,0 +1,172 @@ +# nv_ccdp_provider_rate: Person, float (USD/day), MONTH +# defined_for = nv_ccdp_eligible_child, so the child must be NV-resident, +# under the age limit, and immigration-eligible for a non-zero rate. +# rates/center.yaml and rates/fcc.yaml, keyed by region x age group: +# center MOST_POPULOUS: INFANT 62.50, TODDLER 57, PRESCHOOL 51.50, SCHOOL_AGE 48 +# center LOWEST_PERCENTILE: INFANT 53.50, TODDLER 52.50, PRESCHOOL 47.50, SCHOOL_AGE 42.50 +# fcc MOST_POPULOUS: INFANT 45, TODDLER 45, PRESCHOOL 46.50, SCHOOL_AGE 46 +# fcc LOWEST_PERCENTILE: INFANT 40.50, TODDLER 40, PRESCHOOL 40, SCHOOL_AGE 40 + +- name: Center, Clark County, infant. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person: + age: 1 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + households: + household: + members: [person] + state_code: NV + county: CLARK_COUNTY_NV + output: + # MOST_POPULOUS x CENTER x INFANT = 62.50 + nv_ccdp_provider_rate: 62.50 + +- name: Center, lowest-percentile region, toddler. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person: + age: 3 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + households: + household: + members: [person] + state_code: NV + county: WASHOE_COUNTY_NV + output: + # LOWEST_PERCENTILE x CENTER x TODDLER = 52.50 + nv_ccdp_provider_rate: 52.50 + +- name: Family child care, Clark County, preschool. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person: + age: 4 + immigration_status: CITIZEN + nv_ccdp_provider_type: FCC + households: + household: + members: [person] + state_code: NV + county: CLARK_COUNTY_NV + output: + # MOST_POPULOUS x FCC x PRESCHOOL = 46.50 + nv_ccdp_provider_rate: 46.50 + +- name: Family child care, lowest-percentile region, school age. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person: + age: 6 + immigration_status: CITIZEN + nv_ccdp_provider_type: FCC + households: + household: + members: [person] + state_code: NV + county: WASHOE_COUNTY_NV + output: + # LOWEST_PERCENTILE x FCC x SCHOOL_AGE = 40 + nv_ccdp_provider_rate: 40 + +- name: Center, Clark County, school age (default provider type is CENTER). + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person: + age: 6 + immigration_status: CITIZEN + households: + household: + members: [person] + state_code: NV + county: CLARK_COUNTY_NV + output: + # MOST_POPULOUS x CENTER x SCHOOL_AGE = 48 + nv_ccdp_provider_rate: 48 + +- name: Ineligible child (age 14) gets no rate via defined_for. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person: + age: 14 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + households: + household: + members: [person] + state_code: NV + county: CLARK_COUNTY_NV + output: + # Not an eligible child (>= 13, no special need) -> defined_for zeroes the rate. + nv_ccdp_provider_rate: 0 + +# Edge cases: complete the FCC age-group coverage (infant, toddler) and add a +# lowest-percentile center infant case. + +- name: Family child care, Clark County, infant. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person: + age: 1 + immigration_status: CITIZEN + nv_ccdp_provider_type: FCC + households: + household: + members: [person] + state_code: NV + county: CLARK_COUNTY_NV + output: + # MOST_POPULOUS x FCC x INFANT = 45 + nv_ccdp_provider_rate: 45 + +- name: Family child care, lowest-percentile region, toddler. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person: + age: 3 + immigration_status: CITIZEN + nv_ccdp_provider_type: FCC + households: + household: + members: [person] + state_code: NV + county: WASHOE_COUNTY_NV + output: + # LOWEST_PERCENTILE x FCC x TODDLER = 40 + nv_ccdp_provider_rate: 40 + +- name: Center, lowest-percentile region, infant. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person: + age: 1 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + households: + household: + members: [person] + state_code: NV + county: ELKO_COUNTY_NV + output: + # LOWEST_PERCENTILE x CENTER x INFANT = 53.50 + nv_ccdp_provider_rate: 53.50 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_provider_type.yaml b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_provider_type.yaml new file mode 100644 index 00000000000..0cb7ab6197e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_provider_type.yaml @@ -0,0 +1,31 @@ +# nv_ccdp_provider_type: Person, Enum (NVCCDPProviderType), MONTH — input variable. +# No formula; confirm the enum input parses and defaults to CENTER. + +- name: Provider type defaults to a child care center. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 3 + households: + household: + members: [person] + state_code: NV + output: + nv_ccdp_provider_type: CENTER + +- name: Family child care provider type is read from input. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 3 + nv_ccdp_provider_type: FCC + households: + household: + members: [person] + state_code: NV + output: + nv_ccdp_provider_type: FCC diff --git a/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_region.yaml b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_region.yaml new file mode 100644 index 00000000000..a9dc0db9b77 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_region.yaml @@ -0,0 +1,80 @@ +# nv_ccdp_region: Person, Enum (NVCCDPRegion), MONTH +# Clark County -> MOST_POPULOUS; every other county -> LOWEST_PERCENTILE. +# `county` is a Household-level input enum. + +- name: Clark County maps to the most populous region. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 3 + households: + household: + members: [person] + state_code: NV + county: CLARK_COUNTY_NV + output: + nv_ccdp_region: MOST_POPULOUS + +- name: Washoe County maps to the lowest-percentile region. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 3 + households: + household: + members: [person] + state_code: NV + county: WASHOE_COUNTY_NV + output: + nv_ccdp_region: LOWEST_PERCENTILE + +# Edge cases: additional non-Clark ("rest of state") counties all map to the +# lowest-percentile region; only Clark County maps to the most populous region. + +- name: Rural Elko County maps to the lowest-percentile region. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 3 + households: + household: + members: [person] + state_code: NV + county: ELKO_COUNTY_NV + output: + nv_ccdp_region: LOWEST_PERCENTILE + +- name: Carson City maps to the lowest-percentile region. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 3 + households: + household: + members: [person] + state_code: NV + county: CARSON_CITY_NV + output: + nv_ccdp_region: LOWEST_PERCENTILE + +- name: Default (no county set) falls back to the lowest-percentile region. + period: 2025-01 + absolute_error_margin: 0.1 + input: + people: + person: + age: 3 + households: + household: + members: [person] + state_code: NV + output: + nv_ccdp_region: LOWEST_PERCENTILE diff --git a/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_smi.yaml b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_smi.yaml new file mode 100644 index 00000000000..4ce5ccac814 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_ccdp_smi.yaml @@ -0,0 +1,155 @@ +# nv_ccdp_smi: SPMUnit, float (USD), MONTH +# Monthly 100% State Median Income, federal HHS SMI data at the Oct-1 instant. +# For period 2025-01 (month < 10): uses the 2024-10-01 SMI table. +# NV 4-person base (2024-10-01) = 96_026. +# Size adjustments: first_person 0.52, second_to_sixth 0.16 each. +# Family of 2: 96_026 * (0.52 + 0.16) / 12 = 5_441.47 +# Family of 3: 96_026 * (0.52 + 0.16*2) / 12 = 6_721.82 +# Family of 4: 96_026 * (0.52 + 0.16*3) = 1.0 / 12 = 8_002.17 + +- name: SMI for a family of 2. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NV + output: + # 96_026 * 0.68 / 12 = 5_441.47 + nv_ccdp_smi: 5_441.47 + +- name: SMI for a family of 3. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + person2: + age: 30 + person3: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NV + output: + # 96_026 * 0.84 / 12 = 6_721.82 + nv_ccdp_smi: 6_721.82 + +- name: SMI for a family of 4. + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 35 + person2: + age: 32 + person3: + age: 8 + person4: + age: 5 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + households: + household: + members: [person1, person2, person3, person4] + state_code: NV + output: + # 96_026 * 1.00 / 12 = 8_002.17 + nv_ccdp_smi: 8_002.17 + +# Edge cases: minimum family size (1) and the additional-person branch (size >= 7). +# Size adjustment = 0.52 + 0.16 * clip(size-1, 0, 5) + 0.03 * max(size-6, 0). + +- name: SMI for a family of 1 (minimum size). + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NV + output: + # 96_026 * 0.52 / 12 = 4_161.13 + nv_ccdp_smi: 4_161.13 + +- name: SMI for a family of 6 (cap on second-to-sixth adjustment). + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + person2: + age: 38 + person3: + age: 12 + person4: + age: 10 + person5: + age: 8 + person6: + age: 6 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6] + households: + household: + members: [person1, person2, person3, person4, person5, person6] + state_code: NV + output: + # 96_026 * (0.52 + 0.16*5) / 12 = 96_026 * 1.32 / 12 = 10_562.86 + nv_ccdp_smi: 10_562.86 + +- name: SMI for a family of 7 (additional-person 0.03 branch). + period: 2025-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + person2: + age: 38 + person3: + age: 14 + person4: + age: 12 + person5: + age: 10 + person6: + age: 8 + person7: + age: 6 + spm_units: + spm_unit: + members: + [person1, person2, person3, person4, person5, person6, person7] + households: + household: + members: + [person1, person2, person3, person4, person5, person6, person7] + state_code: NV + output: + # 96_026 * (0.52 + 0.16*5 + 0.03*1) / 12 = 96_026 * 1.35 / 12 = 10_802.93 + nv_ccdp_smi: 10_802.93 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_child_care_subsidies.yaml b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_child_care_subsidies.yaml new file mode 100644 index 00000000000..d3be5469248 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nv/dwss/ccdp/nv_child_care_subsidies.yaml @@ -0,0 +1,59 @@ +# nv_child_care_subsidies: SPMUnit, float (USD), YEAR +# adds = ["nv_ccdp"], so the annual value is the sum of the 12 monthly benefits. + +- name: Annual subsidy sums twelve monthly nv_ccdp benefits. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 24_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + nv_ccdp_enrolled: false + spm_unit_pre_subsidy_childcare_expenses: 30_000 + households: + household: + members: [person1, person2, person3] + state_code: NV + county: CLARK_COUNTY_NV + output: + # Monthly nv_ccdp = 1_235.19 (Clark center toddler, $0 copay), times 12. + # 1_235.19 * 12 = 14_822.28. + nv_child_care_subsidies: 14_822.28 + +- name: Non-Nevada household receives zero subsidy. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 32 + employment_income: 24_000 + person2: + age: 30 + person3: + age: 3 + immigration_status: CITIZEN + nv_ccdp_provider_type: CENTER + spm_units: + spm_unit: + members: [person1, person2, person3] + meets_ccdf_activity_test: true + spm_unit_pre_subsidy_childcare_expenses: 30_000 + households: + household: + members: [person1, person2, person3] + state_code: UT + output: + # defined_for = StateCode.NV zeroes the benefit outside Nevada. + nv_child_care_subsidies: 0 diff --git a/policyengine_us/variables/gov/states/nv/dwss/ccdp/copay/nv_ccdp_copay.py b/policyengine_us/variables/gov/states/nv/dwss/ccdp/copay/nv_ccdp_copay.py new file mode 100644 index 00000000000..03665737174 --- /dev/null +++ b/policyengine_us/variables/gov/states/nv/dwss/ccdp/copay/nv_ccdp_copay.py @@ -0,0 +1,46 @@ +from policyengine_us.model_api import * + + +class nv_ccdp_copay(Variable): + value_type = float + entity = SPMUnit + unit = USD + label = "Nevada CCDP monthly family co-payment" + definition_period = MONTH + defined_for = StateCode.NV + reference = "https://dss.nv.gov/uploadedFiles/dwssnvgov/content/Care/CC%20PT%2006-25%20ANNUAL%20INCOME%20CHANGES%2010.02.2025.pdf#page=1" + + def formula(spm_unit, period, parameters): + # CC PT 06-25 (eff. Oct 1, 2025) replaced the manual's old sliding + # scale with a flat monthly copay keyed by income as a share of SMI: + # <= 32.99% SMI -> $0; 33%-42% SMI -> $90; 43%-49% SMI -> $150. + p = parameters(period).gov.states.nv.dwss.ccdp.copay + # Floor income at 0 before taking the ratio: negative self-employment + # income would otherwise produce a negative ratio and the wrong band. + monthly_income = max_( + spm_unit("nv_ccdp_countable_income", period.this_year) / MONTHS_IN_YEAR, + 0, + ) + monthly_smi = spm_unit("nv_ccdp_smi", period) + smi_ratio = where(monthly_smi > 0, monthly_income / monthly_smi, 0) + copay = p.amount.calc(smi_ratio) + # MS 163 / CCDF Plan Section 3.3.1: copay waived for TANF/NEON + # referrals, CPS/foster placements, and homeless households. Use + # is_tanf_enrolled for the NEON waiver to avoid the CCDP <-> TANF + # circular dependency. Waivers apply at the whole-family level; we + # don't track per-child copay waivers at the moment. + # State Plan Section 3.3.1 additionally lists copay waivers for + # children with disabilities and for Head Start / Early Head Start + # enrollees, which we don't track at the moment: the operative + # Manual MS 163 omits both categories. + is_tanf_enrolled = spm_unit("is_tanf_enrolled", period) + person = spm_unit.members + is_eligible_child = person("nv_ccdp_eligible_child", period) + in_protective_care = is_eligible_child & ( + person("is_in_foster_care", period) + | person("receives_or_needs_protective_services", period.this_year) + ) + has_protective_child = spm_unit.any(in_protective_care) + is_homeless = spm_unit.household("is_homeless", period.this_year) + waived = is_tanf_enrolled | has_protective_child | is_homeless + return where(waived, 0, copay) diff --git a/policyengine_us/variables/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_activity_eligible.py b/policyengine_us/variables/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_activity_eligible.py new file mode 100644 index 00000000000..7c78b0129a7 --- /dev/null +++ b/policyengine_us/variables/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_activity_eligible.py @@ -0,0 +1,36 @@ +from policyengine_us.model_api import * + + +class nv_ccdp_activity_eligible(Variable): + value_type = bool + entity = SPMUnit + label = "Eligible for Nevada CCDP based on Purpose of Care activity" + definition_period = MONTH + defined_for = StateCode.NV + reference = "https://dss.nv.gov/uploadedFiles/dwssnvgov/content/Care/Child%20Care%20Manual%20July%202024.pdf#page=66" + + def formula(spm_unit, period, parameters): + # MS 400 series: each required caretaker must be in an approved Purpose + # of Care activity. Rather than re-deriving each POC category, we reuse + # existing modeled status hooks: + # - meets_ccdf_activity_test covers POC 420 (working), 430 (job + # search), 450 (student), 460 (training), and 470 (disability of a + # caretaker where another adult is in an approved activity); it is a + # user input flagging that the responsible caretaker(s) participate + # in an approved activity. + # - is_tanf_enrolled covers POC 410 (TANF NEON referrals); using the + # enrollment input rather than computed TANF eligibility breaks the + # CCDP <-> TANF circular dependency. + # - is_homeless covers POC 440 (Homeless Self-Sufficiency Plan), a + # need-for-care reason that belongs inside the activity test. + # POC 470 disability is intentionally NOT a standalone bypass: a lone + # idle disabled caretaker is not activity-eligible (it requires another + # adult in an approved activity), so the disability pathway flows + # through meets_ccdf_activity_test rather than a bare is_disabled term. + # We don't track activity-hour verification or overlapping two-parent + # schedules at the moment (MS 400 series), nor the job-search 90-day or + # student/job-search funding-availability limits. + meets_activity_test = spm_unit("meets_ccdf_activity_test", period.this_year) + is_tanf_enrolled = spm_unit("is_tanf_enrolled", period) + is_homeless = spm_unit.household("is_homeless", period.this_year) + return meets_activity_test | is_tanf_enrolled | is_homeless diff --git a/policyengine_us/variables/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_eligible.py b/policyengine_us/variables/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_eligible.py new file mode 100644 index 00000000000..7cac91fcdd7 --- /dev/null +++ b/policyengine_us/variables/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_eligible.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class nv_ccdp_eligible(Variable): + value_type = bool + entity = SPMUnit + label = "Eligible for Nevada CCDP" + definition_period = MONTH + defined_for = StateCode.NV + reference = "https://dss.nv.gov/uploadedFiles/dwssnvgov/content/Care/Child%20Care%20Manual%20July%202024.pdf#page=36" + + def formula(spm_unit, period, parameters): + # MS 218 residency is enforced by defined_for = StateCode.NV. + has_eligible_child = add(spm_unit, period, ["nv_ccdp_eligible_child"]) > 0 + income_eligible = spm_unit("nv_ccdp_income_eligible", period) + # MS 2.2.6 / 45 CFR 98.20: $1,000,000 federal CCDF asset ceiling. + asset_eligible = spm_unit("is_ccdf_asset_eligible", period.this_year) + activity_eligible = spm_unit("nv_ccdp_activity_eligible", period) + return has_eligible_child & income_eligible & asset_eligible & activity_eligible diff --git a/policyengine_us/variables/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_eligible_child.py b/policyengine_us/variables/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_eligible_child.py new file mode 100644 index 00000000000..45557ec667a --- /dev/null +++ b/policyengine_us/variables/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_eligible_child.py @@ -0,0 +1,35 @@ +from policyengine_us.model_api import * + + +class nv_ccdp_eligible_child(Variable): + value_type = bool + entity = Person + label = "Eligible child for Nevada CCDP" + definition_period = MONTH + defined_for = StateCode.NV + reference = "https://dss.nv.gov/uploadedFiles/dwssnvgov/content/Care/Child%20Care%20Manual%20July%202024.pdf#page=36" + + def formula(person, period, parameters): + # MS 210: child must be under 13. MS 211: a child with a special need + # is eligible up to age 19. We don't track 12-month certification + # periods at the moment, so the mid-certification continuity that lets + # a child age past 13 (or 19) keep coverage (MS 210/211) is not modeled. + p = parameters(period).gov.states.nv.dwss.ccdp.eligibility + age = person("age", period.this_year) + # MS 211 defines a special need as a physical or mental condition that + # severely limits self-care, or an at-risk emotional condition; broader + # than developmental delay alone, so OR in the general disability flag. + has_special_need = person("has_developmental_delay", period.this_year) | person( + "is_disabled", period.this_year + ) + age_eligible = where( + has_special_need, + age < p.special_needs_child_age_limit, + age < p.child_age_limit, + ) + # MS 214: the child (not the parent) must be a U.S. citizen or + # lawfully-admitted non-citizen. Reuse the federal CCDF child check. + immigration_eligible = person( + "is_ccdf_immigration_eligible_child", period.this_year + ) + return age_eligible & immigration_eligible diff --git a/policyengine_us/variables/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_income_eligible.py b/policyengine_us/variables/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_income_eligible.py new file mode 100644 index 00000000000..cc87f9e0760 --- /dev/null +++ b/policyengine_us/variables/gov/states/nv/dwss/ccdp/eligibility/nv_ccdp_income_eligible.py @@ -0,0 +1,29 @@ +from policyengine_us.model_api import * + + +class nv_ccdp_income_eligible(Variable): + value_type = bool + entity = SPMUnit + label = "Eligible for Nevada CCDP based on income" + definition_period = MONTH + defined_for = StateCode.NV + reference = "https://dss.nv.gov/uploadedFiles/dwssnvgov/content/Care/CC%20PT%2006-25%20ANNUAL%20INCOME%20CHANGES%2010.02.2025.pdf#page=1" + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nv.dwss.ccdp.income + # Countable income is YEAR-defined; convert to a monthly figure to + # compare against the monthly SMI accessor. + countable_income = ( + spm_unit("nv_ccdp_countable_income", period.this_year) / MONTHS_IN_YEAR + ) + monthly_smi = spm_unit("nv_ccdp_smi", period) + enrolled = spm_unit("nv_ccdp_enrolled", period) + # New applicants must be at or below 41% SMI (intake). Enrolled families + # are redetermined at or below 49% SMI (renewal); this matches the + # published renewal income/copay chart (CC PT 06-25), whose copay bands + # top out at 49% SMI. A separate federal protection (45 CFR + # 98.21(a)(1)(ii)) bars terminating a family mid-certification until + # income exceeds 85% SMI, but we don't track certification periods at + # the moment, so the 85% mid-period ceiling is not modeled. + income_limit = monthly_smi * where(enrolled, p.smi_renewal, p.smi_intake) + return countable_income <= income_limit diff --git a/policyengine_us/variables/gov/states/nv/dwss/ccdp/income/nv_ccdp_countable_income.py b/policyengine_us/variables/gov/states/nv/dwss/ccdp/income/nv_ccdp_countable_income.py new file mode 100644 index 00000000000..801edb0b4ec --- /dev/null +++ b/policyengine_us/variables/gov/states/nv/dwss/ccdp/income/nv_ccdp_countable_income.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class nv_ccdp_countable_income(Variable): + value_type = float + entity = SPMUnit + label = "Nevada CCDP countable income" + definition_period = YEAR + unit = USD + defined_for = StateCode.NV + reference = "https://dss.nv.gov/uploadedFiles/dwssnvgov/content/Care/Child%20Care%20Manual%20July%202024.pdf#page=55" + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nv.dwss.ccdp.income + # MS 306: countable gross income from the enumerated source list. + gross_income = add(spm_unit, period, p.countable_income.sources) + # MS 302.2/302.3: deduct court- or administratively-ordered child + # support a required member is legally obligated to pay and actually + # pays. `child_support_expense` is the amount paid out (not received). + child_support_paid = add(spm_unit, period, ["child_support_expense"]) + return max_(gross_income - child_support_paid, 0) diff --git a/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp.py b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp.py new file mode 100644 index 00000000000..8917a7bea71 --- /dev/null +++ b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp.py @@ -0,0 +1,35 @@ +from policyengine_us.model_api import * + + +class nv_ccdp(Variable): + value_type = float + entity = SPMUnit + unit = USD + label = "Nevada CCDP benefit amount" + definition_period = MONTH + defined_for = "nv_ccdp_eligible" + reference = "https://dss.nv.gov/uploadedFiles/dwssnvgov/content/Care/Child%20Care%20Manual%20July%202024.pdf#page=29" + + def formula(spm_unit, period, parameters): + # MS 163: subsidy = min(provider charge, state maximum rate) - family + # copay, paid up to 100% of the state maximum rate. The state maximum + # rate is the per-day base rate converted to a monthly maximum using a + # standard billed-days figure (Nevada does not publish an explicit + # daily-to-monthly billing convention at the moment; a full-time-month + # proxy is used, capped at actual childcare charges). + # We don't track part-day/fractional-day rate adjustments or per-child + # fee ordering at the moment. + p = parameters(period).gov.states.nv.dwss.ccdp + person = spm_unit.members + daily_rate = person("nv_ccdp_provider_rate", period) + monthly_max_rate = daily_rate * p.billing.monthly_billed_days + # pre_subsidy_childcare_expenses is YEAR-defined; bare `period` in this + # MONTH formula auto-divides it to a monthly amount. + pre_subsidy_per_child = person("pre_subsidy_childcare_expenses", period) + is_eligible_child = person("nv_ccdp_eligible_child", period) + per_child_reimbursement = ( + min_(pre_subsidy_per_child, monthly_max_rate) * is_eligible_child + ) + total_reimbursement = spm_unit.sum(per_child_reimbursement) + copay = spm_unit("nv_ccdp_copay", period) + return max_(total_reimbursement - copay, 0) diff --git a/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_age_group.py b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_age_group.py new file mode 100644 index 00000000000..c67f3db019f --- /dev/null +++ b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_age_group.py @@ -0,0 +1,28 @@ +from policyengine_us.model_api import * + + +class NVCCDPAgeGroup(Enum): + INFANT = "Infant" + TODDLER = "Toddler" + PRESCHOOL = "Preschool" + SCHOOL_AGE = "School Age" + + +class nv_ccdp_age_group(Variable): + value_type = Enum + entity = Person + possible_values = NVCCDPAgeGroup + default_value = NVCCDPAgeGroup.PRESCHOOL + definition_period = MONTH + label = "Nevada CCDP child age group" + defined_for = StateCode.NV + reference = "https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/ACF-118_CCDF_FFY_2025-2027_For_Nevada__3.pdf#page=54" + + def formula(person, period, parameters): + # `monthly_age` reverses PolicyEngine's auto-division by 12, so it + # returns age in YEARS; use the explicit age-in-months form instead. + age_in_months = person("age", period.this_year) * MONTHS_IN_YEAR + p = parameters(period).gov.states.nv.dwss.ccdp.age_group + # The months scale returns the integer index 0=INFANT, 1=TODDLER, + # 2=PRESCHOOL, 3=SCHOOL_AGE, which maps directly onto the enum order. + return p.months.calc(age_in_months) diff --git a/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_enrolled.py b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_enrolled.py new file mode 100644 index 00000000000..61c2bed943e --- /dev/null +++ b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_enrolled.py @@ -0,0 +1,9 @@ +from policyengine_us.model_api import * + + +class nv_ccdp_enrolled(Variable): + value_type = bool + entity = SPMUnit + definition_period = MONTH + label = "Whether the family is currently enrolled in Nevada CCDP" + defined_for = StateCode.NV diff --git a/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_provider_rate.py b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_provider_rate.py new file mode 100644 index 00000000000..68c998cdcde --- /dev/null +++ b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_provider_rate.py @@ -0,0 +1,29 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.nv.dwss.ccdp.nv_ccdp_provider_type import ( + NVCCDPProviderType, +) + + +class nv_ccdp_provider_rate(Variable): + value_type = float + entity = Person + unit = USD + label = "Nevada CCDP daily provider reimbursement rate" + definition_period = MONTH + defined_for = "nv_ccdp_eligible_child" + reference = "https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/ACF-118_CCDF_FFY_2025-2027_For_Nevada__3.pdf#page=54" + + def formula(person, period, parameters): + # CCDF State Plan Section 4.3.1, Tables 1-2: per-day base reimbursement + # rate keyed by region x provider type x age group. + p = parameters(period).gov.states.nv.dwss.ccdp.rates + provider_type = person("nv_ccdp_provider_type", period) + region = person("nv_ccdp_region", period) + age_group = person("nv_ccdp_age_group", period) + center_rate = p.center[region][age_group] + fcc_rate = p.fcc[region][age_group] + return where( + provider_type == NVCCDPProviderType.FCC, + fcc_rate, + center_rate, + ) diff --git a/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_provider_type.py b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_provider_type.py new file mode 100644 index 00000000000..eefccf955f1 --- /dev/null +++ b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_provider_type.py @@ -0,0 +1,17 @@ +from policyengine_us.model_api import * + + +class NVCCDPProviderType(Enum): + CENTER = "Child Care Center" + FCC = "Family Child Care" + + +class nv_ccdp_provider_type(Variable): + value_type = Enum + entity = Person + possible_values = NVCCDPProviderType + default_value = NVCCDPProviderType.CENTER + definition_period = MONTH + label = "Nevada CCDP child care provider type" + defined_for = StateCode.NV + reference = "https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/ACF-118_CCDF_FFY_2025-2027_For_Nevada__3.pdf#page=54" diff --git a/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_region.py b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_region.py new file mode 100644 index 00000000000..4f19b5d3b71 --- /dev/null +++ b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_region.py @@ -0,0 +1,32 @@ +from policyengine_us.model_api import * + + +class NVCCDPRegion(Enum): + MOST_POPULOUS = "Most populous region (Clark County)" + LOWEST_PERCENTILE = "Lowest-percentile region (all other counties)" + + +class nv_ccdp_region(Variable): + value_type = Enum + entity = Person + possible_values = NVCCDPRegion + default_value = NVCCDPRegion.LOWEST_PERCENTILE + definition_period = MONTH + label = "Nevada CCDP reimbursement-rate region" + defined_for = StateCode.NV + reference = "https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/ACF-118_CCDF_FFY_2025-2027_For_Nevada__3.pdf#page=54" + + def formula(person, period, parameters): + # The CCDF State Plan rate table reports two regions: the most populous + # region (Clark County) and the lowest-percentile region (every other + # county). `county_str` is a Household accessor enumerating every US + # county, so non-Nevada county strings flow through this formula in + # microsim even though `defined_for` filters the output. Comparing the + # county string directly (rather than indexing a parameter by it) keeps + # the lookup safe for any county value. + county = person.household("county_str", period.this_year) + return where( + county == "CLARK_COUNTY_NV", + NVCCDPRegion.MOST_POPULOUS, + NVCCDPRegion.LOWEST_PERCENTILE, + ) diff --git a/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_smi.py b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_smi.py new file mode 100644 index 00000000000..2cbf74545ee --- /dev/null +++ b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_ccdp_smi.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.hhs_smi import smi + + +class nv_ccdp_smi(Variable): + value_type = float + entity = SPMUnit + label = "Nevada CCDP monthly 100% State Median Income" + unit = USD + definition_period = MONTH + defined_for = StateCode.NV + reference = "https://www.dss.nv.gov/siteassets/dwss.nv.gov/content/care/ACF-118_CCDF_FFY_2025-2027_For_Nevada__3.pdf#page=36" + + def formula(spm_unit, period, parameters): + # Nevada keys income eligibility and copays to State Median Income (not + # the federal poverty guideline). The income chart updates annually on + # October 1, so select the SMI table in effect for that program year. + year = period.start.year + month = period.start.month + if month >= 10: + instant_str = f"{year}-10-01" + else: + instant_str = f"{year - 1}-10-01" + size = spm_unit("spm_unit_size", period.this_year) + state = spm_unit.household("state_code_str", period.this_year) + annual_smi = smi(size, state, instant_str, parameters) + return annual_smi / MONTHS_IN_YEAR diff --git a/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_child_care_subsidies.py b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_child_care_subsidies.py new file mode 100644 index 00000000000..d0a2469af67 --- /dev/null +++ b/policyengine_us/variables/gov/states/nv/dwss/ccdp/nv_child_care_subsidies.py @@ -0,0 +1,11 @@ +from policyengine_us.model_api import * + + +class nv_child_care_subsidies(Variable): + value_type = float + entity = SPMUnit + label = "Nevada child care subsidies" + unit = USD + definition_period = YEAR + defined_for = StateCode.NV + adds = ["nv_ccdp"]