Skip to content

Add UK childcare source packages - #220

Merged
juaristi22 merged 3 commits into
mainfrom
issue-219-childcare-source-packages
Sep 2, 2026
Merged

Add UK childcare source packages#220
juaristi22 merged 3 commits into
mainfrom
issue-219-childcare-source-packages

Conversation

@juaristi22

Copy link
Copy Markdown
Collaborator

Summary

  • add a pinned HMRC Tax-Free Childcare March 2026 source package with annual unique-child and top-up series plus monthly used-account facts
  • add a pinned DfE funded early education and childcare 2026 source package with complete headline-row lineage and explicit entitlement, age, registration-basis, provision, and eligibility dimensions
  • register both packages in the UK bundle, update the source checklist, and support source-column evidence for wide-table semantic uniqueness

Scope notes

  • preserves the HMRC April 2025 methodological break instead of deriving an activity-duration proxy
  • preserves DfE's overlapping published universes without subtracting them or reproducing the issue's illustrative arithmetic
  • leaves the lower-priority optional CMA add-on out of scope
  • keeps the publisher-supplied DfE CSV byte-for-byte intact, including its CRLF line endings

Validation

  • built and validated both source suites and their consumer artifacts with zero acceptance errors
  • built and validated the UK bundle: 109,008 facts across 84 packages, with zero aggregate duplicate keys and no new semantic duplicate groups
  • passed the default all-source bundle regression: 172,751 facts across 153 packages, with zero aggregate duplicate keys
  • passed 215 relevant tests across source-package, facts-only, consumer-contract, suite, alias, and bundle coverage
  • passed uv run --locked --extra dev ruff check ., changed-file format checks, and git diff --check

Fixes #219

@vahid-ahmadi

Copy link
Copy Markdown

Automated review pass (Claude Code, high effort). Non-test diff reviewed; both manifests and both package YAMLs parsed and cross-checked; CSV/ODS data hunks skipped. Draft, so nits omitted.

Verified clean first, since two of these were the specific risks the scope notes invite: no duplicate record_set_id or value_id, and no semantic-key collisions among the 770 DfE facts — concept+period+geography+filter-set is unique for every row, so the preserved overlapping universes do not collide on fact keys, which is the #202 failure mode avoided. Manifest sha256, size and R2 key leaves all agree with their filename; the DfE source_url ends in an API /csv endpoint, so the basename mismatch there is expected rather than the #198 doubled-path defect.

Four findings. The first is the new mechanism, and it is the important one.

1. chronicle/suite.py:1041 — the wide-table evidence check accepts substring containment, so it passes vacuously

_wide_table_filter_evidenced_by_source_column tests expected_name in _normalize_semantic_name(source_column_id) — containment, not exact or token identity. So the check is satisfied by any column whose name merely contains the token, including columns from a different universe:

  • provision: all (662 DfE rows) is "evidenced" by all_children_count and by registered_all_children_percent;
  • registration_basis: eligible_children is "evidenced" by registered_eligible_children_percent — a registered-share column, a different universe from the eligible-children count it is being offered as evidence for.

The uniqueness claim is therefore asserted over a token that does not determine the row, and the check will confirm the wrong category rather than failing. Given the mechanism exists specifically to establish semantic uniqueness for wide tables, containment is the one comparison that cannot do that job — exact match on the full normalized name, or token-set equality, is what the claim needs.

2. chronicle/suite.py:1023 — the mechanism is gated on a hardcoded set and degrades to "no check"

It is gated on WIDE_TABLE_SOURCE_COLUMN_DIMENSIONS and silently returns False for anything outside it, so a mistyped or renamed dimension degrades to no evidence check rather than a loud failure.

Two entries in that set — receptionstatus and registrationstatus — match no dimension emitted by either new package, so they are dead, while the real names in use are provision, registration_basis and eligibility_basis. That combination is worth noting together: the set contains names nothing uses, and anything not in the set is unchecked, so neither direction reports.

3. packages/hmrc/tax_free_childcare_march_2026/source_package.yaml — the April 2025 break is declared but not enforceable

