Implement West Virginia Child Care Assistance Program (CCAP)#7948
Conversation
Adds eligibility, income testing, copayment, provider rates, and benefit calculation for WV's CCDF-funded child care subsidy program. Ref PolicyEngine#7947 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7948 +/- ##
===========================================
- Coverage 100.00% 97.76% -2.24%
===========================================
Files 1 16 +15
Lines 13 269 +256
Branches 0 2 +2
===========================================
+ Hits 13 263 +250
- Misses 0 6 +6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
- Age groups: Infant 0-24mo, Toddler 25-36mo, Preschool 37-59mo (was wrong) - 18 rate values corrected for Toddler + Preschool across 3 provider types - Initial FPL limit: 150% → 185% (150% was unsupported by documentation) - 16 page references fixed (+7 offset for Policy Manual, off-by-1 for State Plan) - 9 reference titles updated with specific subsections - Activity hours and copay references corrected to proper sections Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ge=8) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address findings from /review-program: - C1: Remove unsupported 150%/185% FPL two-tier income test. Per CCDF Plan Sec 2.2.4 / 2.2.5(a), WV uses a single 85% SMI ceiling with graduated phase-out marked "Not applicable". Drop fpl_limit/ params and wv_ccap_enrolled variable; rewrite wv_ccap_income_eligible to apply only the 85% SMI test (plus is_tanf_enrolled bypass). - C2: Annotate wv_ccap_income_eligible to note the TANF bypass is applied to all enrolled units; the Sec 3.2.1.4 children-only exception is not yet modeled. - C3: Remove is_tax_unit_dependent filter from wv_ccap_eligible_child. Manual Sec 3.2 requires residency, not tax dependency, and the variable-patterns skill forbids is_tax_unit_dependent for benefit eligibility. - C4: Remove min_parent_age=18 hard floor from wv_ccap_eligible. Sec 1.1.2 is a glossary definition; Sec 1.1.10, 4.5.3.6, and 4.5.6 explicitly contemplate minor parents in qualifying activity. - C5: Fix informal/relative age boundary off-by-one. Appendix B groups 24 months as INFANT ($7.50/day); change threshold from 24 to 25 so AGE_2_AND_OVER ($6.00/day) starts at 25 months. - C6: Add inline note in wv_ccap_copay documenting that the percent-of- income scale is a simplified approximation of Appendix A's 176-cell daily-dollar table (kept for now per reviewer direction). - C7: Tighten wv_child_care_subsidies test margin from 1 to 0.01 and add "Case 1," prefix. - C8: Fix countable_income/sources.yaml page anchor from #page=49 (Ch.5 Sec 5.0 overview) to #page=51 (Sec 5.2.1 start of countable income lists). Test updates reflect the SMI-only income test: cases that previously asserted ineligibility above 185% FPL now use income above 85% SMI; cases that toggled wv_ccap_enrolled drop the flag; informal-age 24mo expectation flips to UNDER_2; non-tax-dependent child case asserts eligibility per residency rule; minor-parent case asserts eligibility. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace 85% SMI sole income cap with 185% FPL Appendix A intake cap per Manual §3.2.1. Remove orphan smi_rate parameter. - Multiply copay by number of eligible children, capped at 3, per Manual §6.4.3 per-child fee structure. - Implement Manual §7.2.7.3-7.2.7.4 piecewise billing rule (Monthly Rate = 20 × daily when 13-20 attendance days; per-day otherwise); cap per-child reimbursement at actual provider charge. - Register wv_child_care_subsidies in spm_unit_benefits aggregator and household_state_benefits.yaml so benefits flow to poverty measures. - Delete unreachable minor-parent test case (Case 6) and update docstring to note the modeling gap; trim multi-line policy comments. - Fix #page=1 anchors on Appendix B and Sliding Fee Scale references; copay rate now cites State Plan #page=40 instead of #page=39. - Add wv_ccap_activity_eligible.yaml test (6 cases covering work-hour, student, two-parent, and child-only branches). - Recalibrate income-threshold edge cases to 185% FPL boundaries. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…into wv-ccap # Conflicts: # uv.lock
PavelMakarchuk
left a comment
There was a problem hiding this comment.
Program Review — WV Child Care Assistance Program (PR #7948)
Source Documents
- PDFs: WV BFA Subsidy Policy Manual (Oct 2024, 141 pp), Appendix A Sliding Fee Scale (7+ pages, family sizes 1-9 published, 10-11 partial), Appendix B Rate Structure (2 pp), CCDF State Plan 2025-2027 (212 pp)
- Year: 2024 (Oct 2024 effective dates)
- Scope: New state program (61 files, 4809 additions)
CI Status
All checks pass (including codecov/patch and codecov/project).
Critical (Must Fix)
- Dead variable
wv_ccap_daily_benefit— defined atvariables/gov/states/wv/dhhr/ccap/wv_ccap_daily_benefit.pyand tested intests/.../wv_ccap_daily_benefit.yamlbut never called by any other variable in production. Either remove the file and its tests, OR refactorwv_ccap.pyto use it.
Should Address
- PR body inaccurate about Appendix A coverage — PR claims sizes 8-11 are unpublished extrapolation. In reality sizes 8 and 9 are fully published in Appendix A; only sizes 10 (missing 185% row) and 11 (missing 170%/180%/185% rows) involve any extrapolation, and the carry-forward is correctly implemented. Update PR description.
- Redundant
military_retirement_payin income sources — Policy Manual §5.2.4.4 lists only "Pensions and annuities" and §5.2.4.9 "Veteran's benefits". Likely double-counted withpension_income. Either remove or document why both are needed. monthly_care_days < 13branch never tested — third arm of the monthly-rate transformation inwv_ccap.pyhas no coverage.- Family size 12+ fallback never tested —
selectdefault=size_11branch inwv_ccap_copay.pyis uncovered. - Multi-child 3/N reduction never bites — no test exercises N≥4 eligible children where the cap actually reduces copay.
- Code duplication of
weekly_days × (WEEKS_IN_YEAR / MONTHS_IN_YEAR)across 3 files (wv_ccap.py,wv_ccap_copay.py,wv_ccap_daily_benefit.py) — extract intermediate variable. max_billed_children.yamlusesunit: int— project convention prefers/1.- Page-anchor off-by-one (2 instances):
eligibility/activity_hours.yamlandeligibility/wv_ccap_activity_eligible.pycite#page=32for §3.6.1; actual file page is 33 (offset 8, printed p.25). PDF audit clarifies offset is actually 7, not 8 as the manifest reported — re-verify all#page=anchors. activity_hours.yamldeclaresperiod: yearfor a weekly value (20 hrs/week). Useperiod: eternityinstead.- CCDF State Plan page anchors not verified — 5 references into PDF 4 (
#page=39,#page=42, etc. for §3.1.1 and §2.2) not auto-verified.max_share.yamlcites#page=39while the variable cites#page=42for the same §3.1.1 — fix inconsistency. - Trailing zeros in copay YAMLs — 11
copayment/rate/size_*.yamlfiles have values like1.50,2.00violating project style (use1.5,2). 0.4001-style bracket thresholds — workaround for "above 40% FPL" inclusivity, accepted convention but worth documenting.
Suggestions
- Consider relabeling
TIER_IIIenum to"Tier III - Accreditation"to mirror Appendix B's exact column header. - Add Manual §7.2.7.2.B as secondary reference in
rates/*.yaml(documents the +$3/+$6 tier ladder). billing/monthly_rate_{min,max}_days.yamluseperiod: yearbut represent days-per-month thresholds;period: eternityfits better.- Add a consolidated
wv_ccap_countable_incometest exercising the full source list (unemployment, pension, dividends, etc.). - Add foster-waiver, TANF-bypass, multi-child, supplements, and 7% cap cases to
integration.yaml(currently tested only at unit/edge-case level). - Tighten Manual reference titles to include subsection (e.g., §6.4.3.4) for precision.
Investigated and Cleared
- Tier III rates in Appendix B — initially flagged because manifest said "Tier I/II"; actually Appendix B publishes three tiers labeled "Tier I", "Tier II", "Tier III - Accreditation". All 36 main cells match.
- No
sources/directory orlessons/agent-lessons.mdregression at repo root (unlike PR #8208 from same author). - Aggregator wiring —
wv_child_care_subsidiescorrectly registered inspm_unit_benefits.py,household_state_benefits.yaml,gov/hhs/ccdf/child_care_subsidy_programs.yaml, andprograms.yaml. - Federal helpers correctly reused —
is_ccdf_immigration_eligible_child,is_ccdf_asset_eligible,is_tanf_enrolledused directly, not duplicated. - TANF excluded from countable income sources — intentional to break circular dependency; categorical bypass via
is_tanf_enrolled. Documented. - 85% SMI Over-Income Policy Exception (§4.7.1) correctly noted as Not-Modeled, not accidentally implemented.
PDF Audit Summary
| Topic | Cells Confirmed | Mismatches |
|---|---|---|
| Eligibility (child age, special-needs age, activity hours) | 3 | 0 |
| Income (FPL limit, sources list) | 2 + 14 source vars | 0 |
| Copay (sizes 1-9 published, sizes 10-11 partial carry-forward) | ~150 cells (16 brackets × ~9 sizes) | 0 |
| Provider rates (Center + Family Home + Facility × 4 ages × 3 tiers) | 36 | 0 |
| Out-of-School Time, Informal/Relative, Supplements | 4 | 0 |
| Age categories + billing thresholds | 6 | 0 |
Validation Summary
| Check | Result |
|---|---|
| Regulatory Accuracy | Largely correct; activity_hours formula verified |
| Reference Quality | Clean; 2 minor page-anchor off-by-one warnings |
| Code Patterns | 1 critical (dead variable); ~6 SHOULD items |
| Test Coverage | Strong (102 cases); 5 valuable gaps to add |
| PDF Value Audit | 0 value mismatches across ~200 audited cells |
| CI Status | All passing |
Recommended Severity: COMMENT (close to APPROVE)
Rationale: No value or regulatory mismatches found. One critical code issue (dead variable) and several should-address items (test gaps, redundant income source, PR-body inaccuracy, code duplication). No blockers — author should address the dead variable plus the should items before merge, but the implementation itself is sound.
Next Steps
- Author: address the 1 critical (dead variable) + the should items
- To auto-fix:
/fix-pr 7948
- Switch from childcare_days_per_week to childcare_attending_days_per_month to eliminate the weeks-to-months conversion duplicated across three files - Refactor wv_ccap.py to call wv_ccap_daily_benefit (no longer dead) - Document why military_retirement_pay is included alongside pension_income - Add tests for <13 monthly days, family size 12+, and 3/N billing cap - Fix max_billed_children unit from int to /1 - Bump §3.6.1 page anchor 32 → 33 in activity_hours and activity_eligible - Align wv_ccap_copay CCDF page anchor 42 → 39 with max_share - Strip trailing zeros from all copayment/rate/size_*.yaml Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Implements West Virginia's Child Care Assistance Program (CCAP) — the state's CCDF-funded child care subsidy providing provider reimbursement minus parent copayment for eligible families.
Closes #7947
Regulatory Authority
Eligibility
wv_ccap_eligible_child(age < 13)wv_ccap_eligible_child(has_developmental_delaybranch — matches the manual's developmental-delay definition)is_ccdf_immigration_eligible_child(federal variable)defined_for = StateCode.WVwv_ccap_activity_eligible(weekly hours ≥ 20 OR full-time student)wv_ccap_activity_eligible(all heads/spouses must qualify)wv_ccap_income_eligiblewithincome/fpl_limitparameteris_ccdf_asset_eligible(federal variable)is_tanf_enrolledbypasses income testIncome test notes: We model the Appendix A initial intake cap (~185% FPL across family sizes). The 85% SMI Over-Income Policy Exception (Manual §4.7.1) for already-enrolled recipients is not modeled at the moment — we don't track enrollment status as distinct from initial application.
Income Rules
Countable income (
wv_ccap_countable_income, usesaddsfromsources.yaml):employment_income,self_employment_income,farm_operations_incomesocial_security,ssi,unemployment_compensation,workers_compensation,pension_income,alimony_income,child_support_received,dividend_income,interest_income,rental_income,veterans_benefits,military_retirement_paymilitary_retirement_payis included alongsidepension_incomebecause PolicyEngine records military retirement separately and Manual §5.2.4.4 counts pensions and annuities.Excluded (not in sources list): SNAP, LIEAP/LIHEAP, foster care payments, educational grants/loans, one-time lump sums. TANF is excluded from the list to avoid a circular dependency (
is_tanf_enrolledalready triggers the categorical bypass for active TANF recipients).Provider Rates (Appendix B, daily, effective Oct 1, 2024)
Family Child Care Home
Family Child Care Facility
Child Care Center
Other Care Types
Co-payment
The implementation reads Appendix A's full 2D sliding fee table: 16 FPL brackets (40%, 50%, …, 180%, 185%) × 11 family-size columns (1 through 11+ person). Sizes 1–9 are fully published; size 10 lacks the 185% row and size 11 lacks the 170%/180%/185% rows — the last filled bracket carries forward. Each cell returns a daily fee in USD per child (Manual §6.4.3.1).
Caps and exemptions:
Approximations (documented in code):
3/Nscaling because PolicyEngine doesn't track child age ordering. Exact when all eligible children attend the same days; otherwise diverges slightly based on which children attend more.fpl_ratiowith each simulation period's current FPG instead of the 2024 FPG that Appendix A is calibrated against (~3.5% drift; matters only at bracket boundaries).Benefit Calculation
Per Manual §7.2.7.3-7.2.7.4 (piecewise billing rule) and Manual §7.2.7.2 ("Monthly rates do not apply to rate supplements"):
Where:
daily_rate= base rate from Appendix B (provider type × tier × age)daily_supplement= special needs (+$3/day) and/or non-traditional hours (+$6/day) — billed for actual days of care only, NOT scaled to 20 days under the monthly-rate rulewv_ccap_daily_benefit= a view variable that returnsmin(rate + supplements, daily_charge)— used for per-day billing when monthly days fall outside the 13–20 windowcopay= single per-family amount from the Appendix A 2D lookupNot Modeled (by design)
Eligibility
is_tax_unit_head_or_spouseexcludes under-18sActivity requirements
is_full_time_studentbroader than §3.6.3 credit-hour ruleIncome test
is_tanf_enrolleddoes not distinguish child-only TANF cases at the momentCo-payment / fees
Billing / provider payment
Files Added
Review Response
Addresses @PavelMakarchuk's 2026-05-25 review:
wv_ccap_daily_benefit—wv_ccap.pynow calls it for the per-day billing branch.weekly_days × WEEKS_IN_YEAR / MONTHS_IN_YEAR— replaced with thechildcare_attending_days_per_monthinput variable, so each consumer reads it directly.military_retirement_payrationale — added a comment insources.yamland a note above.<13monthly-days branch untested — addedintegration.yamlCase 8.wv_ccap_copay.yamlCase 5.wv_ccap_copay.yamlCase 6.max_billed_children.yamlunit: int— changed to/1.activity_hours.yamlandwv_ccap_activity_eligible.py.wv_ccap_copay.pynow matchesmax_share.yamlat#page=39.copayment/rate/size_*.yaml— stripped across all 11 files.Test Plan