From b91ef34f85b47782d4d283ab6eaf63a2ba62635c Mon Sep 17 00:00:00 2001 From: vahid-ahmadi Date: Wed, 7 Oct 2026 11:22:04 +0100 Subject: [PATCH] Source the UK extended-hours usage draw from DfE additional-hours PTEs (#1126) The frozen clip(N(15.019, 4.972), 0, 30) gave a working-parent 3- or 4-year-old about 15 funded hours a week, the universal offer alone. DfE's January 2025 census funds 341,988.09 additional-hours PTEs for 379,000 registered 3-4-year-olds: 28.535 weekly hours in total. The mean becomes 31.141 (sd and bounds held), status sourced_administrative_derived. Co-Authored-By: Claude Opus 5.5 --- ...126-uk-extended-hours-usage-dfe.changed.md | 1 + .../microcosm/build/uk/take_up_contract.json | 18 +++++------ .../uk/test_uk_take_up_contract.py | 30 +++++++++++++++++++ 3 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 changelog.d/1126-uk-extended-hours-usage-dfe.changed.md diff --git a/changelog.d/1126-uk-extended-hours-usage-dfe.changed.md b/changelog.d/1126-uk-extended-hours-usage-dfe.changed.md new file mode 100644 index 0000000000..9f61632a2a --- /dev/null +++ b/changelog.d/1126-uk-extended-hours-usage-dfe.changed.md @@ -0,0 +1 @@ +Source the UK `maximum_extended_childcare_hours_usage` draw from DfE: the mean moves from the frozen 15.019 to 31.141 (sd 4.972, clip [0, 30]), so a working-parent 3- or 4-year-old averages the 28.5 weekly hours implied by DfE's January 2025 additional-hours PTEs, lifting 2026-27 free early-years spending from £5.81bn to about £9.30bn against DfE's £9.9bn (#1126). diff --git a/packages/microcosm-build/src/microcosm/build/uk/take_up_contract.json b/packages/microcosm-build/src/microcosm/build/uk/take_up_contract.json index 2cd674dec3..09d21b277b 100644 --- a/packages/microcosm-build/src/microcosm/build/uk/take_up_contract.json +++ b/packages/microcosm-build/src/microcosm/build/uk/take_up_contract.json @@ -520,19 +520,19 @@ "output": "maximum_extended_childcare_hours_usage", "entity": "benunit", "distribution": "normal", - "mean": 15.019, + "mean": 31.141, "sd": 4.972, "lower": 0, "upper": 30, "source": { - "source": "FRS empirical fit, frozen by microcosm#834 adjudication", - "agency": "PolicyEngine", - "citation": "Incumbent offline childcare usage fit, clipped to [0, 30] hours. Frozen because it is underidentified without an independent extended-spending observation; replacing it needs evidence on hours actually used.", - "status": "frozen_by_adjudication", - "freeze": { - "decision": "Hold 15.019 / 4.972 / 0 / 30 fixed while fitting only four take-up rates", - "followup": "microcosm#834" - } + "source": "DfE 2026 to 2027 early years national funding formulae technical note (3- and 4-year-old additional-hours PTEs, January 2025 censuses) and DfE Education provision: children under 5 years of age, January 2025 (3- and 4-year-olds registered for the working parent entitlement)", + "agency": "DfE", + "status": "sourced_administrative_derived", + "url": "https://www.gov.uk/government/publications/early-years-funding-2026-to-2027/2026-to-2027-early-years-national-funding-formulae-technical-note", + "registrations_url": "https://explore-education-statistics.service.gov.uk/find-statistics/education-provision-children-under-5/2025", + "derivation": "15 + 15 * 341,988.09 additional-hours PTEs / 379,000 registered 3-4-year-olds = 28.535 mean weekly funded hours for a working-parent 3- or 4-year-old (one PTE is 15 hours a week for 38 weeks); mean solves E[clip(N(mean, 4.972), 0, 30)] = 28.535 with the sd and bounds held", + "citation": "Replaces the frozen 15.019 / 4.972 incumbent fit (microcosm#834), which #834 held pending evidence on hours actually used. The DfE additional-hours PTEs are that evidence: observed January 2025 census volumes, not a forecast. The variable caps a family's total weekly extended hours, so its mean is the total (universal plus additional) for a 3- or 4-year-old. Only the 3-4 cohort anchors the mean; DfE's under-2 and 2-year-old PTEs are partly forecast. The sd is not identified by any source and keeps the incumbent value. Re-mapping the 2024-25 national release's own draws through this distribution gives 2026-27 free-entitlement spending of 9.30bn against DfE's illustrative 9.9bn (5.81bn before); see microcosm#1126.", + "followup": "microcosm#1126" } } ] diff --git a/packages/microcosm-build/tests/engine_free/uk/test_uk_take_up_contract.py b/packages/microcosm-build/tests/engine_free/uk/test_uk_take_up_contract.py index a1e8c2ba9a..d908febe0b 100644 --- a/packages/microcosm-build/tests/engine_free/uk/test_uk_take_up_contract.py +++ b/packages/microcosm-build/tests/engine_free/uk/test_uk_take_up_contract.py @@ -1,6 +1,8 @@ """Tests split from packages/microcosm-build/tests/test_uk_take_up_contract.py.""" # ruff: noqa: F403, F405 +import numpy as np + from test_support.microcosm_build.uk_take_up_contract import * from test_support.paths import paths_for @@ -191,3 +193,31 @@ def test_forbidden_source_dependency_strings_absent_from_uk_resources() -> None: text = files("microcosm.build.uk").joinpath(resource).read_text().lower() assert "policyengine_" + "uk_data" not in text assert "policyengine-" + "uk-data" not in text + + +def test_extended_hours_usage_reproduces_dfe_additional_hours_ptes() -> None: + """The clipped draw's mean is the DfE-implied total weekly hours (#1126). + + DfE funds 341,988.09 additional-hours PTEs (15 hours x 38 weeks each) for + the 379,000 3- and 4-year-olds registered for the working parent + entitlement in January 2025, on top of the universal 15 hours. + """ + + from microcosm.build.stochastic_assignment import clipped_normal_from_uniforms + + contract = load_uk_take_up_contract() + entry = contract.continuous_entry("maximum_extended_childcare_hours_usage") + assert entry["source"]["status"] == "sourced_administrative_derived" + assert (entry["lower"], entry["upper"]) == (0, 30) + + n = 200_000 + uniforms = (np.arange(n) + 0.5) / n + draws = clipped_normal_from_uniforms( + uniforms, + mean=float(entry["mean"]), + sd=float(entry["sd"]), + lower=float(entry["lower"]), + upper=float(entry["upper"]), + ) + dfe_mean_hours = 15 + 15 * 341_988.09 / 379_000 + assert draws.mean() == pytest.approx(dfe_mean_hours, abs=0.01)