Skip to content

Implement Utah Child Care Assistance Program (CCAP) - #9063

Merged
hua7450 merged 11 commits into
PolicyEngine:mainfrom
hua7450:ut-ccap
Aug 13, 2026
Merged

Implement Utah Child Care Assistance Program (CCAP)#9063
hua7450 merged 11 commits into
PolicyEngine:mainfrom
hua7450:ut-ccap

Conversation

@hua7450

@hua7450 hua7450 commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements Utah Child Care Assistance (CC) — the state's CCDF child care subsidy (Utah DWS Office of Child Care, Utah Admin. Code R986-700) — in PolicyEngine. The program is modeled as the Employment Support (ES) Child Care subsidy: a per-child market-rate reimbursement cap (Table 3) reduced by a sliding-scale family copayment (Table 4), gated by an 85%-of-state-median-income test and the federal CCDF asset limit.

Closes #9061

Regulatory Authority

Income Eligibility Tests

Test How modeled Source
Countable income ≤ 85% of state median income for household size ut_ccap_income_eligible compares ut_ccap_countable_income against smi_rate (0.85) × hhs_smi. Design decision: the eligibility ceiling is computed dynamically from the existing hhs_smi variable rather than reading the encoded Table 4 Group-16 dollar amounts, so the ceiling tracks annual SMI updates. The copay income-group classification (REQ-024) still uses the encoded Table 4 income-group upper bounds — only the eligibility ceiling is dynamic. R986-700-710(3); fact sheet; Table 4
Family assets ≤ CCDF limit ($1,000,000) Reuses federal is_ccdf_asset_eligible (no state parameter) R986-700-710; 45 CFR 98.20
Child under age 13 (under 18 if special needs) ut_ccap_eligible_child with child_age_limit (13) and disabled_child_age_limit (18), using is_disabled as the special-needs proxy R986-700-702; fact sheet
Child is a U.S. citizen or authorized non-citizen ut_ccap_eligible_child reuses federal is_ccdf_immigration_eligible_child; parent SSN/immigration status is not gated R986-700-702; fact sheet
Work/activity hours: single parent ≥15 hr/wk; two-parent one ≥15 and other ≥30 ut_ccap_activity_eligible with single_parent_min_hours (15), two_parent_first_min_hours (15), two_parent_second_min_hours (30) R986-700-709; fact sheet

Income Deductions & Exemptions

Countable income (ut_ccap_countable_income) starts from ut_ccap_gross_income — an adds-list of earned + unearned parent income defined in income/sources.yaml (R986-700-710) — then applies the following, all read at the monthly period:

Item How modeled Source
Count earned + unearned income of parent(s); both parents count income/sources.yaml adds-list summed at SPMUnit R986-700-710
Child support paid directly to a parent = child's unearned income child_support_received included in sources.yaml R986-700-710
Exclude SSI benefits ssi omitted from sources.yaml (never added) R986-700-710
Exclude earned income of a child who is not a parent Subtract minor (non-parent) earned income below child_earned_income_exclusion_age (18) R986-700-710
Deduct first $50 of child support received/month deductions/child_support_received_deduction (50) R986-700-710
Deduct court-ordered child support and alimony paid out child_support_expense + alimony_expense (full deduction, no parameter) R986-700-710
Deduct $100 per person with countable earned income deductions/earned_income_deduction (100) × number of earners R986-700-710
Deduct $100 automatic medical deduction deductions/medical_deduction (100) per SPM unit R986-700-710

Benefit Calculation

ut_ccap (SPMUnit, MONTH) implements the R986-700-713 lower-of rule reduced by the Table 4 copayment:

