Skip to content

Add Connecticut CCAP (Care 4 Kids)#7778

Open
hua7450 wants to merge 10 commits intoPolicyEngine:mainfrom
hua7450:ct-ccap
Open

Add Connecticut CCAP (Care 4 Kids)#7778
hua7450 wants to merge 10 commits intoPolicyEngine:mainfrom
hua7450:ct-ccap

Conversation

@hua7450
Copy link
Collaborator

@hua7450 hua7450 commented Mar 13, 2026

Summary

Implements Connecticut Care 4 Kids (C4K) child care subsidy program in PolicyEngine.

Closes #7777

C4K is Connecticut's CCDF-funded child care assistance program, administered by the Office of Early Childhood (OEC). This implementation covers the complete benefit calculation pipeline: child eligibility, two-tier income eligibility (initial vs. continuing), countable income with exclusions and deductions, sliding-scale family fee, multi-dimensional payment rate lookup (5 regions x 4 provider types x 3 age groups x 4 care levels), special needs supplements, accreditation bonuses, and final subsidy computation. Two rate periods are modeled: Jan-Jun 2025 and Jul 2025-Jun 2026 (11% increase). Reuses existing federal CCDF infrastructure for asset eligibility, immigration eligibility, SMI standards, and enrollment status.

Regulatory Authority

Income Eligibility Tests

Initial Application: < 60% SMI

New applicants must have gross countable income below 60% of State Median Income for their family size.

  • Source: CGA 2023-R-0249; RCSA 17b-749-05(a)
  • Effective Oct 1, 2023 (raised from 50% SMI)
  • Uses is_enrolled_in_ccdf to distinguish new applicants from continuing recipients

Continuing Recipients: < 85% SMI

Active recipients at redetermination must have income below 85% of SMI.

  • Source: C4K-POL-24-01; Governor announcement Oct 2024
  • Effective Oct 1, 2024 (raised from 65% SMI)
  • Historical thresholds parameterized: 50% (2020) → 65% (Oct 2023) → 85% (Oct 2024)

SMI Standards

Uses federal hhs_smi variable which computes State Median Income by state and family size from HHS-published figures.

  • FY 2024-2025: 100% SMI for family of 4 = $145,853
  • Source: C4K-POL-24-01

Income Deductions & Exemptions

Countable Income (RCSA 17b-749-05(b))

Gross earnings of all parents/adult family members + unearned income of ALL family members. Implemented via sources.yaml parameter list with 11 income types:

  • Earned: employment_income, self_employment_income, farm_income
  • Unearned: social_security, pension_income, unemployment_compensation, workers_compensation, alimony_income, rental_income, veterans_benefits, disability_benefits

Excluded Income (RCSA 17b-749-05(b)(2))

22+ income types excluded by omission from the sources list:

  • TFA cash benefits, SNAP, EITC payments
  • Child support received
  • Student grants/loans/scholarships
  • Earnings of minor children (non-parents)
  • Interest/dividends under $600/year, lump sum under $600/year, cash gifts under $1,200/year
  • Tax refunds, disaster assistance, government rental subsidies, energy assistance, and others

Deductions (RCSA 17b-749-05(c))

  • Self-employment business expenses: self_employment_income is already net of IRS standard deductions
  • Child support paid: child_support_expense subtracted via deductions.yaml parameter

Income Standards

SMI-based thresholds by family size (FY 2024-2025, source: C4K-POL-24-01):

Family Size 60% SMI (Initial) 85% SMI (Continuing) 100% SMI
1 $45,505 $64,467 $75,843
2 $59,507 $84,303 $99,180
3 $73,509 $104,139 $122,516
4 $87,511 $123,975 $145,853
5 $101,512 $143,811 $169,189
6 $115,514 $163,647 $192,525
7 $118,139 $167,366 $196,901
8 $120,765 $171,085 $201,277

Benefit Calculation

Formula

subsidy = max(approved_monthly_cost - family_fee, 0)

Where:

  1. Weekly payment rate = lookup by [region][provider_type][care_level][age_group] (240 rate cells per period)
  2. Monthly approved cost = weekly_rate x WEEKS_IN_YEAR / 12 (annualized, then monthly)
  3. Special needs supplement = +15% if child has special needs (RCSA 17b-749-13)
  4. Accreditation bonus = +5% if provider is accredited (RCSA 17b-749-13; CGA 2020-R-0274)
  5. Family fee = gross_countable_income x fee_percentage (from SMI bracket sliding scale)
  6. Subsidy = max(approved_cost - family_fee, 0)

Family Fee Sliding Scale

