Skip to content

Implement Iowa Child Care Assistance Program (CCAP)#8612

Open
hua7450 wants to merge 7 commits into
PolicyEngine:mainfrom
hua7450:ia-ccap
Open

Implement Iowa Child Care Assistance Program (CCAP)#8612
hua7450 wants to merge 7 commits into
PolicyEngine:mainfrom
hua7450:ia-ccap

Conversation

@hua7450

@hua7450 hua7450 commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements Iowa Child Care Assistance (CCA / CCAP) in PolicyEngine — a 3-tier CCDF child care subsidy administered by Iowa HHS. The program covers initial applicants (CCA), already-enrolled families at redetermination (CCA Plus), and a state-funded transitional tier (CCA Exit). The benefit pays the provider's charge up to a published half-day-unit rate ceiling, minus a sliding family fee.

Closes #8611

Regulatory Authority

Current rates and thresholds took effect 2024-07-01 following HF2658 (2024 session), which raised reimbursement rates based on the 2023 Market Rate Survey.

Eligibility Tests

The program has a 3-tier income structure selected by the ia_cca_enrolled boolean input (initial applicants vs. enrolled recipients at redetermination), mirroring the NJ CCAP initial-vs-ongoing pattern:

Tier Basic limit Special-needs limit Applies to
CCA (initial) ≤160% FPL or ≤85% Iowa MFI ≤200% FPL or ≤85% MFI applicants (ia_cca_enrolled = false)
CCA Plus (ongoing) ≤ min(225% FPL, 85% MFI) ≤ min(225% FPL, 85% MFI) enrolled families at redetermination
CCA Exit (state-funded) ≤250% FPL ≤275% FPL enrolled families above the CCA Plus limit

The 85% MFI cap reuses the federal smi() lookup (like nj_ccap_smi); it binds on the initial tier and on the CCA Plus ceiling used for copay-mechanism dispatch, where it falls below 225% FPL only at family sizes 10+ (eligibility for enrolled families is governed by the uncapped a(3) Exit limits).

Other tests:

  • Activity / need-for-service — each parent must work ≥32 hrs/week (28 if the family includes a special-needs child), or be a full-time student, or meet the meets_ccdf_activity_test fallback. Uses weekly_hours_worked_before_lsr (avoids the labor-supply cycle). Two-parent coinciding-hours is simplified to a per-parent gate.
  • Income exceptions — FIP/TANF recipients, protective child care, and foster parents are eligible without regard to income (is_tanf_enrolled OR receives_or_needs_protective_services OR is_in_foster_care). These paths also satisfy the need-for-service requirement through their qualifying condition: protective and foster care are themselves needs for service (170.2(2)"b"(3)/(9)), and FIP families qualify via employment with no minimum-hours requirement, education, or PROMISE JOBS participation (170.2(2)"b"(6)–(7)) — FIP enrollment serves as the proxy since we don't track PROMISE JOBS participation at the moment.
  • Child age — up to age 13, or up to age 19 for a child with special needs.
  • Iowa residencydefined_for = StateCode.IA.
  • Citizenship / qualified alien — reuses the federal is_ccdf_immigration_eligible_child (5-year bar).
  • Resource limit — family resources ≤ $1,000,000 (state-specific parameter; see Modeling Decisions).

Benefit Calculation

ia_cca = max( sum over in-care eligible children of min(child_charge, child_ceiling) − copay, 0 )
  • Per-child ceiling = ia_cca_max_rate (max $/half-day-unit) × ia_cca_monthly_units.
  • A child contributes $0 unless they are actually in care (childcare_hours_per_week > 0).
  • ia_cca is defined at MONTH; the YEAR aggregator ia_child_care_subsidies sums the 12 monthly values and feeds the federal child_care_subsidies.

Rate matrix [5 provider types × 3 age groups × 4 quality tiers × Basic/Special-Needs]:

Provider type Source Quality dimension
Licensed Center Table 1 4 tiers (No rating / QR1-2 / QR3-4 / QR5)
Child Development Home A/B Table 2 4 tiers
Child Development Home C Table 3 4 tiers
Child Care Home (Not Registered) Table 4 none
In-Home provider Table 5 none — flat min-wage rate

Age group (ia_cca_age_group) is derived from age (infant/toddler <3, preschool <5, school-age ≥5) with the _SN row driven by is_disabled. Quality rating and provider type are enum inputs. The In-Home rate ($36.25/unit = Iowa minimum wage $7.25/hr × 5-hr unit) pays $0 unless 3+ children in the family require care.

Copay (two mechanisms)