ut_ccap = max_(min_(billed childcare expenses, Σ per-child market-rate caps) − copay, 0)
  • Per-child market-rate cap (Table 3): rates/market_rate.yaml is a breakdown keyed by ut_ccap_provider_type (5 provider types) × ut_ccap_age_group (6 age bands, derived from age-in-months via rates/age_group.yaml). ut_ccap_market_rate looks up the monthly cap per child.
  • Lower-of rule (simplified): family subsidy = min_(spm_unit_pre_subsidy_childcare_expenses, Σ per-child caps). Billed expenses proxy the provider's actual established rate; the separate hourly-unit-cost × approved-hours path is not modeled (documented simplification, per scope Key Decision 5). Per-child caps are pooled to the SPM unit.
  • Sliding-scale copayment (Table 4): ut_ccap_copay looks up copay/amount.yaml, keyed by household size (2 through 12+, bounded by copay/min_household_size and copay/max_household_size) × income group (ut_ccap_income_group, 16 brackets classified against copay/income_limits.yaml) × child-count group (ut_ccap_child_count_group: 1 / 2 / >2 children in care). The full published dollar table is encoded directly (interior bracket boundaries are irregular and not derived).
  • Group 1 = $0 copay: the lowest income group (upper bound ≈ 100% FPG monthly) is encoded as $0 in copay/amount.yaml.
  • Copay exceeds cost of care ⇒ ineligible: handled naturally by the max_(… − copay, 0) floor and reflected in ut_ccap_eligible via a copay-affordability check.

The state YEAR aggregator ut_child_care_subsidies (adds = ["ut_ccap"], defined_for = StateCode.UT) feeds the federal child_care_subsidies, and is registered in child_care_subsidy_programs.yaml.

Requirements Coverage

All 21 in-scope requirements are implemented with parameter, variable, and test coverage.

REQ Description Param Variable Test
REQ-001 Child under 13 (under 18 if special needs) eligibility/child_age_limit (13), disabled_child_age_limit (18) ut_ccap_eligible_child ut_ccap_eligible_child 1-6
REQ-004 Child citizen / authorized non-citizen (reuse) — (reuse) ut_ccap_eligible_childis_ccdf_immigration_eligible_child ut_ccap_eligible_child 7-9
REQ-006 Activity hours: single ≥15; two-parent 15 & 30 single_parent_min_hours (15), two_parent_first_min_hours (15), two_parent_second_min_hours (30) ut_ccap_activity_eligible ut_ccap_activity_eligible 1-7
REQ-009 Countable income ≤ 85% SMI (dynamic) income/smi_rate (0.85) ut_ccap_income_eligible ut_ccap_income_eligible 1-4 + integration
REQ-010 Assets ≤ CCDF $1,000,000 (reuse) — (reuse) ut_ccap_eligibleis_ccdf_asset_eligible ut_ccap_eligible 4
REQ-012 Count earned + unearned of both parents income/sources.yaml ut_ccap_gross_income, ut_ccap_countable_income ut_ccap_countable_income 1, 2, 10
REQ-013 Child support to parent = child's unearned income income/sources.yaml (child_support_received) ut_ccap_gross_income ut_ccap_countable_income 5, 6, 14
REQ-014 Exclude SSI benefits income/sources.yaml (omits ssi) ut_ccap_gross_income ut_ccap_countable_income 8 + integration 5
REQ-015 Exclude non-parent minor's earned income income/child_earned_income_exclusion_age (18) ut_ccap_countable_income ut_ccap_countable_income 3, 4, 11
REQ-017 Deduct first $50 child support received deductions/child_support_received_deduction (50) ut_ccap_countable_income ut_ccap_countable_income 5, 6, 14
REQ-018 Deduct child support & alimony paid out — (child_support_expense + alimony_expense) ut_ccap_countable_income ut_ccap_countable_income 7, 12
REQ-019 Deduct $100 per earner deductions/earned_income_deduction (100) ut_ccap_countable_income ut_ccap_countable_income 1, 2, 13
REQ-020 Deduct $100 automatic medical deductions/medical_deduction (100) ut_ccap_countable_income all ut_ccap_countable_income cases
REQ-021 Per-child subsidy = lower of (cap, billed expenses) ut_ccap ut_ccap 1, 2
REQ-022 Market-rate cap by provider type (5) × age band (6) rates/market_rate.yaml, rates/age_group.yaml ut_ccap_market_rate, ut_ccap_provider_type, ut_ccap_age_group ut_ccap_market_rate (all 5×6), ut_ccap_age_group
REQ-023 Copay subtracted from standard subsidy ut_ccap ut_ccap 1, 2, 6, 8
REQ-024 Copay sliding scale: size (2-12+) × income group (16) × #children (1/2/>2) copay/amount.yaml, copay/income_limits.yaml, copay/min_household_size, copay/max_household_size ut_ccap_copay, ut_ccap_income_group, ut_ccap_child_count_group ut_ccap_copay 1-10, ut_ccap_income_group 1-12, ut_ccap_child_count_group 1-6
REQ-025 Group 1 = $0 copay copay/amount.yaml (group 1 = 0) ut_ccap_copay ut_ccap_copay 1 + integration 3, 9, 10
REQ-026 Copay > cost of care ⇒ ineligible ut_ccap_eligible (copay-affordable check) ut_ccap_eligible 7, 8; ut_ccap 3, 4, 7; integration 4
REQ-028 Tiered/formula benefit, monthly units ut_ccap (MONTH, formula) ut_ccap (structural)
REQ-029 YEAR aggregator + registry registration ut_child_care_subsidies (adds ut_ccap); registered in child_care_subsidy_programs.yaml ut_child_care_subsidies 1, 2

