From 9adeea8acee5cb6026b5ecc41214ba08257b69fc Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 12 Jun 2026 12:03:44 -0400 Subject: [PATCH 1/7] Initialize Michigan Child Care Assistance Program (CDC) implementation Scaffolds the branch for Michigan's Child Development and Care (CDC) program, the state's CCDF-funded child care subsidy. Co-Authored-By: Claude Fable 5 --- changelog.d/mi-ccap.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/mi-ccap.added.md diff --git a/changelog.d/mi-ccap.added.md b/changelog.d/mi-ccap.added.md new file mode 100644 index 00000000000..3ba6d1040f0 --- /dev/null +++ b/changelog.d/mi-ccap.added.md @@ -0,0 +1 @@ +Initialize Michigan Child Care Assistance Program (CDC) implementation. From 5a24b6fd60fe180150ee501f63de46b745e1ccbc Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 12 Jun 2026 13:50:19 -0400 Subject: [PATCH 2/7] Implement Michigan CCAP (ref #8626) Co-Authored-By: Claude Fable 5 --- .../hhs/ccdf/child_care_subsidy_programs.yaml | 1 + .../mi/mdhhs/ccap/age_group/thresholds.yaml | 25 + .../mi/mdhhs/ccap/authorized_hours/tiers.yaml | 39 ++ .../mi/mdhhs/ccap/block_hours/tiers.yaml | 29 ++ .../ccap/eligibility/child_age_limit.yaml | 11 + .../eligibility/disabled_child_age_limit.yaml | 11 + .../eligibility/max_program_group_size.yaml | 11 + .../family_contribution/family_limit.yaml | 29 ++ .../ccap/family_contribution/per_child.yaml | 28 ++ .../star_waiver_threshold.yaml | 11 + .../ccap/income/countable_income/sources.yaml | 33 ++ .../income/scale/band_thresholds/size_1.yaml | 42 ++ .../income/scale/band_thresholds/size_10.yaml | 42 ++ .../income/scale/band_thresholds/size_2.yaml | 42 ++ .../income/scale/band_thresholds/size_3.yaml | 42 ++ .../income/scale/band_thresholds/size_4.yaml | 42 ++ .../income/scale/band_thresholds/size_5.yaml | 42 ++ .../income/scale/band_thresholds/size_6.yaml | 42 ++ .../income/scale/band_thresholds/size_7.yaml | 42 ++ .../income/scale/band_thresholds/size_8.yaml | 42 ++ .../income/scale/band_thresholds/size_9.yaml | 42 ++ .../mdhhs/ccap/income/scale/entry_limit.yaml | 34 ++ .../mdhhs/ccap/income/scale/exit_limit.yaml | 34 ++ .../states/mi/mdhhs/ccap/rates/center.yaml | 49 ++ .../states/mi/mdhhs/ccap/rates/exempt.yaml | 26 + .../mi/mdhhs/ccap/rates/family_home.yaml | 49 ++ .../ccap/two_parent_uses_highest_hours.yaml | 11 + policyengine_us/programs.yaml | 8 +- .../mi_ccap_activity_eligible.yaml | 201 ++++++++ .../ccap/eligibility/mi_ccap_eligible.yaml | 270 ++++++++++ .../eligibility/mi_ccap_eligible_child.yaml | 195 ++++++++ .../eligibility/mi_ccap_income_eligible.yaml | 205 ++++++++ .../eligibility/mi_ccap_income_waived.yaml | 155 ++++++ .../gov/states/mi/mdhhs/ccap/integration.yaml | 468 ++++++++++++++++++ .../mi/mdhhs/ccap/mi_ccap_age_group.yaml | 125 +++++ .../mdhhs/ccap/mi_ccap_authorized_hours.yaml | 259 ++++++++++ .../mi/mdhhs/ccap/mi_ccap_block_payment.yaml | 236 +++++++++ .../ccap/mi_ccap_family_contribution.yaml | 320 ++++++++++++ .../mi/mdhhs/ccap/mi_ccap_hourly_rate.yaml | 281 +++++++++++ .../ccap/mi_ccap_program_group_size.yaml | 189 +++++++ .../eligibility/mi_ccap_activity_eligible.py | 31 ++ .../ccap/eligibility/mi_ccap_eligible.py | 28 ++ .../eligibility/mi_ccap_eligible_child.py | 30 ++ .../eligibility/mi_ccap_income_eligible.py | 27 + .../ccap/eligibility/mi_ccap_income_waived.py | 35 ++ .../gov/states/mi/mdhhs/ccap/mi_ccap.py | 36 ++ .../states/mi/mdhhs/ccap/mi_ccap_age_group.py | 27 + .../mi/mdhhs/ccap/mi_ccap_authorized_hours.py | 36 ++ .../mi/mdhhs/ccap/mi_ccap_block_payment.py | 42 ++ .../mi/mdhhs/ccap/mi_ccap_countable_income.py | 19 + .../states/mi/mdhhs/ccap/mi_ccap_enrolled.py | 12 + .../mi/mdhhs/ccap/mi_ccap_exempt_level.py | 19 + .../mdhhs/ccap/mi_ccap_family_contribution.py | 85 ++++ .../mi/mdhhs/ccap/mi_ccap_hourly_rate.py | 41 ++ .../mdhhs/ccap/mi_ccap_program_group_size.py | 23 + .../mi/mdhhs/ccap/mi_ccap_provider_type.py | 20 + .../mi/mdhhs/ccap/mi_ccap_star_rating.py | 24 + .../mi/mdhhs/ccap/mi_child_care_subsidies.py | 11 + 58 files changed, 4308 insertions(+), 1 deletion(-) create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/age_group/thresholds.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/authorized_hours/tiers.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/block_hours/tiers.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/eligibility/child_age_limit.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/eligibility/disabled_child_age_limit.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/eligibility/max_program_group_size.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/family_limit.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/per_child.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/star_waiver_threshold.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/countable_income/sources.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_1.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_10.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_2.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_3.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_4.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_5.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_6.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_7.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_8.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_9.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/entry_limit.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/exit_limit.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/rates/center.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/rates/exempt.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/rates/family_home.yaml create mode 100644 policyengine_us/parameters/gov/states/mi/mdhhs/ccap/two_parent_uses_highest_hours.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/integration.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_age_group.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_hourly_rate.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_program_group_size.yaml create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_age_group.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_countable_income.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_enrolled.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_exempt_level.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_hourly_rate.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_program_group_size.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_provider_type.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_star_rating.py create mode 100644 policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_child_care_subsidies.py 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 598ca5cd7a0..cfc5384fbbd 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 @@ -10,6 +10,7 @@ values: - de_child_care_subsidies # Delaware Purchase of Care - ma_child_care_subsidies # Massachusetts Child Care Financial Assistance - me_child_care_subsidies # Maine Child Care Affordability Program + - mi_child_care_subsidies # Michigan Child Development and Care program - ne_child_care_subsidies # Nebraska Child Care Subsidy - vt_child_care_subsidies # Vermont Child Care Financial Assistance Program - nh_child_care_subsidies # New Hampshire Child Care Scholarship Program diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/age_group/thresholds.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/age_group/thresholds.yaml new file mode 100644 index 00000000000..7dd6fc0f3f5 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/age_group/thresholds.yaml @@ -0,0 +1,25 @@ +description: Michigan assigns children to these Child Development and Care rate age groups by age in years. + +brackets: + - threshold: + 2024-09-22: 0 + amount: + 2024-09-22: 0 # INFANT_TODDLER (birth up to age 2 1/2) + - threshold: + 2024-09-22: 2.5 + amount: + 2024-09-22: 1 # PRESCHOOL (age 2 1/2 up to age 5) + - threshold: + 2024-09-22: 5 + amount: + 2024-09-22: 2 # SCHOOL_AGE (age 5 and over) + +metadata: + type: single_amount + threshold_unit: year + amount_unit: /1 + period: year + label: Michigan CDC rate age group thresholds + reference: + - title: RFT 270, Department Hourly Rates (effective September 22, 2024) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=4 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/authorized_hours/tiers.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/authorized_hours/tiers.yaml new file mode 100644 index 00000000000..cb4765950ad --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/authorized_hours/tiers.yaml @@ -0,0 +1,39 @@ +description: Michigan rounds biweekly need hours up to one of these authorized hour tiers per child under the Child Development and Care program. + +brackets: + - threshold: + 2024-09-22: 0 + amount: + 2024-09-22: 0 + - threshold: + 2024-09-22: 0.0001 + amount: + 2024-09-22: 20 + - threshold: + 2024-09-22: 20.0001 + amount: + 2024-09-22: 40 + - threshold: + 2024-09-22: 40.0001 + amount: + 2024-09-22: 60 + - threshold: + 2024-09-22: 60.0001 + amount: + 2024-09-22: 80 + - threshold: + 2024-09-22: 80.0001 + amount: + 2024-09-22: 90 + +metadata: + type: single_amount + threshold_unit: hour + amount_unit: hour + period: year + label: Michigan CDC authorized hour tiers + reference: + - title: BEM 706, Biweekly Authorized Hours + href: https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/706.pdf#page=2 + - title: BEM 710, Need Calculation + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EXF/BP/Public/BEM/710.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/block_hours/tiers.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/block_hours/tiers.yaml new file mode 100644 index 00000000000..32e0eb0f812 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/block_hours/tiers.yaml @@ -0,0 +1,29 @@ +description: Michigan reimburses licensed centers and group and family homes at these block hour amounts per child per two-week pay period under the Child Development and Care program. + +brackets: + - threshold: + 2022-10-09: 0 + amount: + 2022-10-09: 0 + - threshold: + 2022-10-09: 0.0001 + amount: + 2022-10-09: 30 # billing 1-30 hours pays 30 hours + - threshold: + 2022-10-09: 30.0001 + amount: + 2022-10-09: 60 # billing 31-60 hours pays 60 hours + - threshold: + 2022-10-09: 60.0001 + amount: + 2022-10-09: 90 # billing 61 or more hours pays 90 hours + +metadata: + type: single_amount + threshold_unit: hour + amount_unit: hour + period: year + label: Michigan CDC block reimbursement hours + reference: + - title: BEM 706, Block Reimbursement + href: https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/706.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/eligibility/child_age_limit.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/eligibility/child_age_limit.yaml new file mode 100644 index 00000000000..75c47c7fa94 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/eligibility/child_age_limit.yaml @@ -0,0 +1,11 @@ +description: Michigan limits the Child Development and Care program to children younger than this age. +values: + 2024-09-22: 13 + +metadata: + unit: year + period: year + label: Michigan CDC child age limit + reference: + - title: BEM 703, Eligible Children + href: https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/703.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/eligibility/disabled_child_age_limit.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/eligibility/disabled_child_age_limit.yaml new file mode 100644 index 00000000000..07650853387 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/eligibility/disabled_child_age_limit.yaml @@ -0,0 +1,11 @@ +description: Michigan extends Child Development and Care eligibility to children younger than this age who require constant care or are under court-ordered supervision. +values: + 2024-09-22: 18 + +metadata: + unit: year + period: year + label: Michigan CDC constant-care child age limit + reference: + - title: BEM 703, Eligible Children + href: https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/703.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/eligibility/max_program_group_size.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/eligibility/max_program_group_size.yaml new file mode 100644 index 00000000000..c7a50073fa2 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/eligibility/max_program_group_size.yaml @@ -0,0 +1,11 @@ +description: Michigan caps the Child Development and Care program group size at this value for the income eligibility scale. +values: + 2025-09-21: 10 + +metadata: + unit: person + period: year + label: Michigan CDC maximum program group size + reference: + - title: RFT 270, CDC Income Eligibility Scale (effective September 21, 2025) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/family_limit.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/family_limit.yaml new file mode 100644 index 00000000000..a6bef57b540 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/family_limit.yaml @@ -0,0 +1,29 @@ +description: Michigan caps the total family contribution per two-week pay period at this amount under the Child Development and Care program, based on the family income band. + +metadata: + unit: currency-USD + period: year + label: Michigan CDC family contribution family limit per two-week pay period + breakdown: + - range(0, 7) + breakdown_labels: + - Income band + reference: + - title: RFT 270, CDC Income Eligibility Scale (effective September 21, 2025) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3 + +# Band 0 charges no family contribution, so its cap is 0 (non-binding). +0: + 2025-09-21: 0 +1: + 2025-09-21: 45 +2: + 2025-09-21: 83 +3: + 2025-09-21: 121 +4: + 2025-09-21: 143 +5: + 2025-09-21: 164 +6: + 2025-09-21: 186 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/per_child.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/per_child.yaml new file mode 100644 index 00000000000..726e7b6d085 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/per_child.yaml @@ -0,0 +1,28 @@ +description: Michigan charges this flat family contribution per child per two-week pay period under the Child Development and Care program, based on the family income band. + +metadata: + unit: currency-USD + period: year + label: Michigan CDC family contribution per child per two-week pay period + breakdown: + - range(0, 7) + breakdown_labels: + - Income band + reference: + - title: RFT 270, CDC Income Eligibility Scale (effective September 21, 2025) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3 + +0: + 2025-09-21: 0 +1: + 2025-09-21: 15 +2: + 2025-09-21: 30 +3: + 2025-09-21: 45 +4: + 2025-09-21: 60 +5: + 2025-09-21: 69 +6: + 2025-09-21: 78 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/star_waiver_threshold.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/star_waiver_threshold.yaml new file mode 100644 index 00000000000..9d0c722433c --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/star_waiver_threshold.yaml @@ -0,0 +1,11 @@ +description: Michigan waives the Child Development and Care family contribution for income-eligible children at a provider with this star quality level or higher. +values: + 2025-09-21: 3 + +metadata: + unit: "/1" + period: year + label: Michigan CDC family contribution star waiver threshold + reference: + - title: BEM 706, Family Contribution + href: https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/706.pdf#page=2 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/countable_income/sources.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/countable_income/sources.yaml new file mode 100644 index 00000000000..1218fd89e80 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/countable_income/sources.yaml @@ -0,0 +1,33 @@ +description: Michigan counts these income sources of the program group under the Child Development and Care program. +values: + 2024-09-22: + - employment_income + - self_employment_income + - sstb_self_employment_income + - social_security + - ssi + - dividend_income + - interest_income + - rental_income + - pension_income + - veterans_benefits + - military_retirement_pay + - unemployment_compensation + - workers_compensation + - alimony_income + - child_support_received + # The following are counted under BEM 500/502/503 but have no matching + # variables at the moment: + # - adoption_subsidy + # - royalties + # - strike benefits + # - paid jury duty + # - cash gifts and contributions + +metadata: + unit: list + period: year + label: Michigan CDC countable income sources + reference: + - title: BEM 703, Income + href: https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/703.pdf#page=13 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_1.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_1.yaml new file mode 100644 index 00000000000..dbad1ffaeb1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_1.yaml @@ -0,0 +1,42 @@ +description: Michigan assigns a program group of 1 person to a Child Development and Care family contribution band based on monthly gross income. + +brackets: + - threshold: + 2025-09-21: 0 + amount: + 2025-09-21: 0 # band 0 ($0 family contribution) + - threshold: + 2025-09-21: 1_304.0001 + amount: + 2025-09-21: 1 + - threshold: + 2025-09-21: 2_608.0001 + amount: + 2025-09-21: 2 + - threshold: + 2025-09-21: 2_949.0001 + amount: + 2025-09-21: 3 + - threshold: + 2025-09-21: 3_289.0001 + amount: + 2025-09-21: 4 + - threshold: + 2025-09-21: 3_630.0001 + amount: + 2025-09-21: 5 + - threshold: + 2025-09-21: 3_970.0001 + amount: + 2025-09-21: 6 + +metadata: + type: single_amount + threshold_unit: currency-USD + amount_unit: /1 + threshold_period: month + period: year + label: Michigan CDC family contribution band for program group size 1 + reference: + - title: RFT 270, CDC Income Eligibility Scale (effective September 21, 2025) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_10.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_10.yaml new file mode 100644 index 00000000000..0a6e8e7e6f8 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_10.yaml @@ -0,0 +1,42 @@ +description: Michigan assigns a program group of 10 people to a Child Development and Care family contribution band based on monthly gross income. + +brackets: + - threshold: + 2025-09-21: 0 + amount: + 2025-09-21: 0 # band 0 ($0 family contribution) + - threshold: + 2025-09-21: 5_429.0001 + amount: + 2025-09-21: 1 + - threshold: + 2025-09-21: 10_858.0001 + amount: + 2025-09-21: 2 + - threshold: + 2025-09-21: 11_074.0001 + amount: + 2025-09-21: 3 + - threshold: + 2025-09-21: 11_290.0001 + amount: + 2025-09-21: 4 + - threshold: + 2025-09-21: 11_506.0001 + amount: + 2025-09-21: 5 + - threshold: + 2025-09-21: 11_722.0001 + amount: + 2025-09-21: 6 + +metadata: + type: single_amount + threshold_unit: currency-USD + amount_unit: /1 + threshold_period: month + period: year + label: Michigan CDC family contribution band for program group size 10 + reference: + - title: RFT 270, CDC Income Eligibility Scale (effective September 21, 2025) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_2.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_2.yaml new file mode 100644 index 00000000000..81c70872814 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_2.yaml @@ -0,0 +1,42 @@ +description: Michigan assigns a program group of 2 people to a Child Development and Care family contribution band based on monthly gross income. + +brackets: + - threshold: + 2025-09-21: 0 + amount: + 2025-09-21: 0 # band 0 ($0 family contribution) + - threshold: + 2025-09-21: 1_763.0001 + amount: + 2025-09-21: 1 + - threshold: + 2025-09-21: 3_526.0001 + amount: + 2025-09-21: 2 + - threshold: + 2025-09-21: 3_948.0001 + amount: + 2025-09-21: 3 + - threshold: + 2025-09-21: 4_371.0001 + amount: + 2025-09-21: 4 + - threshold: + 2025-09-21: 4_793.0001 + amount: + 2025-09-21: 5 + - threshold: + 2025-09-21: 5_216.0001 + amount: + 2025-09-21: 6 + +metadata: + type: single_amount + threshold_unit: currency-USD + amount_unit: /1 + threshold_period: month + period: year + label: Michigan CDC family contribution band for program group size 2 + reference: + - title: RFT 270, CDC Income Eligibility Scale (effective September 21, 2025) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_3.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_3.yaml new file mode 100644 index 00000000000..dcd02220c6e --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_3.yaml @@ -0,0 +1,42 @@ +description: Michigan assigns a program group of 3 people to a Child Development and Care family contribution band based on monthly gross income. + +brackets: + - threshold: + 2025-09-21: 0 + amount: + 2025-09-21: 0 # band 0 ($0 family contribution) + - threshold: + 2025-09-21: 2_221.0001 + amount: + 2025-09-21: 1 + - threshold: + 2025-09-21: 4_442.0001 + amount: + 2025-09-21: 2 + - threshold: + 2025-09-21: 4_946.0001 + amount: + 2025-09-21: 3 + - threshold: + 2025-09-21: 5_451.0001 + amount: + 2025-09-21: 4 + - threshold: + 2025-09-21: 5_955.0001 + amount: + 2025-09-21: 5 + - threshold: + 2025-09-21: 6_460.0001 + amount: + 2025-09-21: 6 + +metadata: + type: single_amount + threshold_unit: currency-USD + amount_unit: /1 + threshold_period: month + period: year + label: Michigan CDC family contribution band for program group size 3 + reference: + - title: RFT 270, CDC Income Eligibility Scale (effective September 21, 2025) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_4.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_4.yaml new file mode 100644 index 00000000000..383b1765b8a --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_4.yaml @@ -0,0 +1,42 @@ +description: Michigan assigns a program group of 4 people to a Child Development and Care family contribution band based on monthly gross income. + +brackets: + - threshold: + 2025-09-21: 0 + amount: + 2025-09-21: 0 # band 0 ($0 family contribution) + - threshold: + 2025-09-21: 2_679.0001 + amount: + 2025-09-21: 1 + - threshold: + 2025-09-21: 5_358.0001 + amount: + 2025-09-21: 2 + - threshold: + 2025-09-21: 5_945.0001 + amount: + 2025-09-21: 3 + - threshold: + 2025-09-21: 6_531.0001 + amount: + 2025-09-21: 4 + - threshold: + 2025-09-21: 7_118.0001 + amount: + 2025-09-21: 5 + - threshold: + 2025-09-21: 7_704.0001 + amount: + 2025-09-21: 6 + +metadata: + type: single_amount + threshold_unit: currency-USD + amount_unit: /1 + threshold_period: month + period: year + label: Michigan CDC family contribution band for program group size 4 + reference: + - title: RFT 270, CDC Income Eligibility Scale (effective September 21, 2025) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_5.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_5.yaml new file mode 100644 index 00000000000..ff279e1441d --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_5.yaml @@ -0,0 +1,42 @@ +description: Michigan assigns a program group of 5 people to a Child Development and Care family contribution band based on monthly gross income. + +brackets: + - threshold: + 2025-09-21: 0 + amount: + 2025-09-21: 0 # band 0 ($0 family contribution) + - threshold: + 2025-09-21: 3_138.0001 + amount: + 2025-09-21: 1 + - threshold: + 2025-09-21: 6_276.0001 + amount: + 2025-09-21: 2 + - threshold: + 2025-09-21: 6_944.0001 + amount: + 2025-09-21: 3 + - threshold: + 2025-09-21: 7_612.0001 + amount: + 2025-09-21: 4 + - threshold: + 2025-09-21: 8_281.0001 + amount: + 2025-09-21: 5 + - threshold: + 2025-09-21: 8_949.0001 + amount: + 2025-09-21: 6 + +metadata: + type: single_amount + threshold_unit: currency-USD + amount_unit: /1 + threshold_period: month + period: year + label: Michigan CDC family contribution band for program group size 5 + reference: + - title: RFT 270, CDC Income Eligibility Scale (effective September 21, 2025) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_6.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_6.yaml new file mode 100644 index 00000000000..a84bccd64bf --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_6.yaml @@ -0,0 +1,42 @@ +description: Michigan assigns a program group of 6 people to a Child Development and Care family contribution band based on monthly gross income. + +brackets: + - threshold: + 2025-09-21: 0 + amount: + 2025-09-21: 0 # band 0 ($0 family contribution) + - threshold: + 2025-09-21: 3_596.0001 + amount: + 2025-09-21: 1 + - threshold: + 2025-09-21: 7_192.0001 + amount: + 2025-09-21: 2 + - threshold: + 2025-09-21: 7_942.0001 + amount: + 2025-09-21: 3 + - threshold: + 2025-09-21: 8_693.0001 + amount: + 2025-09-21: 4 + - threshold: + 2025-09-21: 9_443.0001 + amount: + 2025-09-21: 5 + - threshold: + 2025-09-21: 10_194.0001 + amount: + 2025-09-21: 6 + +metadata: + type: single_amount + threshold_unit: currency-USD + amount_unit: /1 + threshold_period: month + period: year + label: Michigan CDC family contribution band for program group size 6 + reference: + - title: RFT 270, CDC Income Eligibility Scale (effective September 21, 2025) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_7.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_7.yaml new file mode 100644 index 00000000000..e7cfe44658e --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_7.yaml @@ -0,0 +1,42 @@ +description: Michigan assigns a program group of 7 people to a Child Development and Care family contribution band based on monthly gross income. + +brackets: + - threshold: + 2025-09-21: 0 + amount: + 2025-09-21: 0 # band 0 ($0 family contribution) + - threshold: + 2025-09-21: 4_054.0001 + amount: + 2025-09-21: 1 + - threshold: + 2025-09-21: 8_108.0001 + amount: + 2025-09-21: 2 + - threshold: + 2025-09-21: 8_725.0001 + amount: + 2025-09-21: 3 + - threshold: + 2025-09-21: 9_342.0001 + amount: + 2025-09-21: 4 + - threshold: + 2025-09-21: 9_958.0001 + amount: + 2025-09-21: 5 + - threshold: + 2025-09-21: 10_575.0001 + amount: + 2025-09-21: 6 + +metadata: + type: single_amount + threshold_unit: currency-USD + amount_unit: /1 + threshold_period: month + period: year + label: Michigan CDC family contribution band for program group size 7 + reference: + - title: RFT 270, CDC Income Eligibility Scale (effective September 21, 2025) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_8.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_8.yaml new file mode 100644 index 00000000000..6d51cdb573b --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_8.yaml @@ -0,0 +1,42 @@ +description: Michigan assigns a program group of 8 people to a Child Development and Care family contribution band based on monthly gross income. + +brackets: + - threshold: + 2025-09-21: 0 + amount: + 2025-09-21: 0 # band 0 ($0 family contribution) + - threshold: + 2025-09-21: 4_513.0001 + amount: + 2025-09-21: 1 + - threshold: + 2025-09-21: 9_026.0001 + amount: + 2025-09-21: 2 + - threshold: + 2025-09-21: 9_509.0001 + amount: + 2025-09-21: 3 + - threshold: + 2025-09-21: 9_992.0001 + amount: + 2025-09-21: 4 + - threshold: + 2025-09-21: 10_475.0001 + amount: + 2025-09-21: 5 + - threshold: + 2025-09-21: 10_958.0001 + amount: + 2025-09-21: 6 + +metadata: + type: single_amount + threshold_unit: currency-USD + amount_unit: /1 + threshold_period: month + period: year + label: Michigan CDC family contribution band for program group size 8 + reference: + - title: RFT 270, CDC Income Eligibility Scale (effective September 21, 2025) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_9.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_9.yaml new file mode 100644 index 00000000000..be9cce004bc --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/band_thresholds/size_9.yaml @@ -0,0 +1,42 @@ +description: Michigan assigns a program group of 9 people to a Child Development and Care family contribution band based on monthly gross income. + +brackets: + - threshold: + 2025-09-21: 0 + amount: + 2025-09-21: 0 # band 0 ($0 family contribution) + - threshold: + 2025-09-21: 4_971.0001 + amount: + 2025-09-21: 1 + - threshold: + 2025-09-21: 9_942.0001 + amount: + 2025-09-21: 2 + - threshold: + 2025-09-21: 10_292.0001 + amount: + 2025-09-21: 3 + - threshold: + 2025-09-21: 10_641.0001 + amount: + 2025-09-21: 4 + - threshold: + 2025-09-21: 10_991.0001 + amount: + 2025-09-21: 5 + - threshold: + 2025-09-21: 11_340.0001 + amount: + 2025-09-21: 6 + +metadata: + type: single_amount + threshold_unit: currency-USD + amount_unit: /1 + threshold_period: month + period: year + label: Michigan CDC family contribution band for program group size 9 + reference: + - title: RFT 270, CDC Income Eligibility Scale (effective September 21, 2025) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/entry_limit.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/entry_limit.yaml new file mode 100644 index 00000000000..9a142961f95 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/entry_limit.yaml @@ -0,0 +1,34 @@ +description: Michigan limits Child Development and Care applicants to this maximum monthly gross program-group income by family size. + +metadata: + unit: currency-USD + period: month + label: Michigan CDC income entry limit + breakdown: + - range(1, 11) + breakdown_labels: + - Program group size + reference: + - title: RFT 270, CDC Income Eligibility Scale (effective September 21, 2025) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3 + +1: + 2025-09-21: 2_608 +2: + 2025-09-21: 3_526 +3: + 2025-09-21: 4_442 +4: + 2025-09-21: 5_358 +5: + 2025-09-21: 6_276 +6: + 2025-09-21: 7_192 +7: + 2025-09-21: 8_108 +8: + 2025-09-21: 9_026 +9: + 2025-09-21: 9_942 +10: + 2025-09-21: 10_858 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/exit_limit.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/exit_limit.yaml new file mode 100644 index 00000000000..3519fec8140 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/scale/exit_limit.yaml @@ -0,0 +1,34 @@ +description: Michigan ends Child Development and Care eligibility when ongoing monthly gross program-group income exceeds this amount by family size. + +metadata: + unit: currency-USD + period: month + label: Michigan CDC income exit limit + breakdown: + - range(1, 11) + breakdown_labels: + - Program group size + reference: + - title: RFT 270, CDC Income Eligibility Scale (effective September 21, 2025) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3 + +1: + 2025-09-21: 4_311 +2: + 2025-09-21: 5_638 +3: + 2025-09-21: 6_964 +4: + 2025-09-21: 8_291 +5: + 2025-09-21: 9_617 +6: + 2025-09-21: 10_944 +7: + 2025-09-21: 11_192 +8: + 2025-09-21: 11_441 +9: + 2025-09-21: 11_690 +10: + 2025-09-21: 11_938 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/rates/center.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/rates/center.yaml new file mode 100644 index 00000000000..ae1282b7cac --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/rates/center.yaml @@ -0,0 +1,49 @@ +description: Michigan reimburses child care centers at these hourly rates under the Child Development and Care program, by star quality level and child age group. +metadata: + period: year + unit: currency-USD + label: Michigan CDC child care center hourly rates + breakdown: + - mi_ccap_star_rating + - mi_ccap_age_group + reference: + - title: RFT 270, Department Hourly Rates (effective September 22, 2024) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=4 + +# The Base Rate (blank star) and 1 Star (Maintaining Health & Safety) rows +# carry identical rates, so STAR_1 holds both. +STAR_1: + INFANT_TODDLER: + 2024-09-22: 7.15 + PRESCHOOL: + 2024-09-22: 5.05 + SCHOOL_AGE: + 2024-09-22: 4.90 +STAR_2: + INFANT_TODDLER: + 2024-09-22: 7.55 + PRESCHOOL: + 2024-09-22: 5.45 + SCHOOL_AGE: + 2024-09-22: 5.35 +STAR_3: + INFANT_TODDLER: + 2024-09-22: 8.40 + PRESCHOOL: + 2024-09-22: 6.30 + SCHOOL_AGE: + 2024-09-22: 6.15 +STAR_4: + INFANT_TODDLER: + 2024-09-22: 8.75 + PRESCHOOL: + 2024-09-22: 6.75 + SCHOOL_AGE: + 2024-09-22: 6.55 +STAR_5: + INFANT_TODDLER: + 2024-09-22: 9.60 + PRESCHOOL: + 2024-09-22: 7.55 + SCHOOL_AGE: + 2024-09-22: 7.35 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/rates/exempt.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/rates/exempt.yaml new file mode 100644 index 00000000000..5a32239a589 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/rates/exempt.yaml @@ -0,0 +1,26 @@ +description: Michigan reimburses license-exempt related and unrelated providers at these hourly rates under the Child Development and Care program, by quality level and child age group. +metadata: + period: year + unit: currency-USD + label: Michigan CDC license-exempt hourly rates + breakdown: + - mi_ccap_exempt_level + - mi_ccap_age_group + reference: + - title: RFT 270, Department Hourly Rates (effective September 22, 2024) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=4 + +LEVEL_1: + INFANT_TODDLER: + 2024-09-22: 2.95 + PRESCHOOL: + 2024-09-22: 2.95 + SCHOOL_AGE: + 2024-09-22: 2.95 +LEVEL_2: + INFANT_TODDLER: + 2024-09-22: 4.95 + PRESCHOOL: + 2024-09-22: 4.40 + SCHOOL_AGE: + 2024-09-22: 4.40 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/rates/family_home.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/rates/family_home.yaml new file mode 100644 index 00000000000..6a950b67d09 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/rates/family_home.yaml @@ -0,0 +1,49 @@ +description: Michigan reimburses group and family child care homes at these hourly rates under the Child Development and Care program, by star quality level and child age group. +metadata: + period: year + unit: currency-USD + label: Michigan CDC group and family home hourly rates + breakdown: + - mi_ccap_star_rating + - mi_ccap_age_group + reference: + - title: RFT 270, Department Hourly Rates (effective September 22, 2024) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=4 + +# The Base Rate (blank star) and 1 Star (Maintaining Health & Safety) rows +# carry identical rates, so STAR_1 holds both. +STAR_1: + INFANT_TODDLER: + 2024-09-22: 5.70 + PRESCHOOL: + 2024-09-22: 4.90 + SCHOOL_AGE: + 2024-09-22: 4.80 +STAR_2: + INFANT_TODDLER: + 2024-09-22: 6.15 + PRESCHOOL: + 2024-09-22: 5.35 + SCHOOL_AGE: + 2024-09-22: 5.20 +STAR_3: + INFANT_TODDLER: + 2024-09-22: 7.00 + PRESCHOOL: + 2024-09-22: 6.15 + SCHOOL_AGE: + 2024-09-22: 6.00 +STAR_4: + INFANT_TODDLER: + 2024-09-22: 7.35 + PRESCHOOL: + 2024-09-22: 6.55 + SCHOOL_AGE: + 2024-09-22: 6.45 +STAR_5: + INFANT_TODDLER: + 2024-09-22: 8.20 + PRESCHOOL: + 2024-09-22: 7.35 + SCHOOL_AGE: + 2024-09-22: 7.25 diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/two_parent_uses_highest_hours.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/two_parent_uses_highest_hours.yaml new file mode 100644 index 00000000000..3e37337d4b1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/two_parent_uses_highest_hours.yaml @@ -0,0 +1,11 @@ +description: Michigan authorizes Child Development and Care hours on the parent with the highest need hours in a two-parent household when this is true. +values: + 2024-11-03: true + +metadata: + unit: bool + period: year + label: Michigan CDC two-parent uses highest need hours + reference: + - title: BEM 710, Need Calculation + href: https://mdhhs-pres-prod.michigan.gov/olmweb/EXF/BP/Public/BEM/710.pdf#page=2 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index ff5d4b85fe5..79ad2cc6c6c 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, IL, MA, MD, ME, NC, NH, NJ, PA, RI, SC, TX, VA, VT, WA, WV + coverage: AK, AR, AL, CA, CO, CT, DE, DC, IL, MA, MD, ME, MI, NC, NH, NJ, PA, RI, SC, TX, VA, VT, WA, WV state_implementations: - state: AK status: complete @@ -515,6 +515,12 @@ programs: name: Maine CCAP full_name: Maine Child Care Affordability Program variable: me_ccap + - state: MI + status: complete + name: Michigan CDC + full_name: Child Development and Care program + variable: mi_child_care_subsidies + parameter_prefix: gov.states.mi.mdhhs.ccap - state: NH status: complete name: New Hampshire CCAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.yaml new file mode 100644 index 00000000000..29203ab2b55 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.yaml @@ -0,0 +1,201 @@ +# REQ-006/008/009/010: Each parent/substitute-parent must have a valid need +# reason; in a two-parent household both must (BEM 703 p.4-14). Need reasons +# modeled: employment (hours worked), approved activity / high-school +# completion (full-time student), family preservation (protective services). +# Activity hours use weekly_hours_worked_before_lsr to avoid a labor-supply +# circular dependency. + +- name: Case 1, single working parent has a need reason. + period: 2026-01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 35 + person2: + age: 4 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_activity_eligible: true + +- name: Case 2, single non-working non-student parent has no need reason. + period: 2026-01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 0 + is_full_time_student: false + person2: + age: 4 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_activity_eligible: false + +- name: Case 3, full-time student parent has a need reason. + period: 2026-01 + input: + people: + person1: + age: 22 + weekly_hours_worked_before_lsr: 0 + is_full_time_student: true + person2: + age: 2 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_activity_eligible: true + +- name: Case 4, parent needing protective services has a need reason. + period: 2026-01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 0 + is_full_time_student: false + receives_or_needs_protective_services: true + person2: + age: 4 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_activity_eligible: true + +- name: Case 5, two-parent household with both parents working is eligible. + period: 2026-01 + input: + people: + person1: + age: 32 + weekly_hours_worked_before_lsr: 40 + person2: + age: 30 + weekly_hours_worked_before_lsr: 20 + person3: + age: 4 + tax_units: + tax_unit: + members: [person1, person2, person3] + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: MI + output: + mi_ccap_activity_eligible: true + +- name: Case 6, two-parent household with one parent lacking a need reason is ineligible. + period: 2026-01 + input: + people: + person1: + age: 32 + weekly_hours_worked_before_lsr: 40 + person2: + age: 30 + weekly_hours_worked_before_lsr: 0 + is_full_time_student: false + person3: + age: 4 + tax_units: + tax_unit: + members: [person1, person2, person3] + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: MI + output: + mi_ccap_activity_eligible: false + +# Edge cases. + +- name: Case 7, two-parent household with mixed need reasons is eligible. + period: 2026-01 + input: + people: + person1: + age: 32 + weekly_hours_worked_before_lsr: 40 # employed + person2: + age: 30 + weekly_hours_worked_before_lsr: 0 + is_full_time_student: true # approved activity / HS completion + person3: + age: 4 + tax_units: + tax_unit: + members: [person1, person2, person3] + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: MI + output: + # Each P/SP has a distinct valid need reason -> eligible + mi_ccap_activity_eligible: true + +- name: Case 8, parent working a single hour per week meets the need reason. + period: 2026-01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 1 # any positive hours counts as employed + person2: + age: 4 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # is_employed = hours_worked > 0, so 1 hour is enough + mi_ccap_activity_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible.yaml new file mode 100644 index 00000000000..e8247f2b228 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible.yaml @@ -0,0 +1,270 @@ +# REQ-005/006/017/018: Top-level Michigan CDC eligibility. Requires an +# eligible child, an income-waived OR income-eligible group, the asset test, +# and a valid need reason (BEM 703 p.14). + +- name: Case 1, working parent and young child with low income is eligible. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 30_000 # 2,500/mo, below size 2 entry 3,526 + weekly_hours_worked_before_lsr: 40 + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_eligible: true + +- name: Case 2, income above the entry limit makes an applicant ineligible. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 60_000 # 5,000/mo, above size 2 entry 3,526 + weekly_hours_worked_before_lsr: 40 + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_eligible: false + +- name: Case 3, high income but income-waived foster care family is eligible. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 90_000 # 7,500/mo, far above any income limit + weekly_hours_worked_before_lsr: 40 + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + is_in_foster_care: true + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_eligible: true + +- name: Case 4, no eligible child makes the family ineligible. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 30_000 + weekly_hours_worked_before_lsr: 40 + person2: + age: 14 # over 13, not disabled -> not an eligible child + is_tax_unit_dependent: true + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_eligible: false + +- name: Case 5, parent without a need reason makes the family ineligible. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 30_000 + weekly_hours_worked_before_lsr: 0 + is_full_time_student: false + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_eligible: false + +- name: Case 6, assets above the limit make the family ineligible. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 30_000 + weekly_hours_worked_before_lsr: 40 + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + spm_unit_assets: 1_500_000 # above the $1,000,000 CCDF asset limit + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_eligible: false + +- name: Case 7, non-Michigan family is not eligible. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 30_000 + weekly_hours_worked_before_lsr: 40 + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: OH + output: + mi_ccap_eligible: false + +# Edge cases. + +- name: Case 8, applicant at exactly the entry limit is eligible. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 42_312 # 3,526/mo, exactly the size-2 entry limit + weekly_hours_worked_before_lsr: 40 + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + # Income at exactly the entry limit passes the <= test -> eligible + mi_ccap_eligible: true + +- name: Case 9, income-waived family without an eligible child is ineligible. + period: 2026-01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 40 + person2: + age: 15 # over 13 and not disabled -> not an eligible child + is_tax_unit_dependent: true + immigration_status: CITIZEN + is_in_foster_care: true + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # Income-waived alone is not enough: an eligible child is still required + mi_ccap_eligible: false + +- name: Case 10, income-waived family still needs a valid activity reason. + period: 2026-01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 0 + is_full_time_student: false + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + is_in_foster_care: true + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # Foster care waives the income test but the parent still needs a need + # reason; with none, the family is ineligible + mi_ccap_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.yaml new file mode 100644 index 00000000000..dcbfadd8f59 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.yaml @@ -0,0 +1,195 @@ +# REQ-001/002/005: Eligible child age test. +# Under 13 -> eligible; 13 to under 18 -> eligible only if requires constant +# care (is_disabled proxy); must be a tax-unit dependent and immigration +# eligible (BEM 703 p.1-2, p.14). + +- name: Case 1, young child under 13 is an eligible child. + period: 2026-01 + input: + people: + person1: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_eligible_child: true + +- name: Case 2, child at exactly 13 is not eligible without constant care. + period: 2026-01 + input: + people: + person1: + age: 13 + is_disabled: false + is_tax_unit_dependent: true + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_eligible_child: false + +- name: Case 3, disabled child age 13 to under 18 is an eligible child. + period: 2026-01 + input: + people: + person1: + age: 15 + is_disabled: true + is_tax_unit_dependent: true + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_eligible_child: true + +- name: Case 4, child at exactly 18 is not eligible even with constant care. + period: 2026-01 + input: + people: + person1: + age: 18 + is_disabled: true + is_tax_unit_dependent: true + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_eligible_child: false + +- name: Case 5, non-disabled child age 16 is not eligible. + period: 2026-01 + input: + people: + person1: + age: 16 + is_disabled: false + is_tax_unit_dependent: true + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_eligible_child: false + +- name: Case 6, young child without acceptable immigration status is not eligible. + period: 2026-01 + input: + people: + person1: + age: 4 + is_tax_unit_dependent: true + immigration_status: UNDOCUMENTED + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_eligible_child: false + +- name: Case 7, adult is not an eligible child. + period: 2026-01 + input: + people: + person1: + age: 35 + is_tax_unit_dependent: false + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_eligible_child: false + +# Edge cases. + +- name: Case 8, child just under the 13 age limit is an eligible child. + period: 2026-01 + input: + people: + person1: + age: 12 + is_disabled: false + is_tax_unit_dependent: true + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + # 12 < 13 child age limit -> eligible + mi_ccap_eligible_child: true + +- name: Case 9, disabled child just under the 18 limit is an eligible child. + period: 2026-01 + input: + people: + person1: + age: 17 + is_disabled: true + is_tax_unit_dependent: true + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + # 17 < 18 constant-care age limit -> eligible + mi_ccap_eligible_child: true + +- name: Case 10, newborn is an eligible child. + period: 2026-01 + input: + people: + person1: + age: 0 + is_tax_unit_dependent: true + immigration_status: CITIZEN + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + # Birth (age 0) is below the 13 age limit -> eligible + mi_ccap_eligible_child: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.yaml new file mode 100644 index 00000000000..ec1ca1126bc --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.yaml @@ -0,0 +1,205 @@ +# REQ-017/028: Income-eligible group. Applicants (not enrolled) must have +# monthly gross program-group income at or below the ENTRY limit (RFT 270 +# column 2); ongoing recipients (enrolled) at or below the EXIT limit +# (column 7). Thresholds by program-group size, eff 2025-09-21 (RFT 270 p.3). +# Family of 2: entry 3,526/mo, exit 5,638/mo. + +- name: Case 1, applicant income just below entry limit is income eligible. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 42_000 # 3,500/mo, below 3,526 entry + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_income_eligible: true + +- name: Case 2, applicant income at exactly the entry limit is income eligible. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 42_312 # 3,526/mo, exactly the entry limit + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_income_eligible: true + +- name: Case 3, applicant income just above entry limit is not income eligible. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 43_200 # 3,600/mo, above 3,526 entry + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_income_eligible: false + +- name: Case 4, enrolled recipient between entry and exit limits stays income eligible. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 60_000 # 5,000/mo, above entry 3,526 but below exit 5,638 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: true + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_income_eligible: true + +- name: Case 5, enrolled recipient at exactly the exit limit stays income eligible. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 67_656 # 5,638/mo, exactly the exit limit + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: true + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_income_eligible: true + +- name: Case 6, enrolled recipient just above the exit limit loses income eligibility. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 68_400 # 5,700/mo, above 5,638 exit + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: true + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_income_eligible: false + +- name: Case 7, enrolled recipient above entry but applicant would be ineligible at same income. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 60_000 # 5,000/mo, above entry 3,526 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false # applicant gate uses entry limit + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_income_eligible: false + +# Edge cases: zero income and the size-1 entry boundary. + +- name: Case 8, applicant with zero income is income eligible. + period: 2026-01 + input: + people: + person1: + age: 30 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + # Zero income is far below the entry limit -> eligible + mi_ccap_income_eligible: true + +- name: Case 9, single-person applicant just above the size-1 entry limit is ineligible. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 31_308 # 2,609/mo, above the 2,608 size-1 entry limit + spm_units: + spm_unit: + members: [person1] + mi_ccap_enrolled: false + households: + household: + members: [person1] + state_code: MI + output: + # Size 1 entry limit 2,608/mo; 2,609 > 2,608 -> not eligible + mi_ccap_income_eligible: false + +- name: Case 10, single-person applicant at exactly the size-1 entry limit is eligible. + period: 2026-01 + input: + people: + person1: + age: 30 + employment_income: 31_296 # 2,608/mo, exactly the size-1 entry limit + spm_units: + spm_unit: + members: [person1] + mi_ccap_enrolled: false + households: + household: + members: [person1] + state_code: MI + output: + # Income at exactly the entry limit passes the <= test -> eligible + mi_ccap_income_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.yaml new file mode 100644 index 00000000000..9bd91ca5ee5 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.yaml @@ -0,0 +1,155 @@ +# REQ-012/013/014/015: Income-waived eligibility groups. Children's Protective +# Services, Foster Care, FIP/SSI-related, and Homeless qualify without an +# income test (BEM 703 p.14-18). The migrant farmworker group is not modeled. + +- name: Case 1, open protective services case is income-waived. + period: 2026-01 + input: + people: + person1: + age: 30 + person2: + age: 4 + receives_or_needs_protective_services: true + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_income_waived: true + +- name: Case 2, foster care child is income-waived. + period: 2026-01 + input: + people: + person1: + age: 30 + person2: + age: 4 + is_in_foster_care: true + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_income_waived: true + +- name: Case 3, FIP (TANF) enrollment is income-waived. + period: 2026-01 + input: + people: + person1: + age: 30 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + is_tanf_enrolled: true + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_income_waived: true + +- name: Case 4, SSI receipt is income-waived. + period: 2026-01 + input: + people: + person1: + age: 30 + ssi: 900 # monthly SSI + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_income_waived: true + +- name: Case 5, homeless family is income-waived. + period: 2026-01 + input: + people: + person1: + age: 30 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + is_homeless: true + output: + mi_ccap_income_waived: true + +- name: Case 6, family in none of the waived groups is not income-waived. + period: 2026-01 + input: + people: + person1: + age: 30 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_income_waived: false + +# Edge cases. + +- name: Case 7, parent needing protective services waives the income test. + period: 2026-01 + input: + people: + person1: + age: 30 + receives_or_needs_protective_services: true # flag on the parent, not the child + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # The protective-services check sums across the whole unit -> waived + mi_ccap_income_waived: true + +- name: Case 8, single adult with none of the waived conditions is not income-waived. + period: 2026-01 + input: + people: + person1: + age: 30 + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + # Degenerate single-adult unit with no waived pathway -> false + mi_ccap_income_waived: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/integration.yaml new file mode 100644 index 00000000000..1bb5615757b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/integration.yaml @@ -0,0 +1,468 @@ +# Michigan CDC (CCAP) integration tests. +# +# Subsidy model (BEM 706): per child per two-week pay period, +# reimbursement = hourly_rate x min(block_hours, authorized_hours), +# capped at the child's monthly charge, less the flat family contribution, +# floored at zero, then converted to monthly. There are 26 two-week pay +# periods per year, so the monthly conversion factor is 52/2/12 = 2.16667. +# Billed hours are not tracked separately from authorized hours, so paid +# hours equal the authorized tier. +# +# Rates (RFT 270 Table 2, eff 2024-09-22): +# Center STAR_1: Inf/Tod 7.15, Preschool 5.05, School 4.90 +# Center STAR_5: Inf/Tod 9.60 +# Family home STAR_3: School 6.00 +# License-exempt Level 1: 2.95 all ages +# Income scale (RFT 270 Table 1, eff 2025-09-21): +# Size 2 entry (col 2) 3,526; band floors 0/1,763/3,526/... +# Size 4 entry 5,358; band floors 0/2,679/5,358/... +# FC per child by band: 0/15/30/45/60/69/78; family limit 0/45/83/121/... + +- name: Case 1, single working parent, infant at a 1 Star center. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 42_000 # 3,500/mo + weekly_hours_worked_before_lsr: 40 # authorized 80 + person2: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_1 + pre_subsidy_childcare_expenses: 30_000 # 2,500/mo, above the subsidy so the cap does not bind + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + # Child age 1 < 13, citizen, dependent -> eligible child + mi_ccap_eligible_child: [false, true] + # 3,500 <= 3,526 entry limit -> income eligible + mi_ccap_income_eligible: true + mi_ccap_eligible: true + # 40 hrs/week x 2 = 80 biweekly need -> tier 80 + mi_ccap_authorized_hours: 80 + # Center STAR_1 infant/toddler hourly rate + mi_ccap_hourly_rate: [0, 7.15] + # 7.15 x 80 = 572.00 per pay period + mi_ccap_block_payment: [0, 572.00] + # 3,500/mo -> size 2 band 1, per-child FC 15, 1 paying child, cap 45 -> 15/pp + mi_ccap_family_contribution: 15 + # monthly payment = 572.00 x 2.16667 = 1,239.33 + # monthly FC = 15 x 2.16667 = 32.50 + # benefit = 1,239.33 - 32.50 = 1,206.83 + mi_ccap: 1_206.83 + +- name: Case 2, two working parents, two children at a 1 Star center. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 34 + employment_income: 60_000 # 5,000/mo + weekly_hours_worked_before_lsr: 40 # authorized 80 + person2: + age: 32 + weekly_hours_worked_before_lsr: 20 + person3: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_1 + pre_subsidy_childcare_expenses: 30_000 + person4: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_1 + pre_subsidy_childcare_expenses: 30_000 + tax_units: + tax_unit: + members: [person1, person2, person3, person4] + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2, person3, person4] + state_code: MI + output: + mi_ccap_eligible_child: [false, false, true, true] + # Size 4, 5,000 <= 5,358 entry -> income eligible + mi_ccap_income_eligible: true + mi_ccap_eligible: true + # Both parents work; highest need = 80 biweekly -> tier 80 + mi_ccap_authorized_hours: 80 + # person3 infant 7.15, person4 preschool 5.05 + mi_ccap_block_payment: [0, 0, 572.00, 404.00] + # 5,000/mo -> size 4 band 1, per-child FC 15, 2 paying children = 30, cap 45 -> 30/pp + mi_ccap_family_contribution: 30 + # monthly: person3 572.00 x 2.16667 = 1,239.33; person4 404.00 x 2.16667 = 875.33 + # total = 2,114.67; monthly FC = 30 x 2.16667 = 65.00 + # benefit = 2,114.67 - 65.00 = 2,049.67 + mi_ccap: 2_049.67 + +- name: Case 3, family contribution waived at a 3 Star provider. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 36_000 # 3,000/mo + weekly_hours_worked_before_lsr: 30 # authorized 60 + person2: + age: 8 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: FAMILY_HOME + mi_ccap_star_rating: STAR_3 + pre_subsidy_childcare_expenses: 15_000 # 1,250/mo, cap does not bind + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_eligible: true + # 30 hrs/week x 2 = 60 biweekly -> tier 60 + mi_ccap_authorized_hours: 60 + # Family home STAR_3 school age rate 6.00 + mi_ccap_block_payment: [0, 360.00] + # 3 Star provider waives the contribution for the child + mi_ccap_family_contribution: 0 + # monthly = 360.00 x 2.16667 = 780.00; FC 0 -> benefit 780.00 + mi_ccap: 780.00 + +- name: Case 4, license-exempt provider, family contribution applies. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 42_000 # 3,500/mo + weekly_hours_worked_before_lsr: 20 # authorized 40 + person2: + age: 8 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: LICENSE_EXEMPT + mi_ccap_exempt_level: LEVEL_1 + pre_subsidy_childcare_expenses: 10_000 # 833/mo, cap does not bind + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_eligible: true + # 20 hrs/week x 2 = 40 biweekly -> tier 40 + mi_ccap_authorized_hours: 40 + # License-exempt Level 1 rate 2.95, paid hourly x authorized 40 = 118.00/pp + mi_ccap_block_payment: [0, 118.00] + # 3,500/mo -> size 2 band 1, per-child FC 15; license-exempt never star-waived + mi_ccap_family_contribution: 15 + # monthly payment = 118.00 x 2.16667 = 255.67; monthly FC = 32.50 + # benefit = 255.67 - 32.50 = 223.17 + mi_ccap: 223.17 + +- name: Case 5, subsidy capped at the child's actual child care charge. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 18_000 # 1,500/mo -> size 2 band 0, no FC + weekly_hours_worked_before_lsr: 45 # authorized 90 + person2: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_5 + pre_subsidy_childcare_expenses: 12_000 # 1,000/mo, below the subsidy so the cap binds + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_eligible: true + mi_ccap_authorized_hours: 90 + # Center STAR_5 infant 9.60 x 90 = 864.00/pp -> monthly 864 x 2.16667 = 1,872.00 + mi_ccap_block_payment: [0, 864.00] + # Band 0 -> family contribution 0 + mi_ccap_family_contribution: 0 + # Uncapped monthly 1,872.00 exceeds the 1,000/mo charge -> capped at 1,000.00 + mi_ccap: 1_000.00 + +- name: Case 6, foster care family is income-waived with no family contribution. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 90_000 # 7,500/mo, far above any income limit + weekly_hours_worked_before_lsr: 40 # authorized 80 + person2: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + is_in_foster_care: true + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_1 + pre_subsidy_childcare_expenses: 30_000 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # Income-waived (foster care) -> bypasses the income test + mi_ccap_income_waived: true + mi_ccap_eligible: true + mi_ccap_authorized_hours: 80 + mi_ccap_block_payment: [0, 572.00] + # Income-waived group -> family contribution 0 + mi_ccap_family_contribution: 0 + # monthly = 572.00 x 2.16667 = 1,239.33; FC 0 -> benefit 1,239.33 + mi_ccap: 1_239.33 + +- name: Case 7, applicant income above the entry limit is ineligible. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 68_400 # 5,700/mo, above size 2 entry 3,526 + weekly_hours_worked_before_lsr: 40 + person2: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_1 + pre_subsidy_childcare_expenses: 30_000 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_income_eligible: false + mi_ccap_income_waived: false + mi_ccap_eligible: false + mi_ccap: 0 + +- name: Case 8, annual subsidy flows to the federal child care subsidies total. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 42_000 # 3,500/mo + weekly_hours_worked_before_lsr: 40 + person2: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_1 + pre_subsidy_childcare_expenses: 30_000 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + # Same monthly benefit as Case 1 (1,206.83) summed over 12 months + # = 14,482.00 annually. + mi_child_care_subsidies: 14_482.00 + child_care_subsidies: 14_482.00 + +# Edge cases. + +- name: Case 9, self-employment loss does not inflate the subsidy. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + self_employment_income: -60_000 # -5,000/mo loss + weekly_hours_worked_before_lsr: 40 # authorized 80 + person2: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_1 + pre_subsidy_childcare_expenses: 30_000 # 2,500/mo, cap does not bind + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + # Negative countable income is below the entry limit -> income eligible + mi_ccap_income_eligible: true + mi_ccap_eligible: true + mi_ccap_authorized_hours: 80 + mi_ccap_block_payment: [0, 572.00] + # Negative income maps to the lowest band -> family contribution 0, + # not a negative amount that would inflate the benefit + mi_ccap_family_contribution: 0 + # benefit = 572.00 x 2.16667 = 1,239.33 (block payment only, FC 0) + mi_ccap: 1_239.33 + +- name: Case 10, subsidy capped tightly at a low actual child care charge. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 18_000 # 1,500/mo -> size 2 band 0, no FC + weekly_hours_worked_before_lsr: 40 # authorized 80 + person2: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_1 + pre_subsidy_childcare_expenses: 3_000 # 250/mo, well below the block payment + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_eligible: true + mi_ccap_authorized_hours: 80 + # Uncapped block payment 7.15 x 80 = 572.00/pp -> monthly 1,239.33 + mi_ccap_block_payment: [0, 572.00] + mi_ccap_family_contribution: 0 + # Monthly charge is only 250.00, below the uncapped subsidy -> capped at 250.00 + mi_ccap: 250.00 + +- name: Case 11, two parents at a 5 Star center, family contribution at the cap. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 34 + employment_income: 60_000 # 5,000/mo + weekly_hours_worked_before_lsr: 45 # authorized 90 + person2: + age: 32 + weekly_hours_worked_before_lsr: 30 # 60 biweekly + person3: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_2 + pre_subsidy_childcare_expenses: 30_000 + person4: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_2 + pre_subsidy_childcare_expenses: 30_000 + tax_units: + tax_unit: + members: [person1, person2, person3, person4] + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + mi_ccap_enrolled: false + households: + household: + members: [person1, person2, person3, person4] + state_code: MI + output: + mi_ccap_eligible_child: [false, false, true, true] + # Size 4, 5,000 <= 5,358 entry -> income eligible + mi_ccap_income_eligible: true + mi_ccap_eligible: true + # Highest need = person1's 90 biweekly -> tier 90 + mi_ccap_authorized_hours: 90 + # 2 Star center: infant 7.55, preschool 5.45; x 90 authorized hours + # person3: 7.55 x 90 = 679.50; person4: 5.45 x 90 = 490.50 + mi_ccap_block_payment: [0, 0, 679.50, 490.50] + # 5,000/mo -> size 4 band 1; STAR_2 is below the 3-star waiver, so both pay. + # per-child FC 15 x 2 = 30, family cap 45 -> 30/pp + mi_ccap_family_contribution: 30 + # monthly: person3 679.50 x 2.16667 = 1,472.25; person4 490.50 x 2.16667 = 1,062.75 + # total = 2,535.00; monthly FC = 30 x 2.16667 = 65.00 + # benefit = 2,535.00 - 65.00 = 2,470.00 + mi_ccap: 2_470.00 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_age_group.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_age_group.yaml new file mode 100644 index 00000000000..5b3bb940986 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_age_group.yaml @@ -0,0 +1,125 @@ +# REQ-025: Age groups for the RFT 270 rate table. +# Infant/Toddler (birth up to age 2.5), Preschool (2.5 up to 5), +# School Age (5 and over). Boundary is 2.5 years (RFT 270 p.4). + +- name: Case 1, infant under 2.5 is Infant/Toddler. + period: 2026-01 + input: + people: + person1: + age: 1 + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_age_group: INFANT_TODDLER + +- name: Case 2, child just below the 2.5 boundary is Infant/Toddler. + period: 2026-01 + input: + people: + person1: + age: 2 + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_age_group: INFANT_TODDLER + +- name: Case 3, child at exactly 2.5 is Preschool. + period: 2026-01 + input: + people: + person1: + age: 2.5 + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_age_group: PRESCHOOL + +- name: Case 4, child between 2.5 and 5 is Preschool. + period: 2026-01 + input: + people: + person1: + age: 4 + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_age_group: PRESCHOOL + +- name: Case 5, child at exactly 5 is School Age. + period: 2026-01 + input: + people: + person1: + age: 5 + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_age_group: SCHOOL_AGE + +- name: Case 6, older child is School Age. + period: 2026-01 + input: + people: + person1: + age: 10 + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_age_group: SCHOOL_AGE + +# Edge cases. + +- name: Case 7, child just below 2.5 is Infant/Toddler. + period: 2026-01 + input: + people: + person1: + age: 2.4 + households: + household: + members: [person1] + state_code: MI + output: + # 2.4 < 2.5 boundary -> still Infant/Toddler + mi_ccap_age_group: INFANT_TODDLER + +- name: Case 8, child just below 5 is Preschool. + period: 2026-01 + input: + people: + person1: + age: 4.9 + households: + household: + members: [person1] + state_code: MI + output: + # 4.9 < 5 boundary -> still Preschool + mi_ccap_age_group: PRESCHOOL + +- name: Case 9, oldest eligible child age 12 is School Age. + period: 2026-01 + input: + people: + person1: + age: 12 + households: + household: + members: [person1] + state_code: MI + output: + # The rate table has no group above School Age, so a 12-year-old maps there + mi_ccap_age_group: SCHOOL_AGE diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.yaml new file mode 100644 index 00000000000..a1eaa5506a5 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.yaml @@ -0,0 +1,259 @@ +# REQ-019/020/021: Authorized hours per child per two-week pay period. +# Biweekly need hours = caretaker weekly activity hours x 2 weeks (base model; +# meal/study/travel add-ons not tracked). Mapped to a tier 20/40/60/80/90; +# max 90. Two-parent household authorizes on the parent with the HIGHEST need +# hours (eff 2024-11-03). Uses weekly_hours_worked_before_lsr (default 40) to +# avoid the labor-supply circular dependency (BEM 710 p.1-2; BEM 706 p.2). + +- name: Case 1, parent working 40 hours per week reaches the 80-hour tier. + period: 2026-01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 40 # 40 x 2 = 80 biweekly need + person2: + age: 4 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_authorized_hours: 80 + +- name: Case 2, parent working 10 hours per week reaches the 20-hour tier. + period: 2026-01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 10 # 10 x 2 = 20 biweekly need + person2: + age: 4 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_authorized_hours: 20 + +- name: Case 3, parent working 30 hours per week reaches the 60-hour tier. + period: 2026-01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 30 # 30 x 2 = 60 biweekly need + person2: + age: 4 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_authorized_hours: 60 + +- name: Case 4, parent working 45 hours per week is capped at the 90-hour tier. + period: 2026-01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 45 # 45 x 2 = 90 biweekly need + person2: + age: 4 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_authorized_hours: 90 + +- name: Case 5, parent working 60 hours per week stays at the 90-hour maximum. + period: 2026-01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 60 # 60 x 2 = 120 biweekly need, capped at 90 + person2: + age: 4 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_authorized_hours: 90 + +- name: Case 6, two parents authorize on the parent with the highest hours. + period: 2026-01 + input: + people: + person1: + age: 32 + weekly_hours_worked_before_lsr: 40 # 80 biweekly + person2: + age: 30 + weekly_hours_worked_before_lsr: 20 # 40 biweekly + person3: + age: 4 + tax_units: + tax_unit: + members: [person1, person2, person3] + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: MI + output: + # Highest need = person1's 80 biweekly -> tier 80 + mi_ccap_authorized_hours: 80 + +- name: Case 7, parent with no activity hours gives zero authorized hours. + period: 2026-01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 0 + person2: + age: 4 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_authorized_hours: 0 + +# Edge cases: tier boundaries. Biweekly need = weekly hours x 2; the tiers are +# (0, 20] -> 20, (20, 40] -> 40, (40, 60] -> 60, (60, 80] -> 80, (80+) -> 90. + +- name: Case 8, any positive activity lands in the lowest 20-hour tier. + period: 2026-01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 1 # 2 biweekly, just above 0 -> tier 20 + person2: + age: 4 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_authorized_hours: 20 + +- name: Case 9, biweekly need just above 20 moves up to the 40-hour tier. + period: 2026-01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 11 # 22 biweekly, above 20 -> tier 40 + person2: + age: 4 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_authorized_hours: 40 + +- name: Case 10, biweekly need just above 60 moves up to the 80-hour tier. + period: 2026-01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 31 # 62 biweekly, above 60 -> tier 80 + person2: + age: 4 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_authorized_hours: 80 + +- name: Case 11, two-parent household authorizes on highest hours even when one parent is idle. + period: 2026-01 + input: + people: + person1: + age: 32 + weekly_hours_worked_before_lsr: 40 # 80 biweekly + person2: + age: 30 + weekly_hours_worked_before_lsr: 0 # idle parent does not lower authorization + person3: + age: 4 + tax_units: + tax_unit: + members: [person1, person2, person3] + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: MI + output: + # Highest need = person1's 80 biweekly -> tier 80 (eff 2024-11-03 rule) + mi_ccap_authorized_hours: 80 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.yaml new file mode 100644 index 00000000000..4a4afccb618 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.yaml @@ -0,0 +1,236 @@ +# REQ-022/023: Reimbursement per child per two-week pay period. +# Licensed centers and group/family homes use the block schedule (billed +# 1-30 hours -> 30, 31-60 -> 60, 61+ -> 90), capped at the child's authorized +# hours. License-exempt providers are paid hourly, also capped at authorized +# hours. Because billed hours are not tracked separately from authorized +# hours, paid hours equal the authorized tier for every provider type, so +# the payment is hourly_rate x authorized_hours per pay period. +# Authorized hours come from the parent's activity: 40 hrs/week -> 80 biweekly. + +- name: Case 1, center 1 Star infant, parent 40 hrs/week. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 40 # authorized 80 + person2: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # Child: 7.15 hourly x 80 authorized = 572.00 per pay period + mi_ccap_block_payment: [0, 572.00] + +- name: Case 2, group and family home 1 Star infant, parent 30 hrs/week. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 30 # authorized 60 + person2: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: FAMILY_HOME + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # Child: 5.70 hourly x 60 authorized = 342.00 per pay period + mi_ccap_block_payment: [0, 342.00] + +- name: Case 3, license-exempt Level 1 school age, parent 20 hrs/week. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 20 # authorized 40 + person2: + age: 8 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: LICENSE_EXEMPT + mi_ccap_exempt_level: LEVEL_1 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # Child: 2.95 hourly x 40 authorized = 118.00 per pay period + mi_ccap_block_payment: [0, 118.00] + +- name: Case 4, center 5 Star infant, parent 45 hrs/week reaches the 90-hour max. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 45 # authorized 90 (max) + person2: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_5 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # Child: 9.60 hourly x 90 authorized = 864.00 per pay period + mi_ccap_block_payment: [0, 864.00] + +- name: Case 5, parent with no activity gives zero authorized hours and zero payment. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 0 # authorized 0 + person2: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # Authorized 0 -> 7.15 x 0 = 0 + mi_ccap_block_payment: [0, 0] + +# Edge cases. + +- name: Case 6, family home at the 60-hour block tier boundary, parent 30 hrs/week. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 30 # authorized 60 + person2: + age: 8 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: FAMILY_HOME + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # Authorized 60 -> block tier 60 (min(60, 60)); family home STAR_1 school age + # 4.80 x 60 = 288.00 per pay period + mi_ccap_block_payment: [0, 288.00] + +- name: Case 7, license-exempt Level 2 at the 90-hour maximum, parent 45 hrs/week. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 45 # authorized 90 (max) + person2: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: LICENSE_EXEMPT + mi_ccap_exempt_level: LEVEL_2 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # License-exempt is paid hourly with no block: 4.95 x 90 authorized = 445.50 + mi_ccap_block_payment: [0, 445.50] + +- name: Case 8, center at the 20-hour tier, block schedule capped at authorized hours. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + weekly_hours_worked_before_lsr: 10 # authorized 20 + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # Block schedule for 20 authorized hours pays 30, but is capped at the + # authorized 20: min(30, 20) = 20; center STAR_1 preschool 5.05 x 20 = 101.00 + mi_ccap_block_payment: [0, 101.00] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.yaml new file mode 100644 index 00000000000..34252c87b83 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.yaml @@ -0,0 +1,320 @@ +# REQ-029/031/032: Family contribution per two-week pay period. +# Flat dollar amount per child by income band (RFT 270 p.3), summed across +# paying children and capped at a per-family limit. Waived per child at a +# 3 Star (or higher) provider, and waived entirely for income-waived groups +# (BEM 706 p.2-3). Income is monthly program-group gross income. +# +# Size 2 band floors: 0 / 1,763 / 3,526 / 3,948 / 4,371 / 4,793 / 5,216. +# Size 3 band floors: 0 / 2,221 / 4,442 / 4,946 / 5,451 / 5,955 / 6,460. +# Size 5 band floors: 0 / 3,138 / 6,276 / 6,944 / 7,612 / 8,281 / 8,949. +# Per-child FC by band: 0 / 15 / 30 / 45 / 60 / 69 / 78. +# Family limit by band: 0 / 45 / 83 / 121 / 143 / 164 / 186. + +- name: Case 1, size 2 band 1 one child at a low-star provider pays 15. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 42_000 # 3,500/mo -> size 2 band 1 + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # Band 1 per-child FC 15, 1 paying child, cap 45 -> 15 + mi_ccap_family_contribution: 15 + +- name: Case 2, size 3 band 1 two children pay 30 below the cap. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 36_000 # 3,000/mo -> size 3 band 1 + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_1 + person3: + age: 6 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1, person2, person3] + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: MI + output: + # Band 1 per-child FC 15, 2 paying children = 30, cap 45 -> 30 + mi_ccap_family_contribution: 30 + +- name: Case 3, size 5 band 1 four children hit the family cap of 45. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 36 + employment_income: 60_000 # 5,000/mo -> size 5 band 1 + person2: + age: 9 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_1 + person3: + age: 7 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_1 + person4: + age: 5 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_1 + person5: + age: 3 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1, person2, person3, person4, person5] + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5] + households: + household: + members: [person1, person2, person3, person4, person5] + state_code: MI + output: + # Band 1 per-child FC 15, 4 paying children = 60, capped at family limit 45 + mi_ccap_family_contribution: 45 + +- name: Case 4, family contribution waived per child at a 3 Star provider. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 42_000 # 3,500/mo -> size 2 band 1 + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_3 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # 3 Star provider waives the contribution for the child -> 0 + mi_ccap_family_contribution: 0 + +- name: Case 5, family contribution waived entirely for an income-waived group. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 42_000 # would be band 1, but the group is income-waived + is_in_foster_care: true + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + is_in_foster_care: true + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # Foster care income-waived group -> family contribution 0 + mi_ccap_family_contribution: 0 + +- name: Case 6, lowest income band charges no family contribution. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 12_000 # 1,000/mo -> size 2 band 0 + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # Band 0 per-child FC 0 -> 0 + mi_ccap_family_contribution: 0 + +# Edge cases: band boundaries, the top band, and the family cap at a high band. + +- name: Case 7, size 2 income at exactly the band 0/1 floor stays in band 0. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 21_156 # 1,763/mo, exactly the size-2 band 1 floor + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # 1,763 is below the 1,763.0001 threshold -> band 0 -> FC 0 + mi_ccap_family_contribution: 0 + +- name: Case 8, size 2 income just above the band 0/1 floor moves to band 1. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 21_168 # 1,764/mo, just above the size-2 band 1 floor + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # 1,764 > 1,763.0001 -> band 1 -> per-child FC 15, 1 child, cap 45 -> 15 + mi_ccap_family_contribution: 15 + +- name: Case 9, size 2 income in the top band pays the band 6 per-child amount. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 30 + employment_income: 63_600 # 5,300/mo -> size 2 band 6 (floor 5,216) + person2: + age: 4 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + # Band 6 per-child FC 78, 1 paying child, family cap 186 -> 78 + mi_ccap_family_contribution: 78 + +- name: Case 10, size 5 four children at band 2 hit the band 2 family cap of 83. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 36 + employment_income: 75_600 # 6,300/mo -> size 5 band 2 (floor 6,276) + person2: + age: 9 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_1 + person3: + age: 7 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_1 + person4: + age: 5 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_1 + person5: + age: 3 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1, person2, person3, person4, person5] + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5] + households: + household: + members: [person1, person2, person3, person4, person5] + state_code: MI + output: + # Band 2 per-child FC 30, 4 paying children = 120, capped at family limit 83 + mi_ccap_family_contribution: 83 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_hourly_rate.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_hourly_rate.yaml new file mode 100644 index 00000000000..d92e63b0d19 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_hourly_rate.yaml @@ -0,0 +1,281 @@ +# REQ-024: Department hourly rate looked up by provider type, star/quality +# level, and age group (RFT 270 Table 2, eff 2024-09-22). Tested across all +# three provider types and a spread of star/level and age combinations. +# Rate variable is defined_for the eligible child, so each test child is a +# young citizen dependent. + +- name: Case 1, center, 1 Star, infant/toddler. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_hourly_rate: 7.15 + +- name: Case 2, center, 1 Star, preschool. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 3 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_hourly_rate: 5.05 + +- name: Case 3, center, 3 Star, school age. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 7 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_3 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_hourly_rate: 6.15 + +- name: Case 4, center, 5 Star, infant/toddler. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_5 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_hourly_rate: 9.60 + +- name: Case 5, group and family home, 1 Star, infant/toddler. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: FAMILY_HOME + mi_ccap_star_rating: STAR_1 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_hourly_rate: 5.70 + +- name: Case 6, group and family home, 2 Star, preschool. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 3 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: FAMILY_HOME + mi_ccap_star_rating: STAR_2 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_hourly_rate: 5.35 + +- name: Case 7, license-exempt, Level 1, infant/toddler. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: LICENSE_EXEMPT + mi_ccap_exempt_level: LEVEL_1 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_hourly_rate: 2.95 + +- name: Case 8, license-exempt, Level 2, preschool. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 3 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: LICENSE_EXEMPT + mi_ccap_exempt_level: LEVEL_2 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_hourly_rate: 4.40 + +# Edge cases: cover the remaining star levels and age combinations. + +- name: Case 9, center, 2 Star, school age. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 7 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_2 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_hourly_rate: 5.35 + +- name: Case 10, center, 4 Star, preschool. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 3 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: CENTER + mi_ccap_star_rating: STAR_4 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_hourly_rate: 6.75 + +- name: Case 11, group and family home, 4 Star, infant/toddler. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 1 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: FAMILY_HOME + mi_ccap_star_rating: STAR_4 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_hourly_rate: 7.35 + +- name: Case 12, group and family home, 5 Star, school age. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 8 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: FAMILY_HOME + mi_ccap_star_rating: STAR_5 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + mi_ccap_hourly_rate: 7.25 + +- name: Case 13, license-exempt, Level 1, school age. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 8 + is_tax_unit_dependent: true + immigration_status: CITIZEN + mi_ccap_provider_type: LICENSE_EXEMPT + mi_ccap_exempt_level: LEVEL_1 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + # License-exempt Level 1 is a flat 2.95 across all ages + mi_ccap_hourly_rate: 2.95 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_program_group_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_program_group_size.yaml new file mode 100644 index 00000000000..f046f555ec2 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_program_group_size.yaml @@ -0,0 +1,189 @@ +# REQ-026: Family size for the RFT 270 scale = CDC program group (BEM 205): +# parent(s)/stepparent + children needing care + their unmarried under-18 +# siblings. Modeled approximately as the SPM unit, capped at 10 +# (max_program_group_size). A single eligible parent applying for one child +# gives family size 2. + +- name: Case 1, single parent and one child gives program group size 2. + period: 2026-01 + input: + people: + person1: + age: 30 + person2: + age: 4 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MI + output: + mi_ccap_program_group_size: 2 + +- name: Case 2, two parents and two children gives program group size 4. + period: 2026-01 + input: + people: + person1: + age: 32 + person2: + age: 30 + person3: + age: 6 + person4: + age: 3 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + households: + household: + members: [person1, person2, person3, person4] + state_code: MI + output: + mi_ccap_program_group_size: 4 + +- name: Case 3, program group size is capped at 10. + period: 2026-01 + input: + people: + person1: + age: 40 + person2: + age: 38 + person3: + age: 16 + person4: + age: 15 + person5: + age: 14 + person6: + age: 12 + person7: + age: 10 + person8: + age: 8 + person9: + age: 6 + person10: + age: 4 + person11: + age: 2 + person12: + age: 1 + spm_units: + spm_unit: + members: + [ + person1, + person2, + person3, + person4, + person5, + person6, + person7, + person8, + person9, + person10, + person11, + person12, + ] + households: + household: + members: + [ + person1, + person2, + person3, + person4, + person5, + person6, + person7, + person8, + person9, + person10, + person11, + person12, + ] + state_code: MI + output: + mi_ccap_program_group_size: 10 + +# Edge cases: minimum and exactly-at-cap group sizes. + +- name: Case 4, single-person unit gives the minimum program group size of 1. + period: 2026-01 + input: + people: + person1: + age: 30 + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MI + output: + # Degenerate single-person unit -> size 1 (no child needed for this helper) + mi_ccap_program_group_size: 1 + +- name: Case 5, unit of exactly 10 is not reduced by the cap. + period: 2026-01 + input: + people: + person1: + age: 40 + person2: + age: 38 + person3: + age: 15 + person4: + age: 13 + person5: + age: 11 + person6: + age: 9 + person7: + age: 7 + person8: + age: 5 + person9: + age: 3 + person10: + age: 1 + spm_units: + spm_unit: + members: + [ + person1, + person2, + person3, + person4, + person5, + person6, + person7, + person8, + person9, + person10, + ] + households: + household: + members: + [ + person1, + person2, + person3, + person4, + person5, + person6, + person7, + person8, + person9, + person10, + ] + state_code: MI + output: + # Exactly 10 members -> size 10 (cap is non-binding at the boundary) + mi_ccap_program_group_size: 10 diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.py new file mode 100644 index 00000000000..110d5e6ba27 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.py @@ -0,0 +1,31 @@ +from policyengine_us.model_api import * + + +class mi_ccap_activity_eligible(Variable): + value_type = bool + entity = SPMUnit + label = "Eligible for Michigan CDC based on need reasons" + definition_period = MONTH + defined_for = StateCode.MI + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/703.pdf#page=5" + ) + + def formula(spm_unit, period, parameters): + # BEM 703 p.4-14: each parent/substitute-parent (P/SP) must have a + # valid need reason; in a two-parent household both must. We model + # employment/self-employment (hours worked), approved activity and + # high-school completion (full-time student), and family preservation + # (protective services). We use hours before labor supply responses to + # avoid a circular dependency with the labor supply model. + person = spm_unit.members + is_head_or_spouse = person("is_tax_unit_head_or_spouse", period.this_year) + hours_worked = person("weekly_hours_worked_before_lsr", period.this_year) + is_employed = hours_worked > 0 + is_student = person("is_full_time_student", period.this_year) + needs_protective_services = person( + "receives_or_needs_protective_services", period.this_year + ) + has_need_reason = is_employed | is_student | needs_protective_services + # Every P/SP must individually have a need reason. + return spm_unit.sum(is_head_or_spouse & ~has_need_reason) == 0 diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible.py new file mode 100644 index 00000000000..0ad9f84bdb6 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible.py @@ -0,0 +1,28 @@ +from policyengine_us.model_api import * + + +class mi_ccap_eligible(Variable): + value_type = bool + entity = SPMUnit + label = "Eligible for Michigan CDC" + definition_period = MONTH + defined_for = StateCode.MI + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/703.pdf#page=14" + ) + + def formula(spm_unit, period, parameters): + # BEM 703: assess protective-services / income-waived groups first, + # then the income-eligible group. All groups must pass the asset test + # and have a valid need reason. + has_eligible_child = add(spm_unit, period, ["mi_ccap_eligible_child"]) > 0 + income_waived = spm_unit("mi_ccap_income_waived", period) + income_eligible = spm_unit("mi_ccap_income_eligible", period) + asset_eligible = spm_unit("is_ccdf_asset_eligible", period.this_year) + activity_eligible = spm_unit("mi_ccap_activity_eligible", period) + return ( + has_eligible_child + & (income_waived | income_eligible) + & asset_eligible + & activity_eligible + ) diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.py new file mode 100644 index 00000000000..e16c02406ad --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.py @@ -0,0 +1,30 @@ +from policyengine_us.model_api import * + + +class mi_ccap_eligible_child(Variable): + value_type = bool + entity = Person + label = "Eligible child for Michigan CDC" + definition_period = MONTH + defined_for = StateCode.MI + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/703.pdf#page=1" + ) + + def formula(person, period, parameters): + p = parameters(period).gov.states.mi.mdhhs.ccap.eligibility + age = person("age", period.this_year) + # BEM 703: children under 13 are age-eligible; children 13 to under 18 + # who require constant care are also eligible (is_disabled proxy). We + # don't track court-ordered supervision or the age-18 high-school + # completion pathway at the moment. + requires_constant_care = person("is_disabled", period.this_year) + age_eligible = where( + requires_constant_care, + age < p.disabled_child_age_limit, + age < p.child_age_limit, + ) + immigration_eligible = person( + "is_ccdf_immigration_eligible_child", period.this_year + ) + return age_eligible & immigration_eligible diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.py new file mode 100644 index 00000000000..a2d4bcb551d --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class mi_ccap_income_eligible(Variable): + value_type = bool + entity = SPMUnit + label = "Eligible for Michigan CDC based on income" + definition_period = MONTH + defined_for = StateCode.MI + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3" + ) + + def formula(spm_unit, period, parameters): + # RFT 270 Table 1 / BEM 703 p.17-18: gross monthly program-group income + # must be at or below the entry limit at application and at or below the + # exit limit ongoing. Income-waived groups bypass this test. + p = parameters(period).gov.states.mi.mdhhs.ccap.income.scale + countable_income = spm_unit("mi_ccap_countable_income", period) + size = spm_unit("mi_ccap_program_group_size", period) + enrolled = spm_unit("mi_ccap_enrolled", period) + income_limit = where( + enrolled, + p.exit_limit[size], + p.entry_limit[size], + ) + return countable_income <= income_limit diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.py new file mode 100644 index 00000000000..dd5c208a752 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.py @@ -0,0 +1,35 @@ +from policyengine_us.model_api import * + + +class mi_ccap_income_waived(Variable): + value_type = bool + entity = SPMUnit + label = "Michigan CDC income-waived eligibility group" + definition_period = MONTH + defined_for = StateCode.MI + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/703.pdf#page=14", + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/703.pdf#page=15", + ) + + def formula(spm_unit, period, parameters): + # BEM 703 p.14-18: five groups qualify without an income test and have + # their family contribution waived. We model Children's Protective + # Services, Foster Care, FIP/SSI-related, and Homeless. The migrant + # farmworker group is not modeled at the moment (no migrant status is + # tracked). + # CPS: open Children's Protective Services case. + protective_services = ( + add(spm_unit, period, ["receives_or_needs_protective_services"]) > 0 + ) + # Foster Care: active MDHHS foster care case for the child. + foster_care = add(spm_unit, period, ["is_in_foster_care"]) > 0 + # FIP-related: child or P/SP receives FIP or SSI. MI FIP is TANF; we + # use is_tanf_enrolled (not computed FIP eligibility) to break the + # CCAP-TANF circular dependency. + fip_related = spm_unit("is_tanf_enrolled", period) | ( + add(spm_unit, period, ["ssi"]) > 0 + ) + # Homeless under the McKinney-Vento Act (self-attested). + homeless = spm_unit.household("is_homeless", period.this_year) + return protective_services | foster_care | fip_related | homeless diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap.py new file mode 100644 index 00000000000..075a23a74aa --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap.py @@ -0,0 +1,36 @@ +from policyengine_us.model_api import * + + +class mi_ccap(Variable): + value_type = float + entity = SPMUnit + unit = USD + label = "Michigan CDC benefit amount" + definition_period = MONTH + defined_for = "mi_ccap_eligible" + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/706.pdf#page=2" + ) + + def formula(spm_unit, period, parameters): + # BEM 706: the subsidy is a provider reimbursement computed per + # two-week pay period, capped at the provider's charge per child, less + # the family contribution, floored at zero, then converted to a monthly + # amount. There are 26 two-week pay periods per year. + pay_periods_per_month = WEEKS_IN_YEAR / 2 / MONTHS_IN_YEAR + + person = spm_unit.members + is_eligible_child = person("mi_ccap_eligible_child", period) + # Per-child reimbursement per pay period, converted to monthly. + block_payment = person("mi_ccap_block_payment", period) + monthly_payment = block_payment * pay_periods_per_month + # Cap each child's reimbursement at that child's actual monthly charge. + # pre_subsidy_childcare_expenses is annual; accessing it with the bare + # monthly period auto-divides it to a monthly amount. + monthly_expense = person("pre_subsidy_childcare_expenses", period) + capped_payment = min_(monthly_payment, monthly_expense) + total_payment = spm_unit.sum(capped_payment * is_eligible_child) + + family_contribution = spm_unit("mi_ccap_family_contribution", period) + monthly_family_contribution = family_contribution * pay_periods_per_month + return max_(total_payment - monthly_family_contribution, 0) diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_age_group.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_age_group.py new file mode 100644 index 00000000000..92faedc486c --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_age_group.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class MICCAPAgeGroup(Enum): + # Declared in ordinal order so the age_group/thresholds bracket parameter + # (0/1/2) maps directly to the Enum index. + INFANT_TODDLER = "Infant/Toddler" + PRESCHOOL = "Preschool" + SCHOOL_AGE = "School Age" + + +class mi_ccap_age_group(Variable): + value_type = Enum + entity = Person + possible_values = MICCAPAgeGroup + default_value = MICCAPAgeGroup.INFANT_TODDLER + definition_period = MONTH + label = "Michigan CDC rate age group" + defined_for = StateCode.MI + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=4" + ) + + def formula(person, period, parameters): + p = parameters(period).gov.states.mi.mdhhs.ccap.age_group + age = person("age", period.this_year) + return p.thresholds.calc(age) diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.py new file mode 100644 index 00000000000..2be6f8afc3b --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.py @@ -0,0 +1,36 @@ +from policyengine_us.model_api import * + + +class mi_ccap_authorized_hours(Variable): + value_type = float + entity = SPMUnit + unit = "hour" + definition_period = MONTH + defined_for = StateCode.MI + label = "Michigan CDC authorized hours per two-week pay period" + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/EXF/BP/Public/BEM/710.pdf#page=1", + "https://mdhhs-pres-prod.michigan.gov/olmweb/EXF/BP/Public/BEM/710.pdf#page=2", + ) + + def formula(spm_unit, period, parameters): + # BEM 710: biweekly need hours = activity hours over the two-week pay + # period. We don't track meal periods, study/lab time, or travel + # add-ons at the moment, so we use base activity hours only. We use + # hours before labor supply responses to avoid a circular dependency + # with the labor supply model. + p = parameters(period).gov.states.mi.mdhhs.ccap + person = spm_unit.members + is_head_or_spouse = person("is_tax_unit_head_or_spouse", period.this_year) + weekly_hours = person("weekly_hours_worked_before_lsr", period.this_year) + biweekly_hours = weekly_hours * 2 + # BEM 710 p.2: for two-parent households, authorize on the parent with + # the highest need hours (effective 2024-11-03). Non-P/SP members get + # zero so they never raise the authorized hours. + head_spouse_hours = where(is_head_or_spouse, biweekly_hours, 0) + if p.two_parent_uses_highest_hours: + need_hours = spm_unit.max(head_spouse_hours) + else: # noqa + # Prior rule (before 2024-11-03): authorize on the fewest hours. + need_hours = spm_unit.min(where(is_head_or_spouse, biweekly_hours, np.inf)) + return p.authorized_hours.tiers.calc(need_hours) diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.py new file mode 100644 index 00000000000..330fed803c6 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.py @@ -0,0 +1,42 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.mi.mdhhs.ccap.mi_ccap_provider_type import ( + MICCAPProviderType, +) + + +class mi_ccap_block_payment(Variable): + value_type = float + entity = Person + unit = USD + label = "Michigan CDC reimbursement per child per two-week pay period" + definition_period = MONTH + defined_for = "mi_ccap_eligible_child" + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/706.pdf#page=9", + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/706.pdf#page=11", + ) + + def formula(person, period, parameters): + # BEM 706: licensed centers and group and family homes are reimbursed + # on a block schedule (billing 1-30 hours pays 30, 31-60 pays 60, 61+ + # pays 90), capped at the child's authorized hours. License-exempt + # related and unrelated providers are paid hourly with no block, also + # capped at authorized hours. We don't track hours billed separately + # from authorized hours at the moment, so billed hours equal the + # child's authorized tier. + p = parameters(period).gov.states.mi.mdhhs.ccap + hourly_rate = person("mi_ccap_hourly_rate", period) + authorized_hours = person.spm_unit("mi_ccap_authorized_hours", period) + provider_type = person("mi_ccap_provider_type", period) + + block_hours = p.block_hours.tiers.calc(authorized_hours) + paid_block_hours = min_(block_hours, authorized_hours) + block_pay = hourly_rate * paid_block_hours + # License-exempt: hourly, no block schedule. + exempt_pay = hourly_rate * authorized_hours + + return where( + provider_type == MICCAPProviderType.LICENSE_EXEMPT, + exempt_pay, + block_pay, + ) diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_countable_income.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_countable_income.py new file mode 100644 index 00000000000..45aaa5b74f0 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_countable_income.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class mi_ccap_countable_income(Variable): + value_type = float + entity = SPMUnit + label = "Michigan CDC countable income" + definition_period = MONTH + unit = USD + defined_for = StateCode.MI + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/703.pdf#page=13" + ) + + # BEM 703: counts gross monthly income of the program group. Grandparent + # and guardian P/SP income is excluded by BEM 703, but we don't track the + # relationship of each adult to the child at the moment, so all program + # group income is counted. + adds = "gov.states.mi.mdhhs.ccap.income.countable_income.sources" diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_enrolled.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_enrolled.py new file mode 100644 index 00000000000..23cec632841 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_enrolled.py @@ -0,0 +1,12 @@ +from policyengine_us.model_api import * + + +class mi_ccap_enrolled(Variable): + value_type = bool + entity = SPMUnit + definition_period = MONTH + label = "Currently enrolled in Michigan CDC" + defined_for = StateCode.MI + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/703.pdf#page=17" + ) diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_exempt_level.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_exempt_level.py new file mode 100644 index 00000000000..b3423d7f376 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_exempt_level.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class MICCAPExemptLevel(Enum): + LEVEL_1 = "Level 1" + LEVEL_2 = "Level 2" + + +class mi_ccap_exempt_level(Variable): + value_type = Enum + entity = Person + possible_values = MICCAPExemptLevel + default_value = MICCAPExemptLevel.LEVEL_1 + definition_period = MONTH + label = "Michigan CDC license-exempt provider quality level" + defined_for = StateCode.MI + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=4" + ) diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.py new file mode 100644 index 00000000000..102dc3b4e07 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.py @@ -0,0 +1,85 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.mi.mdhhs.ccap.mi_ccap_star_rating import ( + MICCAPStarRating, +) + + +class mi_ccap_family_contribution(Variable): + value_type = float + entity = SPMUnit + unit = USD + label = "Michigan CDC family contribution per two-week pay period" + definition_period = MONTH + defined_for = StateCode.MI + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=3", + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/706.pdf#page=2", + ) + + def formula(spm_unit, period, parameters): + # RFT 270 / BEM 706: the family contribution is a flat dollar amount + # per child per two-week pay period, based on the family income band, + # and capped at a per-family limit. It is waived for income-waived + # children and for income-eligible children at a 3 Star (or higher) + # provider. + p = parameters(period).gov.states.mi.mdhhs.ccap + fc = p.family_contribution + countable_income = spm_unit("mi_ccap_countable_income", period) + size = spm_unit("mi_ccap_program_group_size", period) + + # Look up the income band index (0-6) from the per-size scale. + bands = p.income.scale.band_thresholds + band = select( + [ + size == 1, + size == 2, + size == 3, + size == 4, + size == 5, + size == 6, + size == 7, + size == 8, + size == 9, + ], + [ + bands.size_1.calc(countable_income), + bands.size_2.calc(countable_income), + bands.size_3.calc(countable_income), + bands.size_4.calc(countable_income), + bands.size_5.calc(countable_income), + bands.size_6.calc(countable_income), + bands.size_7.calc(countable_income), + bands.size_8.calc(countable_income), + bands.size_9.calc(countable_income), + ], + default=bands.size_10.calc(countable_income), + ) + per_child_fc = fc.per_child[band] + family_limit = fc.family_limit[band] + + # The family contribution is waived entirely for income-waived groups. + income_waived = spm_unit("mi_ccap_income_waived", period) + + # BEM 706: the contribution is also waived per child when the child's + # provider is at the star waiver threshold or higher. License-exempt + # providers never reach the threshold, so their income-eligible + # children always pay. + person = spm_unit.members + is_eligible_child = person("mi_ccap_eligible_child", period) + star_rating = person("mi_ccap_star_rating", period) + star_level = select( + [ + star_rating == MICCAPStarRating.STAR_1, + star_rating == MICCAPStarRating.STAR_2, + star_rating == MICCAPStarRating.STAR_3, + star_rating == MICCAPStarRating.STAR_4, + star_rating == MICCAPStarRating.STAR_5, + ], + [1, 2, 3, 4, 5], + ) + star_waived = star_level >= fc.star_waiver_threshold + child_pays = is_eligible_child & ~star_waived + num_paying_children = spm_unit.sum(child_pays) + total_fc = per_child_fc * num_paying_children + capped_fc = min_(total_fc, family_limit) + return where(income_waived, 0, capped_fc) diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_hourly_rate.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_hourly_rate.py new file mode 100644 index 00000000000..6981ca4c221 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_hourly_rate.py @@ -0,0 +1,41 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.mi.mdhhs.ccap.mi_ccap_provider_type import ( + MICCAPProviderType, +) + + +class mi_ccap_hourly_rate(Variable): + value_type = float + entity = Person + unit = USD + label = "Michigan CDC department hourly rate per child" + definition_period = MONTH + defined_for = "mi_ccap_eligible_child" + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=4" + ) + + def formula(person, period, parameters): + # RFT 270 Table 2: the department hourly rate is looked up by provider + # type, star/quality level, and age group. + p = parameters(period).gov.states.mi.mdhhs.ccap.rates + provider_type = person("mi_ccap_provider_type", period) + age_group = person("mi_ccap_age_group", period) + star_rating = person("mi_ccap_star_rating", period) + exempt_level = person("mi_ccap_exempt_level", period) + + center_rate = p.center[star_rating][age_group] + family_home_rate = p.family_home[star_rating][age_group] + exempt_rate = p.exempt[exempt_level][age_group] + + return select( + [ + provider_type == MICCAPProviderType.CENTER, + provider_type == MICCAPProviderType.FAMILY_HOME, + ], + [ + center_rate, + family_home_rate, + ], + default=exempt_rate, + ) diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_program_group_size.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_program_group_size.py new file mode 100644 index 00000000000..060b46833b3 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_program_group_size.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * + + +class mi_ccap_program_group_size(Variable): + value_type = int + entity = SPMUnit + label = "Michigan CDC program group size" + definition_period = MONTH + defined_for = StateCode.MI + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/205.pdf#page=1" + ) + + def formula(spm_unit, period, parameters): + # BEM 205: the CDC program group is the parent(s)/stepparent, the + # children needing care, and their unmarried under-18 siblings — + # roughly the SPM unit minus unrelated adults. We don't track each + # adult's relationship to the child at the moment, so we use the full + # SPM unit size. The RFT 270 income scale stops at family size 10, so + # larger groups use the size-10 column. + p = parameters(period).gov.states.mi.mdhhs.ccap.eligibility + size = spm_unit("spm_unit_size", period.this_year) + return min_(size, p.max_program_group_size) diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_provider_type.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_provider_type.py new file mode 100644 index 00000000000..cc8ae97a490 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_provider_type.py @@ -0,0 +1,20 @@ +from policyengine_us.model_api import * + + +class MICCAPProviderType(Enum): + CENTER = "Child care center" + FAMILY_HOME = "Group and family child care home" + LICENSE_EXEMPT = "License-exempt related and unrelated" + + +class mi_ccap_provider_type(Variable): + value_type = Enum + entity = Person + possible_values = MICCAPProviderType + default_value = MICCAPProviderType.CENTER + definition_period = MONTH + label = "Michigan CDC child care provider type" + defined_for = StateCode.MI + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=4" + ) diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_star_rating.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_star_rating.py new file mode 100644 index 00000000000..805d5aab04c --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_star_rating.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class MICCAPStarRating(Enum): + # STAR_1 holds both the Base Rate (blank star) and the 1 Star + # (Maintaining Health & Safety) level, which carry identical rates. + STAR_1 = "1 Star" + STAR_2 = "2 Stars" + STAR_3 = "3 Stars" + STAR_4 = "4 Stars" + STAR_5 = "5 Stars" + + +class mi_ccap_star_rating(Variable): + value_type = Enum + entity = Person + possible_values = MICCAPStarRating + default_value = MICCAPStarRating.STAR_1 + definition_period = MONTH + label = "Michigan CDC licensed provider star quality level" + defined_for = StateCode.MI + reference = ( + "https://mdhhs-pres-prod.michigan.gov/olmweb/EX/RF/Public/RFT/270.pdf#page=4" + ) diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_child_care_subsidies.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_child_care_subsidies.py new file mode 100644 index 00000000000..0d1f71d1543 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_child_care_subsidies.py @@ -0,0 +1,11 @@ +from policyengine_us.model_api import * + + +class mi_child_care_subsidies(Variable): + value_type = float + entity = SPMUnit + label = "Michigan child care subsidies" + unit = USD + definition_period = YEAR + defined_for = StateCode.MI + adds = ["mi_ccap"] From 7ffe67624f627fa42cbb9ef8fca2d777cbdc0abd Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 12 Jun 2026 13:51:19 -0400 Subject: [PATCH 3/7] Clarify MI CDC changelog fragment Co-Authored-By: Claude Fable 5 --- changelog.d/mi-ccap.added.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/mi-ccap.added.md b/changelog.d/mi-ccap.added.md index 3ba6d1040f0..d9583eb690c 100644 --- a/changelog.d/mi-ccap.added.md +++ b/changelog.d/mi-ccap.added.md @@ -1 +1 @@ -Initialize Michigan Child Care Assistance Program (CDC) implementation. +Add Michigan Child Development and Care (CDC) program. From 0dd55740293a2ccb2a7eaa0ac075b438168871b3 Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 12 Jun 2026 17:15:41 -0400 Subject: [PATCH 4/7] Review-fix round 1: address critical issues from /review-program Co-Authored-By: Claude Fable 5 --- .../ccap/income/countable_income/sources.yaml | 10 +-- .../mi_ccap_activity_eligible.yaml | 2 +- .../ccap/eligibility/mi_ccap_eligible.yaml | 2 +- .../eligibility/mi_ccap_eligible_child.yaml | 2 +- .../eligibility/mi_ccap_income_eligible.yaml | 2 +- .../eligibility/mi_ccap_income_waived.yaml | 2 +- .../gov/states/mi/mdhhs/ccap/integration.yaml | 64 ++++++++++--------- .../mi/mdhhs/ccap/mi_ccap_age_group.yaml | 2 +- .../mdhhs/ccap/mi_ccap_authorized_hours.yaml | 2 +- .../mi/mdhhs/ccap/mi_ccap_block_payment.yaml | 39 +++++------ .../ccap/mi_ccap_family_contribution.yaml | 2 +- .../mi/mdhhs/ccap/mi_ccap_hourly_rate.yaml | 2 +- .../ccap/mi_ccap_program_group_size.yaml | 2 +- .../mi/mdhhs/ccap/mi_ccap_block_payment.py | 21 +++--- .../mi/mdhhs/ccap/mi_ccap_countable_income.py | 4 +- 15 files changed, 85 insertions(+), 73 deletions(-) diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/countable_income/sources.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/countable_income/sources.yaml index 1218fd89e80..1c1e1ac0179 100644 --- a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/countable_income/sources.yaml +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/countable_income/sources.yaml @@ -19,9 +19,7 @@ values: # The following are counted under BEM 500/502/503 but have no matching # variables at the moment: # - adoption_subsidy - # - royalties # - strike benefits - # - paid jury duty # - cash gifts and contributions metadata: @@ -29,5 +27,9 @@ metadata: period: year label: Michigan CDC countable income sources reference: - - title: BEM 703, Income - href: https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/703.pdf#page=13 + - title: BEM 503, Income, Unearned (countable determinations per program, pp.4-44) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/503.pdf#page=31 + - title: BEM 502, Income from Self-Employment (CDC countable self-employment, p.3) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/502.pdf#page=3 + - title: BEM 500, Income Overview (countable income definition; gifts $30/quarter floor) + href: https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/500.pdf#page=9 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.yaml index 29203ab2b55..9bdc00c2005 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.yaml @@ -1,4 +1,4 @@ -# REQ-006/008/009/010: Each parent/substitute-parent must have a valid need +# Each parent/substitute-parent must have a valid need # reason; in a two-parent household both must (BEM 703 p.4-14). Need reasons # modeled: employment (hours worked), approved activity / high-school # completion (full-time student), family preservation (protective services). diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible.yaml index e8247f2b228..5dabc9f6d3d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible.yaml @@ -1,4 +1,4 @@ -# REQ-005/006/017/018: Top-level Michigan CDC eligibility. Requires an +# Top-level Michigan CDC eligibility. Requires an # eligible child, an income-waived OR income-eligible group, the asset test, # and a valid need reason (BEM 703 p.14). diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.yaml index dcbfadd8f59..bb70f3256dd 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.yaml @@ -1,4 +1,4 @@ -# REQ-001/002/005: Eligible child age test. +# Eligible child age test. # Under 13 -> eligible; 13 to under 18 -> eligible only if requires constant # care (is_disabled proxy); must be a tax-unit dependent and immigration # eligible (BEM 703 p.1-2, p.14). diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.yaml index ec1ca1126bc..72fccad9ed7 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.yaml @@ -1,4 +1,4 @@ -# REQ-017/028: Income-eligible group. Applicants (not enrolled) must have +# Income-eligible group. Applicants (not enrolled) must have # monthly gross program-group income at or below the ENTRY limit (RFT 270 # column 2); ongoing recipients (enrolled) at or below the EXIT limit # (column 7). Thresholds by program-group size, eff 2025-09-21 (RFT 270 p.3). diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.yaml index 9bd91ca5ee5..6f9377281e0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.yaml @@ -1,4 +1,4 @@ -# REQ-012/013/014/015: Income-waived eligibility groups. Children's Protective +# Income-waived eligibility groups. Children's Protective # Services, Foster Care, FIP/SSI-related, and Homeless qualify without an # income test (BEM 703 p.14-18). The migrant farmworker group is not modeled. diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/integration.yaml index 1bb5615757b..fa1b38bd551 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/integration.yaml @@ -1,12 +1,15 @@ # Michigan CDC (CCAP) integration tests. # # Subsidy model (BEM 706): per child per two-week pay period, -# reimbursement = hourly_rate x min(block_hours, authorized_hours), -# capped at the child's monthly charge, less the flat family contribution, -# floored at zero, then converted to monthly. There are 26 two-week pay -# periods per year, so the monthly conversion factor is 52/2/12 = 2.16667. -# Billed hours are not tracked separately from authorized hours, so paid -# hours equal the authorized tier. +# reimbursement = hourly_rate x block_hours(authorized), +# where the authorized hours pick the full block they fall into (1-30 -> 30, +# 31-60 -> 60, 61+ -> 90; the authorization is a ceiling on billable hours, +# not a downward cap on the block, BEM 706 p.9-13). License-exempt providers +# are paid hourly with no block. The reimbursement is capped at the child's +# monthly charge, less the flat family contribution, floored at zero, then +# converted to monthly. There are 26 two-week pay periods per year, so the +# monthly conversion factor is 52/2/12 = 2.16667. Billed hours are not tracked +# separately from authorized hours, so the block is keyed to authorized hours. # # Rates (RFT 270 Table 2, eff 2024-09-22): # Center STAR_1: Inf/Tod 7.15, Preschool 5.05, School 4.90 @@ -55,14 +58,14 @@ mi_ccap_authorized_hours: 80 # Center STAR_1 infant/toddler hourly rate mi_ccap_hourly_rate: [0, 7.15] - # 7.15 x 80 = 572.00 per pay period - mi_ccap_block_payment: [0, 572.00] + # Authorized 80 -> 61+ block tier 90; 7.15 x 90 = 643.50 per pay period + mi_ccap_block_payment: [0, 643.50] # 3,500/mo -> size 2 band 1, per-child FC 15, 1 paying child, cap 45 -> 15/pp mi_ccap_family_contribution: 15 - # monthly payment = 572.00 x 2.16667 = 1,239.33 + # monthly payment = 643.50 x 2.16667 = 1,394.25 # monthly FC = 15 x 2.16667 = 32.50 - # benefit = 1,239.33 - 32.50 = 1,206.83 - mi_ccap: 1_206.83 + # benefit = 1,394.25 - 32.50 = 1,361.75 + mi_ccap: 1_361.75 - name: Case 2, two working parents, two children at a 1 Star center. period: 2026-01 @@ -108,14 +111,15 @@ mi_ccap_eligible: true # Both parents work; highest need = 80 biweekly -> tier 80 mi_ccap_authorized_hours: 80 - # person3 infant 7.15, person4 preschool 5.05 - mi_ccap_block_payment: [0, 0, 572.00, 404.00] + # Authorized 80 -> 61+ block tier 90; person3 infant 7.15 x 90 = 643.50, + # person4 preschool 5.05 x 90 = 454.50 + mi_ccap_block_payment: [0, 0, 643.50, 454.50] # 5,000/mo -> size 4 band 1, per-child FC 15, 2 paying children = 30, cap 45 -> 30/pp mi_ccap_family_contribution: 30 - # monthly: person3 572.00 x 2.16667 = 1,239.33; person4 404.00 x 2.16667 = 875.33 - # total = 2,114.67; monthly FC = 30 x 2.16667 = 65.00 - # benefit = 2,114.67 - 65.00 = 2,049.67 - mi_ccap: 2_049.67 + # monthly: person3 643.50 x 2.16667 = 1,394.25; person4 454.50 x 2.16667 = 984.75 + # total = 2,379.00; monthly FC = 30 x 2.16667 = 65.00 + # benefit = 2,379.00 - 65.00 = 2,314.00 + mi_ccap: 2_314.00 - name: Case 3, family contribution waived at a 3 Star provider. period: 2026-01 @@ -263,11 +267,12 @@ mi_ccap_income_waived: true mi_ccap_eligible: true mi_ccap_authorized_hours: 80 - mi_ccap_block_payment: [0, 572.00] + # Authorized 80 -> 61+ block tier 90; 7.15 x 90 = 643.50 per pay period + mi_ccap_block_payment: [0, 643.50] # Income-waived group -> family contribution 0 mi_ccap_family_contribution: 0 - # monthly = 572.00 x 2.16667 = 1,239.33; FC 0 -> benefit 1,239.33 - mi_ccap: 1_239.33 + # monthly = 643.50 x 2.16667 = 1,394.25; FC 0 -> benefit 1,394.25 + mi_ccap: 1_394.25 - name: Case 7, applicant income above the entry limit is ineligible. period: 2026-01 @@ -330,10 +335,10 @@ members: [person1, person2] state_code: MI output: - # Same monthly benefit as Case 1 (1,206.83) summed over 12 months - # = 14,482.00 annually. - mi_child_care_subsidies: 14_482.00 - child_care_subsidies: 14_482.00 + # Same monthly benefit as Case 1 (1,361.75) summed over 12 months + # = 16,341.00 annually. + mi_child_care_subsidies: 16_341.00 + child_care_subsidies: 16_341.00 # Edge cases. @@ -369,12 +374,13 @@ mi_ccap_income_eligible: true mi_ccap_eligible: true mi_ccap_authorized_hours: 80 - mi_ccap_block_payment: [0, 572.00] + # Authorized 80 -> 61+ block tier 90; 7.15 x 90 = 643.50 per pay period + mi_ccap_block_payment: [0, 643.50] # Negative income maps to the lowest band -> family contribution 0, # not a negative amount that would inflate the benefit mi_ccap_family_contribution: 0 - # benefit = 572.00 x 2.16667 = 1,239.33 (block payment only, FC 0) - mi_ccap: 1_239.33 + # benefit = 643.50 x 2.16667 = 1,394.25 (block payment only, FC 0) + mi_ccap: 1_394.25 - name: Case 10, subsidy capped tightly at a low actual child care charge. period: 2026-01 @@ -406,8 +412,8 @@ output: mi_ccap_eligible: true mi_ccap_authorized_hours: 80 - # Uncapped block payment 7.15 x 80 = 572.00/pp -> monthly 1,239.33 - mi_ccap_block_payment: [0, 572.00] + # Authorized 80 -> 61+ block tier 90; 7.15 x 90 = 643.50/pp -> monthly 1,394.25 + mi_ccap_block_payment: [0, 643.50] mi_ccap_family_contribution: 0 # Monthly charge is only 250.00, below the uncapped subsidy -> capped at 250.00 mi_ccap: 250.00 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_age_group.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_age_group.yaml index 5b3bb940986..cd5852daf53 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_age_group.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_age_group.yaml @@ -1,4 +1,4 @@ -# REQ-025: Age groups for the RFT 270 rate table. +# Age groups for the RFT 270 rate table. # Infant/Toddler (birth up to age 2.5), Preschool (2.5 up to 5), # School Age (5 and over). Boundary is 2.5 years (RFT 270 p.4). diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.yaml index a1eaa5506a5..4634ff2efc9 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.yaml @@ -1,4 +1,4 @@ -# REQ-019/020/021: Authorized hours per child per two-week pay period. +# Authorized hours per child per two-week pay period. # Biweekly need hours = caretaker weekly activity hours x 2 weeks (base model; # meal/study/travel add-ons not tracked). Mapped to a tier 20/40/60/80/90; # max 90. Two-parent household authorizes on the parent with the HIGHEST need diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.yaml index 4a4afccb618..ec67e47f994 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.yaml @@ -1,10 +1,12 @@ -# REQ-022/023: Reimbursement per child per two-week pay period. -# Licensed centers and group/family homes use the block schedule (billed -# 1-30 hours -> 30, 31-60 -> 60, 61+ -> 90), capped at the child's authorized -# hours. License-exempt providers are paid hourly, also capped at authorized -# hours. Because billed hours are not tracked separately from authorized -# hours, paid hours equal the authorized tier for every provider type, so -# the payment is hourly_rate x authorized_hours per pay period. +# Reimbursement per child per two-week pay period. +# Licensed centers and group/family homes are paid the full block that the +# billed/authorized hours fall into (1-30 hours -> 30-hour block, 31-60 -> 60, +# 61+ -> 90); the authorization is a ceiling on billable hours, not a downward +# cap on the block, so 80 authorized hours are paid the 90-hour block (BEM 706 +# p.9-13). Billed hours are not tracked separately from authorized hours, so +# the block tier is looked up from the authorized hours: payment is +# hourly_rate x block_hours(authorized) per pay period. License-exempt +# providers are paid hourly with no block: hourly_rate x authorized_hours. # Authorized hours come from the parent's activity: 40 hrs/week -> 80 biweekly. - name: Case 1, center 1 Star infant, parent 40 hrs/week. @@ -32,8 +34,8 @@ members: [person1, person2] state_code: MI output: - # Child: 7.15 hourly x 80 authorized = 572.00 per pay period - mi_ccap_block_payment: [0, 572.00] + # Authorized 80 -> 61+ block tier 90; center STAR_1 infant 7.15 x 90 = 643.50 + mi_ccap_block_payment: [0, 643.50] - name: Case 2, group and family home 1 Star infant, parent 30 hrs/week. period: 2026-01 @@ -60,7 +62,7 @@ members: [person1, person2] state_code: MI output: - # Child: 5.70 hourly x 60 authorized = 342.00 per pay period + # Authorized 60 -> 31-60 block tier 60; family home STAR_1 infant 5.70 x 60 = 342.00 mi_ccap_block_payment: [0, 342.00] - name: Case 3, license-exempt Level 1 school age, parent 20 hrs/week. @@ -116,7 +118,7 @@ members: [person1, person2] state_code: MI output: - # Child: 9.60 hourly x 90 authorized = 864.00 per pay period + # Authorized 90 -> 61+ block tier 90; center STAR_5 infant 9.60 x 90 = 864.00 mi_ccap_block_payment: [0, 864.00] - name: Case 5, parent with no activity gives zero authorized hours and zero payment. @@ -144,12 +146,12 @@ members: [person1, person2] state_code: MI output: - # Authorized 0 -> 7.15 x 0 = 0 + # Authorized 0 -> block tier 0; 7.15 x 0 = 0 mi_ccap_block_payment: [0, 0] # Edge cases. -- name: Case 6, family home at the 60-hour block tier boundary, parent 30 hrs/week. +- name: Case 6, family home at the 31-60 block tier, parent 30 hrs/week. period: 2026-01 absolute_error_margin: 0.01 input: @@ -174,7 +176,7 @@ members: [person1, person2] state_code: MI output: - # Authorized 60 -> block tier 60 (min(60, 60)); family home STAR_1 school age + # Authorized 60 -> 31-60 block tier 60; family home STAR_1 school age # 4.80 x 60 = 288.00 per pay period mi_ccap_block_payment: [0, 288.00] @@ -206,7 +208,7 @@ # License-exempt is paid hourly with no block: 4.95 x 90 authorized = 445.50 mi_ccap_block_payment: [0, 445.50] -- name: Case 8, center at the 20-hour tier, block schedule capped at authorized hours. +- name: Case 8, center at the 20-hour authorization is paid the full 30-hour block. period: 2026-01 absolute_error_margin: 0.01 input: @@ -231,6 +233,7 @@ members: [person1, person2] state_code: MI output: - # Block schedule for 20 authorized hours pays 30, but is capped at the - # authorized 20: min(30, 20) = 20; center STAR_1 preschool 5.05 x 20 = 101.00 - mi_ccap_block_payment: [0, 101.00] + # Authorized 20 -> 1-30 block tier 30 (the authorization is a ceiling on + # billable hours, not a downward cap on the block, BEM 706 p.10); + # center STAR_1 preschool 5.05 x 30 = 151.50 + mi_ccap_block_payment: [0, 151.50] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.yaml index 34252c87b83..667307b1ba3 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.yaml @@ -1,4 +1,4 @@ -# REQ-029/031/032: Family contribution per two-week pay period. +# Family contribution per two-week pay period. # Flat dollar amount per child by income band (RFT 270 p.3), summed across # paying children and capped at a per-family limit. Waived per child at a # 3 Star (or higher) provider, and waived entirely for income-waived groups diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_hourly_rate.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_hourly_rate.yaml index d92e63b0d19..9a8b6fd469f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_hourly_rate.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_hourly_rate.yaml @@ -1,4 +1,4 @@ -# REQ-024: Department hourly rate looked up by provider type, star/quality +# Department hourly rate looked up by provider type, star/quality # level, and age group (RFT 270 Table 2, eff 2024-09-22). Tested across all # three provider types and a spread of star/level and age combinations. # Rate variable is defined_for the eligible child, so each test child is a diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_program_group_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_program_group_size.yaml index f046f555ec2..c171cf54d91 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_program_group_size.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_program_group_size.yaml @@ -1,4 +1,4 @@ -# REQ-026: Family size for the RFT 270 scale = CDC program group (BEM 205): +# Family size for the RFT 270 scale = CDC program group (BEM 205): # parent(s)/stepparent + children needing care + their unmarried under-18 # siblings. Modeled approximately as the SPM unit, capped at 10 # (max_program_group_size). A single eligible parent applying for one child diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.py index 330fed803c6..7dc6d320b66 100644 --- a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.py +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.py @@ -13,26 +13,25 @@ class mi_ccap_block_payment(Variable): defined_for = "mi_ccap_eligible_child" reference = ( "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/706.pdf#page=9", - "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/706.pdf#page=11", ) def formula(person, period, parameters): # BEM 706: licensed centers and group and family homes are reimbursed - # on a block schedule (billing 1-30 hours pays 30, 31-60 pays 60, 61+ - # pays 90), capped at the child's authorized hours. License-exempt - # related and unrelated providers are paid hourly with no block, also - # capped at authorized hours. We don't track hours billed separately - # from authorized hours at the moment, so billed hours equal the - # child's authorized tier. + # on a block schedule keyed to hours billed (1-30 pays the 30-hour + # block, 31-60 pays 60, 61+ pays 90). The authorization is a ceiling on + # billable hours, not a downward cap on the block payment, so a 40-hour + # authorization is paid the full 60-hour block (BEM 706 p.10). We don't + # track hours billed separately from authorized hours at the moment, so + # billed hours equal the child's authorized tier and the block tier is + # looked up directly from the authorized hours. License-exempt related + # and unrelated providers receive no block payment and are paid hourly, + # limited to the authorized hours (BEM 706 p.8, p.11). p = parameters(period).gov.states.mi.mdhhs.ccap hourly_rate = person("mi_ccap_hourly_rate", period) authorized_hours = person.spm_unit("mi_ccap_authorized_hours", period) provider_type = person("mi_ccap_provider_type", period) - block_hours = p.block_hours.tiers.calc(authorized_hours) - paid_block_hours = min_(block_hours, authorized_hours) - block_pay = hourly_rate * paid_block_hours - # License-exempt: hourly, no block schedule. + block_pay = hourly_rate * p.block_hours.tiers.calc(authorized_hours) exempt_pay = hourly_rate * authorized_hours return where( diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_countable_income.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_countable_income.py index 45aaa5b74f0..ae99f28d03e 100644 --- a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_countable_income.py +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_countable_income.py @@ -9,7 +9,9 @@ class mi_ccap_countable_income(Variable): unit = USD defined_for = StateCode.MI reference = ( - "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/703.pdf#page=13" + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/503.pdf#page=31", + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/502.pdf#page=3", + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/500.pdf#page=9", ) # BEM 703: counts gross monthly income of the program group. Grandparent From 60efb2b46ef7ed221f188f6489683cbd23e92e3e Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 12 Jun 2026 17:30:25 -0400 Subject: [PATCH 5/7] Review-fix round 2: reference cleanups from /review-program Co-Authored-By: Claude Fable 5 --- .../ccap/family_contribution/star_waiver_threshold.yaml | 2 +- .../mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.py | 3 +-- .../gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.py | 3 +-- .../gov/states/mi/mdhhs/ccap/mi_ccap_countable_income.py | 7 +++---- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/star_waiver_threshold.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/star_waiver_threshold.yaml index 9d0c722433c..08e010024b8 100644 --- a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/star_waiver_threshold.yaml +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/family_contribution/star_waiver_threshold.yaml @@ -8,4 +8,4 @@ metadata: label: Michigan CDC family contribution star waiver threshold reference: - title: BEM 706, Family Contribution - href: https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/706.pdf#page=2 + href: https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/706.pdf#page=3 diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.py index dd5c208a752..54b491fffab 100644 --- a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.py +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.py @@ -8,8 +8,7 @@ class mi_ccap_income_waived(Variable): definition_period = MONTH defined_for = StateCode.MI reference = ( - "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/703.pdf#page=14", - "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/703.pdf#page=15", + "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/703.pdf#page=14" ) def formula(spm_unit, period, parameters): diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.py index 2be6f8afc3b..eec29e4803e 100644 --- a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.py +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.py @@ -9,8 +9,7 @@ class mi_ccap_authorized_hours(Variable): defined_for = StateCode.MI label = "Michigan CDC authorized hours per two-week pay period" reference = ( - "https://mdhhs-pres-prod.michigan.gov/olmweb/EXF/BP/Public/BEM/710.pdf#page=1", - "https://mdhhs-pres-prod.michigan.gov/olmweb/EXF/BP/Public/BEM/710.pdf#page=2", + "https://mdhhs-pres-prod.michigan.gov/olmweb/EXF/BP/Public/BEM/710.pdf#page=1" ) def formula(spm_unit, period, parameters): diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_countable_income.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_countable_income.py index ae99f28d03e..1e5a68bf0ee 100644 --- a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_countable_income.py +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_countable_income.py @@ -14,8 +14,7 @@ class mi_ccap_countable_income(Variable): "https://mdhhs-pres-prod.michigan.gov/olmweb/ex/BP/Public/BEM/500.pdf#page=9", ) - # BEM 703: counts gross monthly income of the program group. Grandparent - # and guardian P/SP income is excluded by BEM 703, but we don't track the - # relationship of each adult to the child at the moment, so all program - # group income is counted. + # BEM 500/502/503: counts gross monthly income of the program group. We + # don't track each adult's relationship to the child at the moment, so all + # program group income is counted. adds = "gov.states.mi.mdhhs.ccap.income.countable_income.sources" From 62b4dc9fb0a8e30449d4a4cbe8754f8b6453b8e2 Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 15 Jun 2026 16:48:18 -0400 Subject: [PATCH 6/7] Reconcile MI CDC with FFY 2025-2027 State Plan review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Gate family-contribution star waiver to licensed providers; license-exempt children always pay regardless of any star rating on record (RFT 270 p.3). - Remove SSI from countable income; State Plan §2.2.4.c leaves public assistance uncounted, and SSI recipients are income-waived anyway. - Add meets_ccdf_activity_test fallback for approved BEM 703 need reasons we don't model individually (job search, education/training, treatment). - Require tax-unit dependent status for an eligible child. - Fix eligible-child comment: the unmodeled over-13 pathway is court supervision, not high-school completion. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../ccap/income/countable_income/sources.yaml | 4 +++- .../eligibility/mi_ccap_activity_eligible.py | 12 +++++++++++- .../ccap/eligibility/mi_ccap_eligible_child.py | 7 ++++--- .../mi/mdhhs/ccap/mi_ccap_family_contribution.py | 16 +++++++++++----- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/countable_income/sources.yaml b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/countable_income/sources.yaml index 1c1e1ac0179..7a3cf866953 100644 --- a/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/countable_income/sources.yaml +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/countable_income/sources.yaml @@ -5,7 +5,9 @@ values: - self_employment_income - sstb_self_employment_income - social_security - - ssi + # SSI (public assistance) is not counted: State Plan FFY 2025-2027 §2.2.4.c + # leaves "public assistance" unchecked. SSI recipients are income-waived + # anyway (see mi_ccap_income_waived), so this never affects the income test. - dividend_income - interest_income - rental_income diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.py index 110d5e6ba27..c89288c0af2 100644 --- a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.py +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.py @@ -28,4 +28,14 @@ def formula(spm_unit, period, parameters): ) has_need_reason = is_employed | is_student | needs_protective_services # Every P/SP must individually have a need reason. - return spm_unit.sum(is_head_or_spouse & ~has_need_reason) == 0 + all_psp_have_need_reason = ( + spm_unit.sum(is_head_or_spouse & ~has_need_reason) == 0 + ) + # Fallback: the unit can also qualify via the federal CCDF activity-test + # input, which covers approved BEM 703 need reasons we don't model + # individually (job search, education/training, treatment, temporary + # leave from work or school, etc.). + meets_ccdf_activity_test = spm_unit( + "meets_ccdf_activity_test", period.this_year + ) + return all_psp_have_need_reason | meets_ccdf_activity_test diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.py index e16c02406ad..c3bc5f32233 100644 --- a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.py +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.py @@ -16,8 +16,8 @@ def formula(person, period, parameters): age = person("age", period.this_year) # BEM 703: children under 13 are age-eligible; children 13 to under 18 # who require constant care are also eligible (is_disabled proxy). We - # don't track court-ordered supervision or the age-18 high-school - # completion pathway at the moment. + # don't track the court-supervision pathway (also 13 to under 18) at the + # moment. requires_constant_care = person("is_disabled", period.this_year) age_eligible = where( requires_constant_care, @@ -27,4 +27,5 @@ def formula(person, period, parameters): immigration_eligible = person( "is_ccdf_immigration_eligible_child", period.this_year ) - return age_eligible & immigration_eligible + is_dependent = person("is_tax_unit_dependent", period.this_year) + return age_eligible & immigration_eligible & is_dependent diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.py index 102dc3b4e07..575fdc18fb2 100644 --- a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.py +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.py @@ -2,6 +2,9 @@ from policyengine_us.variables.gov.states.mi.mdhhs.ccap.mi_ccap_star_rating import ( MICCAPStarRating, ) +from policyengine_us.variables.gov.states.mi.mdhhs.ccap.mi_ccap_provider_type import ( + MICCAPProviderType, +) class mi_ccap_family_contribution(Variable): @@ -60,12 +63,15 @@ def formula(spm_unit, period, parameters): # The family contribution is waived entirely for income-waived groups. income_waived = spm_unit("mi_ccap_income_waived", period) - # BEM 706: the contribution is also waived per child when the child's - # provider is at the star waiver threshold or higher. License-exempt - # providers never reach the threshold, so their income-eligible - # children always pay. + # BEM 706 / RFT 270 p.3: the contribution is also waived per child when + # the child attends a licensed provider (center or family/group home) at + # the star waiver threshold or higher. License-exempt providers have no + # Great Start to Quality rating, so their income-eligible children + # always pay regardless of any star rating on the record. person = spm_unit.members is_eligible_child = person("mi_ccap_eligible_child", period) + provider_type = person("mi_ccap_provider_type", period) + is_licensed = provider_type != MICCAPProviderType.LICENSE_EXEMPT star_rating = person("mi_ccap_star_rating", period) star_level = select( [ @@ -77,7 +83,7 @@ def formula(spm_unit, period, parameters): ], [1, 2, 3, 4, 5], ) - star_waived = star_level >= fc.star_waiver_threshold + star_waived = is_licensed & (star_level >= fc.star_waiver_threshold) child_pays = is_eligible_child & ~star_waived num_paying_children = spm_unit.sum(child_pays) total_fc = per_child_fc * num_paying_children From bde828b01208cb5bbdc46fd95436c81ed8267636 Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 15 Jun 2026 20:14:57 -0400 Subject: [PATCH 7/7] Fix MI CDC microsim crash on size-0 income-scale lookup mi_ccap_program_group_size is MI-scoped, so non-MI SPM units report size 0. defined_for masks the result but the income-eligible formula still runs vectorized over every unit, and the size-indexed entry/exit breakdown lookup raised ParameterNotFoundError on the masked-out (size 0) rows in microsimulation. Clamp the lookup index to the scale minimum of 1. Unit tests pass because every test household is MI with size >= 1. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.py b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.py index a2d4bcb551d..ae6a1ad9054 100644 --- a/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.py +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.py @@ -18,10 +18,15 @@ def formula(spm_unit, period, parameters): p = parameters(period).gov.states.mi.mdhhs.ccap.income.scale countable_income = spm_unit("mi_ccap_countable_income", period) size = spm_unit("mi_ccap_program_group_size", period) + # Non-MI units are masked out by defined_for but still run this formula + # vectorized, reporting program-group size 0 (the size variable is itself + # MI-scoped). Clamp to the scale's minimum size of 1 so the size-indexed + # breakdown lookup never misses on those masked-out rows. + lookup_size = max_(size, 1) enrolled = spm_unit("mi_ccap_enrolled", period) income_limit = where( enrolled, - p.exit_limit[size], - p.entry_limit[size], + p.exit_limit[lookup_size], + p.entry_limit[lookup_size], ) return countable_income <= income_limit