ia_cca_copay dispatches on ia_cca_in_exit_tier (enrolled & income above the CCA Plus ceiling, min(225% FPL, 85% MFI)):

  • Mechanism A — CCA / CCA Plus (copay/sliding_fee/): a flat $/half-day-unit fee. The level (A–BB, 28 levels) is a bracket lookup keyed by monthly countable income × family size (1–13+); the per-unit fee column varies by number of children in care (1 / 2 / 3+). The fee is assessed on a single child — the one receiving the most units of service.
  • Mechanism B — CCA Exit (copay/exit/): a percentage of the cost of care charged per child (Level A=33%, B=45%, C=60%, D=60%). The Basic vs Special-Needs income-threshold table (Levels A–D) is selected per child from the child's own special-needs status (ia_cca_exit_fee_level is Person-level, per the fee chart's "determine which table to use for each child" instruction), and the per-child fees are summed.

Copay is $0 for the no-income-test population (FIP / protective / foster).

Requirements Coverage

33 of 33 in-scope requirements are covered (see /tmp/ia-ccap-coverage-report.md).

REQ Description Parameter(s) Variable(s) Test
001/004 Child age limit (13 / 19 SN), family composition eligibility/child_age_limit.yaml, special_needs_age_limit.yaml ia_cca_eligible_child ia_cca_eligible_child.yaml
002 Citizenship / qualified alien (federal reuse) ia_cca_eligible_child (is_ccdf_immigration_eligible_child) ia_cca_eligible_child.yaml
003 Iowa residency defined_for = StateCode.IA on all top-level vars all (set state_code: IA)
005/006 Initial CCA ≤160% / 200% FPL income/fpl_rate/initial_basic.yaml, initial_special_needs.yaml ia_cca_income_eligible ia_cca_income_eligible.yaml
007 Initial ≤85% Iowa MFI cap income/smi_rate.yaml ia_cca_smi, ia_cca_income_eligible ia_cca_smi.yaml, ia_cca_income_eligible.yaml
008/009 CCA Plus ≤225%; CCA Exit ≤250%/275% FPL income/fpl_rate/plus_basic.yaml, exit_basic.yaml, exit_special_needs.yaml ia_cca_income_eligible ia_cca_income_eligible.yaml
010 Tier select via enrollment boolean ia_cca_enrolled (input) ia_cca_income_eligible.yaml
011 Income-exception paths (FIP / protective / foster) ia_cca_income_exception ia_cca_income_exception.yaml
012 $1M resource limit eligibility/asset_limit.yaml ia_cca_asset_eligible ia_cca_asset_eligible.yaml
013/015 Activity ≥32 hrs/wk (28 SN); per-parent gate activity_requirements/weekly_hours.yaml, weekly_hours_special_needs.yaml ia_cca_activity_eligible ia_cca_activity_eligible.yaml
014 Other activities (FT student + CCDF fallback) ia_cca_activity_eligible ia_cca_activity_eligible.yaml
016 Top-level eligibility aggregator ia_cca_eligible ia_cca_eligible.yaml
017 Unit of service = half day (5 hrs) payment/hours_per_unit.yaml ia_cca_monthly_units ia_cca_monthly_units.yaml
018 Benefit = min(charge, ceiling) − copay, floor 0 ia_cca integration.yaml
019/022 Rate matrix; derived age group age_group/age_group.yaml ia_cca_age_group, ia_cca_max_rate ia_cca_age_group.yaml, ia_cca_max_rate.yaml
020/021 5 provider types; 4 quality tiers (rate files) ia_cca_provider_type, ia_cca_quality_rating ia_cca_provider_type.yaml, ia_cca_quality_rating.yaml
023 Table 1 Licensed Center payment/rates/licensed_center.yaml ia_cca_max_rate ia_cca_max_rate.yaml
024 Table 2 CDH A/B payment/rates/child_dev_home_ab.yaml ia_cca_max_rate ia_cca_max_rate.yaml
025 Table 3 CDH C payment/rates/child_dev_home_c.yaml ia_cca_max_rate ia_cca_max_rate.yaml
026 Table 4 Home Not-Registered (no quality) payment/rates/child_care_home_not_registered.yaml ia_cca_max_rate ia_cca_max_rate.yaml
027 Table 5 In-Home ($36.25/unit, 3+ kids) payment/in_home_rate.yaml, in_home_min_children.yaml ia_cca_max_rate ia_cca_max_rate.yaml
028 Mechanism A sliding unit fee (Levels A–BB) copay/sliding_fee/unit_fee.yaml, income_thresholds.yaml ia_cca_sliding_fee_level, ia_cca_copay ia_cca_sliding_fee_level.yaml, ia_cca_copay.yaml
029 Mechanism B CCA Exit % of cost (Levels A–D) copay/exit/fee_pct.yaml, income_thresholds_basic.yaml, income_thresholds_special_needs.yaml ia_cca_exit_fee_level, ia_cca_in_exit_tier, ia_cca_copay ia_cca_exit_fee_level.yaml, ia_cca_in_exit_tier.yaml, ia_cca_copay.yaml
030 Copay exempt for no-income-test population ia_cca_copay (income exception → 0) ia_cca_copay.yaml
032/033 Countable income (Census-MFI basis) income/countable_income/earned_sources.yaml, self_employment_sources.yaml, unearned_sources.yaml ia_cca_countable_income ia_cca_countable_income.yaml
034 Minor-child earnings filter (≤14, or ≤18 FT student) income/minor_earnings_age.yaml, minor_student_age.yaml ia_cca_countable_income ia_cca_countable_income.yaml
Federal registration CCDF program registry + metadata child_care_subsidy_programs.yaml, programs.yaml ia_child_care_subsidies ia_child_care_subsidies.yaml

