Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/2226.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add the 15.05% employer Class 1 rate for 2022-23 (Health and Social Care Levy Act 2021 s.5(2)(b)) and its end on 6 November 2022 (Health and Social Care Levy (Repeal) Act 2022 s.2(1)). Model year 2022 now reads 15.05%, as the employee main rate reads 13.25%.
8 changes: 4 additions & 4 deletions docs/book/policy/model-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Budget 2025 keeps the income tax personal allowance and basic rate limit, and th

### Autumn Budget 2024

The government increased the [employer National Insurance rate from 13.8% to 15%](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/rates/employer.yaml#L13-L17) and reduced the [employer secondary threshold from £9,100 to £5,000 annually](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/thresholds/secondary_threshold.yaml#L26-L30), both taking effect in fiscal year 2025-26.
The government increased the [employer National Insurance rate from 13.8% to 15%](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/rates/employer.yaml#L30-L35) and reduced the [employer secondary threshold from £9,100 to £5,000 annually](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/thresholds/secondary_threshold.yaml#L26-L30), both taking effect in fiscal year 2025-26.

### Universal Credit rebalancing

Expand All @@ -28,7 +28,7 @@ Annual benefit uprating applied 4.1% increases to state pensions and 1.7% to wor

### Autumn Statement 2023

The government announced further National Insurance cuts, reducing the [employee main rate from 10% to 8%](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/rates/employee/main.yaml#L22-L27) and the [self-employed Class 4 rate from 9% to 6%](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/rates/main.yaml#L16-L21) in fiscal year 2024-25. [Class 2 National Insurance contributions were abolished](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/flat_rate.yaml#L14-L19) for self-employed people, with the flat rate set to £0 in fiscal year 2024-25.
The government announced further National Insurance cuts, reducing the [employee main rate from 10% to 8%](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/rates/employee/main.yaml#L24-L29) and the [self-employed Class 4 rate from 9% to 6%](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/rates/main.yaml#L16-L21) in fiscal year 2024-25. [Class 2 National Insurance contributions were abolished](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/flat_rate.yaml#L14-L19) for self-employed people, with the flat rate set to £0 in fiscal year 2024-25.

### Capital gains tax changes (Autumn Budget 2024)

Expand All @@ -40,7 +40,7 @@ The government increased the [child benefit high income tax charge threshold fro

### Autumn Statement 2023 (Employee NI cut)

An initial National Insurance cut reduced the [employee main rate from 12% to 10%](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/rates/employee/main.yaml#L16-L21) in fiscal year 2024-25.
An initial National Insurance cut reduced the [employee main rate from 12% to 10%](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/rates/employee/main.yaml#L18-L23) in fiscal year 2024-25.

## 2023 reforms

Expand All @@ -56,7 +56,7 @@ The government announced an increase in the [National Insurance primary threshol

### Autumn Budget 2021 (Health and Social Care Levy)

The government temporarily increased [National Insurance rates by 1.25 percentage points](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/rates/employee/main.yaml#L4-L10) in fiscal year 2022-23, later reversed the same fiscal year.
The Health and Social Care Levy Act 2021 raised the [employee main rate from 12% to 13.25%](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/rates/employee/main.yaml#L4-L17) and the [employer rate from 13.8% to 15.05%](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/rates/employer.yaml#L18-L29) for fiscal year 2022-23. The Health and Social Care Levy (Repeal) Act 2022 restored 12% and 13.8% for earnings paid from 6 November 2022. The model reads each fiscal year's rates at 30 April, so 2022-23 carries the levy rates for the whole year ([#1807](https://github.com/PolicyEngine/policyengine-uk/issues/1807)).

### Mini-Budget 2022 (September)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
description: The Class 1 National Insurance main rate is paid on employment earnings between the Primary Threshold and the Upper Earnings Limit.
values:
2015-04-01: 0.12
2022-04-01:
2022-04-06:
value: 0.1325
metadata:
reference:
- title: Health and Social Care Levy Act 2021 s.5(2)(a)(i)
href: https://www.legislation.gov.uk/ukpga/2021/28/section/5/enacted
- title: Autumn Budget and Spending Review 2022
href: https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/1043689/Budget_AB2021_Web_Accessible.pdf
2022-11-06:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,32 @@ metadata:
label: NI employer rate
propagate_metadata_to_children: true
unit: /1
# Annual values are the rate at 30 April of each tax year, as for the
# employee main rate (no fiscal_year_blend). For 2022-23 that is the levy
# rate of 15.05%, although earnings paid from 6 November 2022 were charged
# at 13.8%; see #1807 for the choice between sampling and averaging.
values:
2015-04-01:
2015-04-01:
value: 0.138
reference:
- title: National Insurance rates and thresholds
href: https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/882271/Table-a4.pdf
2025-01-01:
metadata:
reference:
- title: National Insurance rates and thresholds
href: https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/882271/Table-a4.pdf
2022-04-06:
value: 0.1505
metadata:
reference:
- title: Health and Social Care Levy Act 2021 s.5(2)(b)
href: https://www.legislation.gov.uk/ukpga/2021/28/section/5/enacted
2022-11-06:
value: 0.138
metadata:
reference:
- title: Health and Social Care Levy (Repeal) Act 2022 s.2(1)
href: https://www.legislation.gov.uk/ukpga/2022/43/section/2/enacted
2025-01-01:
value: 0.15
reference:
- title: National Insurance Contributions (Secondary Class 1 Contributions) Act 2025(1)
href: https://www.legislation.gov.uk/ukpga/2025/11/section/1
metadata:
reference:
- title: National Insurance Contributions (Secondary Class 1 Contributions) Act 2025 s.1
href: https://www.legislation.gov.uk/ukpga/2025/11/section/1
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,49 @@
ni_class_1_income: 8_100 # 52 x £155 = £8,060 would charge £5.52
output:
ni_class_1_employer: 0

# 2022-23: the Health and Social Care Levy Act 2021 s.5(2)(b) made the
# secondary percentage 15.05%, until the Health and Social Care Levy (Repeal)
# Act 2022 s.2(1) restored 13.8% for earnings paid from 6 November 2022. Model
# year 2022 takes the rate at 30 April, 15.05%, as the employee main rate takes
# 13.25%. The annual secondary threshold is £175 x 52 = £9,100. HMRC, "Rates and
# thresholds for employers 2022 to 2023": 15.05% from 6 April to 5 November
# 2022, 13.8% from 6 November 2022 to 5 April 2023, £9,100 a year.

- name: Employer NI on £30,000 in 2022-23 is at the 15.05% levy rate
period: 2022
absolute_error_margin: 0.01
input:
age: 40
employment_income: 30_000
output:
ni_class_1_employer: 3_145.45 # 0.1505 x (30,000 - 9,100)
ni_employer: 3_145.45

- name: Employer NI on £30,000 in 2023-24 is back at 13.8%
period: 2023
absolute_error_margin: 0.01
input:
age: 40
employment_income: 30_000
output:
ni_class_1_employer: 2_884.20 # 0.138 x (30,000 - 9,100)
ni_employer: 2_884.20

- name: Employer NI on £30,000 in 2021-22 is at 13.8%
period: 2021
absolute_error_margin: 0.01
input:
age: 40
employment_income: 30_000
output:
ni_class_1_employer: 2_920.08 # 0.138 x (30,000 - 170 x 52)

- name: The 2022-23 levy rate applies above the upper earnings limit
period: 2022
absolute_error_margin: 0.01
input:
age: 40
employment_income: 100_000
output:
ni_class_1_employer: 13_680.45 # 0.1505 x (100,000 - 9,100)
208 changes: 208 additions & 0 deletions policyengine_uk/tests/test_ni_health_and_social_care_levy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
"""Class 1 rates under the Health and Social Care Levy in 2022-23.

The Health and Social Care Levy Act 2021 s.5(2) raised two Class 1
percentages for the tax year 2022-23 by 1.25 points: the main primary
percentage from 12% to 13.25% (s.5(2)(a)(i)) and the secondary percentage
from 13.8% to 15.05% (s.5(2)(b)). The Health and Social Care Levy (Repeal) Act
2022 s.2(1) kept both only for payments of earnings made before 6 November
2022. Para 5(3) of its Schedule gives the annual rates for directors' annual
earnings periods: 12.73% main primary and 14.53% secondary.

The rate parameters store these as dated values. The model reads each tax
year at 30 April (convert_to_fiscal_year_parameters), so model year 2022
takes the levy rate on both sides. Whether to average across the year instead
is open in #1807.

Invariants, checked on the parameter files as stored:

1. Statutory window: for any date d, the employer rate is 15.05% if and only
if 6 April 2022 <= d < 6 November 2022, and the employee main rate is
13.25% on exactly the same dates.
2. Same dates: in the 2022-23 tax year the two rates change on the same
dates, 6 April and 6 November 2022.
3. Same uplift: inside the window each rate is 1.25 points above its value on
5 April 2022.
4. Annual equivalent: the day-weighted average of each rate over 2022-23,
computed exactly, rounds to the Repeal Act's directors' rate.
fiscal_year_average gives the same figure (differential).

And on the model:

5. Same convention: neither rate is blended, so in every model year from 2015
to 2030 each annual rate is the stored rate at 30 April. Model year 2022 is
15.05% and 13.25%; 2021 and 2023 are 13.8% and 12%.
"""

import datetime
from fractions import Fraction
from pathlib import Path

import pytest
from hypothesis import example, given, settings
from hypothesis import strategies as st
from policyengine_core.parameters import load_parameter_file

import policyengine_uk
from policyengine_uk import CountryTaxBenefitSystem
from policyengine_uk.utils.parameters import fiscal_year_average

RATES = (
Path(policyengine_uk.__file__).parent
/ "parameters"
/ "gov"
/ "hmrc"
/ "national_insurance"
/ "class_1"
/ "rates"
)
EMPLOYER_FILE = RATES / "employer.yaml"
EMPLOYEE_MAIN_FILE = RATES / "employee" / "main.yaml"

LEVY_START = datetime.date(2022, 4, 6) # HSCL Act 2021 s.5(1): tax year 2022-23
LEVY_END = datetime.date(2022, 11, 6) # Repeal Act 2022 s.2(1)
TAX_YEAR_END = datetime.date(2023, 4, 6)

EMPLOYER_LEVY_RATE = Fraction("0.1505") # s.5(2)(b)
EMPLOYEE_LEVY_RATE = Fraction("0.1325") # s.5(2)(a)(i)
UPLIFT = Fraction("0.0125")
# Repeal Act 2022 Sch para 5(3), as percentages.
EMPLOYER_DIRECTORS_RATE = Fraction("14.53")
EMPLOYEE_DIRECTORS_RATE = Fraction("12.73")


@pytest.fixture(scope="module")
def employer():
return load_parameter_file(str(EMPLOYER_FILE), "employer")


@pytest.fixture(scope="module")
def employee_main():
return load_parameter_file(str(EMPLOYEE_MAIN_FILE), "main")


@pytest.fixture(scope="module")
def system():
return CountryTaxBenefitSystem()


def rate_on(param, date):
"""The stored rate in force on a date, as an exact fraction."""
return Fraction(str(param(date.isoformat())))


def change_dates(param, start, end):
"""Dates in [start, end) on which the stored rate takes a new value."""
dates = []
for value_at_instant in param.values_list:
date = datetime.date.fromisoformat(value_at_instant.instant_str)
if not start <= date < end:
continue
if rate_on(param, date) != rate_on(param, date - datetime.timedelta(days=1)):
dates.append(date)
return sorted(dates)


def exact_annual_percentage(param, start, end):
"""Day-weighted average of the stored rate over [start, end), in percent."""
total = Fraction(0)
day = start
while day < end:
total += rate_on(param, day)
day += datetime.timedelta(days=1)
return 100 * total / (end - start).days


# Invariant 1: statutory window.
@settings(max_examples=200, deadline=None, derandomize=True)
@given(
date=st.dates(
min_value=datetime.date(2015, 4, 6),
max_value=datetime.date(2031, 4, 5),
)
)
@example(date=datetime.date(2022, 4, 1))
@example(date=datetime.date(2022, 4, 5))
@example(date=datetime.date(2022, 4, 6))
@example(date=datetime.date(2022, 4, 30))
@example(date=datetime.date(2022, 11, 5))
@example(date=datetime.date(2022, 11, 6))
@example(date=datetime.date(2023, 4, 5))
def test_levy_rates_apply_only_in_the_statutory_window(employer, employee_main, date):
in_window = LEVY_START <= date < LEVY_END
assert (rate_on(employer, date) == EMPLOYER_LEVY_RATE) == in_window
assert (rate_on(employee_main, date) == EMPLOYEE_LEVY_RATE) == in_window


# Invariant 2: same dates.
def test_employer_and_employee_rates_change_on_the_levy_dates(employer, employee_main):
employer_dates = change_dates(employer, LEVY_START, TAX_YEAR_END)
employee_dates = change_dates(employee_main, LEVY_START, TAX_YEAR_END)
assert employer_dates == employee_dates == [LEVY_START, LEVY_END]


# Invariant 3: same uplift.
@pytest.mark.parametrize(
"date",
[LEVY_START, datetime.date(2022, 7, 6), LEVY_END - datetime.timedelta(days=1)],
)
def test_levy_adds_one_and_a_quarter_points_to_both_rates(
employer, employee_main, date
):
day_before = LEVY_START - datetime.timedelta(days=1)
for param in (employer, employee_main):
assert rate_on(param, date) - rate_on(param, day_before) == UPLIFT


# Invariant 4: annual equivalent, exact and via fiscal_year_average.
@pytest.mark.parametrize(
"param_name, directors_rate",
[
("employer", EMPLOYER_DIRECTORS_RATE),
("employee_main", EMPLOYEE_DIRECTORS_RATE),
],
)
def test_annual_average_matches_the_directors_rate(request, param_name, directors_rate):
param = request.getfixturevalue(param_name)
exact = exact_annual_percentage(param, LEVY_START, TAX_YEAR_END)
assert round(exact, 2) == directors_rate
assert 100 * fiscal_year_average(param, 2022) == pytest.approx(
float(exact), abs=1e-9
)


# Invariant 5: same convention.
def model_rates(system, year):
class_1 = system.get_parameters_at_instant(
str(year)
).gov.hmrc.national_insurance.class_1
return class_1.rates.employer, class_1.rates.employee.main


def test_neither_rate_is_blended(system):
rates = system.parameters.gov.hmrc.national_insurance.class_1.rates
for param in (rates.employer, rates.employee.main):
assert not (param.metadata or {}).get("fiscal_year_blend", False)


@pytest.mark.parametrize("year", range(2015, 2031))
def test_model_year_rate_is_the_rate_at_30_april(system, employer, employee_main, year):
model_employer, model_employee = model_rates(system, year)
sample = datetime.date(year, 4, 30)
assert Fraction(str(model_employer)) == rate_on(employer, sample)
assert Fraction(str(model_employee)) == rate_on(employee_main, sample)


@pytest.mark.parametrize(
"year, employer_rate, employee_rate",
[
(2021, "0.138", "0.12"),
(2022, "0.1505", "0.1325"),
(2023, "0.138", "0.12"),
],
)
def test_model_year_2022_carries_the_levy_on_both_sides(
system, year, employer_rate, employee_rate
):
model_employer, model_employee = model_rates(system, year)
assert Fraction(str(model_employer)) == Fraction(employer_rate)
assert Fraction(str(model_employee)) == Fraction(employee_rate)
19 changes: 11 additions & 8 deletions policyengine_uk/tests/test_ni_secondary_threshold.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@
From 2025-26 the stored value is the annual figure / 52 (5,000 / 52), the
convention reg 11(3A)(c) uses for multiples of a week. The rounded reg
10(d) £96 would give £4,992 (#1967).
3. Differential: in every year except 2022-23, for any Class 1 earnings
e >= 0 of an earner aged 21 or over who is not an apprentice,
ni_class_1_employer equals rate x max(e - annual ST, 0), computed in exact
arithmetic from STATUTORY. 2022-23 is left out: the Health and Social Care
Levy raised the secondary percentage to 15.05% from 6 April to 5 November
2022, and the rate parameter does not carry it.
3. Differential: in every year, for any Class 1 earnings e >= 0 of an
earner aged 21 or over who is not an apprentice, ni_class_1_employer
equals rate x max(e - annual ST, 0), computed in exact arithmetic from
STATUTORY. For 2022-23 the s.9(2) percentage at 6 April is 15.05% (Health
and Social Care Levy Act 2021 s.5(2)(b)). It applied to earnings paid
before 6 November 2022 (Repeal Act 2022 s.2(1)), and the model reads the
tax year at 30 April, as for the employee main rate (#1807).
4. Threshold edge: employer NI is zero just below the annual ST, less than
half a penny at it, and positive a penny above it. At the ST itself the
stored 5,000 / 52 (96.153846) annualises to £4,999.999992, so exact
Expand Down Expand Up @@ -58,8 +59,8 @@
2019: (Fraction(166), Fraction(8_632), Fraction(138, 1_000)),
2020: (Fraction(169), Fraction(8_788), Fraction(138, 1_000)),
2021: (Fraction(170), Fraction(8_840), Fraction(138, 1_000)),
# 15.05% from 6 April to 5 November 2022, then 13.8%.
2022: (Fraction(175), Fraction(9_100), None),
# 15.05% for earnings paid from 6 April to 5 November 2022, then 13.8%.
2022: (Fraction(175), Fraction(9_100), Fraction(1_505, 10_000)),
2023: (Fraction(175), Fraction(9_100), Fraction(138, 1_000)),
2024: (Fraction(175), Fraction(9_100), Fraction(138, 1_000)),
2025: (Fraction(96), Fraction(5_000), Fraction(15, 100)),
Expand Down Expand Up @@ -199,5 +200,7 @@ def test_employer_ni_at_the_threshold_edge(year):
# between £8,060 and £8,112 in 2016-17 were charged.
@example(year=2025, earnings=[40_000.0, 4_996.0, 5_000.0])
@example(year=2016, earnings=[8_100.0, 8_112.0, 20_000.0])
# 2022-23 was charged at 13.8%, not the 15.05% levy rate.
@example(year=2022, earnings=[30_000.0, 9_100.0, 100_000.0])
def test_employer_ni_follows_the_statutory_threshold(year, earnings):
check_employer_ni(year, earnings)
Loading