Pre-January 2025 (source: RCSA 17b-749-13(f)):

Income % of SMI Fee % of Gross Income
< 20% SMI 2%
20% to < 30% SMI 4%
30% to < 40% SMI 6%
40% to < 50% SMI 8%
50% to 85% SMI 10%

Effective January 1, 2025 (source: C4K-POL-24-02):

Income % of SMI Fee % of Gross Income
< 20% SMI 0% (waived)
20% to < 40% SMI 3%
40% to < 60% SMI 5%
60% to 85% SMI 7%

Rules: Families with only unearned income have no fee. Fee allocated to youngest child first.

Payment Rate Structure

5 regions x 4 provider types x 3 age groups x 4 care levels = 240 rate cells per period.

Care Levels: Full-Time Plus (51-65 hrs/wk), Full-Time (35-50), Half-Time (16-34), Quarter-Time (1-15)

Provider Types: Centers/Group Homes/School Programs, Licensed Family Child Care Homes, Unlicensed Relative Care, Recreational Programs (Summer)

Age Groups: Infant/Toddler, Pre-School, School-Age

Regions (county-to-region approximation): Eastern, North Central, Northwest, South Central, Southwest

Rate Periods:

Example weekly rates (Jul 2025, Centers, Full-Time Plus):

Age Group Eastern North Central Northwest South Central Southwest
Infant/Toddler $424 $559 $475 $549 $696
Pre-School $339 $372 $366 $384 $447
School-Age $238 $262 $275 $302 $314

Supplements

  • Special needs: +15% on payment rate
  • Accreditation bonus: +5% on payment rate
  • Both are multiplicative and can stack

Activity Test

ct_c4k_meets_activity_test is a CT-specific formula variable (RCSA 17b-749-04) that checks:

  • Any adult has employment income > 0 (employed)
  • Any adult has self-employment income > 0 (self-employed)
  • SPM unit is TFA eligible (in approved employment services via TANF)
  • Any person under 20 who is a parent and in K-12 school (teen parent)

Teen parent age threshold (20) is parameterized in age_threshold/teen_parent.yaml.

Not modeled: JFES (Job/Family Employment Services) participation — requires administrative data not available in PolicyEngine.

Not Modeled

REQ Description Reason
REQ-002 JFES participation Administrative program enrollment, not available as input
REQ-005 Child support cooperation requirement Administrative requirement, not simulatable
REQ-018 Fee set at application until redetermination Temporal administrative process, not simulatable in a static model
REQ-024 Max care hours 12/day, 65/wk, 280/month Enforcement rule; rate schedule already stops at 65 hrs/wk
REQ-029 Priority groups PG1-PG6 Wait list management, administrative process

Review Fixes Applied