Modeling Decisions & Limitations

These are explicit, neutral notes on where the model abstracts away from operational detail. We highlight them for reviewer scrutiny.

  • Half-day-unit counting. A unit is a half day of up to 5 hours per 24-hour period, and a full day bills as two half-day units (170.1; 170.4(7)"a" sets the half-day rate at the full-day rate ÷ 2). When the care schedule is known (childcare_days_per_week / childcare_hours_per_day inputs), units are counted per day — one unit for a day of up to 5 hours, two for a longer day — times days per week, annualized monthly (× 52/12); a full-time 5-day, 8-hour schedule yields ~43.3 units/month, consistent with the overview PDF's authorized-unit accounting (~40 units with 4-week months). We don't track authorized units at the moment, so when only childcare_hours_per_week is reported the units fall back to total-hours proration (weekly hours × (52/12) / 5, ~34.67 units at 40 hrs/week), which understates units for schedules that aren't exact 5-hour blocks.
  • School-age boundary. IAC defines the preschool→school-age transition by school attendance rather than a numeric age. We use age 5 (kindergarten entry) as a proxy.
  • Special-needs. The _SN rate/threshold rows and the 19-year age limit / 28-hour activity test are driven by is_disabled.
  • Countable income. TANF/FIP cash is omitted from the countable-income sources list to avoid the CCAP↔TANF circular dependency; FIP recipients already qualify via the income-exception path, so omitting their cash from countable income does not change their result. SSI is counted: Iowa excludes it only in specified FIP-linked cases (170.2(1)"d"(25)–(26)), and those families route through the income-exception path where countable income is moot. Each person's net self-employment subtotal is floored at zero — a net loss cannot offset other earned or unearned income (170.2(1)"c"(1)). The child-earnings exclusions ((13) age ≤14; (35) full-time student ≤18) apply only to persons who are not the tax-unit head or spouse, so a teen parent's own wages stay counted. Income types with no PolicyEngine equivalent are not modeled: the $65 + 50% sheltered-workshop earnings disregard and the income of the parents a teen parent resides with. Most of the ~40 statutory exclusions (EITC, SNAP/WIC value, LIHEAP, capital gains, etc.) require no action because they are simply not added to the earned/unearned sources lists.
  • Copay fee-level lookup. The sliding-fee and CCA Exit level lookups implement the fee charts' rule literally — "move down to the first row greater than the family's income, then use the row above" — as a bottom-up scan that keeps the lowest row whose threshold exceeds income. A count-of-thresholds-met shortcut is not equivalent on the sliding chart: the printed BB row sits below the AA row for family sizes 9–13, and the counting form landed those sizes one level too high in the band between the BB and AA floors. All four CCA Exit fee percentages (Levels A–D = 33/45/60/60%) are reachable under this lookup.
  • Two-parent coinciding hours are simplified to a per-parent gate (each head/spouse must independently meet the hours test), following the NJ CCAP convention.

Not Modeled (by design)

What Source Why excluded
Hardship fee reduction / waiver (REQ-031) 170.4(2) Discretionary (high medical bills, shelter/utility burden, prescribed diet) — not deterministic from PolicyEngine inputs
§237A.13 priority groups / waitlist ordering (REQ-035) 170.2(3) Affects waitlist order only; Iowa currently has no waitlist and operates CCA as an entitlement, so priority does not gate eligibility
Time-limited activity-window timing (REQ-014, partial) 170.2(2)"b" We don't track activity-window timing (90-day job-search clock, medical-incapacity window, PROMISE JOBS duration) at the moment; the steady-state activity gate is modeled and these cases route to the meets_ccdf_activity_test fallback
Half-day / full-day / hourly rate conversion 170.4(7)"a" Provider-rate plumbing; we infer units from expense rather than reading a per-provider authorized-unit schedule