Credit where due: the break is machine-readable, definition_regime: used_account_in_periodpayment_and_open_at_reference_date at 2025-04. But both regimes publish under the same concept: hmrc.tfc_children_with_used_accounts_monthly, so a consumer selecting concept+geography+period gets one continuous series and can sum or difference across the break silently.

The break is only honoured by a consumer that already knows to filter on a dimension nothing requires it to honour — which is the same shape as #206's assertion-policy removal, where the ambiguity remained in the feed after the enforcement left. Distinct concepts either side of the break, or a required regime selector, would make the break structural rather than advisory.

4. packages/dfe/funded_early_education_childcare_2026/source_package.yaml — totals and their own components share a concept

eligible_children_universal_credit_count and eligible_children_legacy_benefit_count carry the same concept: dfe.funded_childcare_eligible_children as the all-eligibility eligible_children_count; likewise nursery and reception against the total under dfe.funded_childcare_registered_children.

So a consumer selecting by concept+geography+period without eligibility_basis / provision receives a total plus its own components and double-counts. The non-subtraction rule — which is a sound decision — lives only in checklist prose. This is the finding I would act on alongside 1: microcosm compiles its target surface from this layer, and a double-counted eligible-children total is the kind of thing that shows up as a calibration target being wrong rather than as an error.


1 and 4 before merge, I think. 1 because the mechanism cannot currently establish what it is named for, and 4 because the ambiguity is reachable by an ordinary consumer query rather than by a mistake. 2 is a tidy-up with a real edge (unchecked dimensions are silent). 3 is a judgement call about how much the feed should enforce versus document — worth a ruling either way, given #206 moved in the direction of consumers owning it.

@juaristi22

Copy link
Copy Markdown
Collaborator Author

Addressed Vahid's review in 2f903c7.

Fixed

  • Replaced substring/allowlist inference for wide-table dimensions with an explicit source_column_dimensions contract. A source column now evidences a filter only when the declared key exists and its typed value matches exactly.
  • Added explicit guarded dimension declarations to all 770 DfE measures. Missing, mistyped, or unrelated declarations now fail normal suite validation instead of being silently accepted.
  • Split the 11 DfE total/component source columns across 11 distinct concepts so concept-only selection cannot mix totals with nursery, reception, UC, or legacy-benefit components.
  • Strengthened HMRC regression coverage at the definition break: 96 monthly facts use the pre-April-2025 regime, 12 use the revised regime, March and April 2025 are pinned explicitly, and every monthly fact must carry matching filter and constraint evidence.

Kept by design

  • I retained one HMRC monthly concept. Microcosm's series identity already includes Chronicle dimensions and constraints, and targets are activated through explicit mappings, so definition_regime separates the two regimes without another concept/mapping pair. Splitting would add cross-repository mapping machinery while leaving Microcosm's separate partial-year averaging behavior unchanged. The checklist now tells consumers combining months to select exactly one regime.

Verification

  • 178 affected tests passed after formatting.
  • 9 source-bundle regression tests passed.
  • DfE suite: 770 facts, 0 acceptance errors; HMRC suite: 126 facts, 0 acceptance errors.
  • UK bundle: 109,008 facts across 84 packages with 0 aggregate duplicates.
  • All-source regression: 172,751 facts across 153 packages.
  • Ruff, changed-file format checks, and git diff --check passed.

@juaristi22

Copy link
Copy Markdown
Collaborator Author

CI follow-up: 12325f6 preserves the explicit source-column dimension contract as suite-only validation metadata rather than adding it to serialized fact layout.

The first hosted run exposed two Eurostat byte-stability failures because an otherwise empty layout field changed every fact payload. The contract now remains explicit in each source-package measure and is indexed by source_record_id only while building the acceptance report, so DfE validation stays strict without changing unrelated materialized facts.

Verified locally:

  • Both exact failing Eurostat prior-vintage digest tests now pass.
  • 131 broader affected tests pass.
  • DfE suite remains valid with all 770 declarations checked and 0 acceptance errors.
  • Ruff and diff checks pass.

@juaristi22
juaristi22 marked this pull request as ready for review September 1, 2026 20:03
@juaristi22
juaristi22 merged commit ff3efd3 into main Sep 2, 2026
2 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.

UK source packages: Tax-Free Childcare and funded early-years facts

2 participants