Not Modeled

REQ What Why excluded
REQ-002 Child needs ≥8 hours of care/month Care-need hours are not in the model; assume any family with billed care meets it
REQ-003 Applicant is parent / foster / relative / guardian No PE flag distinguishes relative/guardian caretaker status; assumed satisfied for the SPM unit
REQ-005 Parent SSN / work-authorization not required Informational — the formula intentionally never gates on parent immigration or SSN; no code needed
REQ-007 Net income ≥ minimum wage for hours worked Verification rule not simulatable without per-parent wage/hours
REQ-008 Education/training pathway (24-month limit) Not tracked in the model; ES-CC working pathway is the modeled scope
REQ-011 12-month certification period Informational — PE computes a snapshot month; no monthly re-test logic needed
REQ-016 Exclude DHHS independent-living grants to minor parents No corresponding PE income variable to exclude
REQ-027 Enhanced Subsidy Grant (CCQS quality supplement, R986-700-742) Deferred by design; no quality-tier input in the model

Historical Notes

  • Parameter vintages and backdating. Table 3 (rates/market_rate.yaml) is effective October 1, 2024; Table 4 (copay/amount.yaml, copay/income_limits.yaml) is effective April 1, 2026; the 85% SMI fact-sheet income limits are effective October 1, 2025. Every parameter entry is placed at a single 2020-01-01 date — PolicyEngine's backdating floor — as a documented approximation. Earlier editions of Tables 3 and 4 are not encoded, so periods before their true effective dates use the current tables. Each file's header comment documents this.
  • Documented source typo. In Table 4, the published lower bound for household size 5, group 3 reads 3,644.01, an apparent typo for 3,664.01 (the size 5, group 2 upper bound is 3,664). Only upper bounds are encoded in income_limits.yaml, so the typo has no effect on the model, but it is documented in that file's header comment for future maintainers.
  • Identical provider rows. In Table 3, the License-Exempt home (family, friend, and neighbor) and Licensed Commercial Preschool rows publish identical rates (495/443/437/416/413/385). They are kept as distinct ut_ccap_provider_type enum values because the source table lists them as separate provider categories.

Files Added