Backdating

Current era only — parameters start 2024-07-01 (HF2658 / 2023 Market Rate Survey). The pre-2024 era (IAC 5/4/22: initial limit 145% FPL, 28 hrs/week with no special-needs distinction, earlier rate tables, and a different sliding-fee start) differs materially and is deferred to a follow-up PR.

Review-fix round 3 (multi-agent review)

A five-dimension adversarially-verified review pass produced these fixes (commit 40e83a8): SSI counted in unearned income (the inline comment had inverted the rule), tax-exempt interest de-duplicated (the interest_income umbrella already includes it), self-employment losses floored per 170.2(1)"c"(1), the minor-earnings exclusions gated to non-head/spouse persons, the CCA Exit Basic/SN table selected per child instead of family-wide, the fee-level lookup switched to the chart's literal first-row-greater scan (non-monotonic BB row, sizes 9–13), plus citation/description corrections and 15 new test cases (exit Levels C/D, mixed Basic/SN family, BB-window regressions, countable-income edge cases).

Round 4 (commit 7711778) addressed two follow-up review findings: half-day units are now counted per day when the care schedule is known (one unit for a day of up to 5 hours, two for a longer day, per 170.1 and 170.4(7)"a"), with the hours-proration retained as the fallback for hours-only inputs; and the a(2) min(225% FPL, 85% MFI) CCA Plus ceiling is now applied at the copay-mechanism dispatch, where the MFI cap binds at family sizes 10+.

Test plan

  • 167 test cases across 22 test files (policyengine-core test policyengine_us/tests/policy/baseline/gov/states/ia/hhs/cca/ -c policyengine_us); review-fix round 3 added 15 cases whose expected values were independently recomputed by simulation
  • make format clean (ruff format, ruff check)
  • CI passes

Files Added

parameters/gov/states/ia/hhs/cca/                       (29 parameter files)
  activity_requirements/weekly_hours.yaml
  activity_requirements/weekly_hours_special_needs.yaml
  age_group/age_group.yaml
  copay/exit/fee_pct.yaml
  copay/exit/income_thresholds_basic.yaml
  copay/exit/income_thresholds_special_needs.yaml
  copay/sliding_fee/income_thresholds.yaml
  copay/sliding_fee/unit_fee.yaml
  eligibility/asset_limit.yaml
  eligibility/child_age_limit.yaml
  eligibility/special_needs_age_limit.yaml
  income/countable_income/earned_sources.yaml
  income/countable_income/self_employment_sources.yaml
  income/countable_income/unearned_sources.yaml
  income/fpl_rate/initial_basic.yaml
  income/fpl_rate/initial_special_needs.yaml
  income/fpl_rate/plus_basic.yaml
  income/fpl_rate/exit_basic.yaml
  income/fpl_rate/exit_special_needs.yaml
  income/minor_earnings_age.yaml
  income/minor_student_age.yaml
  income/smi_rate.yaml
  payment/hours_per_unit.yaml
  payment/in_home_min_children.yaml
  payment/in_home_rate.yaml
  payment/rates/licensed_center.yaml
  payment/rates/child_dev_home_ab.yaml
  payment/rates/child_dev_home_c.yaml
  payment/rates/child_care_home_not_registered.yaml

variables/gov/states/ia/hhs/cca/                        (22 variables)
  ia_cca.py
  ia_cca_age_group.py
  ia_cca_children_in_care.py
  ia_cca_countable_income.py
  ia_cca_enrolled.py
  ia_cca_max_rate.py
  ia_cca_monthly_units.py
  ia_cca_provider_type.py
  ia_cca_quality_rating.py
  ia_cca_smi.py
  ia_child_care_subsidies.py
  copay/ia_cca_copay.py
  copay/ia_cca_exit_fee_level.py
  copay/ia_cca_in_exit_tier.py
  copay/ia_cca_sliding_fee_level.py
  eligibility/ia_cca_eligible.py
  eligibility/ia_cca_eligible_child.py
  eligibility/ia_cca_income_eligible.py
  eligibility/ia_cca_activity_eligible.py
  eligibility/ia_cca_income_exception.py
  eligibility/ia_cca_asset_eligible.py
  eligibility/ia_cca_has_special_needs_child.py

tests/policy/baseline/gov/states/ia/hhs/cca/            (22 test files, 167 cases)
  + one yaml per variable, plus integration.yaml

