Fix CCDF age group crashing on households with adults - #9400
Closed
MaxGhenis wants to merge 1 commit into
Closed
Conversation
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 Report✅ All modified and coverable lines are covered by tests. 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This was referenced Sep 7, 2026
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Calculating
spm_unit_ccdf_subsidy(orccdf_market_rate, orccdf_age_groupitself) 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: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 givesInvalid value(s) ['0']. The state path (va_child_care_subsidies) computed fine; only the federal generic path failed.Root cause
ccdf_age_groupbuilt its result withselect(conditions, [CCDFAgeGroup items])and nodefault.selecthere is numpy'snp.select, whose default is the integer0. For every person no condition matched (age ≥ 13, so every adult) the result was an object array mixing0withCCDFAgeGroupmembers.policyengine-core's
Enum.encodetakes 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 hitsAttributeErroron0.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 < 13branch and give the select an explicitdefault=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 ingov.hhs.ccdf.age_limit) now resolves to SCHOOL_AGE instead of an unencodable 0.is_ccdf_age_eligiblealready screens them out of the subsidy, so no subsidy amount changes for eligible children.in_ccdf_age_groupalready uses this pattern.Tests
ccdf_age_group.yaml: age 13 boundary, an adult, and a mixed adult/preschooler household with the adults listed first.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).policyengine_us/tests/policy/baseline/gov/hhs/ccdfpass. All 5 new cases fail on unfixedmain(04a961a) with the same ValueError: the three mixed-household cases with the exact message above, the two single-person cases withInvalid value(s) ['0'].Related
integration.yaml.selectcalls without adefaultkeyword: 111 calls, 5 without it. Three are exhaustive by construction (pell_grant_household_type,state_filing_status_if_married_filing_separately_on_same_return,age_group) andil_personal_exemption_eligibility_statuspasses its default positionally.md_ccs_service_unitwould hit the same failure only for negative or NaN childcare hours, which its bracket maps to unit 0; left for a follow-up.🤖 Generated with Claude Code