policyengine_us/parameters/gov/states/ut/dwf/ccap/
  copay/amount.yaml
  copay/income_limits.yaml
  copay/max_household_size.yaml
  copay/min_household_size.yaml
  eligibility/child_age_limit.yaml
  eligibility/disabled_child_age_limit.yaml
  eligibility/single_parent_min_hours.yaml
  eligibility/two_parent_first_min_hours.yaml
  eligibility/two_parent_second_min_hours.yaml
  income/child_earned_income_exclusion_age.yaml
  income/deductions/child_support_received_deduction.yaml
  income/deductions/earned_income_deduction.yaml
  income/deductions/medical_deduction.yaml
  income/smi_rate.yaml
  income/sources.yaml
  rates/age_group.yaml
  rates/market_rate.yaml

policyengine_us/variables/gov/states/ut/dwf/ccap/
  ut_ccap.py
  ut_child_care_subsidies.py
  copay/ut_ccap_child_count_group.py
  copay/ut_ccap_copay.py
  copay/ut_ccap_income_group.py
  eligibility/ut_ccap_activity_eligible.py
  eligibility/ut_ccap_eligible.py
  eligibility/ut_ccap_eligible_child.py
  eligibility/ut_ccap_income_eligible.py
  income/ut_ccap_countable_income.py
  income/ut_ccap_gross_income.py
  rates/ut_ccap_age_group.py
  rates/ut_ccap_market_rate.py
  rates/ut_ccap_provider_type.py

policyengine_us/tests/policy/baseline/gov/states/ut/dwf/ccap/
  integration.yaml
  ut_ccap.yaml
  ut_child_care_subsidies.yaml
  copay/ut_ccap_child_count_group.yaml
  copay/ut_ccap_copay.yaml
  copay/ut_ccap_income_group.yaml
  eligibility/ut_ccap_activity_eligible.yaml
  eligibility/ut_ccap_eligible.yaml
  eligibility/ut_ccap_eligible_child.yaml
  eligibility/ut_ccap_income_eligible.yaml
  income/ut_ccap_countable_income.yaml
  rates/ut_ccap_age_group.yaml
  rates/ut_ccap_market_rate.yaml

Also modified: parameters/gov/hhs/ccdf/child_care_subsidy_programs.yaml (registers ut_child_care_subsidies), programs.yaml (adds UT to CCDF coverage and a Utah CCAP state implementation entry), and a changelog.d/ut-ccap.added.md fragment.

Test plan

  • 113/113 tests pass locally (13 YAML files)
  • CI passes

hua7450 and others added 7 commits July 16, 2026 12:19
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…takers

Two fixes from adversarially verified review findings:
- ut_ccap now applies the R986-700-713 lower-of rule per child
  (min of expense share vs Table 3 cap, then summed), mirroring
  mt_ccap, instead of capping pooled expenses against pooled caps.
- Activity and income formulas now treat the client per
  R986-700-702(2) as a parent OR, when no parent is present, the
  tax unit head/spouse (covers specified-relative and guardian
  caretakers while keeping co-resident non-client relatives
  excluded per R986-700-710).

130/130 UT CCAP tests pass, including new pins for per-child
capping and working nonparent caretakers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Correct foster-parent comments in ut_ccap_gross_income and
  ut_ccap_activity_eligible: current R986-700-702(2)(a) makes
  DHHS-reimbursed foster parents eligible clients whose income counts
  (710(3)(g), (4)(c)); the repealed 702(11) "categorically ineligible"
  claim asserted the opposite of current law.
- Drop stale utrules.elaws.us (pre-amendment 2019 text) reference URLs
  from three variables, keeping the current Cornell LII citations.
- Fix ut_ccap_age_group comment subsection: 702(4)(b) -> 702(5)(b).
- Narrow earned.yaml citation to R986-200-236(2)(a)-(e); (f) is
  employer-funded temporary disability, not listed in the comment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hua7450
hua7450 marked this pull request as ready for review July 18, 2026 17:59
@hua7450
hua7450 requested a review from DTrim99 August 12, 2026 13:31
@DTrim99