Registry edits (2):
  parameters/gov/hhs/ccdf/child_care_subsidy_programs.yaml  (+ ia_child_care_subsidies)
  programs.yaml                                             (+ IA CCA state implementation)

changelog.d/ia-ccap.added.md

hua7450 and others added 3 commits June 8, 2026 21:34
Closes PolicyEngine#8611

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
)

Full 3-tier CCDF child care subsidy: CCA (initial <=160%/200% FPL or
85% MFI), CCA Plus (ongoing <=225%), CCA Exit (ongoing <=250%/275%,
state-funded). Benefit = min(provider charge, max rate ceiling) - copay
per half-day unit, across a 5-provider x 3-age x 4-quality x Basic/SN
rate matrix, with both copay mechanisms (sliding flat unit fee and
CCA Exit %-of-cost). 28 params, 22 variables, 135 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (bd1ae42) to head (7711778).
⚠️ Report is 26 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##             main     #8612       +/-   ##
============================================
+ Coverage   77.77%   100.00%   +22.22%     
============================================
  Files           1        22       +21     
  Lines           9       404      +395     
  Branches        0         5        +5     
============================================
+ Hits            7       404      +397     
+ Misses          2         0        -2     
Flag Coverage Δ
unittests 100.00% <100.00%> (+22.22%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

hua7450 and others added 4 commits June 8, 2026 23:57
- Fix citation 170.4(8)"d" -> 170.4(7)"d" (in-home care; no 170.4(8) exists)
- Fix #page anchors on income exclusion / minor-age params (p.4/p.5)
- Add ia_cca_has_special_needs_child unit test (was untested)
- Use age-based child eligibility per IAC 170.2(1)"e"(3) (drop is_tax_unit_dependent)
- Make ia_cca_max_rate provider selection explicit (no silent default)
- Strip internal REQ- markers from tests; add ia_cca.yaml + matrix/negative-income cases

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The CCA/CCA Plus sliding-fee and CCA Exit fee-level lookups used
sum(income > threshold), placing families whose income falls strictly
between two thresholds one level too high (over-charging copay). The
fee charts assign the level via "first row greater than income, use the
row above" — each printed threshold is the inclusive floor of its level.
Corrected both to max(sum(income >= threshold) - 1, 0). This makes CCA
Exit Level A (33%) reachable. Recomputed affected copay/level/integration
test expectations against the corrected logic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Count SSI in countable income (excluded only in FIP-linked cases per
  IAC 441-170.2(1)"d"(25)-(26), which route through the income-exception
  path); drop tax_exempt_interest_income (already inside the
  interest_income umbrella)
- Floor each person's net self-employment subtotal at zero per
  170.2(1)"c"(1) (new self_employment_sources.yaml split)
- Restrict the minor-earnings exclusions to non-head/spouse persons so a
  teen parent's own wages stay counted
- Make ia_cca_exit_fee_level Person-level: the Basic vs Special Needs
  exit table is selected per child per the fee chart, and ia_cca_copay
  sums per-child percentages
- Replace the count-based fee-level lookup with the chart's literal
  first-row-greater scan (the BB row sits below AA for family sizes
  9-13, so the counting shortcut landed those sizes one level too high)
- Fix need-for-service comment citations (170.2(2)"b"(3)/(9)/(6)-(7)),
  inverted threshold descriptions, and two page anchors
- Tests: +15 cases (exit Levels C/D, mixed Basic/SN family, sliding-fee
  BB-window regressions, SSI/SE-loss/interest/teen-head countable
  income); boundary-test names corrected to actual offsets

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Count half-day units per day when the care schedule is known
  (childcare_days_per_week / childcare_hours_per_day inputs): one unit
  for a day of up to 5 hours, two units for a longer day, per
  IAC 441-170.1 ("per 24-hour period") and 170.4(7)"a" (full-day rate =
  2 half-day rates). Falls back to total-hours proration when only
  weekly hours are reported, preserving existing test and microsim
  behavior for hours-only inputs
- Apply the IAC 441-170.2(1)"a"(2) ceiling — min(225% FPL, 85% MFI) —
  to the CCA Plus / CCA Exit copay-mechanism dispatch; the MFI cap
  binds below 225% FPL at family sizes 10+ with current parameters
- Tests: +7 cases (part-day, full-day, 5-hour-boundary, 10-hour-day,
  zero-hours schedules; size-10 dispatch on both sides of the MFI cap)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hua7450 hua7450 marked this pull request as ready for review June 9, 2026 21:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Iowa Child Care Assistance Program (CCAP / CCDF)

1 participant