Repository navigation
Keep Tax-Free Childcare over a smaller UC award, and take no deductions from awards only a reform creates - #2754
Conversation
Any Universal Credit award before the benefit cap disqualifies a family from Tax-Free Childcare, and a family the data marks as a claimant took any award, however small. Universal Credit is paid only on a claim, so a family better off with the top-up does not claim (#2689). forgoes_universal_credit_for_tax_free_childcare compares the award before the cap and deductions with tax_free_childcare_without_universal_credit; where the top-up is worth more, universal_credit_pre_benefit_cap is nil. A family receiving a legacy award that a claim would end keeps its claim. The choice zeroes the award rather than claims_universal_credit, which the non-dependant rules read for the housing element. Tax credits count while in force rather than by amount, which reads Pension Credit and so Universal Credit. The Tax-Free Childcare conditions and top-up move to _top_up.py unchanged, so both awards share them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deduction incidence, rates and types describe the existing caseload's debts, but they came out of any award, including one a reform had just created, whether the dataset supplied them or the model drew them. A taper cut gave some families a first award that a deduction then mostly took away (#2753). uc_award_carries_deductions is false where a simulation built with a reform has an award and its baseline does not. uc_deductions and uc_benefit_cap_reduction_after_floor gate the demand, so supplied rates are covered and the two still agree; uc_has_deduction reads it too. The baseline award is worked out only for households holding an award, in a household_subset branch of the baseline. Simulation records built_with_reform: one built without a reform keeps every deduction, since its baseline is its own copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Synthetic London couples with two children in childcare, across the earnings where the cut gave a first award worth less than Tax-Free Childcare or a deduction took it below, with and without a supplied deduction rate; plus the forgone and claimed awards, the engine's own draw, an existing award's deduction, a simulation built without a reform, and a three-household case through the subset branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review of 2f05622. I traced the policyengine.py routes into the gate, checked the new variables for cycles and for the claim-readers left unguarded, and ran the new tests locally. Verdict: the fix is right for a reform passed at construction. One finding (1) means the same taper cut still leaves the #2753 household worse off when it reaches policyengine-uk as a simulation modifier. What I ran
Findings
Checked, no finding
|
|
Review of The diagnosis is right and the legal basis holds. Childcare Payments Act 2014 s.11(1) excludes a family only where UC is payable or a claim subsists, and s.11(3) counts an award reduced to nil, so not claiming is the route out and the pre-cap award is the right thing to test. The reproducer fails on main and passes here. One must before this leaves draft.
Verdict: not ready: 1 is a must. It is a draft, so no approval either way. 2 is wording only. What I ran (fresh worktree and uv venv at
Not checked: the national-file numbers, the run-time figures, and how the gate behaves inside labour-supply branches. I read the diff in full but not every caller of 🤖 Generated with Claude Code |
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 18 (thresholds in Notes
Moves flagged for review
Spending by benefit and service
Caseloads
Government balance, each component counted once
Model totals
Household income totals
Inequality
Mean household net income in each side's own deciles
Standard reform costings (change in gov_balance)
Standard reforms: change in each reform's target
Household net income by decileMean change in household net income per household, and as a share of the decile's base income; households stay in the base run's deciles.
No change in any year: receipts by tax head; population; poverty rates; people in poverty. Checks failing on both base and head (1)
Counted-once checkEach of 91 components was raised by £1 per unit in 2026-27, and every head of its totals was read household by household: its own head must rise by exactly £1, no other tax head may move, no other benefit may rise, and the model's own total must move by what its heads moved. A head that breaks a rule is measured again twice: with every other head held at its baseline value, and with every other head held at its value after the first £1 while the component rises again. It fails where either still shows the response. 0 failed; 0 were skipped.
Provenance
Dataset: Receipts by tax head: head levels
Spending by benefit and service: head levels
Caseloads: head levels
Government balance, each component counted once: head levels
Model totals: head levels
Household income totals: head levels
Population: head levels
Poverty rates: head levels
People in poverty: head levels
Inequality: head levels
Mean household net income in each side's own deciles: head levels
Standard reform costings (change in gov_balance): head levels
Standard reforms: change in each reform's target: head levels
|
…dcare In Scotland any Universal Credit award qualifies the children for Scottish Child Payment, which has no earnings limit. The comparison weighed the award alone, so a Scottish family could forgo a small award for Tax-Free Childcare and lose more in the payment (review on #2754). The payment a claim brings is now added to the award. A family that could forgo has no other qualifying benefit: the others are legacy awards, which keep the claim, or Pension Credit, which rules Universal Credit out. It is built from is_scp_eligible_child, the country, would_claim_scp and the weekly rate, since scottish_child_payment reads universal_credit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deduction gate compared only simulations built with a reform, so the same taper cut applied as a simulation modifier, as policyengine.py does for a policy with no parameter values, still took deductions from the new awards (review on #2754). Simulation.changed_from_baseline replaces built_with_reform. It is true for a simulation built with a reform and is set when apply_reform or drop_computed_arrays runs on a built simulation; policyengine.py's _apply_step ends every step with drop_computed_arrays. The baseline is then the copy made at construction, which a later parameter change does not reach. A bare set_input still does not count, so a current-law run does not work out Universal Credit twice. The gate's documentation and the docs page now state the rule as an assumption that holds in every year and slightly raises the cost of reforms that widen Universal Credit. Tests cover the modifier and apply_reform routes and the Scottish Child Payment comparison. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, Vahid. Both reviews are addressed in eda8136 (Scottish Child Payment) and 2af61e7 (the modifier route, the wording and the tests). Must 1: Scottish Child Payment. Done as you suggested. The comparison now adds the Scottish Child Payment a claim brings to the award. It's built from
Should 2: an assumption. Reworded in the variable's documentation and in the docs page section:
The aggregate-impact run at 2f05622 puts that at £0.39bn to £0.53bn a year more for the standard cut of the taper to 20%, about 2%. Nit 3. The "Not weighed" list now says Scottish Child Payment is weighed. Finding 1: the modifier route.
Finding 3: tests. Your modifier case is a test now: the taper cut applied after the build, then Finding 2: current-law moves. On the certified Enhanced FRS, the aggregate-impact run at 2f05622 shows the current-law change for 2025-26 to 2031-32:
Still running. Two checks are queued behind another job on the shared machine; I'll post them here when they finish:
|
|
Round 1 went up twice by mistake; this round covers both. Review of Thanks, María. Every round-1 point is addressed, and I checked each fix by running it. One new should, on the new design itself. Status of round-1 findings
New
Verdict: ready from my side once the checks at this head are green; neither point above blocks. It's still a draft, so no approval either way. What I ran (fresh clone and uv venv at
Not checked: the national-file and survey-receipt checks you're running, and the run-time cost of the second UC calculation on modifier routes. 🤖 Generated with Claude Code |
|
Follow-up on the two checks above, at 2af61e7. Reform-route tests. All passed locally: 404 passed, 1 skipped (the data test that needs credentials). The files cover modifiers, construction wrappers, reform representations, the The taper cut at this head. No household loses in 2027-28, on either the certified Enhanced FRS or the Microcosm national file.
Reported receipt. Yes, a few families that forgo report UC receipt in the survey.
So for a handful of families the model now prefers Tax-Free Childcare where the survey records a claim. I've left that as it is. Making a family that reports UC keep its claim would bring the #2753 cliff back for one with no modelled award at baseline: it would lose Tax-Free Childcare as soon as a reform gave it an award. The aggregate-impact run at this head will replace the current-law figures in my reply when it posts. |
Fixes #2753
Fixes #2689
Cutting the Universal Credit taper from 55% to 50% left some families worse off. The lower taper gave them a first, small award, and two things then went wrong:
claims_universal_credittakes any award, the family lost a top-up worth more than the award. UC is paid only on a claim, so a real family would not claim.#2753 has the trace: the £630 that Vahid's guide couldn't find is Tax-Free Childcare.
What changes
A family takes Tax-Free Childcare instead of a smaller award.
forgoes_universal_credit_for_tax_free_childcarecompares what a claim brings with the top-up the family would get without one (tax_free_childcare_without_universal_credit). Where the top-up is worth more,universal_credit_pre_benefit_capis nil, so Tax-Free Childcare stays.is_scp_eligible_child, the country,would_claim_scpand the weekly rate, sincescottish_child_paymentreadsuniversal_credit.council_tax_reduction/_legacy.py). That last one has the same shape as Scottish Child Payment but is smaller. The others have earnings limits near or below the Tax-Free Childcare work condition's floor, and an award worth less than the top-up comes only near the top of the UC taper.claims_universal_credit. Putting it in the claim would make a cycle, becauseuc_non_dependants_countedreads every family's claim to build the housing element. So rules that read the claim still see the family as claiming. With legacy awards excluded, that leaves only the household's non-dependant ordering, and only when a forgoing family shares a household with another claim.tax_free_childcare_eligible_without_universal_credittreats a tax credit award as in force while it's reported and DWP hasn't closed it, asis_CTC_eligibleandis_WTC_eligibledo. It doesn't read the amount: tax credit income counts Pension Credit, and Pension Credit eligibility readsis_uc_entitled, which is another cycle.tax_free_childcareandtax_free_childcare_eligiblemove their conditions and top-up into_top_up.pyunchanged, so the comparison uses the same code.No deduction comes out of an award only a reform creates.
uc_award_carries_deductionsis false where a simulation has an award and its baseline doesn't.uc_deductionsanduc_benefit_cap_reduction_after_floorgate the deduction demand, so supplied rates are covered and the two still agree.uc_has_deductionreads the gate too.Simulationnow recordschanged_from_baseline.apply_reformor by a step that ends withdrop_computed_arrays: its baseline is the copy made at construction, which a parameter change after the build doesn't reach. policyengine.py's_apply_stepends every modifier step withdrop_computed_arrays, so a policy that has only a simulation modifier is compared too, with no policyengine.py change.set_inputdoesn't count as a change.household_subsetbranch of the baseline (Compare property allowance routes on only the households with a choice #2732).Docs. The deductions validation page gets an "Awards a reform creates" section. The Tax-Free Childcare page says how the UC choice works.
Evidence
Reproducer: policyengine_uk/tests/test_uc_tax_free_childcare_choice.py::test_taper_cut_leaves_tax_free_childcare_families_no_worse_off
Synthetic households (
test_uc_tax_free_childcare_choice.py). Take a London couple renting privately for £24,000 a year, with two children in £6,000 of childcare each, under the taper cut in 2027-28:The same file also checks that:
apply_reformis compared with the baseline;set_input, and doesn't compute UC in its baseline copy;National file (at 2af61e7). Microcosm UK 2024-25 national (
microcosm-uk-2024-25-national-20261025T091414Z-a6299504), 2027-28, taper cut from 2027. Main is at 64cf582. On the certified Enhanced FRS too, no household loses.Commands run
uv run pytest policyengine_uk/tests/test_uc_tax_free_childcare_choice.py: 20 passed.uv run policyengine-core testonpolicy/baseline/gov/hmrc/tax_free_childcare,policy/baseline/finance/benefit/family/universal_creditandpolicy/baseline/gov/social_security_scotland: 780 passed, including the new YAML files.uv run pytest -n 4on the reform-route files (modifiers, construction wrappers, reform representations, thedrop_computed_arrayscallers, deductions, household subsets, labour supply baselines): 404 passed, 1 skipped (the same data test).uv run pytest -n 4on 26 related files andcode_health/: 3,589 passed, 1 skipped (a data test that needs dataset credentials). The files cover deductions, the benefit cap, household subsets, the Childcare Grant, legacy closure, the Housing Benefit UC passport, reform and modifier construction, labour supply baselines, code sharing, module release, units, determinism and the program registry.ruff formatandruff checkon the changed files.microsimulation/test_uc_deductions_aggregates.py(needs the certified dataset). CI runs them.Related
uc_has_deduction. The gate here covers reform-created awards either way; A supplied UC latent deduction rate can give deductions to a unit flagged as having none #2739 itself is unchanged.axiom: n/a: a claim choice and debt-deduction modelling; the Tax-Free Childcare disqualification and Universal Credit rules themselves are unchanged
🤖 Generated with Claude Code