DTrim99 commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Program Review — Utah Child Care Assistance (new program)

PR #9063 — Utah Child Care Assistance (CCAP / Employment Support Child Care, CCDF). NEW state program. Consolidated from five independent read-only review passes (regulatory, Table 3/4 value audit, code pattern audit, test coverage, PR context).

Scope

Implements Utah's Child Care Assistance Program (CCAP) as a new state benefit under the federal CCDF child-care-subsidy aggregator:

  • Eligibility — eligible child (under 13; up to 18 special-needs), 85% SMI net-income test, CCDF asset limit (reused), activity/work-hours test, and a copay-affordability gate (family ineligible if copay exceeds cost of care).
  • Market-rate cap (Table 3) — 5 provider types × 6 age bands.
  • Copay (Table 4) — copay amount + income-limit schedule across 11 household sizes × 16 income groups × 3 child-count columns.
  • Subsidy + aggregationut_ccap = Σ_child min(expense, market_rate) − copay, floored at 0 (MONTH/SPMUnit) → ut_child_care_subsidies (YEAR wrapper, adds = ["ut_ccap"]) → federal child_care_subsidies.
  • 15 new variables (13 with formulas + 2 input Enums), full parameter tree under gov.states.ut.dwf.ccap.*, plus programs.yaml + child_care_subsidy_programs.yaml wiring.

CI: 31/31 passing.

Branch status: 7 ahead / 425 behind main — VERY STALE. This is the one operational blocker: the branch must be rebased on main and re-tested before merge; merge conflicts are likely at 425 commits of drift.

Sources: Utah Admin. Code R986-700 (sections -702, -707, -709, -710, -713; law.cornell.edu), Table 3 market-rate PDF (table30824.pdf, eff. Oct 1 2024), Table 4 copay/income-limit HTM (eff. Apr 1 2026), and the DWS OCC subsidy fact sheet PDF (85% SMI). All three data sources confirmed reachable.

Verified correct

Rate/copay value audit — SAMPLE, 38 cells checked, 0 mismatch.

  • Table 3: 17 market-rate cells across the 5 provider types × 6 age bands — all exact (full extracted grid legible; 15+ spot-checked to the cent).
  • Table 4: 11 income-limit upper-bound cells + 10 copay rows — all exact.
  • License-Exempt Home == Licensed Commercial Preschool equivalence is genuinely in the source — the PDF prints both rows with identical values (495/443/437/416/413/385). The PR encodes them as two separate provider blocks with identical values and documents it. Faithful, not an invented equivalence.
  • The documented 3,644.01→3,664.01 is a REAL source typo, correctly handled — the transposition lives in a group lower bound; income_limits.yaml encodes only upper bounds, so it never enters the parameters. The PR's comment documents it accurately and the encoding is unaffected. No action needed.
  • n/a-padding is sound — structurally-impossible child-count columns (size-2 can't have 2+ children; size-3 can't have 3+) are padded by repeating the largest published column; the child_count_group/spm_unit_size clamps mean the padded cells can never bind.
  • 85% SMI confirmed verbatim on the DWS fact sheet — "household size is set at 85% of the state median income effective Oct. 1, 2025," corroborating income/smi_rate.yaml = 0.85. The reg (R986-700-710(6)(b)) delegates the percentage to the Department; citing both the fact sheet and the reg is the correct sourcing.

Regulatory confirmations (researched independently, then compared). Every material rule matches R986-700: net-countable ≤ %-of-SMI income test (710(6)(b), correctly on post-deduction income); the four deductions ($50 child support received, support/alimony paid, $100/earner, $100 automatic medical) (710(5)); copay-exceeds-cost ineligibility verbatim (707(3)); Group-1 (≤100% FPL) $0 copay (707(5)(a)); 15 / 30-15 activity hours (709); lesser-of market-rate payment (713); under-13 / under-18-special-needs child ages (702). Asset limit reuses is_ccdf_asset_eligible; immigration reuses is_ccdf_immigration_eligible_child; activity reuses meets_ccdf_activity_test — none reinvented.