Addressed findings from program review (#7778 comment by @PavelMakarchuk):

Critical (fixed)

  1. Period mismatch in ct_c4k_eligible.py: is_ccdf_asset_eligible (YEAR boolean) now uses period.this_year
  2. Period mismatch in ct_c4k_income_eligible.py: is_enrolled_in_ccdf (YEAR boolean) in add() now uses period.this_year
  3. Activity test: Replaced federal meets_ccdf_activity_test (bare input) with formula-based ct_c4k_meets_activity_test

Should-address

  1. continuing_limit_smi.yaml already had the 2023-10-01: 0.65 transition (no change needed)
  2. Recreational rate at Jan 2024 ($198) confirmed correct (no change needed)

Suggestions (applied)

  1. Removed redundant comment in ct_c4k.py
  2. Shortened region mapping comment in ct_c4k_region.py
  3. Tightened integration test absolute_error_margin from 1 to 0.1 with precise expected values

hua7450 and others added 2 commits March 13, 2026 09:07
Closes PolicyEngine#7777

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…yEngine#7777)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@codecov
Copy link

codecov bot commented Mar 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (19ec305) to head (8a120ec).
⚠️ Report is 330 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##              main     #7778    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files            3        14    +11     
  Lines           33       228   +195     
==========================================
+ Hits            33       228   +195     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

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

☔ View full report in Codecov by Sentry.
📢 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 7 commits March 13, 2026 10:51
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Jan 2024 and Jul 2024 rate data to all 16 C4K payment rate
parameter files (center, family, relative, recreational × 4 care
levels). Fixes microsimulation crash for 2024 where rate parameter
lookups failed with empty vectorial nodes. Remove lessons/agent-lessons.md
which duplicates local memory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Change all C4K variables to definition_period = MONTH (except aggregator)
- Fix care_level bracket amounts to match Enum indices directly (remove - 1 hack)
- Fix 2 swapped values in family/quarter_time.yaml (NC/NW School-Age 2024-07-01)
- Remove spurious 2025-01-01 entries that were artifacts of the swap
- Remove unnecessary index.yaml files
- Add comment noting county approximation limitation for C4K regions
  (C4K uses towns, not counties; PolicyEngine lacks town-level geography)

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

- Consolidate 16 rate files into 4 with care_level Enum breakdown
- Simplify ct_c4k_payment_rate.py from 100+ lines to ~20 lines
- Fix care_level bracket amounts to match Enum indices (remove -1 hack)
- Fix 2 swapped values in family quarter-time (NC/NW School-Age Jul 2024)
- Change all C4K variables to MONTH definition_period
- Update ct_c4k.py formula for monthly computation
- Cap ct_c4k_countable_income at 0 (child_support_expense can exceed income)
- Add county approximation comment for C4K regions (towns vs counties)
- Remove index.yaml files and sources/working_references.md
- Update all test files for MONTH periods with correct monthly values
- All 122 tests pass

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix is_disabled YEAR→MONTH period bug in ct_c4k_payment_rate.py
- Add C4K-POL-24-02 reference for 2025 family fee schedule
- Specify RCSA 17b-749-13 subsections: (d)(1) accreditation, (c)(11) special needs
- Add 45 CFR 98.21(b) federal CCDF reference for 85% SMI continuing limit
- Add Jul 2025 and pre-2025 rate period tests
- Add pre-2025 family fee bracket test (4% at 25% SMI)
- Add age group boundary tests (ages 3, 5, 6)
- Remove redundant eligible_child test case

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@hua7450 hua7450 marked this pull request as ready for review March 18, 2026 15:08
@PavelMakarchuk
Copy link
Collaborator

Program Review Report: Connecticut Care 4 Kids (CCAP/C4K)

PR: #7778 -- Add Connecticut CCAP (Care 4 Kids)
Author: hua7450
State: Connecticut
Program: CCAP / C4K (Care 4 Kids) -- child care assistance
CI Status: All passing
Date: 2026-03-24


Source Documents

Document Scope
/tmp/review-program-regulatory.md Regulatory accuracy review
/tmp/review-program-code.md Code pattern validation
/tmp/review-program-pdf-rates.md PDF-to-parameter audit (payment rates, fees, thresholds)
/tmp/review-program-context.md PR context and file inventory

Critical Issues (Must Fix)

CRITICAL 1: Period mismatch -- YEAR booleans accessed with MONTH period in ct_c4k_eligible.py

Source: Code validator (/tmp/review-program-code.md, Critical 1)

File: policyengine_us/variables/gov/states/ct/oec/c4k/eligibility/ct_c4k_eligible.py

Two YEAR-defined boolean variables are accessed with period (MONTH) instead of period.this_year:

# Current (broken):
asset_eligible = spm_unit("is_ccdf_asset_eligible", period)
activity_eligible = spm_unit("meets_ccdf_activity_test", period)

# Fix:
asset_eligible = spm_unit("is_ccdf_asset_eligible", period.this_year)
activity_eligible = spm_unit("meets_ccdf_activity_test", period.this_year)

Both is_ccdf_asset_eligible and meets_ccdf_activity_test have definition_period = YEAR. Accessing a YEAR boolean with a MONTH period causes policyengine-core auto-conversion: True (1.0) becomes 1.0 / 12 = ~0.083. This value is still truthy in Python, so current tests pass, but the behavior is incorrect and fragile. The Maine implementation (me_ccap_eligible.py) correctly uses period.this_year as a reference pattern.

Impact: Silently produces ~0.083 instead of True (1.0). Tests pass only because 0.083 is truthy. Any downstream code that checks == True or == 1 would break.


CRITICAL 2: Period mismatch -- YEAR boolean in add() with MONTH period in ct_c4k_income_eligible.py

Source: Code validator (/tmp/review-program-code.md, Critical 2)

File: policyengine_us/variables/gov/states/ct/oec/c4k/eligibility/ct_c4k_income_eligible.py

# Current (broken):
is_enrolled = add(spm_unit, period, ["is_enrolled_in_ccdf"]) > 0

# Fix:
is_enrolled = add(spm_unit, period.this_year, ["is_enrolled_in_ccdf"]) > 0

is_enrolled_in_ccdf has definition_period = YEAR. Using add() with a MONTH period on a YEAR boolean produces True/12 = 0.083, which passes > 0 but is incorrect. Same root cause as Critical 1.

Impact: Same as Critical 1. Fragile behavior that works by accident.


Should Address

SHOULD ADDRESS 1: Missing 2020-10-01 continuing_limit_smi transition (50% to 65%)

Source: Regulatory review (/tmp/review-program-regulatory.md, Section on Income Eligibility)

The regulatory reviewer documents three historical thresholds for the continuing income limit:

  • Before Oct 2023: 50% SMI
  • Oct 2023 to Sep 2024: 65% SMI
  • Oct 2024 onward: 85% SMI

Per CGA 2020-R-0274, the continuing limit was 50% SMI historically and moved to 65% before reaching 85%. If the parameter file continuing_limit_smi.yaml only has transitions at 2020-01-01 (50%) and 2024-10-01 (85%), it is missing the intermediate 2023-10-01 step to 65%. This means simulations for the Oct 2023 -- Sep 2024 period would use incorrect thresholds.

Impact: Incorrect eligibility determinations for the Oct 2023 -- Sep 2024 historical period. Not blocking for current-year (2025+) simulations.

Recommendation: Add a 2023-10-01: 0.65 entry to continuing_limit_smi.yaml if not already present.


SHOULD ADDRESS 2: Possible stale recreational rate ($198 vs $193 at 2024-01-01)

Source: Regulatory review (/tmp/review-program-regulatory.md, Issue 3)

For some pre-Jul-2025 combinations (e.g., Eastern FTP School-Age), the recreational rate ($198) differs slightly from the center rate ($193) in the Jan 2024 period. The regulatory reviewer notes this is "consistent with the official rate sheets" but the exact 2024 rate sheet was not provided for verification.

Impact: Low. Only affects the Jan 2024 -- Jun 2025 historical period. Jul 2025+ values are confirmed correct (recreational matches center per PDF-2).

Recommendation: Verify against the Jan 2024 rate sheet if available. If the $198 recreational value is confirmed, no change needed.


SHOULD ADDRESS 3: Parameter description verbs not in standard verb list

Source: Code validator (/tmp/review-program-code.md, Warnings 1-3)

8 parameter files use non-standard description verbs:

  • 2 files use "determines" (should be "sets")
  • 1 file uses "counts" (should be "uses")
  • 5 region files use "defines" (should be "sets")

Files affected:

  • age_threshold/age_group.yaml, care_level.yaml -- "determines" to "sets"
  • income/sources.yaml -- "counts" to "uses"
  • region/eastern.yaml, region/north_central.yaml, region/northwest.yaml, region/south_central.yaml, region/southwest.yaml -- "defines" to "sets"

Suggestions (Non-blocking)

SUGGESTION 1: Region mapping uses county approximation of town-level assignments

Source: Both regulatory review and PDF audit (/tmp/review-program-regulatory.md Issue 4; /tmp/review-program-pdf-rates.md Section D1)

CT C4K regions are defined by town, not county. Some towns in New Haven County (Naugatuck, Waterbury, Cheshire, etc.) belong to the Northwest region per the official town list, but the repo assigns all of New Haven County to South Central. This is a known modeling simplification due to PolicyEngine's county-level geography and is well-documented in the code.

Impact: Some families near region boundaries may receive slightly incorrect rates. No fix available within the current geographic model.


SUGGESTION 2: Weekly rate stored in MONTH-defined variable (ct_c4k_payment_rate)

Source: Regulatory review (/tmp/review-program-regulatory.md, Issue 2)

ct_c4k_payment_rate has definition_period = MONTH but returns a weekly dollar amount. The label says "weekly payment rate per child" which helps, but if a future developer accesses this from a YEAR formula, auto-conversion would produce 12x weekly instead of 52x weekly.

Recommendation: Add a brief code comment noting the value is weekly despite the MONTH period.


SUGGESTION 3: Remove unnecessary code comment in ct_c4k.py

Source: Code validator (/tmp/review-program-code.md, Critical 3 -- reclassified as Suggestion)

# Cap at actual childcare expenses   <-- remove this
actual_expenses = spm_unit("spm_unit_pre_subsidy_childcare_expenses", period)

The variable name is self-documenting; the comment is redundant per code style guidelines.


SUGGESTION 4: Integration test absolute_error_margin: 1 is coarse

Source: Regulatory review (/tmp/review-program-regulatory.md, Issue 1)

Integration tests use absolute_error_margin: 1 which allows $1 tolerance. This is adequate for the weekly-to-monthly conversion rounding (rate * 52 / 12) but coarser than the 0.01 used in unit tests.

Impact: None -- the margin covers legitimate floating-point rounding. No change needed.


SUGGESTION 5: Shorten region mapping comment block

Source: Code validator (/tmp/review-program-code.md, Warning 7)

The 5-line comment in ct_c4k_region.py explaining the county approximation could be condensed to a one-liner:

# NOTE: Uses county approximation; CT C4K regions are town-based.

PDF Audit Summary

Source: /tmp/review-program-pdf-rates.md

Category Checked Matched Mismatched
Center-based rates (Jul 2025) 60 60 0
Licensed family rates (Jul 2025) 60 60 0
Unlicensed relative rates (Jul 2025) 60 60 0
Recreational rates (Jul 2025) 60 60 0
Care level thresholds 4 4 0
Age group thresholds 3 3 0
Family fee brackets (post-2025) 4 4 0
Family fee brackets (pre-2025) 5 5 0
Total 256 256 0

Items needing external verification (not in provided PDFs): special needs supplement (15%), accreditation bonus (5%), child age thresholds (13/19), income sources/deductions, initial income limit (60% SMI). These are referenced to RCSA regulations and CGA reports not included in the audit set.


Validation Summary

Aspect Result
Regulatory accuracy CORRECT -- all major components verified against official sources
Payment rates (PDF audit) 240/240 match, 0 mismatches (256/256 including thresholds and fees)
Code patterns 2 critical period mismatches, 10 warnings (mostly style)
Test coverage 127 test cases across 11 files, 9/10 coverage score
CCDF integration Correct reuse of federal variables
Naming conventions All follow ct_c4k_* pattern
Vectorization All formulas use numpy vectorized operations
Hardcoded values None found -- all parameterized
Changelog Present (changelog.d/ct-ccap.added.md)

Review Severity

REQUEST_CHANGES

Two critical period mismatch bugs in ct_c4k_eligible.py and ct_c4k_income_eligible.py must be fixed before merge. These cause YEAR-defined boolean variables to be auto-converted to ~0.083 when accessed with MONTH period. While current tests pass (0.083 is truthy), the behavior is incorrect and fragile.

Required before merge:

  1. Fix ct_c4k_eligible.py: use period.this_year for is_ccdf_asset_eligible and meets_ccdf_activity_test
  2. Fix ct_c4k_income_eligible.py: use period.this_year for is_enrolled_in_ccdf in add()

Should address (not blocking):

  1. Add missing 2023-10-01: 0.65 transition to continuing_limit_smi.yaml if absent
  2. Verify recreational rate at Jan 2024
  3. Fix 8 parameter description verbs

Suggestions (optional):

  1. Add comment about weekly-rate-in-MONTH-variable
  2. Remove redundant code comment
  3. Shorten region comment block

- Fix YEAR boolean period mismatches in ct_c4k_eligible.py (is_ccdf_asset_eligible)
  and ct_c4k_income_eligible.py (is_enrolled_in_ccdf) to use period.this_year
- Replace federal meets_ccdf_activity_test with formula-based ct_c4k_meets_activity_test
  checking employment, self-employment, TFA eligibility, and teen parent in school
- Add teen_parent age threshold parameter (RCSA 17b-749-04)
- Tighten integration test error margin from 1 to 0.1 with precise expected values
- Update integration Case 10 to realistic TFA-eligible scenario
- Remove redundant comment in ct_c4k.py, shorten region comment in ct_c4k_region.py

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@hua7450
Copy link
Collaborator Author

hua7450 commented Mar 25, 2026

Review Fixes Applied

Addressed findings from the program review:

Critical (fixed)

  1. Period mismatch in ct_c4k_eligible.py: is_ccdf_asset_eligible (YEAR boolean) now uses period.this_year
  2. Period mismatch in ct_c4k_income_eligible.py: is_enrolled_in_ccdf (YEAR boolean) in add() now uses period.this_year
  3. Activity test: Replaced federal meets_ccdf_activity_test (bare input) with formula-based ct_c4k_meets_activity_test — checks employment, self-employment, TFA eligibility, and teen parent in K-12 school per RCSA 17b-749-04. Teen parent age threshold (20) parameterized in age_threshold/teen_parent.yaml. JFES not modeled (requires administrative data).

Should-address

  1. continuing_limit_smi.yaml already had the 2023-10-01: 0.65 transition — no change needed
  2. Recreational rate at Jan 2024 ($198) confirmed correct — no change needed

Suggestions (applied)

  1. Removed redundant comment in ct_c4k.py
  2. Shortened region mapping comment in ct_c4k_region.py
  3. Tightened integration test absolute_error_margin from 1 to 0.1 with precise expected values

Not applied (per author decision)

  • Parameter description verb changes (determines/defines/counts) — kept as-is

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.

Add Connecticut Child Care Assistance Program (CCAP)

2 participants