Skip to content

Fix CCDF age group crashing on households with adults - #9400

Closed
MaxGhenis wants to merge 1 commit into
mainfrom
fix-ccdf-age-group-enum
Closed

Fix CCDF age group crashing on households with adults#9400
MaxGhenis wants to merge 1 commit into
mainfrom
fix-ccdf-age-group-enum

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Calculating spm_unit_ccdf_subsidy (or ccdf_market_rate, or ccdf_age_group itself) crashed for any household containing a person aged 13 or older. For a Virginia household with two adults (35 and 33) and a 3-year-old, Simulation(situation=...).calculate("spm_unit_ccdf_subsidy", 2026) raised:

ValueError: Invalid value(s) ['0', 'CCDFAgeGroup.PRESCHOOLER'] for enum CCDFAgeGroup. Valid values are: ['INFANT', 'TODDLER', 'PRESCHOOLER', 'SCHOOL_AGE']

The exact text depends on who is listed first. A child listed first gives AttributeError: 'int' object has no attribute 'index', and an adults-only household gives Invalid value(s) ['0']. The state path (va_child_care_subsidies) computed fine; only the federal generic path failed.

Root cause

ccdf_age_group built its result with select(conditions, [CCDFAgeGroup items]) and no default. select here is numpy's np.select, whose default is the integer 0. For every person no condition matched (age ≥ 13, so every adult) the result was an object array mixing 0 with CCDFAgeGroup members.

policyengine-core's Enum.encode takes its Enum-item fast path only when the first element is an Enum. With an adult first, the array is cast to strings ('0', 'CCDFAgeGroup.PRESCHOOLER') and the string lookup rejects both. With a child first, the fast path hits AttributeError on 0.index.

Before policyengine-core 3.22.0 the string path encoded unmatched values as index 0 (INFANT) with no warning, so the adults-first case silently produced INFANT for adults and, because str(CCDFAgeGroup.PRESCHOOLER) is not a member name, for the children too. Core 3.22.0 added a warning and 3.23.0 (December 2025) made it raise. This repo requires core ≥ 3.30.1, which is what surfaced the bug. The child-first ordering has always raised.

Fix

Drop the redundant age < 13 branch and give the select an explicit default=CCDFAgeGroup.SCHOOL_AGE. Children under 13 get the same answer as before (verified over a dense age grid for both home-based and center-based care). Anyone 13 or older (the CCDF age limit in gov.hhs.ccdf.age_limit) now resolves to SCHOOL_AGE instead of an unencodable 0. is_ccdf_age_eligible already screens them out of the subsidy, so no subsidy amount changes for eligible children. in_ccdf_age_group already uses this pattern.

Tests

  • ccdf_age_group.yaml: age 13 boundary, an adult, and a mixed adult/preschooler household with the adults listed first.
  • New integration.yaml: the reported Virginia household (computes, subsidy 0 with no childcare hours) and a Nassau County household with a preschooler in full-time center care ($310/wk × 52 = $16,120 market rate, $3,536 copay, $12,584 subsidy).
  • All 71 tests under policyengine_us/tests/policy/baseline/gov/hhs/ccdf pass. All 5 new cases fail on unfixed main (04a961a) with the same ValueError: the three mixed-household cases with the exact message above, the two single-person cases with Invalid value(s) ['0'].
  • A 55-person vectorised simulation with adults first across five states computes the whole chain without error.

Related

🤖 Generated with Claude Code

ccdf_age_group built its result with numpy select and no default, so every
person aged 13 or older (every adult) got the integer 0 instead of a
CCDFAgeGroup item. policyengine-core's Enum.encode cannot encode that mixed
array: with an adult listed first it raises ValueError on the string '0',
and with a child listed first it raises AttributeError on 0.index. Either
way ccdf_market_rate and spm_unit_ccdf_subsidy failed for any household
that included an adult. Before core 3.23.0 the adults-first ordering was
silently encoded as INFANT for everyone instead of raising.

People past the school-age bracket now default to SCHOOL_AGE, which leaves
every age under 13 unchanged; is_ccdf_age_eligible already screens them out
of the subsidy.

Adds age-group cases for age 13, an adult, and a mixed household, plus
household integration tests for a Virginia family with no childcare hours
and a Nassau County family with a preschooler in full-time center care.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

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

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #9400   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            3         1    -2     
  Lines           33        18   -15     
=========================================
- Hits            33        18   -15     
Flag Coverage Δ
unittests 100.00% <ø> (ø)

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.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Superseded by #9401, which removed ccdf_age_group and the rest of the legacy New York CCDF path outright (merge commit f627b0b). The crash this PR fixed no longer has a file to live in.

@MaxGhenis MaxGhenis closed this Sep 7, 2026
@MaxGhenis
MaxGhenis deleted the fix-ccdf-age-group-enum branch September 7, 2026 10:35
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.

1 participant