Code cleanliness. No hard-coded policy values (every literal is 0/1/2/np.inf; all magnitudes are parameters). Breakdown metadata complete and correct (30 market-rate combos; 11×16×3 copay combos; integer keys via range(), string keys match enums). Entity/period/aggregation chain correct (MONTH ut_ccap → YEAR wrapper → federal roll-up; Person/YEAR expense read at MONTH auto-÷12 to match the monthly cap). All CCDF helpers reused not reinvented. Copay used once as an affordability gate and subtracted once from the subsidy — not double-counted. income_group floored to 1 outside UT; household size clamped. defined_for = StateCode.UT throughout. Changelog + programs.yaml present. Backdated cleanly to a single 2020-01-01 floor (no mid-series toggles).

Test thoroughness. All 13 formula-bearing variables have dedicated would-fail-if-broken tests (plus the 2 input Enums exercised as inputs); no zero-coverage formula. Every core mechanic pinned: 85% SMI boundary (below/at/above/zero + dynamic per-size ceiling in integration), market-rate provider×age lookup, copay schedule (group-1 zero, mid, top, size clamp, group-16 overflow, single-adult floor), all five eligibility gates individually, the copay≤cost-of-care boundary (Cases 7/8), per-child lower-of with per-child capping (strong regression pin against pooled-cap bugs), copay floor at 0 while still eligible, and end-to-end per-child→unit→YEAR→federal roll-up asserting the federal total. No invalid test periods; no non-functional boolean margins.

Critical (must fix)

None.

Should address

  1. Factor the duplicated client-identification block into a shared helper. income/ut_ccap_gross_income.py:47-52, income/ut_ccap_countable_income.py:21-26, and eligibility/ut_ccap_activity_eligible.py:33-36 repeat verbatim:

    is_parent = person("is_parent", period.this_year)
    is_head_or_spouse = person("is_tax_unit_head_or_spouse", period.this_year)
    no_parent_present = spm_unit.sum(is_parent) == 0
    is_client = is_parent | (spm_unit.project(no_parent_present) & is_head_or_spouse)

    The scoping is correctly narrow (person-level is_parent; the head/spouse fallback fires only when no parent is present) — not an over-broad any(), and it is regulatorily defensible per R986-700-710(4). The issue is drift risk from verbatim triplication: extract a person-level ut_ccap_is_client helper. (Combines code-audit S + regulatory G4 — same client-scoping item.)

  2. Independently corroborate 85% SMI via the Group-16 cross-check. Sourced value is confirmed verbatim on the fact sheet (see Verified correct), but the YAML comment's claim that "Table 4 Group-16 upper bounds equal 85% of SMI by household size" is worth a one-line confirmation: Group-16 upper bound ÷ 0.85 should reproduce HHS/Utah SMI by household size. income/smi_rate.yaml:10, ut_ccap_income_eligible. (Regulatory S1.)

  3. Confirm the copay-affordability gate is intentionally on uncapped unit cost. ut_ccap_eligible gates on whole-unit spm_unit_pre_subsidy_childcare_expenses (uncapped) while the benefit builds from per-child min(expense, market_rate). R986-700-707(3) speaks to "the actual cost of child care" (unit-level), so gating on uncapped cost is defensible; the mild inconsistency can let a unit be "eligible" yet receive a benefit floored to 0. Add a one-line docstring note so future editors don't "fix" the apparent divergence. ut_ccap.py:22, ut_ccap_eligible.py:20-22. (Regulatory S3 + code-audit suggestion, deduped.)

  4. Note the bare-input-expense population effect. The copay-affordability gate depends on spm_unit_pre_subsidy_childcare_expenses, a bare input defaulting to 0 in microsim unless imputed. Where expense = 0 the program is inert (benefit = min(expense, cap) − copay, floored). This is the established convention across every state CCAP, NOT a UT defect and not a code change here — record it as a population-level limitation (remedy is dataset imputation). (Regulatory S2.)

  5. Add a paired test asserting License-Exempt-Home == Commercial-Preschool at the same age band. Both rows are priced identically, but tests pin them only at different bands (LICENSE_EXEMPT_HOME under_two=495/school_age=385; COMMERCIAL_PRESCHOOL four_year=416). A one-line divergence in one row at an untested band would pass CI. Add a paired case at one shared band. rates/ut_ccap_market_rate.yaml. (Test MODERATE 1.)

  6. Force a size-2 n/a-padded copay cell to a TWO/THREE_PLUS child-count group. ut_ccap_copay.yaml reaches size-2 only via the one-child column; no case forces a padded TWO/THREE_PLUS cell to confirm it returns the same value. Low risk (padding never binds), but the would-fail-if-broken test is absent. copay/ut_ccap_copay.yaml. (Test MODERATE 2.)

