diff --git a/changelog.d/2321.fixed.md b/changelog.d/2321.fixed.md new file mode 100644 index 0000000000..4c44b28086 --- /dev/null +++ b/changelog.d/2321.fixed.md @@ -0,0 +1 @@ +- Round the High Income Child Benefit Charge as ITEPA 2003 s. 681C(3) requires: the percentage down to a whole number, and the Child Benefit and the charge down to whole pounds. diff --git a/policyengine_uk/parameters/gov/hmrc/child_benefit/README.md b/policyengine_uk/parameters/gov/hmrc/child_benefit/README.md index a5190ffabe..3e5e568dd5 100644 --- a/policyengine_uk/parameters/gov/hmrc/child_benefit/README.md +++ b/policyengine_uk/parameters/gov/hmrc/child_benefit/README.md @@ -29,11 +29,15 @@ data in a release; updating model code alone does not repair existing data. `gov.hmrc.child_benefit.opt_out_charge_share` is a behavioural assumption, not a statutory eligibility rule. It defaults to one: a flagged family continues -to opt out only while the charge would recover the full benefit. The charge -share is calculated independently of payment and clipped to [0, 1]. A reform -that reduces the share below the threshold, removes the charge or neutralises -`CB_HITC` restores payment. A zero charge never sustains an opt-out, even if -the assumed threshold is zero. Genuine nonclaimants remain outside payment. +to opt out only while the continuous charge share is one, that is while the +higher adjusted net income in the family is at or above the end of the +phase-out band. The share is the unrounded fraction of the band crossed, calculated independently of +payment and clipped to [0, 1]. A reform that reduces the share below the +threshold, removes the charge or neutralises `CB_HITC` restores payment. A zero +continuous charge share never sustains an opt-out, even if the assumed +threshold is zero. The statutory charge (`CB_HITC`) is different: ITEPA 2003 +s. 681C(3) rounds its percentage and amount down, so it can be nil while the +share is small but positive. Genuine nonclaimants remain outside payment. Microcosm selects fully charged families first and can draw from partly charged families if needed to meet its target. The model's default threshold is an diff --git a/policyengine_uk/reforms/conservatives/household_based_hitc.py b/policyengine_uk/reforms/conservatives/household_based_hitc.py index 861c04d3d0..05295e581d 100644 --- a/policyengine_uk/reforms/conservatives/household_based_hitc.py +++ b/policyengine_uk/reforms/conservatives/household_based_hitc.py @@ -1,4 +1,8 @@ from policyengine_uk.model_api import * +from policyengine_uk.utils.child_benefit import ( + child_benefit_charge_percentage, + round_down_to_pounds, +) class CB_HITC(Variable): @@ -15,10 +19,12 @@ def formula(person, period, parameters): hitc = parameters(period).gov.hmrc.income_tax.charges.CB_HITC personal_income = person("adjusted_net_income", period) income = person.benunit.sum(personal_income) - percentage = max_(income - hitc.phase_out_start, 0) / ( - hitc.phase_out_end - hitc.phase_out_start + # The same s. 681C(3) rounding as the baseline charge, on the + # household's combined income. + percentage = child_benefit_charge_percentage( + income, hitc.phase_out_start, hitc.phase_out_end ) - return min_(percentage, 1) * CB_received + return round_down_to_pounds(percentage * round_down_to_pounds(CB_received)) class make_cb_hitc_household_based(Variable): diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/uc_earnings_deductions.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/uc_earnings_deductions.yaml index d5e2aef012..99132fcbd3 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/uc_earnings_deductions.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/uc_earnings_deductions.yaml @@ -272,10 +272,10 @@ household: members: [parent, child] output: - # Child Benefit 27.05 x 52 = 1,406.60; the charge takes - # (70,000 - 60,000) / 200 = 50% of it, 703.30. - CB_HITC: [703.30, 0] - income_tax: [16_135.30, 0] + # Child Benefit 27.05 x 52 = 1,406.60, rounded down to £1,406; the charge + # takes (70,000 - 60,000) / 200 = 50% of it, £703 (s. 681C(3)). + CB_HITC: [703, 0] + income_tax: [16_135, 0] # 37,700 x 20% + (57,430 - 37,700) x 40%. uc_income_tax_on_earnings: [15_432, 0] diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/child_benefit/opt_out.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/child_benefit/opt_out.yaml index 248ef3aa10..45d122fe22 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/child_benefit/opt_out.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/child_benefit/opt_out.yaml @@ -87,7 +87,7 @@ absolute_error_margin: 0.01 input: people: - person1: {age: 40, adjusted_net_income: 100_000} + person1: {age: 40, adjusted_net_income: 200_000} person2: {age: 8} person3: {age: 5} benunits: @@ -98,8 +98,9 @@ output: child_benefit_entitlement: 2_337.40 child_benefit: 2_337.40 - # (100,000 - 60,000) / (10,000,000 - 60,000) * 2,337.40. - CB_HITC: [9.406036, 0, 0] + # (200,000 - 60,000) / (10,000,000 - 60,000) = 1.41%, rounded down to 1% + # (ITEPA 2003 s. 681C(3)); 1% of £2,337 is £23.37, rounded down to £23. + CB_HITC: [23, 0, 0] - name: Case 9, the configurable opt-out threshold includes its exact boundary. period: 2026 @@ -111,9 +112,10 @@ gov.hmrc.child_benefit.opt_out_charge_share: 0.5 output: child_benefit: [1_000, 0, 0] - CB_HITC: [499.95, 0, 0] + # 49.995% is rounded down to 49% (s. 681C(3)): £490. + CB_HITC: [490, 0, 0] -- name: Case 10, a zero behavioural threshold still requires a positive charge. +- name: Case 10, a zero behavioural threshold still requires a positive continuous charge share. period: 2026 input: adjusted_net_income: [59_999, 60_000, 60_001] diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/charges/child_benefit_hitc.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/charges/child_benefit_hitc.yaml index c20990a727..ecfd90acb5 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/charges/child_benefit_hitc.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/charges/child_benefit_hitc.yaml @@ -47,6 +47,150 @@ members: [person1, person2, person3, person4] output: child_benefit: 2_337.40 - CB_HITC: [2_337.40, 0, 0, 0] + # s. 681C(3): the Child Benefit total is rounded down to £2,337. + CB_HITC: [2_337, 0, 0, 0] income_tax_pre_charges: [35_742, 4_486, 0, 0] - income_tax: [38_079.40, 4_486, 0, 0] + income_tax: [38_079, 4_486, 0, 0] + +# ITEPA 2003 s. 681C(3): the Child Benefit total and the charge are rounded +# down to whole pounds, and the appropriate percentage ((ANI - £60,000) / £200 +# from 2024-25) to a whole number. +- name: Case 6, the percentage, the Child Benefit and the charge are rounded down. + period: 2026 + absolute_error_margin: 0 + input: + people: + parent: + age: 40 + employment_income: 71_336 + child: + age: 5 + benunit: + members: [parent, child] + output: + # (71,336 - 60,000) / 200 = 56.68, so 56%; £1,406.60 becomes £1,406; + # 56% of £1,406 is £787.36, so £787. + child_benefit: 1_406.60 + CB_HITC: [787, 0] + +- name: Case 7, a whole-number percentage is not rounded below itself. + period: 2026 + absolute_error_margin: 0 + input: + people: + parent: + age: 40 + employment_income: 70_000 + child: + age: 5 + benunit: + members: [parent, child] + output: + # (70,000 - 60,000) / 200 = 50%; 50% of £1,406 is £703. + CB_HITC: [703, 0] + +- name: Case 8, just under the next £200 step keeps the lower percentage. + period: 2026 + absolute_error_margin: 0 + input: + people: + parent: + age: 40 + employment_income: 70_199 + child: + age: 5 + benunit: + members: [parent, child] + output: + # 50.995% rounds down to 50%: £703. + CB_HITC: [703, 0] + +- name: Case 9, at or above £80,000 the whole Child Benefit, in whole pounds. + period: 2026 + absolute_error_margin: 0 + input: + people: + parent: + age: 40 + employment_income: 90_000 + child1: + age: 5 + child2: + age: 3 + benunit: + members: [parent, child1, child2] + output: + child_benefit: 2_337.40 + CB_HITC: [2_337, 0, 0] + +- name: Case 10, the 2023-24 rules, 1% per £100 above £50,000. + period: 2023 + absolute_error_margin: 0 + input: + people: + parent: + age: 40 + employment_income: 55_550 + child: + age: 5 + benunit: + members: [parent, child] + child_benefit: 1_000 + output: + # (55,550 - 50,000) / 100 = 55.5%, so 55%; 55% of £1,000 is £550. + CB_HITC: [550, 0] + +- name: Case 11, the Child Benefit total is rounded down before the percentage applies. + period: 2026 + absolute_error_margin: 0 + input: + people: + parent: + age: 40 + employment_income: 79_800 + child: + age: 5 + benunit: + members: [parent, child] + output: + # (79,800 - 60,000) / 200 = 99%; 99% of £1,406 is £1,391.94, so £1,391. + # Rounding only the charge (99% of £1,406.60 = £1,392.53) would give £1,392. + CB_HITC: [1_391, 0] + +- name: Case 12, an income on an exact £200 step gets that many points. + period: 2026 + absolute_error_margin: 0 + input: + people: + parent: + age: 40 + employment_income: 60_200 + child: + age: 5 + benunit: + members: [parent, child] + output: + # 1% of £1,406 is £14.06, so £14. + CB_HITC: [14, 0] + +- name: Case 13, four children's £4,199 is not rounded down to £4,198. + period: 2026 + absolute_error_margin: 0 + input: + people: + parent: + age: 40 + employment_income: 90_000 + child1: + age: 2 + child2: + age: 4 + child3: + age: 6 + child4: + age: 8 + benunit: + members: [parent, child1, child2, child3, child4] + output: + # 52 x (27.05 + 3 x 17.90) = 4,199. + CB_HITC: [4_199, 0, 0, 0, 0] diff --git a/policyengine_uk/tests/test_child_benefit_charge_rounding_properties.py b/policyengine_uk/tests/test_child_benefit_charge_rounding_properties.py new file mode 100644 index 0000000000..7e448efab2 --- /dev/null +++ b/policyengine_uk/tests/test_child_benefit_charge_rounding_properties.py @@ -0,0 +1,182 @@ +"""High Income Child Benefit Charge rounding (ITEPA 2003 s. 681C(3)). + +The charge is the appropriate percentage of the Child Benefit concerned: +(ANI - L) / X percent, capped at 100% (s. 681C(2); L = £60,000 and X = £200 +from 2024-25). Subsection (3) rounds the Child Benefit total and the charge +down to whole pounds, and the percentage down to a whole number. + +Invariants, for adjusted net income in whole pounds and Child Benefit totals in whole +pence (the amounts the model produces), including their float32 storage: + +1. The charge is a whole number of pounds. +2. Bounds: 0 <= charge <= Child Benefit rounded down to pounds. +3. Differential: the charge equals floor(points / 100 x floor(CB)), where + points = min(100, max(0, floor((ANI - L) / X))), computed here from the + statute in integer arithmetic. +4. Monotonic: with the Child Benefit fixed, a higher income never lowers the + charge. +5. Precision: every exact step, held as float32 as in a microsimulation, + gives exactly that many points, under the statutory band and under wider + tapers a reform can set; a penny below a step gives one point fewer. +6. The model's charge from a full simulation matches the statute, with one to + four children. +""" + +import numpy as np +from hypothesis import given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation +from policyengine_uk.utils.scenario import Scenario +from policyengine_uk.utils.child_benefit import ( + child_benefit_charge_percentage, + round_down_to_pounds, +) + +L = 60_000 +X = 200 +END = L + 100 * X + + +def statutory_charge(ani_pounds: int, child_benefit_pence: int) -> int: + """s. 681C in integer arithmetic: whole pounds, whole percentage points.""" + if ani_pounds <= L: + return 0 + points = min(100, (ani_pounds - L) // X) + pounds = child_benefit_pence // 100 + return (points * pounds) // 100 + + +def model_charge(ani: float, child_benefit: float) -> float: + percentage = child_benefit_charge_percentage(np.array([ani]), L, END) + return float( + round_down_to_pounds(percentage * round_down_to_pounds(child_benefit))[0] + ) + + +incomes = st.integers(min_value=0, max_value=200_000) +benefit_pence = st.integers(min_value=0, max_value=1_000_000) + + +@settings(max_examples=500, deadline=None) +@given(ani=incomes, cb_pence=benefit_pence) +def test_charge_matches_the_statute(ani, cb_pence): + child_benefit = cb_pence / 100 + charge = model_charge(ani, child_benefit) + assert charge == int(charge) + assert 0 <= charge <= cb_pence // 100 + assert charge == statutory_charge(ani, cb_pence) + + +@settings(max_examples=300, deadline=None) +@given( + ani=incomes, rise=st.integers(min_value=0, max_value=50_000), cb_pence=benefit_pence +) +def test_a_higher_income_never_lowers_the_charge(ani, rise, cb_pence): + child_benefit = cb_pence / 100 + assert model_charge(ani + rise, child_benefit) >= model_charge(ani, child_benefit) + + +@settings(max_examples=8, deadline=None) +@given(pay=st.integers(min_value=55_000, max_value=90_000), children=st.integers(1, 4)) +def test_simulated_charge_matches_the_statute(pay, children): + people = {"parent": {"age": {"2026": 40}, "employment_income": {"2026": pay}}} + for i in range(children): + people[f"child{i}"] = {"age": {"2026": 3 + i}} + members = list(people) + sim = Simulation( + situation={ + "people": people, + "benunits": {"b": {"members": members}}, + "households": {"h": {"members": members, "country": {"2026": "ENGLAND"}}}, + } + ) + ani = float(sim.calculate("adjusted_net_income", 2026)[0]) + child_benefit = float(sim.calculate("child_benefit", 2026)[0]) + charge = float(sim.calculate("CB_HITC", 2026)[0]) + assert charge == statutory_charge(int(ani), round(child_benefit * 100)) + + +def test_every_exact_step_gives_exactly_its_points_in_float32(): + for points in range(0, 101): + income = np.array([L + X * points], dtype=np.float32) + assert child_benefit_charge_percentage(income, L, END)[0] == points / 100 + + +def test_a_float32_annual_amount_is_taken_to_the_penny_first(): + # Four children's £4,199 accumulated by month and stored as float32. + assert round_down_to_pounds(np.float32(4198.99951171875)) == 4199 + assert round_down_to_pounds(np.float32(1406.5999755859375)) == 1406 + + +# Structural reforms are chosen when the simulation is built, so the switch has +# to be set before data load. +HOUSEHOLD_BASED = Scenario( + applied_before_data_load=True, + parameter_changes={"gov.contrib.conservatives.cb_hitc_household": {"2025": True}}, +) + + +def _charge(pay: list[int], scenario=None) -> float: + people = { + f"parent{i}": {"age": {2026: 40}, "employment_income": {2026: p}} + for i, p in enumerate(pay) + } + people["child"] = {"age": {2026: 5}} + situation = { + "people": people, + "benunits": {"family": {"members": list(people)}}, + "households": {"home": {"members": list(people), "country": {2026: "ENGLAND"}}}, + } + return float( + Simulation(situation=situation, scenario=scenario) + .calculate("CB_HITC", 2026) + .sum() + ) + + +def test_the_household_based_reform_rounds_as_the_baseline_does(): + """A single parent's household income is their own, so the reform charges + what the baseline does: 99% of £1,406 at £79,800 is £1,391, not £1,392.53.""" + assert _charge([79_800]) == 1_391 + assert _charge([79_800], HOUSEHOLD_BASED) == 1_391 + + +def test_the_household_based_reform_charges_on_combined_income(): + """A couple on £50,000 and £30,000 pay nothing in the baseline; on £80,000 + combined the reform takes the whole Child Benefit, in whole pounds.""" + assert _charge([50_000, 30_000]) == 0 + assert _charge([50_000, 30_000], HOUSEHOLD_BASED) == 1_406 + + +WIDE_TAPER_ENDS = [END, 2_000_000, 10_000_000] + + +def test_exact_steps_and_the_income_just_below_under_wide_tapers(): + """A reform can widen the band (tests use £2,000,000 and £10,000,000). + Each step is width / 100. The largest float32 income below a step stays + one point lower: no slack rounds it up. (Above about £524,288, float32 + cannot hold a penny below a step, so the nearest float32 below it is + used.)""" + for end in WIDE_TAPER_ENDS: + step = (end - L) / 100 + for points in [1, 2, 37, 99, 100]: + on_step = np.float32(L + step * points) + below = np.nextafter(on_step, np.float32(0)) + assert ( + child_benefit_charge_percentage(np.array([on_step]), L, end)[0] + == points / 100 + ) + assert ( + child_benefit_charge_percentage(np.array([below]), L, end)[0] + == (points - 1) / 100 + ) + + +def test_a_penny_below_the_first_wide_taper_step_is_not_charged(): + # £79,399.99 is £79,399.9921875 in float32: 0 points under a £60,000 to + # £2,000,000 band, so no charge on one child's £1,406. + income = np.array([79_399.99], dtype=np.float32) + percentage = child_benefit_charge_percentage(income, L, 2_000_000) + assert percentage[0] == 0 + assert round_down_to_pounds(percentage * round_down_to_pounds(1_406.60))[0] == 0 diff --git a/policyengine_uk/tests/test_child_benefit_opt_out.py b/policyengine_uk/tests/test_child_benefit_opt_out.py index 012ef7ceca..54e235fea5 100644 --- a/policyengine_uk/tests/test_child_benefit_opt_out.py +++ b/policyengine_uk/tests/test_child_benefit_opt_out.py @@ -76,7 +76,9 @@ def test_cliff_charge_has_no_division_warnings(income, opts_out, taper_end): charge = simulation.calculate("CB_HITC", 2026).sum() entitlement = simulation.calculate("child_benefit_entitlement", 2026)[0] assert payment == (0 if opts_out and income > 60_000 else entitlement) - assert charge == (payment if income > 60_000 else 0) + # A cliff charges the whole Child Benefit, rounded down to whole pounds + # (ITEPA 2003 s. 681C(3)(a)). + assert charge == (np.floor(payment) if income > 60_000 else 0) @pytest.mark.usefixtures("cloned_uk_tax_benefit_system") @@ -106,9 +108,13 @@ def test_cliff_charge_has_no_division_warnings(income, opts_out, taper_end): 0, ), ( - {"gov.hmrc.income_tax.charges.CB_HITC.phase_out_end": {"2026": 10_000_000}}, + # A softer taper: (100,000 - 60,000) / (2,000,000 - 60,000) = 2.06%, + # rounded down to 2% (ITEPA 2003 s. 681C(3)(b)); 2% of £2,337 is + # £46.74, rounded down to £46. Payment resumes because the share is + # below the opt-out threshold. + {"gov.hmrc.income_tax.charges.CB_HITC.phase_out_end": {"2026": 2_000_000}}, 2_337.40, - 2_337.40 * 40_000 / 9_940_000, + 46, ), ], ) diff --git a/policyengine_uk/utils/child_benefit.py b/policyengine_uk/utils/child_benefit.py index e8d1092c38..f6c94270be 100644 --- a/policyengine_uk/utils/child_benefit.py +++ b/policyengine_uk/utils/child_benefit.py @@ -14,3 +14,38 @@ def child_benefit_charge_share( where=width > 0, ) return np.clip(share, 0, 1) + + +def child_benefit_charge_percentage( + income: np.ndarray, phase_out_start: float, phase_out_end: float +) -> np.ndarray: + """The appropriate percentage of ITEPA 2003 s. 681C(2), as a fraction. + + One percentage point per hundredth of the phase-out band crossed (per £200 + from 2024-25), capped at 100 and rounded down to a whole number + (s. 681C(3)(b)). Worked in float64, multiplying before dividing. With + whole-pound band edges, an income on an exact step (exact in float32 too, + below £16.7m) gives an integer-valued quotient, which IEEE division returns + exactly, so no slack is added: slack would round a penny income just + below a step up to it. A nonpositive band width is a cliff (100 points + above the start). + """ + income = np.asarray(income, dtype=np.float64) + width = float(phase_out_end) - float(phase_out_start) + excess = np.maximum(income - float(phase_out_start), 0) + if width > 0: + points = np.minimum(excess * 100 / width, 100) + else: + points = np.where(income > float(phase_out_start), 100.0, 0.0) + return np.floor(points) / 100 + + +def round_down_to_pounds(amount: np.ndarray) -> np.ndarray: + """ITEPA 2003 s. 681C(3)(a): amounts not a whole number of pounds are + rounded down. The amount is first taken to the nearest penny in float64, + so that £4,199 stored as 4,198.9995 is not rounded down to £4,198, then + to whole pounds by integer division of the pence. Child Benefit totals and + charges are whole pence (weekly rates in pence, and whole percentage + points of whole pounds), so the penny step loses nothing.""" + pence = np.round(np.asarray(amount, dtype=np.float64) * 100) + return np.floor_divide(pence, 100) diff --git a/policyengine_uk/variables/gov/hmrc/child_benefit_opts_out.py b/policyengine_uk/variables/gov/hmrc/child_benefit_opts_out.py index 758358a59c..03fb18e275 100644 --- a/policyengine_uk/variables/gov/hmrc/child_benefit_opts_out.py +++ b/policyengine_uk/variables/gov/hmrc/child_benefit_opts_out.py @@ -7,10 +7,12 @@ class child_benefit_opts_out(Variable): "Whether a family would opt out of Child Benefit payments because of the " "High Income Child Benefit Charge. This is independent of " "would_claim_child_benefit and never identifies a claim by itself. " - "For claimants, payments resume when the simulated charge share falls below " - "the assumed " - "opt_out_charge_share threshold (one by default), or the charge is zero " - "or neutralised. Entitlement and nonclaimant status are unchanged." + "For claimants, payments resume when the continuous charge share (the " + "unrounded share of the phase-out band crossed) falls below the assumed " + "opt_out_charge_share threshold (one by default), or is zero, or the " + "charge is neutralised. The statutory charge itself is rounded " + "(ITEPA 2003 s. 681C(3)) and can be nil where the share is small but " + "positive. Entitlement and nonclaimant status are unchanged." ) entity = BenUnit definition_period = YEAR diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/charges/child_benefit_hitc.py b/policyengine_uk/variables/gov/hmrc/income_tax/charges/child_benefit_hitc.py index b5add0442d..a18a822f9e 100644 --- a/policyengine_uk/variables/gov/hmrc/income_tax/charges/child_benefit_hitc.py +++ b/policyengine_uk/variables/gov/hmrc/income_tax/charges/child_benefit_hitc.py @@ -1,21 +1,39 @@ from policyengine_uk.model_api import * -from policyengine_uk.utils.child_benefit import child_benefit_charge_share +from policyengine_uk.utils.child_benefit import ( + child_benefit_charge_percentage, + round_down_to_pounds, +) class CB_HITC(Variable): value_type = float entity = Person label = "Child Benefit High-Income Tax Charge" + documentation = ( + "The appropriate percentage of the Child Benefit received, charged on " + "the partner with the higher adjusted net income. Section 681C(3) " + "rounds down the Child Benefit total and the charge to whole pounds, " + "and the percentage to a whole number." + ) definition_period = YEAR - reference = "https://www.legislation.gov.uk/ukpga/2003/1/part/10/chapter/8" + reference = [ + dict( + title="Income Tax (Earnings and Pensions) Act 2003 s. 681C", + href="https://www.legislation.gov.uk/ukpga/2003/1/section/681C", + ), + dict( + title="Income Tax (Earnings and Pensions) Act 2003 Part 10 Chapter 8", + href="https://www.legislation.gov.uk/ukpga/2003/1/part/10/chapter/8", + ), + ] unit = GBP defined_for = "is_higher_earner" def formula(person, period, parameters): - CB_received = person.benunit("child_benefit", period) + CB_received = round_down_to_pounds(person.benunit("child_benefit", period)) hitc = parameters(period).gov.hmrc.income_tax.charges.CB_HITC income = person("adjusted_net_income", period) - percentage = child_benefit_charge_share( + percentage = child_benefit_charge_percentage( income, hitc.phase_out_start, hitc.phase_out_end ) - return percentage * CB_received + return round_down_to_pounds(percentage * CB_received)