Repository navigation
Round the High Income Child Benefit Charge as s. 681C(3) requires - #2323
Merged
Merged
Conversation
…equires Fixes #2321. The charge used the unrounded share of the phase-out band. The percentage is rounded down to a whole number, and the Child Benefit total and the charge down to whole pounds. Three tests that encoded unrounded charges now expect the statutory ones; the softer-taper opt-out case moves its income so the rounded charge stays positive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ge applies Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cliff case now expects the Child Benefit rounded down to pounds, and the dataset softer-taper case moves its taper end so the rounded charge stays positive (2%, £46). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fixes for #2323: - Work out the appropriate percentage in float64, multiplying before dividing, so every exact £200 step gives exactly that many points when incomes are held as float32 (50 of 100 steps lost a point before). - Take an amount to the nearest penny before rounding down to pounds, so four children's £4,199 stored as 4,198.9995 stays £4,199. - Apply the s. 681C(3) rounding in the household-based charge reform too. - Describe the opt-out test as the continuous charge share, as distinct from the rounded statutory charge. - Regression tests: float32 steps, four children, single-parent reform equivalence and the couple's combined charge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Second-round review fix for #2323. With whole-pound band edges, an income on an exact step gives an integer-valued quotient when multiplied before dividing in float64, which IEEE division returns exactly, so the 1e-6 slack was not needed. Under a wide taper it rounded a penny income just below a step up to it: £79,399.99 under a £60,000-£2,000,000 band (as a reform or test can set) charged £14 instead of £0. round_down_to_pounds now floors whole pence by integer division. Regressions: every tested step and the float32 income just below it under the statutory, £2,000,000 and £10,000,000 band ends, and the £79,399.99 case. The property docstring states the whole-pound and whole-pence domain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
marked this pull request as ready for review
October 11, 2026 22:47
Collaborator
Author
|
CI run cancelled to free runners (UK hub, 2026-10-11 23:30Z). The PolicyEngine org is at GitHub's limit of 60 concurrent jobs, with about 360 queued. Fleet ops asked the hub to let the Budget-critical #2305 take runners first. This PR's queued run had not started, so I cancelled it. Nothing is wrong with the PR. Please don't re-run it or push just to retrigger CI. The hub re-runs this run itself once #2305 has runners. The record is |
Collaborator
Author
|
Hand-off to the UK hub (session local_66e4b1fd, PolicyBench UK round 2).
|
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.
Fixes #2321
axiom: ukpga/2003/1/681C TheAxiomFoundation/rulespec-uk#451 queued (rulespec-uk already rounds the percentage and the charge; #451 adds the rounding of the Child Benefit total; the signed encoder run needs its own approval)
What changes
ITEPA 2003 s. 681C(3) rounds the Child Benefit total and the charge down to whole pounds, and the appropriate percentage down to a whole number.
CB_HITCrounded none of them.policyengine_uk/utils/child_benefit.pygains two helpers:child_benefit_charge_percentage: the s. 681C(2) percentage as whole points, capped at 100. It works in float64 and multiplies before dividing, so an income on an exact £200 step gives exactly that many points even when incomes are stored as float32. At the first head, 50 of the 100 exact steps lost a point in float32.round_down_to_pounds: takes the amount to the nearest penny in float64 first, so four children's £4,199, stored as 4,198.9995, stays £4,199.Neither adds slack.
CB_HITC= round_down(percentage × round_down(Child Benefit)).The household-based charge reform (
gov.contrib.conservatives.cb_hitc_household) overridesCB_HITCand applies the same rounding to the household's combined income. A single parent therefore pays the same under the reform as under the baseline: £1,391 at £79,800.Unchanged: the Child Benefit opt-out (
child_benefit) still reads the continuous, unrounded charge share against its behavioural threshold,opt_out_charge_share. That rule is a modelling assumption, not s. 681C. The README, thechild_benefit_opts_outdescription and opt-out test Case 10 now call it the continuous charge share, to separate it from the rounded statutory charge.Worked example: adjusted net income £71,336, Child Benefit £1,406.60. The percentage is 56.68%, rounded down to 56%, and 56% × £1,406 = £787.36, so the charge is £787.
maingives £797.26.Impact (real run)
Measured on
enhanced_frs_2023_24.h5(local policyengine-uk-data storage, 6 October build) for 2026, at head a0516c0. The result is identical at 14f3f88, which had the slack. In one simulation of this branch,main's formula (the unrounded share × Child Benefit, for the higher earner) is computed from the same simulation's variables and compared withCB_HITC(impact_hicbc_direct.py).mainformulaCB_HITC(capped_mcad), and tax can't go below nil.Tests
New YAML cases in
gov/hmrc/income_tax/charges/child_benefit_hitc.yaml:Changed tests, which encoded unrounded charges:
child_benefit_hitc.yamlCase 5: £2,337.40 → £2,337, with Income Tax down by the same 40p.uc_earnings_deductions.yaml: £703.30 → £703.child_benefit/opt_out.yamlCase 9: 49.995% → 49%, so £490.child_benefit/opt_out.yamlCase 8: under its softer taper, the old income gave 0.4%, which rounds to nil. The case exists to show a positive charge, so its income moves to £200,000 (1.41%, so 1%: £23).test_child_benefit_opt_out.py: the cliff test expects the whole Child Benefit rounded down to pounds. The softer-taper case moves its taper end from £10,000,000 to £2,000,000, so the charge stays positive after rounding (2.06%, so 2%: £46).Property and regression tests in
test_child_benefit_charge_rounding_properties.py(Hypothesis). They hold for whole-pound incomes and whole-pence Child Benefit totals, including their float32 storage:CB_HITCmatches the statute, for one to four children;Run locally, at a0516c0:
test_child_benefit_charge_rounding_properties.pyandtest_child_benefit_opt_out.py, 34 passed.Reviews: two GPT-6.1 Sol reviews asked for changes:
Both are addressed here.
🤖 Generated with Claude Code