Suggestions

  1. Masked-multiply style — ut_ccap_gross_income.py:57-58 and ut_ccap.py:29 use value * bool_mask inside spm_unit.sum(...). Correct and vectorized; house style prefers spm_unit.sum(where(mask, value, 0)).
  2. copay/ut_ccap_income_group.py:15-16 reaches into private parameter internals (next(iter(p.income_limits._children.values())), len(size_node._children)) to derive the group count; a group_count parameter or reused range(1, 17) is more robust to core changes.
  3. copay/ut_ccap_income_group.py:21np.ones_like(size) on a possibly-float spm_unit_size; np.ones(len(size), dtype=int) is clearer intent.
  4. ut_ccap_activity_eligible.py — reference is a bare string in parentheses (a plain str, not a 1-tuple); visually resembles a tuple missing its trailing comma. Cosmetic.
  5. Pre-2026 copay values are not period-accurate — Table 4 (eff. Apr 1 2026) is backdated to 2020, so 2020–2025 copay/income-limit computations use forward-dated values. Intentional per the scope note; flagged for awareness. (copay/*; also rates/* from Table 3 eff. Oct 1 2024.)
  6. Activity two-parent "second" (15h) boundary tested only from below (Case 3 = 30 & 15 pass; Case 5 = 29 & 29 fail). No case at highest=30, lowest=14 isolating the second bar binding independently when the first is exactly met. Minor; individual thresholds pinned elsewhere. (Test MODERATE 3 / polish.)
  7. Documented modeling limitations (all disclosed in code comments, no action needed): activity min-wage-for-hours and education/training pathways only partially modeled; special-needs proxied by is_disabled, court-supervision and 8-hours/month-need conditions not tracked. (Regulatory G1/G2.)
  8. Optional test hardening: no case varies the monthly benefit across months within the year for the annual roll-up. (Test LOW.)

Caveats

  • The rate/copay audit is a SAMPLE (38 cells / 0 mismatch), not a full cell-by-cell verification. The full extracted Table 3 grid was legible and structure confirmed; Table 4 was sampled repeatedly via WebFetch. Confidence is high but the audit did not exhaustively check every one of the ~500 copay cells.
  • The branch is 425 commits behind main — VERY STALE. It MUST be rebased on main and the full test suite re-run before merge. At 425 commits of drift, merge conflicts are likely (especially in programs.yaml and child_care_subsidy_programs.yaml, both high-churn shared files). CI's current 31/31 is against a stale base and does not guarantee a clean merge. This is the single operational item standing between the PR and merge.

Validation summary

Dimension Result
Regulatory (R986-700) 0 critical; faithful to -702/-707/-709/-710/-713; 3 should (all verification/confirmation asks), 5 suggestion
Rate/copay value audit (SAMPLE) 38 cells / 0 mismatch; typo handled correctly; n/a-padding sound; 85% SMI verbatim; License-Exempt==Commercial equivalence genuine
Code pattern audit 0 critical; no hard-codes; breakdown/entity/period/reuse all correct; 3 should, 5 suggestion
Test coverage 0 critical gaps; all 13 formula vars + core mechanics pinned; 3 moderate (2 equivalence/padding + 1 activity boundary)
CI 31/31 passing (against a stale base — see Caveats)

Review severity: APPROVE

0 criticals across all five passes, the rate/copay sample is 0-mismatch, and the three things a reviewer would most suspect (the 3,644.01 typo, the n/a-padding, the 85% SMI figure) are each independently confirmed correct — the only operational item to close is the 425-behind staleness, resolved by a rebase + re-test, not a code fix.

Next steps

  • /fix-pr 9063 — extract the shared ut_ccap_is_client helper (should-address 1), add the two moderate tests (should-address 5, 6), add the docstring/comment notes (should-address 3, 4), and record the 85% SMI cross-check (should-address 2).
  • Rebase on main (425 behind) and re-run the full YAML/test suite before marking ready — resolve conflicts in programs.yaml and child_care_subsidy_programs.yaml.

🤖 Generated by /review-program (Claude Code)

hua7450 and others added 3 commits August 12, 2026 18:00
Extract the shared ut_ccap_is_client helper, document the uncapped
copay-affordability gate and bare-input expense limitation, record the
85% SMI Group-16 cross-check, and pin the License-Exempt Home ==
Commercial Preschool equivalence and the padded size-2 copay cell.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	policyengine_us/programs.yaml
…s in tests

person.spm_unit.sum already broadcasts to members, so the explicit
project call raised an entity-size error. Tests previously relied on the
removed weekly_hours_worked_before_lsr default of 40; every client adult
now sets hours explicitly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@DTrim99

DTrim99 commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Follow-up — review items verified at 2c116a2965

Re-checked the two fix commits (987666fb14, 2c116a2965) against the review above. All 6 should-address items are resolved, and the 425-behind staleness — the one operational blocker — is fully cleared (new head is 0 behind / 10 ahead of main).

  • Client-identification helper (S1) — the triplicated is_parent | head-or-spouse block is now a single ut_ccap_is_client variable (Person/YEAR) consumed by ut_ccap_gross_income, ut_ccap_countable_income, and ut_ccap_activity_eligible, with the verbatim block deleted from each. The entity-projection fix in 2c116a2965 is correct — person.spm_unit.sum(is_parent) already broadcasts the unit total back to members, so the earlier spm_unit.project() inside a Person formula was redundant; the head-or-spouse cross-tax-unit fallback is preserved.
  • 85% SMI cross-check (S2)smi_rate.yaml comment now shows each Table 4 Group-16 upper bound ÷ 0.85 reproducing FFY2026 Utah SMI across sizes 2–12.
  • Uncapped-cost gate (S3) and bare-input-expense population note (S4) — both documented in the ut_ccap_eligible docstring.
  • License-Exempt == Commercial-Preschool at same age band (S5) — new ut_ccap_market_rate.yaml Case 14 asserts [437, 437] for two age-3 children across the two provider blocks — a genuine one-sided-edit-fails pin.
  • Size-2 n/a-padded copay cell (S6) — new Case 11 forces ut_ccap_child_count_group: TWO and confirms the padded column repeats the one-child value (18).

The 8 suggestions were all optional (2 accept-by-design, the rest style/hardening) and left as-is — none blocking. You also pinned explicit work hours across the suite. Clean from this review's perspective. 👍

🤖 Generated by /review-program re-verification (Claude Code)

@hua7450
hua7450 merged commit ff429c7 into PolicyEngine:main Aug 13, 2026
31 checks passed
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 Utah Child Care Assistance Program (CCAP)

2 participants