diff --git a/changelog.d/mi-ccap.added.md b/changelog.d/mi-ccap.added.md new file mode 100644 index 00000000000..d9583eb690c --- /dev/null +++ b/changelog.d/mi-ccap.added.md @@ -0,0 +1 @@ +Add Michigan Child Development and Care (CDC) program. diff --git a/policyengine_us/parameters/gov/hhs/ccdf/child_care_subsidy_programs.yaml b/policyengine_us/parameters/gov/hhs/ccdf/child_care_subsidy_programs.yaml index 748e5f5bffc..df2e721cb79 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 @@ -17,6 +17,7 @@ values: - ky_child_care_subsidies # Kentucky Child Care Assistance Program - 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..08e010024b8 --- /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=3 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..7a3cf866953 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mi/mdhhs/ccap/income/countable_income/sources.yaml @@ -0,0 +1,37 @@ +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 (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 + - 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 + # - strike benefits + # - cash gifts and contributions + +metadata: + unit: list + period: year + label: Michigan CDC countable income sources + reference: + - 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/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 83ef1f1f1a0..c84d56b1bba 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -460,7 +460,7 @@ programs: category: Benefits agency: HHS status: partial - coverage: AK, AR, AL, CA, CO, CT, DE, DC, FL, HI, IA, ID, IL, IN, KS, KY, MA, MD, ME, NC, NH, NJ, PA, RI, SC, TX, VA, VT, WA, WV + coverage: AK, AR, AL, CA, CO, CT, DE, DC, FL, HI, IA, ID, IL, IN, KS, KY, MA, MD, ME, MI, NC, NH, NJ, PA, RI, SC, TX, VA, VT, WA, WV state_implementations: - state: AK status: complete @@ -554,6 +554,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: IA status: complete name: Iowa CCA 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..9bdc00c2005 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.yaml @@ -0,0 +1,201 @@ +# 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..5dabc9f6d3d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible.yaml @@ -0,0 +1,270 @@ +# 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..bb70f3256dd --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.yaml @@ -0,0 +1,195 @@ +# 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..72fccad9ed7 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.yaml @@ -0,0 +1,205 @@ +# 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..6f9377281e0 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.yaml @@ -0,0 +1,155 @@ +# 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..fa1b38bd551 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/integration.yaml @@ -0,0 +1,474 @@ +# Michigan CDC (CCAP) integration tests. +# +# Subsidy model (BEM 706): per child per two-week pay period, +# 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 +# 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] + # 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 = 643.50 x 2.16667 = 1,394.25 + # monthly FC = 15 x 2.16667 = 32.50 + # 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 + 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 + # 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 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 + 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 + # 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 = 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 + 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,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. + +- 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 + # 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 = 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 + 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 + # 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 + +- 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..cd5852daf53 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_age_group.yaml @@ -0,0 +1,125 @@ +# 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..4634ff2efc9 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.yaml @@ -0,0 +1,259 @@ +# 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..ec67e47f994 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.yaml @@ -0,0 +1,239 @@ +# 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. + 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: + # 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 + 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: + # 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. + 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: + # 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. + 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 -> block tier 0; 7.15 x 0 = 0 + mi_ccap_block_payment: [0, 0] + +# Edge cases. + +- name: Case 6, family home at the 31-60 block tier, 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 -> 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] + +- 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 authorization is paid the full 30-hour block. + 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: + # 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 new file mode 100644 index 00000000000..667307b1ba3 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.yaml @@ -0,0 +1,320 @@ +# 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..9a8b6fd469f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_hourly_rate.yaml @@ -0,0 +1,281 @@ +# 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..c171cf54d91 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/mdhhs/ccap/mi_ccap_program_group_size.yaml @@ -0,0 +1,189 @@ +# 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..c89288c0af2 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_activity_eligible.py @@ -0,0 +1,41 @@ +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. + 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.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..c3bc5f32233 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_eligible_child.py @@ -0,0 +1,31 @@ +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 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, + age < p.disabled_child_age_limit, + age < p.child_age_limit, + ) + immigration_eligible = person( + "is_ccdf_immigration_eligible_child", period.this_year + ) + 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/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..ae6a1ad9054 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_eligible.py @@ -0,0 +1,32 @@ +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) + # 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[lookup_size], + p.entry_limit[lookup_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..54b491fffab --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/eligibility/mi_ccap_income_waived.py @@ -0,0 +1,34 @@ +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" + ) + + 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..eec29e4803e --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_authorized_hours.py @@ -0,0 +1,35 @@ +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" + ) + + 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..7dc6d320b66 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_block_payment.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_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", + ) + + def formula(person, period, parameters): + # BEM 706: licensed centers and group and family homes are reimbursed + # 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_pay = hourly_rate * p.block_hours.tiers.calc(authorized_hours) + 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..1e5a68bf0ee --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_countable_income.py @@ -0,0 +1,20 @@ +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/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 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" 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..575fdc18fb2 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/mdhhs/ccap/mi_ccap_family_contribution.py @@ -0,0 +1,91 @@ +from policyengine_us.model_api import * +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): + 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 / 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( + [ + 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 = 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 + 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"]