From 434485f53df68bb70ea74a6337e877d56076da10 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 27 Sep 2026 16:05:02 -0400 Subject: [PATCH 1/8] Fix Class 2 NICs against s.11 SSCBA 1992 for 2015-16 to 2023-24 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 2023-24 weekly rate £3.45 (SI 2023/236 reg 3(a)); model year 2023 charged £3.15. - From 2022-23 Class 2 is payable only on profits that exceed the lower profits threshold (£11,908, SI 2022/1329; £12,570, SI 2023/236 reg 3(b)). Profits from the small profits threshold up to it are treated as paid (s.11(5A)-(5B)); the model charged 52 weeks on them. - Small profits threshold: £6,725 in 2023-24 and 2024-25 (not CPI uprated), £5,965 in 2015-16 and 2016-17 (not 3,965). CPI projections now run from the latest statutory value. - Compulsory Class 2 ends 6 April 2024 (NICRRA 2023 s.3), not 1 January. - Every value cites its amending instrument. - Tests: YAML cases per year and threshold; a statute-table differential, range and monotonicity property test over model years 2015-2030; a 2023-24 regulation 100 pin using the default £3.45 rate. - NI notebook: describe the rule and regenerate saved charts; the rates chart now selects percentage parameters by unit. Fixes #1887 Co-Authored-By: Claude Opus 5.5 --- changelog.d/1887.fixed.md | 1 + docs/book/policy/model-baseline.md | 2 +- .../gov/hmrc/national-insurance.ipynb | 558 ++++++++---------- .../national_insurance/class_2/flat_rate.yaml | 66 ++- .../class_2/lower_profits_threshold.yaml | 21 + .../lower_profits_threshold_applies.yaml | 21 + .../class_2/small_profits_threshold.yaml | 81 ++- .../class_2/ni_class_2.yaml | 125 +++- policyengine_uk/tests/test_ni_class_2.py | 202 +++++++ .../tests/test_ni_class_4_properties.py | 11 +- .../national_insurance/class_2/ni_class_2.py | 14 +- 11 files changed, 747 insertions(+), 355 deletions(-) create mode 100644 changelog.d/1887.fixed.md create mode 100644 policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/lower_profits_threshold.yaml create mode 100644 policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/lower_profits_threshold_applies.yaml create mode 100644 policyengine_uk/tests/test_ni_class_2.py diff --git a/changelog.d/1887.fixed.md b/changelog.d/1887.fixed.md new file mode 100644 index 0000000000..7126853599 --- /dev/null +++ b/changelog.d/1887.fixed.md @@ -0,0 +1 @@ +- Fix Class 2 National Insurance for 2015-16 to 2023-24 against s.11 SSCBA 1992: charge £3.45 a week in 2023-24; from 2022-23 charge only profits above the Class 2 lower profits threshold (£11,908, then £12,570), with profits from the small profits threshold up to it treated as paid; keep the small profits threshold at £6,725 in 2023-24 and 2024-25 and correct it to £5,965 in 2015-16 and 2016-17; date the abolition 6 April 2024. Every value now cites its amending instrument. diff --git a/docs/book/policy/model-baseline.md b/docs/book/policy/model-baseline.md index 59b1749fc1..18a96cd70a 100644 --- a/docs/book/policy/model-baseline.md +++ b/docs/book/policy/model-baseline.md @@ -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#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#L51-L56) for self-employed people, with the flat rate set to £0 in fiscal year 2024-25. ### Capital gains tax changes (Autumn Budget 2024) diff --git a/docs/book/programs/gov/hmrc/national-insurance.ipynb b/docs/book/programs/gov/hmrc/national-insurance.ipynb index 8a3e68d368..7098a199e6 100644 --- a/docs/book/programs/gov/hmrc/national-insurance.ipynb +++ b/docs/book/programs/gov/hmrc/national-insurance.ipynb @@ -20,9 +20,9 @@ "NI has four main classes:\n", "\n", "- **Class 1**: Paid by employees and their employers. This is based on the employee's earnings and is deducted directly from wages.\n", - "- **Class 2**: Paid by self-employed people with profits above a certain threshold.\n", + "- **Class 2**: A flat weekly contribution paid by self-employed people with profits above a certain threshold. It stopped being compulsory on 6 April 2024.\n", "- **Class 3**: Voluntary contributions, often made by people who want to fill gaps in their National Insurance record.\n", - "- **Class 4**: Paid by self-employed people with profits above a certain threshold, in addition to Class 2 NICs." + "- **Class 4**: Paid by self-employed people with profits above a certain threshold (in addition to Class 2 NICs before April 2024)." ] }, { @@ -56,78 +56,8 @@ "execution_count": 1, "metadata": {}, "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
ClassMethodology & BasisThresholds & LimitsRate ApplicationReference
0Class 1Based on employment income. \\n- Monthly and an...Primary Threshold, Upper Earnings LimitMain and Additional ratesSocial Security Contributions and Benefits Act...
1Class 2Based on self-employment income. \\n- Weekly fl...Small Profits ThresholdFlat rateSocial Security Contributions and Benefits Act...
2Class 3Voluntary contributions for those filling gaps...None (voluntary)Flat rateSocial Security Contributions and Benefits Act...
3Class 4Based on self-employment profits. \\n- Annual m...Lower Profits Limit, Upper Profits LimitMain and Additional ratesSocial Security Contributions and Benefits Act...
\n", - "
" - ], - "text/plain": [ - " Class ... Reference\n", - "0 Class 1 ... Social Security Contributions and Benefits Act...\n", - "1 Class 2 ... Social Security Contributions and Benefits Act...\n", - "2 Class 3 ... Social Security Contributions and Benefits Act...\n", - "3 Class 4 ... Social Security Contributions and Benefits Act...\n", - "\n", - "[4 rows x 5 columns]" - ] + "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
ClassMethodology & BasisThresholds & LimitsRate ApplicationReference
0Class 1Based on employment income. \\n- Monthly and an...Primary Threshold, Upper Earnings LimitMain and Additional ratesSocial Security Contributions and Benefits Act...
1Class 2Based on self-employment income. \\n- Weekly fl...Small Profits Threshold; lower profits thresho...Flat rateSocial Security Contributions and Benefits Act...
2Class 3Voluntary contributions for those filling gaps...None (voluntary)Flat rateSocial Security Contributions and Benefits Act...
3Class 4Based on self-employment profits. \\n- Annual m...Lower Profits Limit, Upper Profits LimitMain and Additional ratesSocial Security Contributions and Benefits Act...
\n
", + "text/plain": " Class Methodology & Basis \\\n0 Class 1 Based on employment income. \\n- Monthly and an... \n1 Class 2 Based on self-employment income. \\n- Weekly fl... \n2 Class 3 Voluntary contributions for those filling gaps... \n3 Class 4 Based on self-employment profits. \\n- Annual m... \n\n Thresholds & Limits \\\n0 Primary Threshold, Upper Earnings Limit \n1 Small Profits Threshold; lower profits thresho... \n2 None (voluntary) \n3 Lower Profits Limit, Upper Profits Limit \n\n Rate Application \\\n0 Main and Additional rates \n1 Flat rate \n2 Flat rate \n3 Main and Additional rates \n\n Reference \n0 Social Security Contributions and Benefits Act... \n1 Social Security Contributions and Benefits Act... \n2 Social Security Contributions and Benefits Act... \n3 Social Security Contributions and Benefits Act... " } } ], @@ -146,7 +76,7 @@ " ],\n", " \"Thresholds & Limits\": [\n", " \"Primary Threshold, Upper Earnings Limit\",\n", - " \"Small Profits Threshold\",\n", + " \"Small Profits Threshold; lower profits threshold (2022-23 and 2023-24)\",\n", " \"None (voluntary)\",\n", " \"Lower Profits Limit, Upper Profits Limit\",\n", " ],\n", @@ -171,7 +101,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": "## How PolicyEngine computes National Insurance liability\n\nFor each person, PolicyEngine computes NI contributions class-by-class:\n\n- **Class 1 employee** (`ni_class_1_employee`): the sum of `ni_class_1_employee_primary` (main rate on earnings between the Primary Threshold and the Upper Earnings Limit) and `ni_class_1_employee_additional` (additional rate on earnings above the UEL). These primary contributions are due from age 16 until state pension age (`ni_liable`): SSCBA 1992 s.6(3) ends them at pensionable age.\n- **Class 1 employer** (`ni_class_1_employer`): paid on earnings above the Secondary Threshold; surfaced through the `ni_employer` variable. These secondary contributions are due on earners over 16 with no upper age limit (`ni_class_1_secondary_liable`), so employers keep paying them for employees over state pension age (SSCBA 1992 s.6(1)(b) and s.6(3)).\n- **Class 2** (`ni_class_2`): a flat weekly rate paid by the self-employed with profits above the Small Profits Threshold.\n- **Class 3** (`ni_class_3`): a voluntary flat weekly rate; the variable is exposed but defaults to zero unless explicitly set.\n- **Class 4** (`ni_class_4`): main rate between the Lower and Upper Profits Limits plus additional rate above the UPL. Where the person also pays primary Class 1 contributions (or, before 6 April 2024, Class 2 contributions), liability is capped at the annual maximum in regulation 100 of the Social Security (Contributions) Regulations 2001 (`ni_class_4_maximum`).\n\nThe individual total `national_insurance` sums the four employee and self-employed components directly — `ni_class_1_employee + ni_class_2 + ni_class_3 + ni_class_4` — so voluntary Class 3 contributions are included in the household-side liability. The convenience aggregates `ni_employee` (Class 1 employee only) and `ni_self_employed` (Class 2 + Class 4) are also exposed for reporting. The Class 1 *employer* charge (`ni_class_1_employer`, surfaced as `ni_employer`) is an employer-side cost rather than part of the individual's `national_insurance`, so it is tracked separately and feeds the government revenue aggregates on its own.\n\nParameters live in `policyengine_uk/parameters/gov/hmrc/national_insurance/` and the per-class formulas in `policyengine_uk/variables/gov/hmrc/national_insurance/`." + "source": "## How PolicyEngine computes National Insurance liability\n\nFor each person, PolicyEngine computes NI contributions class-by-class:\n\n- **Class 1 employee** (`ni_class_1_employee`): the sum of `ni_class_1_employee_primary` (main rate on earnings between the Primary Threshold and the Upper Earnings Limit) and `ni_class_1_employee_additional` (additional rate on earnings above the UEL). These primary contributions are due from age 16 until state pension age (`ni_liable`): SSCBA 1992 s.6(3) ends them at pensionable age.\n- **Class 1 employer** (`ni_class_1_employer`): paid on earnings above the Secondary Threshold; surfaced through the `ni_employer` variable. These secondary contributions are due on earners over 16 with no upper age limit (`ni_class_1_secondary_liable`), so employers keep paying them for employees over state pension age (SSCBA 1992 s.6(1)(b) and s.6(3)).\n- **Class 2** (`ni_class_2`): 52 weeks of a flat weekly rate for the self-employed. Before 2022-23 it is payable on profits at or above the Small Profits Threshold. In 2022-23 and 2023-24 it is payable only on profits above the Class 2 lower profits threshold; profits from the Small Profits Threshold up to it are treated as paid and cost nothing. Compulsory Class 2 ended on 6 April 2024; voluntary Class 2 is not modelled.\n- **Class 3** (`ni_class_3`): a voluntary flat weekly rate; the variable is exposed but defaults to zero unless explicitly set.\n- **Class 4** (`ni_class_4`): main rate between the Lower and Upper Profits Limits plus additional rate above the UPL. Where the person also pays primary Class 1 contributions (or, before 6 April 2024, Class 2 contributions), liability is capped at the annual maximum in regulation 100 of the Social Security (Contributions) Regulations 2001 (`ni_class_4_maximum`).\n\nThe individual total `national_insurance` sums the four employee and self-employed components directly — `ni_class_1_employee + ni_class_2 + ni_class_3 + ni_class_4` — so voluntary Class 3 contributions are included in the household-side liability. The convenience aggregates `ni_employee` (Class 1 employee only) and `ni_self_employed` (Class 2 + Class 4) are also exposed for reporting. The Class 1 *employer* charge (`ni_class_1_employer`, surfaced as `ni_employer`) is an employer-side cost rather than part of the individual's `national_insurance`, so it is tracked separately and feeds the government revenue aggregates on its own.\n\nParameters live in `policyengine_uk/parameters/gov/hmrc/national_insurance/` and the per-class formulas in `policyengine_uk/variables/gov/hmrc/national_insurance/`." }, { "cell_type": "markdown", @@ -200,8 +130,8 @@ }, "data": [ { - "hovertemplate": "Label=NI Lower Earnings Limit
Instant=%{x}
Value=%{y}", - "legendgroup": "NI Lower Earnings Limit", + "hovertemplate": "Label=NI lower earnings limit
Instant=%{x}
Value=%{y}", + "legendgroup": "NI lower earnings limit", "line": { "color": "#636efa", "dash": "solid", @@ -211,7 +141,7 @@ "symbol": "circle" }, "mode": "lines", - "name": "NI Lower Earnings Limit", + "name": "NI lower earnings limit", "orientation": "v", "showlegend": true, "type": "scatter", @@ -233,26 +163,26 @@ ], "xaxis": "x", "y": [ - 120, - 120, - 123, - 123, - 123, - 123, - 123, - 123, - 123, - 123, - 123, - 123, - 123, - 123 + 120.0, + 120.0, + 123.0, + 123.0, + 123.0, + 123.0, + 129.0, + 131.57979643487917, + 134.2112670927332, + 136.8951949163344, + 139.63314579122738, + 142.42590266018465, + 145.27424846597842, + 148.17974909415344 ], "yaxis": "y" }, { - "hovertemplate": "Label=NI Primary Threshold
Instant=%{x}
Value=%{y}", - "legendgroup": "NI Primary Threshold", + "hovertemplate": "Label=NI primary threshold
Instant=%{x}
Value=%{y}", + "legendgroup": "NI primary threshold", "line": { "color": "#EF553B", "dash": "solid", @@ -262,7 +192,7 @@ "symbol": "circle" }, "mode": "lines", - "name": "NI Primary Threshold", + "name": "NI primary threshold", "orientation": "v", "showlegend": true, "type": "scatter", @@ -284,26 +214,26 @@ ], "xaxis": "x", "y": [ - 183, - 184, - 190, + 183.0, + 184.0, + 190.0, 241.73, 241.73, 241.73, 241.73, 241.73, - 241.73, - 241.73, - 241.73, - 241.73, - 241.73, - 241.73 + 246.56436986040532, + 251.49511067607608, + 256.5250991919456, + 261.65577378048056, + 266.8885728141475, + 272.2263730378798 ], "yaxis": "y" }, { - "hovertemplate": "Label=NI Upper Earnings Limit
Instant=%{x}
Value=%{y}", - "legendgroup": "NI Upper Earnings Limit", + "hovertemplate": "Label=NI upper earnings limit
Instant=%{x}
Value=%{y}", + "legendgroup": "NI upper earnings limit", "line": { "color": "#00cc96", "dash": "solid", @@ -313,7 +243,7 @@ "symbol": "circle" }, "mode": "lines", - "name": "NI Upper Earnings Limit", + "name": "NI upper earnings limit", "orientation": "v", "showlegend": true, "type": "scatter", @@ -335,10 +265,7 @@ ], "xaxis": "x", "y": [ - 962, - 966.73, - 966.73, - 966.73, + 962.0, 966.73, 966.73, 966.73, @@ -346,15 +273,18 @@ 966.73, 966.73, 966.73, - 966.73, - 966.73, - 966.73 + 986.0636796225112, + 1005.7827673184257, + 1025.8987678063527, + 1046.4174334455963, + 1067.3445165954613, + 1088.6915219745565 ], "yaxis": "y" }, { - "hovertemplate": "Label=NI Secondary Threshold
Instant=%{x}
Value=%{y}", - "legendgroup": "NI Secondary Threshold", + "hovertemplate": "Label=NI secondary threshold
Instant=%{x}
Value=%{y}", + "legendgroup": "NI secondary threshold", "line": { "color": "#ab63fa", "dash": "solid", @@ -364,7 +294,7 @@ "symbol": "circle" }, "mode": "lines", - "name": "NI Secondary Threshold", + "name": "NI secondary threshold", "orientation": "v", "showlegend": true, "type": "scatter", @@ -386,26 +316,26 @@ ], "xaxis": "x", "y": [ - 169, - 170, - 175, - 175, - 175, - 175, - 175, - 175, - 175, - 175, - 175, - 175, - 175, - 175 + 169.0, + 170.0, + 175.0, + 175.0, + 175.0, + 96.0, + 96.0, + 96.0, + 96.0, + 96.0, + 96.0, + 97.92006459426726, + 99.8783474633293, + 101.87592517830709 ], "yaxis": "y" }, { - "hovertemplate": "Label=NI Class 2 Small Profits Threshold
Instant=%{x}
Value=%{y}", - "legendgroup": "NI Class 2 Small Profits Threshold", + "hovertemplate": "Label=NI class 2 small profits threshold
Instant=%{x}
Value=%{y}", + "legendgroup": "NI class 2 small profits threshold", "line": { "color": "#FFA15A", "dash": "solid", @@ -415,7 +345,7 @@ "symbol": "circle" }, "mode": "lines", - "name": "NI Class 2 Small Profits Threshold", + "name": "NI class 2 small profits threshold", "orientation": "v", "showlegend": true, "type": "scatter", @@ -437,26 +367,26 @@ ], "xaxis": "x", "y": [ - 6475, - 6515, - 6725, - 6725, - 6725, - 6725, - 6725, - 6725, - 6725, - 6725, - 6725, - 6725, - 6725, - 6725 + 6475.0, + 6515.0, + 6725.0, + 6725.0, + 6725.0, + 6845.0, + 7105.0, + 7247.088788138113, + 7392.0236642935615, + 7539.84775101206, + 7690.647293385044, + 7844.465413958232, + 8001.345235277337, + 8161.373002433799 ], "yaxis": "y" }, { - "hovertemplate": "Label=NI Lower Profits Limit
Instant=%{x}
Value=%{y}", - "legendgroup": "NI Lower Profits Limit", + "hovertemplate": "Label=NI lower profits limit
Instant=%{x}
Value=%{y}", + "legendgroup": "NI lower profits limit", "line": { "color": "#19d3f3", "dash": "solid", @@ -466,7 +396,7 @@ "symbol": "circle" }, "mode": "lines", - "name": "NI Lower Profits Limit", + "name": "NI lower profits limit", "orientation": "v", "showlegend": true, "type": "scatter", @@ -488,26 +418,26 @@ ], "xaxis": "x", "y": [ - 9500, - 9568, - 11908, - 11908, - 11908, - 11908, - 12570, - 12570, - 12570, - 12570, - 12570, - 12570, - 12570, - 12570 + 9500.0, + 9568.0, + 11908.0, + 11908.0, + 12570.0, + 12570.0, + 12570.0, + 12821.380164235901, + 13077.795560896562, + 13339.32248138235, + 13606.113508494018, + 13878.244933631944, + 14155.793048196501, + 14438.910434988438 ], "yaxis": "y" }, { - "hovertemplate": "Label=NI Upper Profits Limit
Instant=%{x}
Value=%{y}", - "legendgroup": "NI Upper Profits Limit", + "hovertemplate": "Label=NI upper profits limit
Instant=%{x}
Value=%{y}", + "legendgroup": "NI upper profits limit", "line": { "color": "#FF6692", "dash": "solid", @@ -517,7 +447,7 @@ "symbol": "circle" }, "mode": "lines", - "name": "NI Upper Profits Limit", + "name": "NI upper profits limit", "orientation": "v", "showlegend": true, "type": "scatter", @@ -539,20 +469,20 @@ ], "xaxis": "x", "y": [ - 50000, - 50270, - 50270, - 50270, - 50270, - 50270, - 50270, - 50270, - 50270, - 50270, - 50270, - 50270, - 50270, - 50270 + 50000.0, + 50270.0, + 50270.0, + 50270.0, + 50270.0, + 50270.0, + 50270.0, + 51275.32067272385, + 52300.77826939301, + 53346.67789491573, + 54413.62975910853, + 55501.9389668797, + 56611.910623137475, + 57744.1549376984 ], "yaxis": "y" } @@ -565,10 +495,10 @@ "height": 600, "images": [ { - "sizex": 0.2, - "sizey": 0.2, + "sizex": 0.15, + "sizey": 0.15, "source": "https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.png", - "x": 1, + "x": 1.1, "xanchor": "right", "xref": "paper", "y": -0.15, @@ -670,7 +600,7 @@ }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -706,7 +636,7 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ], @@ -730,7 +660,7 @@ }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -766,7 +696,7 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ], @@ -781,7 +711,7 @@ }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -817,7 +747,7 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ], @@ -844,7 +774,7 @@ }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -880,7 +810,7 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ], @@ -895,7 +825,7 @@ }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -931,7 +861,7 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ], @@ -1076,7 +1006,7 @@ }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -1112,7 +1042,7 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ], @@ -1203,7 +1133,7 @@ ], "sequential": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -1239,13 +1169,13 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ], "sequentialminus": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -1281,7 +1211,7 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ] @@ -1417,8 +1347,8 @@ "xaxis": { "anchor": "y", "domain": [ - 0, - 1 + 0.0, + 1.0 ], "title": { "text": "Instant" @@ -1427,8 +1357,8 @@ "yaxis": { "anchor": "x", "domain": [ - 0, - 1 + 0.0, + 1.0 ], "title": { "text": "Value" @@ -1463,7 +1393,7 @@ " for param in ni.get_descendants()\n", " if param not in threshold_parameters\n", " and isinstance(param, Parameter)\n", - " and param.metadata.get(\"unit\") != \"bool\"\n", + " and param.metadata.get(\"unit\") == \"/1\"\n", "]\n", "\n", "instants = [f\"{2020 + i}-04-06\" for i in range(0, 14)]\n", @@ -1548,8 +1478,8 @@ }, "data": [ { - "hovertemplate": "Label=NI Class 4 additional rate
Instant=%{x}
Value=%{y}", - "legendgroup": "NI Class 4 additional rate", + "hovertemplate": "Label=NI class 1 additional rate
Instant=%{x}
Value=%{y}", + "legendgroup": "NI class 1 additional rate", "line": { "color": "#636efa", "dash": "solid", @@ -1559,7 +1489,7 @@ "symbol": "circle" }, "mode": "lines", - "name": "NI Class 4 additional rate", + "name": "NI class 1 additional rate", "orientation": "v", "showlegend": true, "type": "scatter", @@ -1583,24 +1513,24 @@ "y": [ 0.02, 0.02, - 0.035, - 0.035, - 0.035, - 0.035, - 0.035, - 0.035, - 0.035, - 0.035, - 0.035, - 0.035, - 0.035, - 0.035 + 0.0325, + 0.0325, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02 ], "yaxis": "y" }, { - "hovertemplate": "Label=NI Class 4 main rate
Instant=%{x}
Value=%{y}", - "legendgroup": "NI Class 4 main rate", + "hovertemplate": "Label=NI class 1 main rate
Instant=%{x}
Value=%{y}", + "legendgroup": "NI class 1 main rate", "line": { "color": "#EF553B", "dash": "solid", @@ -1610,7 +1540,7 @@ "symbol": "circle" }, "mode": "lines", - "name": "NI Class 4 main rate", + "name": "NI class 1 main rate", "orientation": "v", "showlegend": true, "type": "scatter", @@ -1632,26 +1562,26 @@ ], "xaxis": "x", "y": [ - 0.09, - 0.09, - 0.1025, - 0.09, - 0.09, - 0.09, - 0.09, - 0.09, - 0.09, - 0.09, - 0.09, - 0.09, - 0.09, - 0.09 + 0.12, + 0.12, + 0.1325, + 0.12, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08 ], "yaxis": "y" }, { - "hovertemplate": "Label=NI Class 1 additional rate
Instant=%{x}
Value=%{y}", - "legendgroup": "NI Class 1 additional rate", + "hovertemplate": "Label=NI employer rate
Instant=%{x}
Value=%{y}", + "legendgroup": "NI employer rate", "line": { "color": "#00cc96", "dash": "solid", @@ -1661,7 +1591,7 @@ "symbol": "circle" }, "mode": "lines", - "name": "NI Class 1 additional rate", + "name": "NI employer rate", "orientation": "v", "showlegend": true, "type": "scatter", @@ -1683,26 +1613,26 @@ ], "xaxis": "x", "y": [ - 0.02, - 0.02, - 0.0325, - 0.0325, - 0.0325, - 0.0325, - 0.0325, - 0.0325, - 0.0325, - 0.0325, - 0.0325, - 0.0325, - 0.0325, - 0.0325 + 0.138, + 0.138, + 0.138, + 0.138, + 0.138, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15, + 0.15 ], "yaxis": "y" }, { - "hovertemplate": "Label=NI Class 1 main rate
Instant=%{x}
Value=%{y}", - "legendgroup": "NI Class 1 main rate", + "hovertemplate": "Label=NI class 4 additional rate
Instant=%{x}
Value=%{y}", + "legendgroup": "NI class 4 additional rate", "line": { "color": "#ab63fa", "dash": "solid", @@ -1712,7 +1642,7 @@ "symbol": "circle" }, "mode": "lines", - "name": "NI Class 1 main rate", + "name": "NI class 4 additional rate", "orientation": "v", "showlegend": true, "type": "scatter", @@ -1734,26 +1664,26 @@ ], "xaxis": "x", "y": [ - 0.12, - 0.12, - 0.1325, - 0.12, - 0.12, - 0.12, - 0.12, - 0.12, - 0.12, - 0.12, - 0.12, - 0.12, - 0.12, - 0.12 + 0.02, + 0.02, + 0.035, + 0.035, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02, + 0.02 ], "yaxis": "y" }, { - "hovertemplate": "Label=NI Employer rate
Instant=%{x}
Value=%{y}", - "legendgroup": "NI Employer rate", + "hovertemplate": "Label=NI class 4 main rate
Instant=%{x}
Value=%{y}", + "legendgroup": "NI class 4 main rate", "line": { "color": "#FFA15A", "dash": "solid", @@ -1763,7 +1693,7 @@ "symbol": "circle" }, "mode": "lines", - "name": "NI Employer rate", + "name": "NI class 4 main rate", "orientation": "v", "showlegend": true, "type": "scatter", @@ -1785,20 +1715,20 @@ ], "xaxis": "x", "y": [ - 0.138, - 0.138, - 0.138, - 0.138, - 0.138, - 0.138, - 0.138, - 0.138, - 0.138, - 0.138, - 0.138, - 0.138, - 0.138, - 0.138 + 0.09, + 0.09, + 0.1025, + 0.09, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06 ], "yaxis": "y" } @@ -1811,10 +1741,10 @@ "height": 600, "images": [ { - "sizex": 0.2, - "sizey": 0.2, + "sizex": 0.15, + "sizey": 0.15, "source": "https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.png", - "x": 1, + "x": 1.1, "xanchor": "right", "xref": "paper", "y": -0.15, @@ -1916,7 +1846,7 @@ }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -1952,7 +1882,7 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ], @@ -1976,7 +1906,7 @@ }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -2012,7 +1942,7 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ], @@ -2027,7 +1957,7 @@ }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -2063,7 +1993,7 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ], @@ -2090,7 +2020,7 @@ }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -2126,7 +2056,7 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ], @@ -2141,7 +2071,7 @@ }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -2177,7 +2107,7 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ], @@ -2322,7 +2252,7 @@ }, "colorscale": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -2358,7 +2288,7 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ], @@ -2449,7 +2379,7 @@ ], "sequential": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -2485,13 +2415,13 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ], "sequentialminus": [ [ - 0, + 0.0, "#0d0887" ], [ @@ -2527,7 +2457,7 @@ "#fdca26" ], [ - 1, + 1.0, "#f0f921" ] ] @@ -2663,8 +2593,8 @@ "xaxis": { "anchor": "y", "domain": [ - 0, - 1 + 0.0, + 1.0 ], "title": { "text": "Instant" @@ -2673,8 +2603,8 @@ "yaxis": { "anchor": "x", "domain": [ - 0, - 1 + 0.0, + 1.0 ], "title": { "text": "Value" @@ -2689,7 +2619,7 @@ ], "source": [ "fig = px.line(\n", - " df[(df[\"Threshold\"] == False) & (df[\"Value\"] < 1)], # Don't plot the flat rate,\n", + " df[df[\"Threshold\"] == False],\n", " x=\"Instant\",\n", " y=\"Value\",\n", " color=\"Label\",\n", diff --git a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/flat_rate.yaml b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/flat_rate.yaml index ff37748484..8f0acf62ac 100644 --- a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/flat_rate.yaml +++ b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/flat_rate.yaml @@ -1,25 +1,65 @@ -description: Flat rate National Insurance contribution for self-employed earners. +description: Weekly Class 2 National Insurance contribution that self-employed earners are liable to pay under section 11(2) of the Social Security Contributions and Benefits Act 1992. Compulsory Class 2 ended on 6 April 2024; voluntary Class 2 is not modelled. values: - 2015-04-01: 2.8 - 2017-04-01: 2.85 - 2018-04-01: 2.95 - 2019-04-01: 3 - 2020-04-01: 3.05 + 2015-04-06: + value: 2.8 + metadata: + reference: + - title: National Insurance Contributions Act 2015 Sch 1 para 3 (substituting s 11(2) of the Social Security Contributions and Benefits Act 1992) + href: https://www.legislation.gov.uk/ukpga/2015/5/schedule/1/paragraph/3/enacted + - title: Social Security Contributions and Benefits Act 1992 s 11(2) (as at 6 April 2016, unchanged for 2016-17) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/11/2016-04-06 + 2017-04-06: + value: 2.85 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2017 reg 3(a) + href: https://www.legislation.gov.uk/uksi/2017/415/regulation/3/made + 2018-04-06: + value: 2.95 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2018 reg 3(a) + href: https://www.legislation.gov.uk/uksi/2018/337/regulation/3/made + 2019-04-06: + value: 3 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2019 reg 3(a) + href: https://www.legislation.gov.uk/uksi/2019/262/regulation/3/made + 2020-04-06: + value: 3.05 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2020 reg 3(a) + href: https://www.legislation.gov.uk/uksi/2020/299/regulation/3/made + - title: Social Security Contributions and Benefits Act 1992 s 11(2) (as at 6 April 2021, unchanged for 2021-22) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/11/2021-04-06 2022-04-06: value: 3.15 metadata: reference: - - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2022(3)(a) - href: https://www.legislation.gov.uk/uksi/2022/232/regulation/3/made#regulation-3-a - 2024-01-01: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2022 reg 3(a) + href: https://www.legislation.gov.uk/uksi/2022/232/regulation/3/made + 2023-04-06: + value: 3.45 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2023 reg 3(a) + href: https://www.legislation.gov.uk/uksi/2023/236/regulation/3/made + - title: Social Security Contributions and Benefits Act 1992 s 11(2) (as at 6 April 2023) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/11/2023-04-06 + 2024-04-06: value: 0 metadata: reference: - - title: "Autumn Statement 2023: National Insurance Factsheet" - href: https://www.gov.uk/government/publications/autumn-statement-2023-national-insurance-factsheet/autumn-statement-2023-national-insurance-factsheet + - title: National Insurance Contributions (Reduction in Rates) Act 2023 s 3(1)(a) and (3) (omitting s 11(2) of the Social Security Contributions and Benefits Act 1992 from 6 April 2024) + href: https://www.legislation.gov.uk/ukpga/2023/57/section/3/enacted metadata: unit: currency-GBP + period: week label: NI class 2 flat rate reference: - - https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/882271/Table-a4.pdf - - https://www.gov.uk/government/publications/rates-and-allowances-national-insurance-contributions/rates-and-allowances-national-insurance-contributions + - title: Social Security Contributions and Benefits Act 1992 s 11 + href: https://www.legislation.gov.uk/ukpga/1992/4/section/11 + - title: "GOV.UK | Rates and allowances: National Insurance contributions" + href: https://www.gov.uk/government/publications/rates-and-allowances-national-insurance-contributions/rates-and-allowances-national-insurance-contributions diff --git a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/lower_profits_threshold.yaml b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/lower_profits_threshold.yaml new file mode 100644 index 0000000000..b0354aa651 --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/lower_profits_threshold.yaml @@ -0,0 +1,21 @@ +description: Lower profits threshold for Class 2 National Insurance contributions under section 11(4)(a) of the Social Security Contributions and Benefits Act 1992. In 2022-23 and 2023-24 self-employed earners were liable for Class 2 only on profits above it. Section 11(4)(a) was omitted from 6 April 2024, when compulsory Class 2 ended; the 2023-24 value applies to reforms that restore a Class 2 rate. +values: + 2022-04-06: + value: 11_908 + metadata: + reference: + - title: The Social Security (Class 2 National Insurance Contributions Increase of Threshold) Regulations 2022 reg 2(3) + href: https://www.legislation.gov.uk/uksi/2022/1329/regulation/2/made + 2023-04-06: + value: 12_570 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2023 reg 3(b) + href: https://www.legislation.gov.uk/uksi/2023/236/regulation/3/made +metadata: + unit: currency-GBP + period: year + label: NI class 2 lower profits threshold + reference: + - title: Social Security Contributions and Benefits Act 1992 s 11(2), (4) and (5A) (as at 6 April 2023) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/11/2023-04-06 diff --git a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/lower_profits_threshold_applies.yaml b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/lower_profits_threshold_applies.yaml new file mode 100644 index 0000000000..70c16fd04a --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/lower_profits_threshold_applies.yaml @@ -0,0 +1,21 @@ +description: Class 2 National Insurance liability starts above the lower profits threshold rather than at the small profits threshold. From 2022-23 profits from the small profits threshold up to the lower profits threshold are treated as paid. +values: + 2015-04-06: + value: false + metadata: + reference: + - title: Social Security Contributions and Benefits Act 1992 s 11(2) (as at 6 April 2021) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/11/2021-04-06 + 2022-04-06: + value: true + metadata: + reference: + - title: The Social Security (Class 2 National Insurance Contributions Increase of Threshold) Regulations 2022 regs 1 and 2(2) + href: https://www.legislation.gov.uk/uksi/2022/1329/regulation/2/made +metadata: + unit: bool + period: year + label: Class 2 liability starts above the lower profits threshold + reference: + - title: Social Security Contributions and Benefits Act 1992 s 11(2), (5A) and (5B) (as at 6 April 2023) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/11/2023-04-06 diff --git a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/small_profits_threshold.yaml b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/small_profits_threshold.yaml index 25af299979..e41baf8e24 100644 --- a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/small_profits_threshold.yaml +++ b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/small_profits_threshold.yaml @@ -1,35 +1,80 @@ -description: Small profits National Insurance threshold for self-employed earners. +description: Small profits threshold for Class 2 National Insurance contributions under section 11(4) of the Social Security Contributions and Benefits Act 1992. Before 2022-23 self-employed earners with profits at or above it were liable for Class 2; in 2022-23 and 2023-24 profits from this threshold up to the lower profits threshold were treated as paid. values: - 2015-04-06: 3_965 - 2017-04-06: 6_025 - 2018-04-06: 6_205 - 2019-04-06: 6_365 - 2020-04-06: 6_475 - 2021-04-06: 6_515 + 2015-04-06: + value: 5_965 + metadata: + reference: + - title: National Insurance Contributions Act 2015 Sch 1 para 3 (substituting s 11(4) of the Social Security Contributions and Benefits Act 1992) + href: https://www.legislation.gov.uk/ukpga/2015/5/schedule/1/paragraph/3/enacted + - title: Social Security Contributions and Benefits Act 1992 s 11(4) (as at 6 April 2016, unchanged for 2016-17) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/11/2016-04-06 + 2017-04-06: + value: 6_025 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2017 reg 3(b) + href: https://www.legislation.gov.uk/uksi/2017/415/regulation/3/made + 2018-04-06: + value: 6_205 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2018 reg 3(b) + href: https://www.legislation.gov.uk/uksi/2018/337/regulation/3/made + 2019-04-06: + value: 6_365 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2019 reg 3(b) + href: https://www.legislation.gov.uk/uksi/2019/262/regulation/3/made + 2020-04-06: + value: 6_475 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2020 reg 3(b) + href: https://www.legislation.gov.uk/uksi/2020/299/regulation/3/made + 2021-04-06: + value: 6_515 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2021 reg 3 + href: https://www.legislation.gov.uk/uksi/2021/157/regulation/3/made 2022-04-06: value: 6_725 metadata: reference: - - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2022(3)(b) - href: https://www.legislation.gov.uk/uksi/2022/232/regulation/3/made#regulation-3-b + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2022 reg 3(b) + href: https://www.legislation.gov.uk/uksi/2022/232/regulation/3/made + 2023-04-06: + value: 6_725 + metadata: + reference: + - title: Social Security Contributions and Benefits Act 1992 s 11(4)(b) (as at 6 April 2023) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/11/2023-04-06 + 2024-04-06: + value: 6_725 + metadata: + reference: + - title: Social Security Contributions and Benefits Act 1992 s 11(4)(b) (as at 6 April 2024) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/11/2024-04-06 2025-04-06: value: 6_845 metadata: reference: - - title: "GOV.UK | Rates and allowances: National Insurance contributions" - href: https://www.gov.uk/government/publications/rates-and-allowances-national-insurance-contributions/rates-and-allowances-national-insurance-contributions + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments, National Insurance Funds Payments and Extension of Veteran's Relief) Regulations 2025 reg 3(a) + href: https://www.legislation.gov.uk/uksi/2025/288/regulation/3/made 2026-04-06: value: 7_105 metadata: reference: - - title: "GOV.UK | Rates and allowances: National Insurance contributions" - href: https://www.gov.uk/government/publications/rates-and-allowances-national-insurance-contributions/rates-and-allowances-national-insurance-contributions + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments, National Insurance Funds Payments and Extension of Veteran's Relief) Regulations 2026 reg 3(a) + href: https://www.legislation.gov.uk/uksi/2026/231/regulation/3/made metadata: unit: currency-GBP + period: year label: NI class 2 small profits threshold - uprating: - parameter: gov.economic_assumptions.indices.obr.consumer_price_index - start_instant: 2022-04-06 + uprating: gov.economic_assumptions.indices.obr.consumer_price_index reference: - - https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/882271/Table-a4.pdf - - https://www.gov.uk/government/publications/rates-and-allowances-national-insurance-contributions/rates-and-allowances-national-insurance-contributions + - title: Social Security Contributions and Benefits Act 1992 s 11 + href: https://www.legislation.gov.uk/ukpga/1992/4/section/11 + - title: "GOV.UK | Rates and allowances: National Insurance contributions" + href: https://www.gov.uk/government/publications/rates-and-allowances-national-insurance-contributions/rates-and-allowances-national-insurance-contributions diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml index 3a458e7faa..472cb2ef28 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml @@ -1,13 +1,134 @@ -- name: NI Class 2 - under LPL. +# Expected values come from s.11 SSCBA 1992 as in force for each tax year +# (legislation.gov.uk point-in-time text) and the SIs that set its figures. + +# 2023-24: £3.45 a week (SI 2023/236 reg 3(a)) on profits that exceed the +# £12,570 lower profits threshold (reg 3(b)). Profits from the £6,725 small +# profits threshold up to it are treated as paid (s.11(5A)-(5B)). +- name: NI Class 2 - 2023-24, below the small profits threshold. period: 2023 input: self_employment_income: 5_000 output: ni_class_2: 0 -- name: NI Class 2 - over LPL. +- name: NI Class 2 - 2023-24, at the small profits threshold, treated as paid. + period: 2023 + input: + self_employment_income: 6_725 + output: + ni_class_2: 0 + +- name: NI Class 2 - 2023-24, between the thresholds, treated as paid. + period: 2023 + input: + self_employment_income: 10_000 + output: + ni_class_2: 0 + +- name: NI Class 2 - 2023-24, at the lower profits threshold, not liable. + period: 2023 + input: + self_employment_income: 12_570 + output: + ni_class_2: 0 + +- name: NI Class 2 - 2023-24, just above the lower profits threshold. + period: 2023 + input: + self_employment_income: 12_571 + output: + ni_class_2: 3.45 * 52 + +- name: NI Class 2 - 2023-24, above the lower profits threshold. period: 2023 input: self_employment_income: 15_000 + output: + ni_class_2: 3.45 * 52 + +- name: NI Class 2 - 2023-24, above the upper profits limit. + period: 2023 + input: + self_employment_income: 60_000 + output: + ni_class_2: 179.4 + +- name: NI Class 2 - 2023-24, over State Pension age. + period: 2023 + input: + age: 70 + self_employment_income: 60_000 + output: + ni_class_2: 0 + +# 2022-23: £3.15 a week (SI 2022/232 reg 3(a)) on profits that exceed the +# £11,908 lower profits threshold, with effect from 6 April 2022 +# (SI 2022/1329 regs 1 and 2). +- name: NI Class 2 - 2022-23, between the thresholds, treated as paid. + period: 2022 + input: + self_employment_income: 8_000 + output: + ni_class_2: 0 + +- name: NI Class 2 - 2022-23, at the lower profits threshold, not liable. + period: 2022 + input: + self_employment_income: 11_908 + output: + ni_class_2: 0 + +- name: NI Class 2 - 2022-23, above the lower profits threshold. + period: 2022 + input: + self_employment_income: 11_909 output: ni_class_2: 3.15 * 52 + +# 2021-22: £3.05 a week (unchanged from SI 2020/299) on profits of, or +# exceeding, the £6,515 small profits threshold (SI 2021/157 reg 3). +- name: NI Class 2 - 2021-22, just below the small profits threshold. + period: 2021 + input: + self_employment_income: 6_514 + output: + ni_class_2: 0 + +- name: NI Class 2 - 2021-22, at the small profits threshold. + period: 2021 + input: + self_employment_income: 6_515 + output: + ni_class_2: 3.05 * 52 + +# 2020-21: £3.05 a week from £6,475 (SI 2020/299 reg 3). +- name: NI Class 2 - 2020-21, at the small profits threshold. + period: 2020 + input: + self_employment_income: 6_475 + output: + ni_class_2: 3.05 * 52 + +# 2015-16: £2.80 a week from £5,965 (NICA 2015 Sch 1 para 3). +- name: NI Class 2 - 2015-16, just below the small profits threshold. + period: 2015 + input: + self_employment_income: 5_964 + output: + ni_class_2: 0 + +- name: NI Class 2 - 2015-16, at the small profits threshold. + period: 2015 + input: + self_employment_income: 5_965 + output: + ni_class_2: 2.8 * 52 + +# 2024-25: s.11(2) omitted from 6 April 2024 (NICA (Reduction in Rates) Act +# 2023 s.3); no compulsory Class 2. +- name: NI Class 2 - 2024-25, abolished. + period: 2024 + input: + self_employment_income: 60_000 + output: + ni_class_2: 0 diff --git a/policyengine_uk/tests/test_ni_class_2.py b/policyengine_uk/tests/test_ni_class_2.py new file mode 100644 index 0000000000..4e7b68ef60 --- /dev/null +++ b/policyengine_uk/tests/test_ni_class_2.py @@ -0,0 +1,202 @@ +"""Class 2 National Insurance against s.11 SSCBA 1992, tax year by tax year. + +STATUTE holds each tax year's figures as enacted, read from legislation.gov.uk +(the amending instrument's made text and the point-in-time text of s.11), +independently of the parameter files. + +Invariants, for every model year and profits >= 0: + +1. Differential: ni_class_2 is 52 weeks at the statutory weekly rate when + s.11(2) makes the earner liable, and 0 otherwise. Before 2022-23 the + earner is liable on profits of, or exceeding, the small profits threshold. + In 2022-23 and 2023-24 only profits that exceed the lower profits + threshold are liable; profits from the small profits threshold up to it + are treated as paid (s.11(5A)-(5B)), which costs nothing. From 2024-25 + s.11(2) is omitted and no one is liable. +2. ni_class_2 is either 0 or 52 x the weekly rate. +3. ni_class_2 is non-decreasing in profits. + +The model counts 52 weeks of self-employment in a year. Profits are stored +as float32, so the reference is evaluated on the float32 value. +""" + +from fractions import Fraction + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation +from policyengine_uk.system import system + +WEEKS = 52 + +# Tax year starting in April of the key: (weekly rate under s.11(2), small +# profits threshold, lower profits threshold or None where s.11(2) keys on the +# small profits threshold). A rate of 0 means s.11(2) is omitted. +STATUTE = { + 2015: ("2.80", 5_965, None), # NICA 2015 Sch 1 para 3 + 2016: ("2.80", 5_965, None), # unchanged (s.11 as at 6 April 2016) + 2017: ("2.85", 6_025, None), # SI 2017/415 reg 3 + 2018: ("2.95", 6_205, None), # SI 2018/337 reg 3 + 2019: ("3.00", 6_365, None), # SI 2019/262 reg 3 + 2020: ("3.05", 6_475, None), # SI 2020/299 reg 3 + 2021: ("3.05", 6_515, None), # SI 2021/157 reg 3; rate unchanged + 2022: ("3.15", 6_725, 11_908), # SI 2022/232 reg 3; SI 2022/1329 reg 2 + 2023: ("3.45", 6_725, 12_570), # SI 2023/236 reg 3; SPT unchanged + 2024: ("0", 6_725, None), # s.11(2) omitted: NICRRA 2023 (c. 57) s.3 + 2025: ("0", 6_845, None), # SI 2025/288 reg 3(a) + 2026: ("0", 7_105, None), # SI 2026/231 reg 3(a) +} +MODEL_YEARS = list(range(2015, 2031)) +PROPERTY_SETTINGS = settings( + max_examples=10, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) + + +def statute(year): + # Class 2 stays abolished after the last tabulated year. + return STATUTE[min(year, max(STATUTE))] + + +def statutory_class_2(year, profits): + rate, small_profits_threshold, lower_profits_threshold = statute(year) + profits = Fraction(float(np.float32(profits))) + if lower_profits_threshold is None: + liable = profits >= small_profits_threshold + else: + liable = profits > lower_profits_threshold + return WEEKS * Fraction(rate) if liable else Fraction(0) + + +# Every threshold s.11 has used, with points either side of each. +THRESHOLD_PROFITS = sorted( + {0.0} + | { + float(threshold) + offset + for _, spt, lpt in STATUTE.values() + for threshold in filter(None, (spt, lpt)) + for offset in (-1, -0.01, 0, 0.01, 1) + } +) + + +def simulate(profits_list): + # One person per profit level, with the same profits in every model year, + # so a single simulation covers every year. + people = { + f"p{i}": { + "age": {year: 40 for year in MODEL_YEARS}, + "self_employment_income": {year: profits for year in MODEL_YEARS}, + } + for i, profits in enumerate(profits_list) + } + return Simulation( + situation={ + "people": people, + "benunits": {f"b{i}": {"members": [f"p{i}"]} for i in range(len(people))}, + "households": {f"h{i}": {"members": [f"p{i}"]} for i in range(len(people))}, + } + ) + + +def assert_invariants(profits_list): + sim = simulate(profits_list) + order = np.argsort(np.asarray(profits_list, dtype=np.float32), kind="stable") + for year in MODEL_YEARS: + class_2 = sim.calculate("ni_class_2", year) + full_year = WEEKS * float(statute(year)[0]) + for i, profits in enumerate(profits_list): + expected = float(statutory_class_2(year, profits)) + assert class_2[i] == pytest.approx(expected, abs=0.01), (year, profits) + assert class_2[i] == 0 or class_2[i] == pytest.approx(full_year, abs=0.01) + assert np.all(np.diff(class_2[order]) >= 0), (year, profits_list, class_2) + + +@pytest.mark.parametrize("year", sorted(STATUTE)) +def test_class_2_parameters_match_statute(year): + class_2 = system.parameters(f"{year}-01-01").gov.hmrc.national_insurance.class_2 + rate, small_profits_threshold, lower_profits_threshold = STATUTE[year] + + assert class_2.flat_rate == pytest.approx(float(rate)) + assert class_2.small_profits_threshold == small_profits_threshold + if Fraction(rate) > 0: + assert class_2.lower_profits_threshold_applies == ( + lower_profits_threshold is not None + ) + if lower_profits_threshold is not None: + assert class_2.lower_profits_threshold == lower_profits_threshold + + +def test_ni_class_2_matches_statute_at_every_threshold(): + rng = np.random.default_rng(1_992) + spread = list(rng.uniform(0, 200_000, 200).round(2)) + assert_invariants(THRESHOLD_PROFITS + spread + [60_000.0, 1_000_000.0]) + + +@PROPERTY_SETTINGS +@given( + st.lists( + st.one_of( + st.floats(0, 200_000, allow_nan=False, allow_infinity=False), + st.integers(0, 200_000).map(float), + st.sampled_from(THRESHOLD_PROFITS), + ), + min_size=1, + max_size=24, + ) +) +def test_ni_class_2_properties(profits_list): + assert_invariants(profits_list) + + +def test_2023_annual_maximum_uses_the_2023_24_class_2_rate(): + # Regulation 100 of SI 2001/1004 for 2023-24, with Class 2 at the + # default £3.45 a week. The Class 4 limits and rates are pinned to their + # 2023-24 statutory values (s.15(3) and (3ZA) SSCBA 1992) so the test + # exercises Class 2 alone. Profits £60,000; primary Class 1 £3,000. + # Step 3: 9% x (50,270 - 12,570) + 53 x 3.45 = 3,393 + 182.85 + # Step 4: 3,575.85 - 179.40 (Class 2) - 3,000 (Class 1) = 396.45 + # Case 2: 396.45 + 2% x (37,700 - 396.45 / 9%) + 2% x 9,730 + year = 2023 + all_years = "2000-01-01.2100-12-31" + class_4 = "gov.hmrc.national_insurance.class_4" + sim = Simulation( + situation={ + "people": { + "person": { + "age": {year: 40}, + "self_employment_income": {year: 60_000}, + "ni_class_1_employee_primary": { + f"{year}-{month:02d}": 250 for month in range(1, 13) + }, + } + }, + "benunits": {"benunit": {"members": ["person"]}}, + "households": {"household": {"members": ["person"]}}, + }, + reform={ + f"{class_4}.thresholds.lower_profits_limit": {all_years: 12_570}, + f"{class_4}.thresholds.upper_profits_limit": {all_years: 50_270}, + f"{class_4}.rates.main": {all_years: 0.09}, + f"{class_4}.rates.additional": {all_years: 0.02}, + }, + ) + + step_4 = Fraction("0.09") * 37_700 + 53 * Fraction("3.45") - Fraction("179.40") + step_4 -= 3_000 + step_5 = step_4 / Fraction("0.09") + maximum = step_4 + Fraction("0.02") * (37_700 - step_5) + Fraction("0.02") * 9_730 + + assert sim.calculate("ni_class_2", year)[0] == pytest.approx(179.40, abs=0.01) + assert float(maximum) == pytest.approx(1_256.95, abs=0.005) + assert sim.calculate("ni_class_4_maximum", year)[0] == pytest.approx( + float(maximum), abs=0.01 + ) + assert sim.calculate("ni_class_4", year)[0] == pytest.approx( + float(maximum), abs=0.01 + ) diff --git a/policyengine_uk/tests/test_ni_class_4_properties.py b/policyengine_uk/tests/test_ni_class_4_properties.py index 9f0e3fa512..0019b2246c 100644 --- a/policyengine_uk/tests/test_ni_class_4_properties.py +++ b/policyengine_uk/tests/test_ni_class_4_properties.py @@ -12,15 +12,18 @@ implementation of s.15(3) SSCBA 1992 capped by the literal regulation 100 steps (with the Case 1 comparison done exactly). The one exception is a Case 1 tie that holds in real numbers but not in binary (before April - 2024: 53 x weekly Class 2 = 2 x (Class 1 + Class 2), UPL below the small - profits threshold); the model then follows the real-valued tie. + 2024: 53 x weekly Class 2 = 2 x (Class 1 + Class 2), UPL below the profits + at which Class 2 becomes payable); the model then follows the real-valued + tie. 3. The maximum only ever reduces liability: 0 <= ni_class_4 <= the pre-maximum amount. 4. Monotonicity: with employment income held fixed, ni_class_4 is non-decreasing in self-employment profits wherever Class 2 does not change. Before April 2024 regulation 100 subtracts Class 2, so crossing - the small profits threshold can lower Class 4; Class 2 + Class 4 is then - still non-decreasing when the main rate is at least the additional rate. + the profits at which Class 2 becomes payable (the small profits threshold, + or from 2022-23 the Class 2 lower profits threshold) can lower Class 4; + Class 2 + Class 4 is then still non-decreasing when the main rate is at + least the additional rate. Class 2 counts in regulation 100 only before 6 April 2024 (SI 2024/377). diff --git a/policyengine_uk/variables/gov/hmrc/national_insurance/class_2/ni_class_2.py b/policyengine_uk/variables/gov/hmrc/national_insurance/class_2/ni_class_2.py index a36358bb4d..0b3ef93fae 100644 --- a/policyengine_uk/variables/gov/hmrc/national_insurance/class_2/ni_class_2.py +++ b/policyengine_uk/variables/gov/hmrc/national_insurance/class_2/ni_class_2.py @@ -6,7 +6,6 @@ class ni_class_2(Variable): entity = Person label = "NI Class 2 contributions" definition_period = YEAR - reference = "Social Security and Benefits Act 1992 s. 11" unit = GBP defined_for = "ni_liable" reference = "https://www.legislation.gov.uk/ukpga/1992/4/section/11" @@ -14,5 +13,14 @@ class ni_class_2(Variable): def formula(person, period, parameters): class_2 = parameters(period).gov.hmrc.national_insurance.class_2 profits = person("self_employment_income", period) - over_threshold = profits >= class_2.small_profits_threshold - return over_threshold * class_2.flat_rate * WEEKS_IN_YEAR + # Section 11(2) SSCBA 1992. From 2022-23 only profits that exceed the + # lower profits threshold are liable; profits from the small profits + # threshold up to it are treated as paid (s.11(5A)-(5B)) and cost + # nothing. Before then liability started at the small profits + # threshold. The flat rate is 0 once s.11(2) is omitted in 2024-25. + liable = where( + class_2.lower_profits_threshold_applies, + profits > class_2.lower_profits_threshold, + profits >= class_2.small_profits_threshold, + ) + return liable * class_2.flat_rate * WEEKS_IN_YEAR From db447818e4be93619e34e553fc10baf187214569 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 07:48:28 -0400 Subject: [PATCH 2/8] Fix Class 4 NICs against s.15 SSCBA 1992 for 2015-16 to 2026-27 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 2023-24 lower profits limit £12,570 from 6 April 2023 (NICs (Increase of Thresholds) Act 2022 s.2(3)-(4)); model year 2023 used £11,908. - 2022-23 main and additional rates 9.73% and 2.73% for the whole year (Health and Social Care Levy (Repeal) Act 2022 s.2(2)); the model used 10.25% and 3.5%. - 2023-24 additional rate 2% (s.15(3ZA)(b)); the model used 3.5%. - 2015-16 upper profits limit £42,385 (SI 2015/588 art 3(b)), not £42,386; the £43,000 limit dated 6 April 2016 (SI 2016/343 reg 2). - Main rate cut to 6% dated 6 April 2024 (NICRRA 2024 s.1(3)(a)). - Every Class 4 limit and rate cites its amending instrument. - Regulation 100 already reads the rate parameters; a comment records that its written percentages track the Class 4 rates in every year, including the 2022-23 modification (HSCL (Repeal) Act 2022 Sch para 6). - Tests: the four stale 2023 YAML expectations replaced with hand-derived statutory values (ni_class_4 1,568.70; ni_class_4_main 3,393; ni_class_4_maximum 1,748.60 by reg 100 Case 3), plus 2022-23, 2015-16 and 2016-17 cases; test_ni_class_4_statute.py adds a statute table for every year, 17 hand-derived liabilities and an exact-rational differential (s.15(3) capped by reg 100) at every limit and as a Hypothesis property. - Docs: NI notebook chart values and the model-baseline link. Fixes #1968 Co-Authored-By: Claude Opus 5.5 --- changelog.d/1968.fixed.md | 1 + docs/book/policy/model-baseline.md | 2 +- .../gov/hmrc/national-insurance.ipynb | 8 +- .../class_4/rates/additional.yaml | 32 +- .../class_4/rates/main.yaml | 40 +- .../thresholds/lower_profits_limit.yaml | 54 ++- .../thresholds/upper_profits_limit.yaml | 45 ++- .../class_4/ni_class_4.yaml | 69 +++- .../class_4/ni_class_4_main.yaml | 17 +- .../class_4/ni_class_4_maximum.yaml | 39 +- policyengine_uk/tests/test_ni_class_2.py | 15 +- .../tests/test_ni_class_4_statute.py | 341 ++++++++++++++++++ .../class_4/ni_class_4_maximum.py | 6 + 13 files changed, 609 insertions(+), 60 deletions(-) create mode 100644 changelog.d/1968.fixed.md create mode 100644 policyengine_uk/tests/test_ni_class_4_statute.py diff --git a/changelog.d/1968.fixed.md b/changelog.d/1968.fixed.md new file mode 100644 index 0000000000..ff228cc29e --- /dev/null +++ b/changelog.d/1968.fixed.md @@ -0,0 +1 @@ +- Fixed Class 4 NICs against s.15 SSCBA 1992: the 2023-24 lower profits limit is £12,570 (it was £11,908), the 2022-23 rates are 9.73% and 2.73% for the whole year (they were 10.25% and 3.5%), the 2023-24 additional rate is 2% (it was 3.5%), and the 2015-16 upper profits limit is £42,385. Every Class 4 limit and rate now cites its amending instrument. diff --git a/docs/book/policy/model-baseline.md b/docs/book/policy/model-baseline.md index 18a96cd70a..b17083ae42 100644 --- a/docs/book/policy/model-baseline.md +++ b/docs/book/policy/model-baseline.md @@ -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#L51-L56) 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#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#L26-L33) 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#L51-L56) for self-employed people, with the flat rate set to £0 in fiscal year 2024-25. ### Capital gains tax changes (Autumn Budget 2024) diff --git a/docs/book/programs/gov/hmrc/national-insurance.ipynb b/docs/book/programs/gov/hmrc/national-insurance.ipynb index 7098a199e6..38e8e6e620 100644 --- a/docs/book/programs/gov/hmrc/national-insurance.ipynb +++ b/docs/book/programs/gov/hmrc/national-insurance.ipynb @@ -421,7 +421,7 @@ 9500.0, 9568.0, 11908.0, - 11908.0, + 12570.0, 12570.0, 12570.0, 12570.0, @@ -1666,8 +1666,8 @@ "y": [ 0.02, 0.02, - 0.035, - 0.035, + 0.0273, + 0.02, 0.02, 0.02, 0.02, @@ -1717,7 +1717,7 @@ "y": [ 0.09, 0.09, - 0.1025, + 0.0973, 0.09, 0.06, 0.06, diff --git a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/rates/additional.yaml b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/rates/additional.yaml index 5326d9ae35..81d3c7d5c6 100644 --- a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/rates/additional.yaml +++ b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/rates/additional.yaml @@ -1,19 +1,33 @@ description: The additional National Insurance rate paid above the Upper Profits Limit for self-employed profits. values: - 2015-04-01: 0.02 - 2022-04-01: - value: 0.035 + 2015-04-06: + value: 0.02 + metadata: + reference: + - title: National Insurance Contributions Act 2011 s 2(1)(b) (additional Class 4 percentage of 2% from 6 April 2011) + href: https://www.legislation.gov.uk/ukpga/2011/3/section/2/enacted + - title: Social Security Contributions and Benefits Act 1992 s 15(3ZA)(b) (as at 6 April 2015) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/15/2015-04-06 + # Class 4 is charged on the year's profits, so 2022-23 has one rate for the + # whole tax year. The Health and Social Care Levy (Repeal) Act 2022 replaced + # the 3.25% that the Health and Social Care Levy Act 2021 s 5(2) had set. + 2022-04-06: + value: 0.0273 metadata: reference: - - 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 - 2024-01-01: + - title: Health and Social Care Levy (Repeal) Act 2022 s 2(2)(b) (additional Class 4 percentage of 2.73% for the tax year 2022-23) + href: https://www.legislation.gov.uk/ukpga/2022/43/section/2 + 2023-04-06: value: 0.02 metadata: reference: - - title: National Insurance Contributions (Reduction in Rates) Act 2024 s 1 - href: https://www.legislation.gov.uk/ukpga/2024/5/section/1 + - title: Social Security Contributions and Benefits Act 1992 s 15(3ZA)(b) (as at 6 April 2023) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/15/2023-04-06 metadata: label: NI class 4 additional rate unit: /1 - reference: https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/882271/Table-a4.pdf + reference: + - title: Social Security Contributions and Benefits Act 1992 s 15(3ZA) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/15 + - title: "GOV.UK | Rates and allowances: National Insurance contributions" + href: https://www.gov.uk/government/publications/rates-and-allowances-national-insurance-contributions/rates-and-allowances-national-insurance-contributions diff --git a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/rates/main.yaml b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/rates/main.yaml index 1ef51cea61..5f3dab92c3 100644 --- a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/rates/main.yaml +++ b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/rates/main.yaml @@ -1,25 +1,41 @@ description: The main National Insurance rate paid between the Lower and Upper Profits Limits for self-employed profits. values: - 2015-04-01: 0.09 - 2022-04-01: - value: 0.1025 + 2015-04-06: + value: 0.09 metadata: reference: - - 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: - value: 0.09 + - title: National Insurance Contributions Act 2011 s 2(1)(a) (main Class 4 percentage of 9% from 6 April 2011) + href: https://www.legislation.gov.uk/ukpga/2011/3/section/2/enacted + - title: Social Security Contributions and Benefits Act 1992 s 15(3ZA)(a) (as at 6 April 2015) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/15/2015-04-06 + # Class 4 is charged on the year's profits, so 2022-23 has one rate for the + # whole tax year. The Health and Social Care Levy (Repeal) Act 2022 replaced + # the 10.25% that the Health and Social Care Levy Act 2021 s 5(2) had set. + 2022-04-06: + value: 0.0973 metadata: reference: - - title: Health and Social Care Levy Act (Repeal) 2022 + - title: Health and Social Care Levy (Repeal) Act 2022 s 2(2)(a) (main Class 4 percentage of 9.73% for the tax year 2022-23) href: https://www.legislation.gov.uk/ukpga/2022/43/section/2 - 2024-04-04: + 2023-04-06: + value: 0.09 + metadata: + reference: + - title: Social Security Contributions and Benefits Act 1992 s 15(3ZA)(a) (as at 6 April 2023) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/15/2023-04-06 + 2024-04-06: value: 0.06 metadata: reference: - - title: National Insurance Contributions (Reduction in Rates) Act 2024 s 1 - href: https://www.legislation.gov.uk/ukpga/2024/5/section/1 + - title: National Insurance Contributions (Reduction in Rates) Act 2024 ss 1(3)(a) and 3(1) + href: https://www.legislation.gov.uk/ukpga/2024/5/section/1/enacted + - title: Social Security Contributions and Benefits Act 1992 s 15(3ZA)(a) (as at 6 April 2024) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/15/2024-04-06 metadata: label: NI class 4 main rate unit: /1 - reference: https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/882271/Table-a4.pdf + reference: + - title: Social Security Contributions and Benefits Act 1992 s 15(3ZA) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/15 + - title: "GOV.UK | Rates and allowances: National Insurance contributions" + href: https://www.gov.uk/government/publications/rates-and-allowances-national-insurance-contributions/rates-and-allowances-national-insurance-contributions diff --git a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/thresholds/lower_profits_limit.yaml b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/thresholds/lower_profits_limit.yaml index b36a83b7ba..89f9a4587a 100644 --- a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/thresholds/lower_profits_limit.yaml +++ b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/thresholds/lower_profits_limit.yaml @@ -4,24 +4,60 @@ description: The Lower Profits Limit is the threshold at which self-employed ear # keeping it until April 2031. CPI uprating from 2031-32 is a modelling # assumption. values: - 2015-04-06: 8_060 - 2017-04-06: 8_164 - 2018-04-06: 8_424 - 2019-04-06: 8_632 - 2020-04-06: 9_500 - 2021-04-06: 9_568 + 2015-04-06: + value: 8_060 + metadata: + reference: + - title: The Social Security (Contributions) (Re-rating and National Insurance Funds Payments) Order 2015 art 3(a) + href: https://www.legislation.gov.uk/uksi/2015/588/article/3/made + - title: Social Security Contributions and Benefits Act 1992 s 15(3)(a) (as at 6 April 2016, unchanged for 2016-17) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/15/2016-04-06 + 2017-04-06: + value: 8_164 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2017 reg 5(a) + href: https://www.legislation.gov.uk/uksi/2017/415/regulation/5/made + 2018-04-06: + value: 8_424 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2018 reg 5(a) + href: https://www.legislation.gov.uk/uksi/2018/337/regulation/5/made + 2019-04-06: + value: 8_632 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2019 reg 5(a) + href: https://www.legislation.gov.uk/uksi/2019/262/regulation/5/made + 2020-04-06: + value: 9_500 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2020 reg 5 + href: https://www.legislation.gov.uk/uksi/2020/299/regulation/5/made + 2021-04-06: + value: 9_568 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2021 reg 5(1) + href: https://www.legislation.gov.uk/uksi/2021/157/regulation/5/made + # SI 2022/232 reg 5 first set £9,880 for 2022-23; the 2022 Act replaced it + # with £11,908 for the whole year. 2022-04-06: value: 11_908 metadata: reference: - - title: National Insurance Contributions (Increase of Thresholds) Act 2022 + - title: National Insurance Contributions (Increase of Thresholds) Act 2022 s 2(1) and (2) href: https://www.legislation.gov.uk/ukpga/2022/16/section/2/enacted - 2024-01-01: + 2023-04-06: value: 12_570 metadata: reference: - - title: National Insurance Contributions (Increase of Thresholds) Act 2022 + - title: National Insurance Contributions (Increase of Thresholds) Act 2022 s 2(3) and (4) href: https://www.legislation.gov.uk/ukpga/2022/16/section/2/enacted + - title: Social Security Contributions and Benefits Act 1992 s 15(3)(a) (as at 6 April 2023) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/15/2023-04-06 2026-04-06: value: 12_570 metadata: diff --git a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/thresholds/upper_profits_limit.yaml b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/thresholds/upper_profits_limit.yaml index 37fa1a7b67..b415b7f89e 100644 --- a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/thresholds/upper_profits_limit.yaml +++ b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_4/thresholds/upper_profits_limit.yaml @@ -4,13 +4,44 @@ description: The Upper Profits Limit is the threshold at which self-employed ear # keeping it until April 2031. CPI uprating from 2031-32 is a modelling # assumption. values: - 2015-04-06: 42_386 - 2015-06-05: 43_000 - 2017-04-06: 45_000 - 2018-04-06: 46_350 - 2019-04-06: 50_000 - 2020-04-06: 50_000 - 2021-04-06: 50_270 + 2015-04-06: + value: 42_385 + metadata: + reference: + - title: The Social Security (Contributions) (Re-rating and National Insurance Funds Payments) Order 2015 art 3(b) + href: https://www.legislation.gov.uk/uksi/2015/588/article/3/made + 2016-04-06: + value: 43_000 + metadata: + reference: + - title: The Social Security (Contributions) (Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2016 reg 2 + href: https://www.legislation.gov.uk/uksi/2016/343/regulation/2/made + 2017-04-06: + value: 45_000 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2017 reg 5(b) + href: https://www.legislation.gov.uk/uksi/2017/415/regulation/5/made + 2018-04-06: + value: 46_350 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2018 reg 5(b) + href: https://www.legislation.gov.uk/uksi/2018/337/regulation/5/made + 2019-04-06: + value: 50_000 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2019 reg 5(b) + href: https://www.legislation.gov.uk/uksi/2019/262/regulation/5/made + - title: Social Security Contributions and Benefits Act 1992 s 15(3) (as at 6 April 2020, unchanged for 2020-21) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/15/2020-04-06 + 2021-04-06: + value: 50_270 + metadata: + reference: + - title: The Social Security (Contributions) (Rates, Limits and Thresholds Amendments and National Insurance Funds Payments) Regulations 2021 reg 5(2) + href: https://www.legislation.gov.uk/uksi/2021/157/regulation/5/made 2026-04-06: value: 50_270 metadata: diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4.yaml index 7e59dec612..269e59e34f 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4.yaml @@ -1,10 +1,75 @@ +# Expected values come from s.15(3) and (3ZA) SSCBA 1992 as in force for +# each tax year and, where Class 1 or Class 2 is also payable, the regulation +# 100 annual maximum of SI 2001/1004 (worked through below; it never binds in +# these sole-trader cases). + - name: NI Class 4 - under UPL. + # 2023-24: 9% between £12,570 and £50,270 (s.15(3)(a), (3ZA)(a) as at + # 6 April 2023). (30,000 - 12,570) x 9% = 1,568.70. + # Reg 100: Class 2 is 52 x £3.45 = £179.40. Step 4 = 37,700 x 9% + # + 53 x 3.45 - 179.40 = 3,396.45, which exceeds 179.40 + 1,568.70, so + # Case 1 caps Class 4 at 3,396.45; it does not bind. period: 2023 - absolute_error_margin: 1 + absolute_error_margin: 0.01 + input: + self_employment_income: 30_000 + output: + ni_class_4: 1_568.70 + +- name: NI Class 4 - 2022-23 main rate of 9.73% on profits over £11,908. + # Health and Social Care Levy (Repeal) Act 2022 s.2(2)(a); NICs (Increase + # of Thresholds) Act 2022 s.2(1). (30,000 - 11,908) x 9.73% = 1,760.35. + # Reg 100 (as modified for 2022-23): Class 2 is 52 x £3.15 = £163.80. + # Step 4 = 38,362 x 9.73% + 53 x 3.15 - 163.80 = 3,735.77, more than + # 163.80 + 1,760.35, so Case 1 caps at 3,735.77; it does not bind. + period: 2022 + absolute_error_margin: 0.01 input: self_employment_income: 30_000 output: - ni_class_4: 1628 + ni_class_4: 1_760.35 + +- name: NI Class 4 - 2022-23 additional rate of 2.73% over the UPL. + # s.2(2)(b) of the 2022 Repeal Act. 38,362 x 9.73% + 9,730 x 2.73% + # = 3,732.62 + 265.63 = 3,998.25. + # Reg 100: Step 4 = 3,735.77 does not exceed 163.80 + 3,732.62 (Case 2). + # Step 5 = 3,735.77 / 9.73% = 38,394.37, more than Step 6 (38,362), so + # Steps 7 and 8 are 0. Step 9 = 9,730 x 2.73% = 265.63. Maximum + # = 3,735.77 + 265.63 = 4,001.40, which does not bind. + period: 2022 + absolute_error_margin: 0.01 + input: + self_employment_income: 60_000 + output: + ni_class_4: 3_998.25 + +- name: NI Class 4 - 2015-16 upper profits limit of £42,385. + # SI 2015/588 art 3: limits £8,060 and £42,385; rates 9% and 2%. + # (42,385 - 8,060) x 9% + (50,000 - 42,385) x 2% = 3,089.25 + 152.30 + # = 3,241.55. + # Reg 100: Class 2 is 52 x £2.80 = £145.60. Step 4 = 3,089.25 + # + 53 x 2.80 - 145.60 = 3,092.05, below 145.60 + 3,089.25 (Case 2). + # Step 5 = 34,356.11 exceeds Step 6 (34,325), so Step 8 is 0. Step 9 + # = 7,615 x 2% = 152.30. Maximum 3,244.35, which does not bind. + period: 2015 + absolute_error_margin: 0.01 + input: + self_employment_income: 50_000 + output: + ni_class_4: 3_241.55 + +- name: NI Class 4 - 2016-17 upper profits limit of £43,000. + # SI 2016/343 reg 2. (43,000 - 8,060) x 9% + 7,000 x 2% = 3,144.60 + # + 140 = 3,284.60. + # Reg 100: Step 4 = 3,144.60 + 148.40 - 145.60 = 3,147.40 (Case 2). + # Step 5 = 34,971.11 exceeds Step 6 (34,940); Step 9 = 140. Maximum + # 3,287.40, which does not bind. + period: 2016 + absolute_error_margin: 0.01 + input: + self_employment_income: 50_000 + output: + ni_class_4: 3_284.60 - name: NI Class 4 - 2026 lower profits limit is frozen. period: 2026 diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4_main.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4_main.yaml index c4189d2a66..a4c4d5e375 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4_main.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4_main.yaml @@ -1,7 +1,20 @@ - name: NI Class 4 - over UPL has maximum capped. + # 2023-24: 9% of profits between £12,570 and £50,270 (SSCBA 1992 + # s.15(3)(a) and (3ZA)(a) as at 6 April 2023). (50,270 - 12,570) x 9% + # = 3,393. period: 2023 - absolute_error_margin: 1 + absolute_error_margin: 0.01 input: self_employment_income: 100_000 output: - ni_class_4_main: 3452 + ni_class_4_main: 3_393 + +- name: NI Class 4 - 2022-23 main band at 9.73%. + # Health and Social Care Levy (Repeal) Act 2022 s.2(2)(a); NICs (Increase + # of Thresholds) Act 2022 s.2(1). (50,270 - 11,908) x 9.73% = 3,732.62. + period: 2022 + absolute_error_margin: 0.01 + input: + self_employment_income: 100_000 + output: + ni_class_4_main: 3_732.62 diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.yaml index f1f8ea9999..0a43ff9c05 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.yaml @@ -1,8 +1,43 @@ - name: NI Class 4 with employment and self-employment income - over UPL has maximum capped. + # SI 2001/1004 reg 100(3) as at 6 April 2023, with the 2023-24 SSCBA 1992 + # amounts: lower profits limit £12,570, upper £50,270, main 9%, + # additional 2% (s.15(3), (3ZA)) and Class 2 of £3.45 a week (s.11(2)). + # Step 1: 50,270 - 12,570 = 37,700. Step 2: 37,700 x 9% = 3,393. + # Step 3: 3,393 + 53 x 3.45 = 3,575.85. + # Step 4: less Class 2 of 52 x 3.45 = 179.40 and primary Class 1 at the + # main percentage on £100,000 of pay. That is at least 10% (the lowest + # main primary percentage in 2023-24) of the £37,700 between the primary + # threshold and the upper earnings limit, £3,770, so Step 4 is negative: + # Case 3, treated as nil. + # Step 5: 0. Step 6: min(50,270, 100,000) - 12,570 = 37,700. + # Step 7: 37,700 - 0 = 37,700. Step 8: 37,700 x 2% = 754. + # Step 9: (100,000 - 50,270) x 2% = 994.60. + # Maximum (Steps 4 + 8 + 9): 0 + 754 + 994.60 = 1,748.60. period: 2023 - absolute_error_margin: 1 + absolute_error_margin: 0.01 input: self_employment_income: 100_000 employment_income: 100_000 output: - ni_class_4_maximum: 3_083.22 + ni_class_4_maximum: 1_748.60 + ni_class_4: 1_748.60 + +- name: NI Class 4 maximum in 2022-23 uses the 2.73% additional rate. + # Reg 100 as modified for 2022-23 by the Health and Social Care Levy + # (Repeal) Act 2022 Sch para 6(2): 9.73% in Step 2, 100/9.73 in Step 5, + # 2.73% in Steps 8 and 9. Limits £11,908 and £50,270. + # Step 3: 38,362 x 9.73% + 53 x 3.15 = 3,899.57. Step 4 less Class 2 of + # 52 x 3.15 = 163.80 and primary Class 1 on £100,000 of pay, at least + # 12% of the £37,700 between £12,570 and £50,270 (£4,524), is negative: + # Case 3, nil. + # Step 7 = Step 6 = 50,270 - 11,908 = 38,362. Step 8: 38,362 x 2.73% + # = 1,047.28. Step 9: 49,730 x 2.73% = 1,357.63. + # Maximum: 0 + 1,047.28 + 1,357.63 = 2,404.91. + period: 2022 + absolute_error_margin: 0.01 + input: + self_employment_income: 100_000 + employment_income: 100_000 + output: + ni_class_4_maximum: 2_404.91 + ni_class_4: 2_404.91 diff --git a/policyengine_uk/tests/test_ni_class_2.py b/policyengine_uk/tests/test_ni_class_2.py index 4e7b68ef60..e9bb4d9489 100644 --- a/policyengine_uk/tests/test_ni_class_2.py +++ b/policyengine_uk/tests/test_ni_class_2.py @@ -155,16 +155,13 @@ def test_ni_class_2_properties(profits_list): def test_2023_annual_maximum_uses_the_2023_24_class_2_rate(): - # Regulation 100 of SI 2001/1004 for 2023-24, with Class 2 at the - # default £3.45 a week. The Class 4 limits and rates are pinned to their - # 2023-24 statutory values (s.15(3) and (3ZA) SSCBA 1992) so the test - # exercises Class 2 alone. Profits £60,000; primary Class 1 £3,000. + # Regulation 100 of SI 2001/1004 for 2023-24, at default parameters: + # Class 2 at £3.45 a week and the 2023-24 Class 4 limits and rates + # (s.15(3) and (3ZA) SSCBA 1992). Profits £60,000; primary Class 1 £3,000. # Step 3: 9% x (50,270 - 12,570) + 53 x 3.45 = 3,393 + 182.85 # Step 4: 3,575.85 - 179.40 (Class 2) - 3,000 (Class 1) = 396.45 # Case 2: 396.45 + 2% x (37,700 - 396.45 / 9%) + 2% x 9,730 year = 2023 - all_years = "2000-01-01.2100-12-31" - class_4 = "gov.hmrc.national_insurance.class_4" sim = Simulation( situation={ "people": { @@ -179,12 +176,6 @@ def test_2023_annual_maximum_uses_the_2023_24_class_2_rate(): "benunits": {"benunit": {"members": ["person"]}}, "households": {"household": {"members": ["person"]}}, }, - reform={ - f"{class_4}.thresholds.lower_profits_limit": {all_years: 12_570}, - f"{class_4}.thresholds.upper_profits_limit": {all_years: 50_270}, - f"{class_4}.rates.main": {all_years: 0.09}, - f"{class_4}.rates.additional": {all_years: 0.02}, - }, ) step_4 = Fraction("0.09") * 37_700 + 53 * Fraction("3.45") - Fraction("179.40") diff --git a/policyengine_uk/tests/test_ni_class_4_statute.py b/policyengine_uk/tests/test_ni_class_4_statute.py new file mode 100644 index 0000000000..adb9f3e497 --- /dev/null +++ b/policyengine_uk/tests/test_ni_class_4_statute.py @@ -0,0 +1,341 @@ +"""Class 4 National Insurance against s.15 SSCBA 1992 and regulation 100 of +SI 2001/1004, tax year by tax year. + +CLASS_4 and CLASS_2 hold each tax year's figures as enacted, read from +legislation.gov.uk (the amending instrument's made or enacted text and the +point-in-time text of s.11 and s.15), independently of the parameter files. + +Invariants, for every tax year from 2015-16 to 2026-27, profits >= 0 and +primary Class 1 contributions >= 0: + +1. Parameters: the model's Class 4 limits and rates are the statutory ones. +2. Differential: ni_class_4 equals an exact-rational reference. That is the + s.15(3) amount on the full profits, capped by the regulation 100 maximum + where primary Class 1 or, before 2024-25, Class 2 contributions are + payable. Regulation 100's percentages are the year's Class 4 rates: 9% and + 2%, but 9.73% and 2.73% in 2022-23 (Health and Social Care Levy (Repeal) + Act 2022 Sch para 6(2)) and 6% and 2% from 2024-25 (National Insurance + Contributions (Reduction in Rates) Act 2024 s 2(3)). Before 2024-25 Step + Three adds 53 weekly Class 2 contributions and Step Four subtracts the + Class 2 paid. +3. Bounds: 0 <= ni_class_4 <= the s.15(3) amount. +4. ni_class_4_main is the main-rate band of s.15(3)(a) alone. + +The model stores amounts as float32, so the reference reads profits and +Class 1 back from the simulation and comparisons allow a few float32 ulps. +""" + +from fractions import Fraction + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation +from policyengine_uk.system import system + +# Tax year starting in April of the key: (lower profits limit, upper profits +# limit, main Class 4 percentage, additional Class 4 percentage). +CLASS_4 = { + 2015: (8_060, 42_385, "0.09", "0.02"), # SI 2015/588 art 3; NICA 2011 s.2 + 2016: (8_060, 43_000, "0.09", "0.02"), # SI 2016/343 reg 2 + 2017: (8_164, 45_000, "0.09", "0.02"), # SI 2017/415 reg 5 + 2018: (8_424, 46_350, "0.09", "0.02"), # SI 2018/337 reg 5 + 2019: (8_632, 50_000, "0.09", "0.02"), # SI 2019/262 reg 5 + 2020: (9_500, 50_000, "0.09", "0.02"), # SI 2020/299 reg 5 + 2021: (9_568, 50_270, "0.09", "0.02"), # SI 2021/157 reg 5 + # NICs (Increase of Thresholds) Act 2022 s.2(1)-(2); Health and Social + # Care Levy (Repeal) Act 2022 s.2(2). + 2022: (11_908, 50_270, "0.0973", "0.0273"), + 2023: ( + 12_570, + 50_270, + "0.09", + "0.02", + ), # NICs (Increase of Thresholds) Act 2022 s.2(3) + 2024: (12_570, 50_270, "0.06", "0.02"), # NICs (Reduction in Rates) Act 2024 s.1(3) + 2025: (12_570, 50_270, "0.06", "0.02"), + 2026: (12_570, 50_270, "0.06", "0.02"), +} +# Class 2 under s.11(2), which regulation 100 counts before 2024-25: +# (weekly rate, threshold, True where liability needs profits that exceed the +# lower profits threshold rather than profits of, or exceeding, the small +# profits threshold). From 2024-25 s.11(2) is omitted (NICs (Reduction in +# Rates) Act 2023 s.3) and Class 2 leaves regulation 100 (SI 2024/377 reg 6(5)). +CLASS_2 = { + 2015: ("2.80", 5_965, False), # NICA 2015 Sch 1 para 3 + 2016: ("2.80", 5_965, False), + 2017: ("2.85", 6_025, False), # SI 2017/415 reg 3 + 2018: ("2.95", 6_205, False), # SI 2018/337 reg 3 + 2019: ("3.00", 6_365, False), # SI 2019/262 reg 3 + 2020: ("3.05", 6_475, False), # SI 2020/299 reg 3 + 2021: ("3.05", 6_515, False), # SI 2021/157 reg 3 + 2022: ("3.15", 11_908, True), # SI 2022/232 reg 3; SI 2022/1329 reg 2 + 2023: ("3.45", 12_570, True), # SI 2023/236 reg 3 +} +YEARS = sorted(CLASS_4) +WEEKS = 52 +PROPERTY_SETTINGS = settings( + max_examples=6, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) + + +def exact(x): + return Fraction(float(x)) + + +def statutory_class_2(year, profits): + if year not in CLASS_2: + return Fraction(0) + rate, threshold, exceed = CLASS_2[year] + liable = profits > threshold if exceed else profits >= threshold + return WEEKS * Fraction(rate) if liable else Fraction(0) + + +def statutory_class_4(year, profits, class_1): + """Class 4 for `year` on `profits` with `class_1` primary Class 1. + + Returns every amount regulation 100 allows: two where Step Four equals + the Case 1 aggregate to within float32 noise, one otherwise. + """ + lpl, upl, main, additional = CLASS_4[year] + main, additional = Fraction(main), Fraction(additional) + main_band = main * min(max(profits - lpl, 0), upl - lpl) + s15_amount = main_band + additional * max(profits - upl, 0) + class_2 = statutory_class_2(year, profits) + if not (class_1 > 0 or class_2 > 0): + return s15_amount, {s15_amount} + + step_3 = (upl - lpl) * main + if year in CLASS_2: + step_3 += 53 * Fraction(CLASS_2[year][0]) + step_4 = step_3 - class_2 - class_1 + aggregate = class_1 + class_2 + main_band + + def case_2_or_3(): + floored = max(step_4, 0) + step_7 = max(min(upl, profits) - lpl - floored / main, 0) + return floored + step_7 * additional + max(profits - upl, 0) * additional + + maxima = set() + if step_4 > 0 and step_4 >= aggregate - Fraction(1, 10**6): + maxima.add(step_4) # Case 1 + if not (step_4 > 0 and step_4 > aggregate + Fraction(1, 10**6)): + maxima.add(case_2_or_3()) + return s15_amount, {min(s15_amount, maximum) for maximum in maxima} + + +def tolerance(*amounts): + return 0.01 + 4e-7 * max(abs(float(a)) for a in amounts) + + +def simulate(people, years=YEARS): + """One person per (profits, annual primary Class 1) pair, the same in + every year, so one simulation covers every tax year. Class 1 is entered + in January so the annual sum is exact.""" + return Simulation( + situation={ + "people": { + f"p{i}": { + "age": {year: 40 for year in years}, + "self_employment_income": {year: profits for year in years}, + "ni_class_1_employee_primary": { + f"{year}-01": class_1 for year in years + }, + } + for i, (profits, class_1) in enumerate(people) + }, + "benunits": {f"b{i}": {"members": [f"p{i}"]} for i in range(len(people))}, + "households": {f"h{i}": {"members": [f"p{i}"]} for i in range(len(people))}, + } + ) + + +@pytest.mark.parametrize("year", YEARS) +def test_class_4_parameters_match_statute(year): + class_4 = system.parameters(f"{year}-01-01").gov.hmrc.national_insurance.class_4 + lpl, upl, main, additional = CLASS_4[year] + + assert class_4.thresholds.lower_profits_limit == lpl + assert class_4.thresholds.upper_profits_limit == upl + assert class_4.rates.main == pytest.approx(float(main), abs=1e-12) + assert class_4.rates.additional == pytest.approx(float(additional), abs=1e-12) + assert class_4.annual_maximum.includes_class_2 == (year in CLASS_2) + + +# Hand-derived liabilities. Class 1 is the year's primary Class 1 at the main +# percentage. Amounts in pounds; regulation 100 steps are numbered as in reg +# 100(3) and "max" is the reg 100 maximum. +HAND_DERIVED = [ + # 2015-16. s.15(3): 9% x (42,385 - 8,060) + 2% x (50,000 - 42,385) + # = 3,089.25 + 152.30 = 3,241.55. Class 2 52 x 2.80 = 145.60. + # Step 4 = 3,089.25 + 53 x 2.80 - 145.60 = 3,092.05 < 145.60 + 3,089.25 + # (Case 2); Step 5 = 34,356.11 > Step 6 = 34,325, so max = 3,092.05 + # + 2% x 7,615 = 3,244.35. Not binding. + (2015, 50_000, 0, 145.60, 3_241.55), + # 2016-17. 9% x (43,000 - 8,060) + 2% x 7,000 = 3,144.60 + 140 + # = 3,284.60. Max = 3,147.40 + 140 = 3,287.40. + (2016, 50_000, 0, 145.60, 3_284.60), + # 2017-18. 9% x (30,000 - 8,164) = 1,965.24. Class 2 52 x 2.85 + # = 148.20. Step 4 = 9% x 36,836 + 53 x 2.85 - 148.20 = 3,318.09 + # exceeds 148.20 + 1,965.24 (Case 1). Not binding. + (2017, 30_000, 0, 148.20, 1_965.24), + # 2018-19. 9% x (46,350 - 8,424) + 2% x (60,000 - 46,350) = 3,413.34 + # + 273 = 3,686.34. Class 2 52 x 2.95 = 153.40. Step 4 = 3,413.34 + # + 156.35 - 153.40 = 3,416.29 (Case 2); Step 5 = 37,958.78 > 37,926; + # max = 3,416.29 + 273 = 3,689.29. Not binding. + (2018, 60_000, 0, 153.40, 3_686.34), + # 2019-20 with £2,000 of Class 1. s.15(3): 9% x (50,000 - 8,632) + # = 3,723.12. Class 2 52 x 3 = 156. Step 4 = 3,723.12 + 159 - 156 + # - 2,000 = 1,726.12 (Case 2). Step 5 = 1,726.12 / 9% = 19,179.11; + # Step 7 = 41,368 - 19,179.11 = 22,188.89; Step 8 = 443.78. + # Max = 1,726.12 + 443.78 = 2,169.90. Binding. + (2019, 50_000, 2_000, 156.00, 2_169.90), + # 2020-21. 9% x (20,000 - 9,500) = 945. Class 2 52 x 3.05 = 158.60. + (2020, 20_000, 0, 158.60, 945.00), + # 2021-22 at the lower profits limit: Class 2 (profits of, or exceeding, + # £6,515) but no Class 4. + (2021, 9_568, 0, 158.60, 0), + # 2022-23 at £11,908: Class 2 needs profits that exceed the lower + # profits threshold, and Class 4 profits above the limit. + (2022, 11_908, 0, 0, 0), + # 2022-23. 9.73% x (30,000 - 11,908) = 1,760.35. Class 2 52 x 3.15 + # = 163.80. Step 4 = 9.73% x 38,362 + 53 x 3.15 - 163.80 = 3,735.77 + # (Case 1). Not binding. + (2022, 30_000, 0, 163.80, 1_760.35), + # 2022-23 with £6,000 of Class 1. Step 4 = 3,899.57 - 163.80 - 6,000 < 0 + # (Case 3). Max = 2.73% x 38,362 + 2.73% x 49,730 = 1,047.28 + # + 1,357.63 = 2,404.91. + (2022, 100_000, 6_000, 163.80, 2_404.91), + # 2023-24. 9% x (30,000 - 12,570) = 1,568.70. Class 2 52 x 3.45 + # = 179.40. Case 1, not binding. + (2023, 30_000, 0, 179.40, 1_568.70), + # 2023-24 at £12,570: no Class 2 (not exceeding) and no Class 4. + (2023, 12_570, 0, 0, 0), + # 2023-24 with £3,000 of Class 1. Step 4 = 3,393 + 182.85 - 179.40 + # - 3,000 = 396.45 (Case 2). Step 5 = 4,405; Step 7 = 37,700 - 4,405 + # = 33,295; Step 8 = 665.90; Step 9 = 2% x 9,730 = 194.60. + # Max = 396.45 + 665.90 + 194.60 = 1,256.95. Binding. + (2023, 60_000, 3_000, 179.40, 1_256.95), + # 2024-25 with £1,000 of Class 1. s.15(3): 6% x 37,700 + 2% x 9,730 + # = 2,262 + 194.60 = 2,456.60. No Class 2 and no Step Three. + # Step 4 = 2,262 - 1,000 = 1,262 (Case 2). Step 5 = 21,033.33; + # Step 7 = 16,666.67; Step 8 = 333.33. Max = 1,262 + 333.33 + 194.60 + # = 1,789.93. Binding. + (2024, 60_000, 1_000, 0, 1_789.93), + # 2024-25 with £200 of Class 1. 6% x 7,430 = 445.80. Step 4 = 2,062 + # exceeds 200 + 445.80 (Case 1). Not binding. + (2024, 20_000, 200, 0, 445.80), + # 2025-26, no Class 1: regulation 100 does not apply. + # 6% x (30,000 - 12,570) = 1,045.80. + (2025, 30_000, 0, 0, 1_045.80), + # 2026-27 with £6,000 of Class 1. Step 4 = 2,262 - 6,000 < 0 (Case 3). + # Max = 2% x 37,700 + 2% x 49,730 = 754 + 994.60 = 1,748.60. + (2026, 100_000, 6_000, 0, 1_748.60), +] + + +@pytest.fixture(scope="module") +def hand_derived_simulation(): + # One person per row, with the row's inputs in every year; each case + # reads its own person in its own year. + return simulate([(profits, class_1) for _, profits, class_1, _, _ in HAND_DERIVED]) + + +@pytest.mark.parametrize( + "row", + range(len(HAND_DERIVED)), + ids=[ + f"{year}-{profits}-{class_1}" for year, profits, class_1, _, _ in HAND_DERIVED + ], +) +def test_class_2_and_class_4_hand_derived(hand_derived_simulation, row): + year, profits, class_1, class_2, class_4 = HAND_DERIVED[row] + sim = hand_derived_simulation + assert sim.calculate("ni_class_1_employee_primary", year)[row] == class_1 + assert sim.calculate("ni_class_2", year)[row] == pytest.approx(class_2, abs=0.01) + assert sim.calculate("ni_class_4", year)[row] == pytest.approx(class_4, abs=0.01) + # The table is independent arithmetic; the reference must agree with it. + _, allowed = statutory_class_4(year, Fraction(profits), Fraction(class_1)) + assert any(abs(float(a) - class_4) < 0.005 for a in allowed), allowed + + +def assert_matches_statute(people): + sim = simulate(people) + for year in YEARS: + lpl, upl, main, _ = CLASS_4[year] + profits = sim.calculate("self_employment_income", year) + class_1 = sim.calculate("ni_class_1_employee_primary", year) + class_2 = sim.calculate("ni_class_2", year) + class_4 = sim.calculate("ni_class_4", year) + class_4_main = sim.calculate("ni_class_4_main", year) + for i in range(len(people)): + p, c1 = exact(profits[i]), exact(class_1[i]) + s15_amount, allowed = statutory_class_4(year, p, c1) + tol = tolerance(p, upl, s15_amount) + assert float(class_2[i]) == pytest.approx( + float(statutory_class_2(year, p)), abs=0.01 + ), (year, people[i]) + assert any(abs(float(class_4[i]) - float(a)) <= tol for a in allowed), ( + year, + people[i], + float(class_4[i]), + [float(a) for a in allowed], + ) + assert -tol <= float(class_4[i]) <= float(s15_amount) + tol + main_band = Fraction(main) * min(max(p - lpl, 0), upl - lpl) + assert abs(float(class_4_main[i]) - float(main_band)) <= tol + + +# Every limit and threshold used from 2015-16 to 2026-27, with points either +# side, each with no Class 1 and with Class 1 that reaches each case. +THRESHOLD_PROFITS = sorted( + {0.0, 100_000.0} + | { + float(limit) + offset + for lpl, upl, _, _ in CLASS_4.values() + for limit in (lpl, upl) + for offset in (-1, -0.01, 0, 0.01, 1) + } + | { + float(threshold) + offset + for _, threshold, _ in CLASS_2.values() + for offset in (-0.01, 0, 0.01) + } +) + + +def test_class_4_matches_statute_at_every_limit(): + assert_matches_statute( + [ + (profits, class_1) + for profits in THRESHOLD_PROFITS + for class_1 in (0, 250, 1_500, 6_000) + ] + ) + + +@PROPERTY_SETTINGS +@given( + st.lists( + st.tuples( + st.one_of( + st.floats(0, 300_000, allow_nan=False, allow_infinity=False), + st.integers(0, 300_000).map(float), + st.sampled_from(THRESHOLD_PROFITS), + ), + st.one_of( + st.just(0.0), + st.integers(0, 800_000).map(lambda pence: pence / 100), + ), + ), + min_size=1, + max_size=16, + ) +) +def test_class_4_matches_statute_property(people): + assert_matches_statute(people) diff --git a/policyengine_uk/variables/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.py b/policyengine_uk/variables/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.py index 1d9cd4e3ba..aba2cb373b 100644 --- a/policyengine_uk/variables/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.py +++ b/policyengine_uk/variables/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.py @@ -14,6 +14,12 @@ def formula(person, period, parameters): upl = ni.class_4.thresholds.upper_profits_limit lpl = ni.class_4.thresholds.lower_profits_limit step_1 = upl - lpl + # Regulation 100 writes its percentages out, but they have always + # matched the Class 4 rates: 9% and 2% (Steps Two, Five, Eight and + # Nine) to 2021-22 and in 2023-24; 9.73% and 2.73% for 2022-23 (Health + # and Social Care Levy (Repeal) Act 2022 Sch para 6(2)); 6% and 2% + # from 2024-25 (National Insurance Contributions (Reduction in Rates) + # Act 2024 s 2(3)). So the steps read the rate parameters. main_rate = ni.class_4.rates.main add_rate = ni.class_4.rates.additional step_2 = step_1 * main_rate From a8919d470013d6b8abcc077534fd288c7c06613e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 09:32:46 -0400 Subject: [PATCH 3/8] Apply review r1: Case 2 and 3 rows, wording fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Hand-derived table: add regulation 100 Case 3 before 2022-23 (2019-20, £827.36), Case 2 in 2022-23 (£3,998.25) and Case 3 in 2023-24 (£1,748.60), so each regime has all three cases. - ni_class_4_maximum comment: the percentages match from 2015-16; the 2024 Act changed only Steps Two and Five. - Class 4 maximum YAML: state the pay-spread assumption behind Case 3. - lower_profits_threshold_applies description: the treated-as-paid band applied in 2022-23 and 2023-24. - ni_class_2.yaml: Act name; changelog: citations run to 2025-26; notebook table: reg 100 also applied where Class 2 was paid before 2024-25. Co-Authored-By: Claude Opus 5.5 --- changelog.d/1968.fixed.md | 2 +- .../gov/hmrc/national-insurance.ipynb | 2 +- .../lower_profits_threshold_applies.yaml | 2 +- .../class_2/ni_class_2.yaml | 2 +- .../class_4/ni_class_4_maximum.yaml | 9 ++++---- .../tests/test_ni_class_4_statute.py | 21 ++++++++++++++++--- .../class_4/ni_class_4_maximum.py | 14 +++++++------ 7 files changed, 35 insertions(+), 17 deletions(-) diff --git a/changelog.d/1968.fixed.md b/changelog.d/1968.fixed.md index ff228cc29e..9739681d0f 100644 --- a/changelog.d/1968.fixed.md +++ b/changelog.d/1968.fixed.md @@ -1 +1 @@ -- Fixed Class 4 NICs against s.15 SSCBA 1992: the 2023-24 lower profits limit is £12,570 (it was £11,908), the 2022-23 rates are 9.73% and 2.73% for the whole year (they were 10.25% and 3.5%), the 2023-24 additional rate is 2% (it was 3.5%), and the 2015-16 upper profits limit is £42,385. Every Class 4 limit and rate now cites its amending instrument. +- Fixed Class 4 NICs against s.15 SSCBA 1992: the 2023-24 lower profits limit is £12,570 (it was £11,908), the 2022-23 rates are 9.73% and 2.73% for the whole year (they were 10.25% and 3.5%), the 2023-24 additional rate is 2% (it was 3.5%), and the 2015-16 upper profits limit is £42,385. Every Class 4 limit and rate from 2015-16 to 2025-26 now cites its amending instrument. diff --git a/docs/book/programs/gov/hmrc/national-insurance.ipynb b/docs/book/programs/gov/hmrc/national-insurance.ipynb index 38e8e6e620..2cfc8480a2 100644 --- a/docs/book/programs/gov/hmrc/national-insurance.ipynb +++ b/docs/book/programs/gov/hmrc/national-insurance.ipynb @@ -72,7 +72,7 @@ " \"Based on employment income. \\n- Monthly and annual calculations.\",\n", " \"Based on self-employment income. \\n- Weekly flat rate.\",\n", " \"Voluntary contributions for those filling gaps in their NI record. \\n- Weekly flat rate.\",\n", - " \"Based on self-employment profits. \\n- Annual maximum (reg 100) where Class 1 is also paid.\",\n", + " \"Based on self-employment profits. \\n- Annual maximum (reg 100) where Class 1, or before 2024-25 Class 2, is also paid.\",\n", " ],\n", " \"Thresholds & Limits\": [\n", " \"Primary Threshold, Upper Earnings Limit\",\n", diff --git a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/lower_profits_threshold_applies.yaml b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/lower_profits_threshold_applies.yaml index 70c16fd04a..3d96ec8e74 100644 --- a/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/lower_profits_threshold_applies.yaml +++ b/policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/lower_profits_threshold_applies.yaml @@ -1,4 +1,4 @@ -description: Class 2 National Insurance liability starts above the lower profits threshold rather than at the small profits threshold. From 2022-23 profits from the small profits threshold up to the lower profits threshold are treated as paid. +description: Class 2 National Insurance liability starts above the lower profits threshold rather than at the small profits threshold. In 2022-23 and 2023-24 profits from the small profits threshold up to the lower profits threshold were treated as paid. Compulsory Class 2 ended on 6 April 2024; the switch stays on so that a reform restoring a Class 2 rate uses the 2023-24 rule. values: 2015-04-06: value: false diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml index 472cb2ef28..12506d1bcb 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml @@ -124,7 +124,7 @@ output: ni_class_2: 2.8 * 52 -# 2024-25: s.11(2) omitted from 6 April 2024 (NICA (Reduction in Rates) Act +# 2024-25: s.11(2) omitted from 6 April 2024 (NICs (Reduction in Rates) Act # 2023 s.3); no compulsory Class 2. - name: NI Class 2 - 2024-25, abolished. period: 2024 diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.yaml index 0a43ff9c05..b4d76c29ec 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.yaml @@ -5,10 +5,11 @@ # Step 1: 50,270 - 12,570 = 37,700. Step 2: 37,700 x 9% = 3,393. # Step 3: 3,393 + 53 x 3.45 = 3,575.85. # Step 4: less Class 2 of 52 x 3.45 = 179.40 and primary Class 1 at the - # main percentage on £100,000 of pay. That is at least 10% (the lowest - # main primary percentage in 2023-24) of the £37,700 between the primary - # threshold and the upper earnings limit, £3,770, so Step 4 is negative: - # Case 3, treated as nil. + # main percentage on £100,000 of pay. With pay spread over the year that is + # at least 10% (the lowest main primary percentage in 2023-24; a director's + # annual rate was 11.5%) of the £37,700 between the primary threshold and + # the upper earnings limit, £3,770, so Step 4 is negative: Case 3, treated + # as nil. The model charges 12%, £4,524. # Step 5: 0. Step 6: min(50,270, 100,000) - 12,570 = 37,700. # Step 7: 37,700 - 0 = 37,700. Step 8: 37,700 x 2% = 754. # Step 9: (100,000 - 50,270) x 2% = 994.60. diff --git a/policyengine_uk/tests/test_ni_class_4_statute.py b/policyengine_uk/tests/test_ni_class_4_statute.py index adb9f3e497..2337ae292e 100644 --- a/policyengine_uk/tests/test_ni_class_4_statute.py +++ b/policyengine_uk/tests/test_ni_class_4_statute.py @@ -167,9 +167,11 @@ def test_class_4_parameters_match_statute(year): assert class_4.annual_maximum.includes_class_2 == (year in CLASS_2) -# Hand-derived liabilities. Class 1 is the year's primary Class 1 at the main -# percentage. Amounts in pounds; regulation 100 steps are numbered as in reg -# 100(3) and "max" is the reg 100 maximum. +# Hand-derived liabilities, with regulation 100 Cases 1, 2 and 3 in each +# regime (to 2021-22, 2022-23, 2023-24 and from 2024-25). Class 1 is the +# year's primary Class 1 at the main percentage. Amounts in pounds; +# regulation 100 steps are numbered as in reg 100(3) and "max" is the reg 100 +# maximum. HAND_DERIVED = [ # 2015-16. s.15(3): 9% x (42,385 - 8,060) + 2% x (50,000 - 42,385) # = 3,089.25 + 152.30 = 3,241.55. Class 2 52 x 2.80 = 145.60. @@ -195,6 +197,10 @@ def test_class_4_parameters_match_statute(year): # Step 7 = 41,368 - 19,179.11 = 22,188.89; Step 8 = 443.78. # Max = 1,726.12 + 443.78 = 2,169.90. Binding. (2019, 50_000, 2_000, 156.00, 2_169.90), + # 2019-20 with £6,000 of Class 1. Step 4 = 3,723.12 + 159 - 156 - 6,000 + # < 0 (Case 3, nil). Step 7 = Step 6 = 50,000 - 8,632 = 41,368; + # Step 8 = 2% x 41,368 = 827.36; Step 9 = 0. Max = 827.36. Binding. + (2019, 50_000, 6_000, 156.00, 827.36), # 2020-21. 9% x (20,000 - 9,500) = 945. Class 2 52 x 3.05 = 158.60. (2020, 20_000, 0, 158.60, 945.00), # 2021-22 at the lower profits limit: Class 2 (profits of, or exceeding, @@ -207,6 +213,11 @@ def test_class_4_parameters_match_statute(year): # = 163.80. Step 4 = 9.73% x 38,362 + 53 x 3.15 - 163.80 = 3,735.77 # (Case 1). Not binding. (2022, 30_000, 0, 163.80, 1_760.35), + # 2022-23. 9.73% x 38,362 + 2.73% x 9,730 = 3,732.62 + 265.63 + # = 3,998.25. Step 4 = 3,735.77 does not exceed 163.80 + 3,732.62 + # (Case 2). Step 5 = 3,735.77 / 9.73% = 38,394.37 exceeds Step 6 + # (38,362), so Step 8 is 0; Step 9 = 265.63. Max = 4,001.40. + (2022, 60_000, 0, 163.80, 3_998.25), # 2022-23 with £6,000 of Class 1. Step 4 = 3,899.57 - 163.80 - 6,000 < 0 # (Case 3). Max = 2.73% x 38,362 + 2.73% x 49,730 = 1,047.28 # + 1,357.63 = 2,404.91. @@ -221,6 +232,10 @@ def test_class_4_parameters_match_statute(year): # = 33,295; Step 8 = 665.90; Step 9 = 2% x 9,730 = 194.60. # Max = 396.45 + 665.90 + 194.60 = 1,256.95. Binding. (2023, 60_000, 3_000, 179.40, 1_256.95), + # 2023-24 with £6,000 of Class 1. Step 4 = 3,575.85 - 179.40 - 6,000 + # < 0 (Case 3, nil). Max = 2% x 37,700 + 2% x 49,730 = 754 + 994.60 + # = 1,748.60. Binding (s.15(3): 3,393 + 994.60 = 4,387.60). + (2023, 100_000, 6_000, 179.40, 1_748.60), # 2024-25 with £1,000 of Class 1. s.15(3): 6% x 37,700 + 2% x 9,730 # = 2,262 + 194.60 = 2,456.60. No Class 2 and no Step Three. # Step 4 = 2,262 - 1,000 = 1,262 (Case 2). Step 5 = 21,033.33; diff --git a/policyengine_uk/variables/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.py b/policyengine_uk/variables/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.py index aba2cb373b..d0fad06692 100644 --- a/policyengine_uk/variables/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.py +++ b/policyengine_uk/variables/gov/hmrc/national_insurance/class_4/ni_class_4_maximum.py @@ -14,12 +14,14 @@ def formula(person, period, parameters): upl = ni.class_4.thresholds.upper_profits_limit lpl = ni.class_4.thresholds.lower_profits_limit step_1 = upl - lpl - # Regulation 100 writes its percentages out, but they have always - # matched the Class 4 rates: 9% and 2% (Steps Two, Five, Eight and - # Nine) to 2021-22 and in 2023-24; 9.73% and 2.73% for 2022-23 (Health - # and Social Care Levy (Repeal) Act 2022 Sch para 6(2)); 6% and 2% - # from 2024-25 (National Insurance Contributions (Reduction in Rates) - # Act 2024 s 2(3)). So the steps read the rate parameters. + # Regulation 100 writes its percentages out, but in every year from + # 2015-16 they match the Class 4 rates: 9% and 2% (Steps Two, Five, + # Eight and Nine) to 2021-22 and in 2023-24; 9.73% and 2.73% for + # 2022-23 (Health and Social Care Levy (Repeal) Act 2022 Sch para + # 6(2)); from 2024-25, 6% and 100/6 in Steps Two and Five (National + # Insurance Contributions (Reduction in Rates) Act 2024 s 2(3)) with + # Steps Eight and Nine still at 2%. So the steps read the rate + # parameters. main_rate = ni.class_4.rates.main add_rate = ni.class_4.rates.additional step_2 = step_1 * main_rate From 6cb67d6a335aabd9fc61e6544f6820fef1fd559b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 09:34:38 -0400 Subject: [PATCH 4/8] Refresh NI notebook chart values for the threshold freeze (#1959) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1959 holds the PT, UEL, LPL and UPL through 2030-31 and corrects the 2025-26 LEL to £125, but left the notebook's saved chart on the old CPI-uprated values. Only the chart's y values change. Co-Authored-By: Claude Opus 5.5 --- .../gov/hmrc/national-insurance.ipynb | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/book/programs/gov/hmrc/national-insurance.ipynb b/docs/book/programs/gov/hmrc/national-insurance.ipynb index 2cfc8480a2..1bc2ea6b0c 100644 --- a/docs/book/programs/gov/hmrc/national-insurance.ipynb +++ b/docs/book/programs/gov/hmrc/national-insurance.ipynb @@ -168,7 +168,7 @@ 123.0, 123.0, 123.0, - 123.0, + 125.0, 129.0, 131.57979643487917, 134.2112670927332, @@ -222,12 +222,12 @@ 241.73, 241.73, 241.73, - 246.56436986040532, - 251.49511067607608, - 256.5250991919456, - 261.65577378048056, - 266.8885728141475, - 272.2263730378798 + 241.73, + 241.73, + 241.73, + 246.56476264971067, + 251.49575971156867, + 256.52570201408514 ], "yaxis": "y" }, @@ -273,12 +273,12 @@ 966.73, 966.73, 966.73, - 986.0636796225112, - 1005.7827673184257, - 1025.8987678063527, - 1046.4174334455963, - 1067.3445165954613, - 1088.6915219745565 + 966.73, + 966.73, + 966.73, + 986.0652504709999, + 1005.7853629502536, + 1025.901178621092 ], "yaxis": "y" }, @@ -425,13 +425,13 @@ 12570.0, 12570.0, 12570.0, - 12821.380164235901, - 13077.795560896562, - 13339.32248138235, - 13606.113508494018, - 13878.244933631944, - 14155.793048196501, - 14438.910434988438 + 12570.0, + 12570.0, + 12570.0, + 12570.0, + 12821.408457811869, + 13077.821120979681, + 13339.378953034586 ], "yaxis": "y" }, @@ -476,13 +476,13 @@ 50270.0, 50270.0, 50270.0, - 51275.32067272385, - 52300.77826939301, - 53346.67789491573, - 54413.62975910853, - 55501.9389668797, - 56611.910623137475, - 57744.1549376984 + 50270.0, + 50270.0, + 50270.0, + 50270.0, + 51275.43382451891, + 52300.8804893913, + 53346.903736598935 ], "yaxis": "y" } From d978a9fbe6fbe42a22ad075343c6d4e3a8425285 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 3 Oct 2026 18:29:18 -0400 Subject: [PATCH 5/8] Apply delta review r2 nits: docstring wording, changelog source Co-Authored-By: Claude Opus 5.5 --- changelog.d/1968.fixed.md | 2 +- policyengine_uk/tests/test_ni_class_4_statute.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/changelog.d/1968.fixed.md b/changelog.d/1968.fixed.md index 9739681d0f..7aaf2acabd 100644 --- a/changelog.d/1968.fixed.md +++ b/changelog.d/1968.fixed.md @@ -1 +1 @@ -- Fixed Class 4 NICs against s.15 SSCBA 1992: the 2023-24 lower profits limit is £12,570 (it was £11,908), the 2022-23 rates are 9.73% and 2.73% for the whole year (they were 10.25% and 3.5%), the 2023-24 additional rate is 2% (it was 3.5%), and the 2015-16 upper profits limit is £42,385. Every Class 4 limit and rate from 2015-16 to 2025-26 now cites its amending instrument. +- Fixed Class 4 NICs against s.15 SSCBA 1992: the 2023-24 lower profits limit is £12,570 (it was £11,908), the 2022-23 rates are 9.73% and 2.73% for the whole year (they were 10.25% and 3.5%), the 2023-24 additional rate is 2% (it was 3.5%), and the 2015-16 upper profits limit is £42,385. Every Class 4 limit and rate from 2015-16 to 2025-26 now cites its legal source. diff --git a/policyengine_uk/tests/test_ni_class_4_statute.py b/policyengine_uk/tests/test_ni_class_4_statute.py index 2337ae292e..0f0cd2e60c 100644 --- a/policyengine_uk/tests/test_ni_class_4_statute.py +++ b/policyengine_uk/tests/test_ni_class_4_statute.py @@ -14,10 +14,10 @@ where primary Class 1 or, before 2024-25, Class 2 contributions are payable. Regulation 100's percentages are the year's Class 4 rates: 9% and 2%, but 9.73% and 2.73% in 2022-23 (Health and Social Care Levy (Repeal) - Act 2022 Sch para 6(2)) and 6% and 2% from 2024-25 (National Insurance - Contributions (Reduction in Rates) Act 2024 s 2(3)). Before 2024-25 Step - Three adds 53 weekly Class 2 contributions and Step Four subtracts the - Class 2 paid. + Act 2022 Sch para 6(2)); from 2024-25, 6% and 100/6 in Steps Two and Five + (National Insurance Contributions (Reduction in Rates) Act 2024 s 2(3)) + with Steps Eight and Nine still at 2%. Before 2024-25 Step Three adds 53 + weekly Class 2 contributions and Step Four subtracts the Class 2 paid. 3. Bounds: 0 <= ni_class_4 <= the s.15(3) amount. 4. ni_class_4_main is the main-rate band of s.15(3)(a) alone. From 0278f13bdf608fb5e505e07662725aec858099db Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 7 Oct 2026 12:05:19 -0400 Subject: [PATCH 6/8] Test Class 2 thresholds on Class 4 profits (s.11(3) SSCBA 1992) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit s.11(3) defines Class 2 "relevant profits" as the profits on which Class 4 is payable under s.15, which s.15(3) computes under Schedule 2. Since #1919 those are ni_class_4_profits: profit after capital allowances, the trading allowance and loss relief. ni_class_2 still read self_employment_income, so in 2023-24 £13,000 of profits with £1,000 of capital allowances paid £179.40 of Class 2 on relevant profits of £12,000, below the £12,570 threshold. - ni_class_2 reads ni_class_4_profits; documentation and references. - ni_class_2.yaml: 8 cases with capital allowances, the trading allowance and losses on each side of the 2015-16, 2021-22, 2022-23 and 2023-24 thresholds (6 fail on the previous formula). - test_ni_class_2.py: the statute differential and Hypothesis property now vary capital allowances, and check the shared-base invariant against the model's own ni_class_4_profits. - Notebook and changelog wording. Found by delta review r3 (job 20261003-220428-ni-class-2-4-review-r3c). Co-Authored-By: Claude Opus 5.5 --- changelog.d/1887.fixed.md | 2 +- .../gov/hmrc/national-insurance.ipynb | 2 +- .../class_2/ni_class_2.yaml | 85 +++++++++++++++ policyengine_uk/tests/test_ni_class_2.py | 100 +++++++++++++----- .../national_insurance/class_2/ni_class_2.py | 27 ++++- 5 files changed, 183 insertions(+), 33 deletions(-) diff --git a/changelog.d/1887.fixed.md b/changelog.d/1887.fixed.md index 7126853599..f927115ca2 100644 --- a/changelog.d/1887.fixed.md +++ b/changelog.d/1887.fixed.md @@ -1 +1 @@ -- Fix Class 2 National Insurance for 2015-16 to 2023-24 against s.11 SSCBA 1992: charge £3.45 a week in 2023-24; from 2022-23 charge only profits above the Class 2 lower profits threshold (£11,908, then £12,570), with profits from the small profits threshold up to it treated as paid; keep the small profits threshold at £6,725 in 2023-24 and 2024-25 and correct it to £5,965 in 2015-16 and 2016-17; date the abolition 6 April 2024. Every value now cites its amending instrument. +- Fix Class 2 National Insurance for 2015-16 to 2023-24 against s.11 SSCBA 1992: charge £3.45 a week in 2023-24; from 2022-23 charge only profits above the Class 2 lower profits threshold (£11,908, then £12,570), with profits from the small profits threshold up to it treated as paid; keep the small profits threshold at £6,725 in 2023-24 and 2024-25 and correct it to £5,965 in 2015-16 and 2016-17; date the abolition 6 April 2024. Class 2 now tests its thresholds on the profits Class 4 is charged on (s.11(3)), after capital allowances, the trading allowance and loss relief, not on self-employment income. Every value now cites its amending instrument. diff --git a/docs/book/programs/gov/hmrc/national-insurance.ipynb b/docs/book/programs/gov/hmrc/national-insurance.ipynb index c1fedc3b51..2cbaf7462b 100644 --- a/docs/book/programs/gov/hmrc/national-insurance.ipynb +++ b/docs/book/programs/gov/hmrc/national-insurance.ipynb @@ -101,7 +101,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": "## How PolicyEngine computes National Insurance liability\n\nFor each person, PolicyEngine computes NI contributions class-by-class:\n\n- **Class 1 employee** (`ni_class_1_employee`): the sum of `ni_class_1_employee_primary` (main rate on earnings between the Primary Threshold and the Upper Earnings Limit) and `ni_class_1_employee_additional` (additional rate on earnings above the UEL). These primary contributions are due from age 16 until state pension age (`ni_liable`): SSCBA 1992 s.6(3) ends them at pensionable age.\n- **Class 1 employer** (`ni_class_1_employer`): paid on earnings above the Secondary Threshold; surfaced through the `ni_employer` variable. These secondary contributions are due on earners over 16 with no upper age limit (`ni_class_1_secondary_liable`), so employers keep paying them for employees over state pension age (SSCBA 1992 s.6(1)(b) and s.6(3)).\n- **Class 2** (`ni_class_2`): 52 weeks of a flat weekly rate for the self-employed. Before 2022-23 it is payable on profits at or above the Small Profits Threshold. In 2022-23 and 2023-24 it is payable only on profits above the Class 2 lower profits threshold; profits from the Small Profits Threshold up to it are treated as paid and cost nothing. Compulsory Class 2 ended on 6 April 2024; voluntary Class 2 is not modelled.\n- **Class 3** (`ni_class_3`): a voluntary flat weekly rate; the variable is exposed but defaults to zero unless explicitly set.\n- **Class 4** (`ni_class_4`): main rate between the Lower and Upper Profits Limits plus additional rate above the UPL, charged on `ni_class_4_profits`. Those are the trade profits chargeable to income tax under ITTOIA 2005 Part 2 Chapter 2 (`ni_class_4_profits_before_losses`: self-employment profit after capital allowances and the trading allowance), less trading losses as SSCBA 1992 Schedule 2 paragraph 3 allows (`ni_class_4_loss_relief`). A loss reduces only trade profits for Class 4: each year's losses (those brought forward, then the year's own loss, `ni_class_4_trading_loss`) are set against that year's trade profits, and the rest is carried forward until used (`ni_class_4_losses_brought_forward`, `ni_class_4_losses_carried_forward`). A year's own loss is the `trading_loss` entered for that year, so a loss entered once is relieved once: it is not counted again in the later years that `trading_loss` carries into. Carry-back claims and the paragraph 3(5) deductions for trade interest and royalties are not modelled. Where the person also pays primary Class 1 contributions (or, before 6 April 2024, Class 2 contributions), liability is capped at the annual maximum in regulation 100 of the Social Security (Contributions) Regulations 2001 (`ni_class_4_maximum`). Class 4 is due from age 16 until the first tax year that begins on or after the day the person reaches state pension age (`ni_class_4_liable`): regulation 91(a) excepts anyone over pensionable age at the beginning of the tax year, so a person who reaches it during a year pays for that whole year.\n\nThe individual total `national_insurance` sums the four employee and self-employed components directly \u2014 `ni_class_1_employee + ni_class_2 + ni_class_3 + ni_class_4` \u2014 so voluntary Class 3 contributions are included in the household-side liability. The convenience aggregates `ni_employee` (Class 1 employee only) and `ni_self_employed` (Class 2 + Class 4) are also exposed for reporting. The Class 1 *employer* charge (`ni_class_1_employer`, surfaced as `ni_employer`) is an employer-side cost rather than part of the individual's `national_insurance`, so it is tracked separately and feeds the government revenue aggregates on its own.\n\nParameters live in `policyengine_uk/parameters/gov/hmrc/national_insurance/` and the per-class formulas in `policyengine_uk/variables/gov/hmrc/national_insurance/`." + "source": "## How PolicyEngine computes National Insurance liability\n\nFor each person, PolicyEngine computes NI contributions class-by-class:\n\n- **Class 1 employee** (`ni_class_1_employee`): the sum of `ni_class_1_employee_primary` (main rate on earnings between the Primary Threshold and the Upper Earnings Limit) and `ni_class_1_employee_additional` (additional rate on earnings above the UEL). These primary contributions are due from age 16 until state pension age (`ni_liable`): SSCBA 1992 s.6(3) ends them at pensionable age.\n- **Class 1 employer** (`ni_class_1_employer`): paid on earnings above the Secondary Threshold; surfaced through the `ni_employer` variable. These secondary contributions are due on earners over 16 with no upper age limit (`ni_class_1_secondary_liable`), so employers keep paying them for employees over state pension age (SSCBA 1992 s.6(1)(b) and s.6(3)).\n- **Class 2** (`ni_class_2`): 52 weeks of a flat weekly rate for the self-employed. Its thresholds apply to relevant profits, which are the profits Class 4 is charged on (`ni_class_4_profits`; SSCBA 1992 s.11(3)). Before 2022-23 it is payable on relevant profits at or above the Small Profits Threshold. In 2022-23 and 2023-24 it is payable only on relevant profits above the Class 2 lower profits threshold; relevant profits from the Small Profits Threshold up to it are treated as paid and cost nothing. Compulsory Class 2 ended on 6 April 2024; voluntary Class 2 is not modelled.\n- **Class 3** (`ni_class_3`): a voluntary flat weekly rate; the variable is exposed but defaults to zero unless explicitly set.\n- **Class 4** (`ni_class_4`): main rate between the Lower and Upper Profits Limits plus additional rate above the UPL, charged on `ni_class_4_profits`. Those are the trade profits chargeable to income tax under ITTOIA 2005 Part 2 Chapter 2 (`ni_class_4_profits_before_losses`: self-employment profit after capital allowances and the trading allowance), less trading losses as SSCBA 1992 Schedule 2 paragraph 3 allows (`ni_class_4_loss_relief`). A loss reduces only trade profits for Class 4: each year's losses (those brought forward, then the year's own loss, `ni_class_4_trading_loss`) are set against that year's trade profits, and the rest is carried forward until used (`ni_class_4_losses_brought_forward`, `ni_class_4_losses_carried_forward`). A year's own loss is the `trading_loss` entered for that year, so a loss entered once is relieved once: it is not counted again in the later years that `trading_loss` carries into. Carry-back claims and the paragraph 3(5) deductions for trade interest and royalties are not modelled. Where the person also pays primary Class 1 contributions (or, before 6 April 2024, Class 2 contributions), liability is capped at the annual maximum in regulation 100 of the Social Security (Contributions) Regulations 2001 (`ni_class_4_maximum`). Class 4 is due from age 16 until the first tax year that begins on or after the day the person reaches state pension age (`ni_class_4_liable`): regulation 91(a) excepts anyone over pensionable age at the beginning of the tax year, so a person who reaches it during a year pays for that whole year.\n\nThe individual total `national_insurance` sums the four employee and self-employed components directly \u2014 `ni_class_1_employee + ni_class_2 + ni_class_3 + ni_class_4` \u2014 so voluntary Class 3 contributions are included in the household-side liability. The convenience aggregates `ni_employee` (Class 1 employee only) and `ni_self_employed` (Class 2 + Class 4) are also exposed for reporting. The Class 1 *employer* charge (`ni_class_1_employer`, surfaced as `ni_employer`) is an employer-side cost rather than part of the individual's `national_insurance`, so it is tracked separately and feeds the government revenue aggregates on its own.\n\nParameters live in `policyengine_uk/parameters/gov/hmrc/national_insurance/` and the per-class formulas in `policyengine_uk/variables/gov/hmrc/national_insurance/`." }, { "cell_type": "markdown", diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml index 12506d1bcb..dd9caf353f 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml @@ -132,3 +132,88 @@ self_employment_income: 60_000 output: ni_class_2: 0 + +# Relevant profits (s.11(3)) are the profits on which Class 4 is payable +# under s.15, computed under Schedule 2: after capital allowances (CAA 2001 +# s.247), the trading allowance (ITTOIA 2005 s.783AI) and loss relief (Sch 2 +# para 3). The thresholds are tested on those profits, not on +# self_employment_income. +- name: NI Class 2 - 2023-24, capital allowances take profits to the lower profits threshold. + period: 2023 + input: + self_employment_income: 13_570 + capital_allowances: 1_000 + output: + # 13,570 - 1,000 = 12,570, which does not exceed £12,570. + ni_class_4_profits: 12_570 + ni_class_2: 0 + +- name: NI Class 2 - 2023-24, capital allowances leave profits above the lower profits threshold. + period: 2023 + input: + self_employment_income: 13_571 + capital_allowances: 1_000 + output: + ni_class_4_profits: 12_571 + ni_class_2: 3.45 * 52 + +- name: NI Class 2 - 2023-24, the trading allowance takes profits below the lower profits threshold. + period: 2023 + input: + # No expenses, so the £1,000 allowance is deducted from receipts instead. + self_employment_gross_receipts: 13_000 + self_employment_income: 13_000 + output: + ni_class_4_profits: 12_000 + ni_class_2: 0 + +- name: NI Class 2 - 2023-24, a loss brought forward takes profits below the lower profits threshold. + period: 2023 + input: + self_employment_income: + 2022: 0 + 2023: 13_000 + trading_loss: + 2022: 1_000 + 2023: 0 + output: + ni_class_4_losses_brought_forward: 1_000 + ni_class_4_profits: 12_000 + ni_class_2: 0 + +- name: NI Class 2 - 2022-23, capital allowances take profits below the lower profits threshold. + period: 2022 + input: + self_employment_income: 12_500 + capital_allowances: 1_000 + output: + # 11,500 does not exceed £11,908. + ni_class_4_profits: 11_500 + ni_class_2: 0 + +- name: NI Class 2 - 2021-22, capital allowances take profits below the small profits threshold. + period: 2021 + input: + self_employment_income: 7_514 + capital_allowances: 1_000 + output: + ni_class_4_profits: 6_514 + ni_class_2: 0 + +- name: NI Class 2 - 2021-22, capital allowances leave profits at the small profits threshold. + period: 2021 + input: + self_employment_income: 7_515 + capital_allowances: 1_000 + output: + ni_class_4_profits: 6_515 + ni_class_2: 3.05 * 52 + +- name: NI Class 2 - 2015-16, the year's own trading loss takes profits below the small profits threshold. + period: 2015 + input: + self_employment_income: 6_464 + trading_loss: 500 + output: + ni_class_4_profits: 5_964 + ni_class_2: 0 diff --git a/policyengine_uk/tests/test_ni_class_2.py b/policyengine_uk/tests/test_ni_class_2.py index e9bb4d9489..12bdf5b84f 100644 --- a/policyengine_uk/tests/test_ni_class_2.py +++ b/policyengine_uk/tests/test_ni_class_2.py @@ -4,20 +4,32 @@ (the amending instrument's made text and the point-in-time text of s.11), independently of the parameter files. -Invariants, for every model year and profits >= 0: +The thresholds apply to relevant profits: the profits on which Class 4 is +payable under s.15, computed under Schedule 2 (s.11(3)). With no gross +receipts or losses supplied, those are self-employment profit less capital +allowances (CAA 2001 s.247). A profit of £1,000 or less is treated as covered +by the trading allowance and gives nil relevant profits; that never changes +liability, because every threshold is far above £1,000. + +Invariants, for every model year, profits >= 0 and capital allowances >= 0: 1. Differential: ni_class_2 is 52 weeks at the statutory weekly rate when s.11(2) makes the earner liable, and 0 otherwise. Before 2022-23 the - earner is liable on profits of, or exceeding, the small profits threshold. - In 2022-23 and 2023-24 only profits that exceed the lower profits - threshold are liable; profits from the small profits threshold up to it - are treated as paid (s.11(5A)-(5B)), which costs nothing. From 2024-25 - s.11(2) is omitted and no one is liable. + earner is liable on relevant profits of, or exceeding, the small profits + threshold. In 2022-23 and 2023-24 only relevant profits that exceed the + lower profits threshold are liable; relevant profits from the small + profits threshold up to it are treated as paid (s.11(5A)-(5B)), which + costs nothing. From 2024-25 s.11(2) is omitted and no one is liable. 2. ni_class_2 is either 0 or 52 x the weekly rate. -3. ni_class_2 is non-decreasing in profits. +3. ni_class_2 is non-decreasing in relevant profits, so non-decreasing in + profit and non-increasing in capital allowances. +4. Shared base: ni_class_2 is the s.11(2) rule applied to the model's own + ni_class_4_profits, the base Class 4 is charged on. The model counts 52 weeks of self-employment in a year. Profits are stored -as float32, so the reference is evaluated on the float32 value. +as float32, so the reference is evaluated on the float32 value. Capital +allowances are whole pounds here, so profit less allowances is exact in +float32. """ from fractions import Fraction @@ -63,9 +75,15 @@ def statute(year): return STATUTE[min(year, max(STATUTE))] -def statutory_class_2(year, profits): +def relevant_profits(profits, capital_allowances=0): + # s.11(3): the Class 4 profits, here profit less capital allowances. + exact = Fraction(float(np.float32(profits))) - capital_allowances + return max(exact, Fraction(0)) + + +def statutory_class_2(year, profits, capital_allowances=0): rate, small_profits_threshold, lower_profits_threshold = statute(year) - profits = Fraction(float(np.float32(profits))) + profits = relevant_profits(profits, capital_allowances) if lower_profits_threshold is None: liable = profits >= small_profits_threshold else: @@ -85,15 +103,20 @@ def statutory_class_2(year, profits): ) -def simulate(profits_list): - # One person per profit level, with the same profits in every model year, - # so a single simulation covers every year. +# Whole-pound capital allowances that move profits across each threshold. +CAPITAL_ALLOWANCES = [0, 500, 1_000, 2_500] + + +def simulate(cases): + # One person per (profits, capital allowances) case, with the same values + # in every model year, so a single simulation covers every year. people = { f"p{i}": { "age": {year: 40 for year in MODEL_YEARS}, "self_employment_income": {year: profits for year in MODEL_YEARS}, + "capital_allowances": {year: allowances for year in MODEL_YEARS}, } - for i, profits in enumerate(profits_list) + for i, (profits, allowances) in enumerate(cases) } return Simulation( situation={ @@ -104,17 +127,22 @@ def simulate(profits_list): ) -def assert_invariants(profits_list): - sim = simulate(profits_list) - order = np.argsort(np.asarray(profits_list, dtype=np.float32), kind="stable") +def assert_invariants(cases): + cases = [case if isinstance(case, tuple) else (case, 0) for case in cases] + sim = simulate(cases) + relevant = [relevant_profits(*case) for case in cases] + order = sorted(range(len(cases)), key=lambda i: relevant[i]) for year in MODEL_YEARS: class_2 = sim.calculate("ni_class_2", year) + class_4_profits = sim.calculate("ni_class_4_profits", year) full_year = WEEKS * float(statute(year)[0]) - for i, profits in enumerate(profits_list): - expected = float(statutory_class_2(year, profits)) - assert class_2[i] == pytest.approx(expected, abs=0.01), (year, profits) + for i, case in enumerate(cases): + expected = float(statutory_class_2(year, *case)) + assert class_2[i] == pytest.approx(expected, abs=0.01), (year, case) assert class_2[i] == 0 or class_2[i] == pytest.approx(full_year, abs=0.01) - assert np.all(np.diff(class_2[order]) >= 0), (year, profits_list, class_2) + shared_base = float(statutory_class_2(year, class_4_profits[i])) + assert class_2[i] == pytest.approx(shared_base, abs=0.01), (year, case) + assert np.all(np.diff(class_2[order]) >= 0), (year, cases, class_2) @pytest.mark.parametrize("year", sorted(STATUTE)) @@ -138,20 +166,38 @@ def test_ni_class_2_matches_statute_at_every_threshold(): assert_invariants(THRESHOLD_PROFITS + spread + [60_000.0, 1_000_000.0]) +def test_ni_class_2_tests_thresholds_on_profits_after_capital_allowances(): + # Gross profits set so that profit less capital allowances lands on, and + # either side of, every threshold s.11 has used. + assert_invariants( + [ + (relevant + allowances, allowances) + for relevant in THRESHOLD_PROFITS + for allowances in CAPITAL_ALLOWANCES[1:] + ] + ) + + @PROPERTY_SETTINGS @given( st.lists( - st.one_of( - st.floats(0, 200_000, allow_nan=False, allow_infinity=False), - st.integers(0, 200_000).map(float), - st.sampled_from(THRESHOLD_PROFITS), + st.tuples( + st.one_of( + st.floats(0, 200_000, allow_nan=False, allow_infinity=False), + st.integers(0, 200_000).map(float), + st.sampled_from(THRESHOLD_PROFITS), + ), + st.one_of( + st.sampled_from(CAPITAL_ALLOWANCES), + st.integers(0, 20_000), + ), ), min_size=1, max_size=24, ) ) -def test_ni_class_2_properties(profits_list): - assert_invariants(profits_list) +def test_ni_class_2_properties(cases): + assert_invariants(cases) def test_2023_annual_maximum_uses_the_2023_24_class_2_rate(): diff --git a/policyengine_uk/variables/gov/hmrc/national_insurance/class_2/ni_class_2.py b/policyengine_uk/variables/gov/hmrc/national_insurance/class_2/ni_class_2.py index 0b3ef93fae..5ad489fd29 100644 --- a/policyengine_uk/variables/gov/hmrc/national_insurance/class_2/ni_class_2.py +++ b/policyengine_uk/variables/gov/hmrc/national_insurance/class_2/ni_class_2.py @@ -5,16 +5,35 @@ class ni_class_2(Variable): value_type = float entity = Person label = "NI Class 2 contributions" + documentation = ( + "Compulsory Class 2 contributions under s.11(2) SSCBA 1992, tested on " + "the earner's relevant profits: the profits on which Class 4 is " + "payable under s.15 (s.11(3)), so after capital allowances, the " + "trading allowance and Schedule 2 loss relief (ni_class_4_profits)." + ) definition_period = YEAR unit = GBP defined_for = "ni_liable" - reference = "https://www.legislation.gov.uk/ukpga/1992/4/section/11" + reference = [ + dict( + title="Social Security Contributions and Benefits Act 1992, s. 11", + href="https://www.legislation.gov.uk/ukpga/1992/4/section/11", + ), + dict( + title="HMRC National Insurance Manual NIM70300", + href="https://www.gov.uk/hmrc-internal-manuals/national-insurance-manual/nim70300", + ), + ] def formula(person, period, parameters): class_2 = parameters(period).gov.hmrc.national_insurance.class_2 - profits = person("self_employment_income", period) - # Section 11(2) SSCBA 1992. From 2022-23 only profits that exceed the - # lower profits threshold are liable; profits from the small profits + # Section 11(3) SSCBA 1992: "relevant profits" are the profits in + # respect of which Class 4 is payable under s.15 (or would be, if they + # exceeded the lower profits limit), which s.15(3) computes under + # Schedule 2. Class 2 and Class 4 therefore share one profit base. + profits = person("ni_class_4_profits", period) + # Section 11(2). From 2022-23 only profits that exceed the lower + # profits threshold are liable; profits from the small profits # threshold up to it are treated as paid (s.11(5A)-(5B)) and cost # nothing. Before then liability started at the small profits # threshold. The flat rate is 0 once s.11(2) is omitted in 2024-25. From c1364a42f5b2e8081e495d9e29e18e4c856c1d4e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 7 Oct 2026 16:12:54 -0400 Subject: [PATCH 7/8] Share the Class 2 rule with the UC floor; count 53 weeks in 2019-20 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review r4 (job 20261007-121526-ni-class-2-4-review-r4): - The UC minimum income floor computed its notional Class 2 with the pre-2022 rule (threshold >= small profits threshold). In 2022-23 and 2023-24 it deducted £163.80 or £179.40 from thresholds that s.11(5A)-(5B) treats as paid, so it disagreed with ni_class_2 and moved UC away from the law (lone parent, 16-hour floor of £8,669.44 in 2023-24: UC £90.09 too high on main's figures, £98.67 on this branch's previous head). New policyengine_uk/utils/class_2.py holds the s.11(2) test and the contribution-week count; ni_class_2 and the floor both use it. - A contribution week starts at midnight between Saturday and Sunday (SI 2001/1004 reg 1(2)) and the year's first week on the first Sunday after 5 April (HMRC NIM70200), so a full year is 53 weeks in 2019-20 (and 2025-26, when the rate is nil). The model charged 52: £156 instead of £159. - Tests: floor YAML for 2019-20, 2022-23 and 2023-24; floor property years 2019, 2022 and 2023 with a lone-parent example; Class 2 YAML for 2019-20 and 2020-21 weeks; test_ni_class_2 draws relevant profits first, adds in-year trading losses, checks the week count against the calendar and NIM70200's example; the 2019-20 reg 100 rows in test_ni_class_4_statute use 53 weeks (£2,167.56). - Docstring, documentation, notebook and changelog wording. Co-Authored-By: Claude Opus 5.5 --- changelog.d/1887.fixed.md | 2 +- .../gov/hmrc/national-insurance.ipynb | 2 +- .../income_floor/uc_minimum_income_floor.yaml | 80 ++++++++++ .../class_2/ni_class_2.yaml | 20 +++ policyengine_uk/tests/test_ni_class_2.py | 138 ++++++++++++------ .../tests/test_ni_class_4_statute.py | 23 +-- ...test_uc_minimum_income_floor_properties.py | 32 +++- policyengine_uk/utils/class_2.py | 45 ++++++ ...minimum_income_floor_national_insurance.py | 9 +- .../national_insurance/class_2/ni_class_2.py | 32 ++-- 10 files changed, 302 insertions(+), 81 deletions(-) create mode 100644 policyengine_uk/utils/class_2.py diff --git a/changelog.d/1887.fixed.md b/changelog.d/1887.fixed.md index f927115ca2..34002dea94 100644 --- a/changelog.d/1887.fixed.md +++ b/changelog.d/1887.fixed.md @@ -1 +1 @@ -- Fix Class 2 National Insurance for 2015-16 to 2023-24 against s.11 SSCBA 1992: charge £3.45 a week in 2023-24; from 2022-23 charge only profits above the Class 2 lower profits threshold (£11,908, then £12,570), with profits from the small profits threshold up to it treated as paid; keep the small profits threshold at £6,725 in 2023-24 and 2024-25 and correct it to £5,965 in 2015-16 and 2016-17; date the abolition 6 April 2024. Class 2 now tests its thresholds on the profits Class 4 is charged on (s.11(3)), after capital allowances, the trading allowance and loss relief, not on self-employment income. Every value now cites its amending instrument. +- Fix Class 2 National Insurance for 2015-16 to 2023-24 against s.11 SSCBA 1992: charge £3.45 a week in 2023-24; from 2022-23 charge only profits above the Class 2 lower profits threshold (£11,908, then £12,570), with profits from the small profits threshold up to it treated as paid; keep the small profits threshold at £6,725 in 2023-24 and 2024-25 and correct it to £5,965 in 2015-16 and 2016-17; date the abolition 6 April 2024. Class 2 now tests its thresholds on the profits Class 4 is charged on (s.11(3)), after capital allowances, the trading allowance and loss relief, not on self-employment income. A full year of Class 2 is every contribution week in it, so 53 weeks in 2019-20. The Universal Credit minimum income floor's notional Class 2 follows the same rule, so it deducts no Class 2 in 2022-23 and 2023-24 on a threshold the lower profits threshold covers. Every value now cites its amending instrument. diff --git a/docs/book/programs/gov/hmrc/national-insurance.ipynb b/docs/book/programs/gov/hmrc/national-insurance.ipynb index 2cbaf7462b..c64696002a 100644 --- a/docs/book/programs/gov/hmrc/national-insurance.ipynb +++ b/docs/book/programs/gov/hmrc/national-insurance.ipynb @@ -101,7 +101,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": "## How PolicyEngine computes National Insurance liability\n\nFor each person, PolicyEngine computes NI contributions class-by-class:\n\n- **Class 1 employee** (`ni_class_1_employee`): the sum of `ni_class_1_employee_primary` (main rate on earnings between the Primary Threshold and the Upper Earnings Limit) and `ni_class_1_employee_additional` (additional rate on earnings above the UEL). These primary contributions are due from age 16 until state pension age (`ni_liable`): SSCBA 1992 s.6(3) ends them at pensionable age.\n- **Class 1 employer** (`ni_class_1_employer`): paid on earnings above the Secondary Threshold; surfaced through the `ni_employer` variable. These secondary contributions are due on earners over 16 with no upper age limit (`ni_class_1_secondary_liable`), so employers keep paying them for employees over state pension age (SSCBA 1992 s.6(1)(b) and s.6(3)).\n- **Class 2** (`ni_class_2`): 52 weeks of a flat weekly rate for the self-employed. Its thresholds apply to relevant profits, which are the profits Class 4 is charged on (`ni_class_4_profits`; SSCBA 1992 s.11(3)). Before 2022-23 it is payable on relevant profits at or above the Small Profits Threshold. In 2022-23 and 2023-24 it is payable only on relevant profits above the Class 2 lower profits threshold; relevant profits from the Small Profits Threshold up to it are treated as paid and cost nothing. Compulsory Class 2 ended on 6 April 2024; voluntary Class 2 is not modelled.\n- **Class 3** (`ni_class_3`): a voluntary flat weekly rate; the variable is exposed but defaults to zero unless explicitly set.\n- **Class 4** (`ni_class_4`): main rate between the Lower and Upper Profits Limits plus additional rate above the UPL, charged on `ni_class_4_profits`. Those are the trade profits chargeable to income tax under ITTOIA 2005 Part 2 Chapter 2 (`ni_class_4_profits_before_losses`: self-employment profit after capital allowances and the trading allowance), less trading losses as SSCBA 1992 Schedule 2 paragraph 3 allows (`ni_class_4_loss_relief`). A loss reduces only trade profits for Class 4: each year's losses (those brought forward, then the year's own loss, `ni_class_4_trading_loss`) are set against that year's trade profits, and the rest is carried forward until used (`ni_class_4_losses_brought_forward`, `ni_class_4_losses_carried_forward`). A year's own loss is the `trading_loss` entered for that year, so a loss entered once is relieved once: it is not counted again in the later years that `trading_loss` carries into. Carry-back claims and the paragraph 3(5) deductions for trade interest and royalties are not modelled. Where the person also pays primary Class 1 contributions (or, before 6 April 2024, Class 2 contributions), liability is capped at the annual maximum in regulation 100 of the Social Security (Contributions) Regulations 2001 (`ni_class_4_maximum`). Class 4 is due from age 16 until the first tax year that begins on or after the day the person reaches state pension age (`ni_class_4_liable`): regulation 91(a) excepts anyone over pensionable age at the beginning of the tax year, so a person who reaches it during a year pays for that whole year.\n\nThe individual total `national_insurance` sums the four employee and self-employed components directly \u2014 `ni_class_1_employee + ni_class_2 + ni_class_3 + ni_class_4` \u2014 so voluntary Class 3 contributions are included in the household-side liability. The convenience aggregates `ni_employee` (Class 1 employee only) and `ni_self_employed` (Class 2 + Class 4) are also exposed for reporting. The Class 1 *employer* charge (`ni_class_1_employer`, surfaced as `ni_employer`) is an employer-side cost rather than part of the individual's `national_insurance`, so it is tracked separately and feeds the government revenue aggregates on its own.\n\nParameters live in `policyengine_uk/parameters/gov/hmrc/national_insurance/` and the per-class formulas in `policyengine_uk/variables/gov/hmrc/national_insurance/`." + "source": "## How PolicyEngine computes National Insurance liability\n\nFor each person, PolicyEngine computes NI contributions class-by-class:\n\n- **Class 1 employee** (`ni_class_1_employee`): the sum of `ni_class_1_employee_primary` (main rate on earnings between the Primary Threshold and the Upper Earnings Limit) and `ni_class_1_employee_additional` (additional rate on earnings above the UEL). These primary contributions are due from age 16 until state pension age (`ni_liable`): SSCBA 1992 s.6(3) ends them at pensionable age.\n- **Class 1 employer** (`ni_class_1_employer`): paid on earnings above the Secondary Threshold; surfaced through the `ni_employer` variable. These secondary contributions are due on earners over 16 with no upper age limit (`ni_class_1_secondary_liable`), so employers keep paying them for employees over state pension age (SSCBA 1992 s.6(1)(b) and s.6(3)).\n- **Class 2** (`ni_class_2`): a flat weekly rate for the self-employed, for each contribution week in the tax year (53 in 2019-20, otherwise 52 up to 2023-24). Its thresholds apply to relevant profits, which are the profits Class 4 is charged on (`ni_class_4_profits`; SSCBA 1992 s.11(3)). Before 2022-23 it is payable on relevant profits at or above the Small Profits Threshold. In 2022-23 and 2023-24 it is payable only on relevant profits above the Class 2 lower profits threshold; relevant profits from the Small Profits Threshold up to it are treated as paid and cost nothing. Compulsory Class 2 ended on 6 April 2024; voluntary Class 2 is not modelled.\n- **Class 3** (`ni_class_3`): a voluntary flat weekly rate; the variable is exposed but defaults to zero unless explicitly set.\n- **Class 4** (`ni_class_4`): main rate between the Lower and Upper Profits Limits plus additional rate above the UPL, charged on `ni_class_4_profits`. Those are the trade profits chargeable to income tax under ITTOIA 2005 Part 2 Chapter 2 (`ni_class_4_profits_before_losses`: self-employment profit after capital allowances and the trading allowance), less trading losses as SSCBA 1992 Schedule 2 paragraph 3 allows (`ni_class_4_loss_relief`). A loss reduces only trade profits for Class 4: each year's losses (those brought forward, then the year's own loss, `ni_class_4_trading_loss`) are set against that year's trade profits, and the rest is carried forward until used (`ni_class_4_losses_brought_forward`, `ni_class_4_losses_carried_forward`). A year's own loss is the `trading_loss` entered for that year, so a loss entered once is relieved once: it is not counted again in the later years that `trading_loss` carries into. Carry-back claims and the paragraph 3(5) deductions for trade interest and royalties are not modelled. Where the person also pays primary Class 1 contributions (or, before 6 April 2024, Class 2 contributions), liability is capped at the annual maximum in regulation 100 of the Social Security (Contributions) Regulations 2001 (`ni_class_4_maximum`). Class 4 is due from age 16 until the first tax year that begins on or after the day the person reaches state pension age (`ni_class_4_liable`): regulation 91(a) excepts anyone over pensionable age at the beginning of the tax year, so a person who reaches it during a year pays for that whole year.\n\nThe individual total `national_insurance` sums the four employee and self-employed components directly \u2014 `ni_class_1_employee + ni_class_2 + ni_class_3 + ni_class_4` \u2014 so voluntary Class 3 contributions are included in the household-side liability. The convenience aggregates `ni_employee` (Class 1 employee only) and `ni_self_employed` (Class 2 + Class 4) are also exposed for reporting. The Class 1 *employer* charge (`ni_class_1_employer`, surfaced as `ni_employer`) is an employer-side cost rather than part of the individual's `national_insurance`, so it is tracked separately and feeds the government revenue aggregates on its own.\n\nParameters live in `policyengine_uk/parameters/gov/hmrc/national_insurance/` and the per-class formulas in `policyengine_uk/variables/gov/hmrc/national_insurance/`." }, { "cell_type": "markdown", diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/income_floor/uc_minimum_income_floor.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/income_floor/uc_minimum_income_floor.yaml index 939c0389e0..3568e6e7e5 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/income_floor/uc_minimum_income_floor.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/income/income_floor/uc_minimum_income_floor.yaml @@ -149,6 +149,86 @@ # 7.20 x 35 x 52. uc_minimum_income_floor_gross: 13_104 +# The notional Class 2 follows s.11(2) SSCBA 1992 as ni_class_2 does. In +# 2022-23 and 2023-24 profits from the small profits threshold up to the +# lower profits threshold (11,908, then 12,570) are treated as paid and cost +# nothing (s.11(5A)-(5B)). A full year is 53 contribution weeks in 2019-20 +# (HMRC NIM70200). +- name: Net floor in 2023-24 deducts no Class 2 on profits treated as paid + period: 2023 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + child: + age: 3 + households: + household: + members: [parent, child] + region: NORTH_EAST + output: + # A lone parent of a child aged 3: 16 hours at the 2023-24 National + # Living Wage, 10.42 x 16 x 52. + uc_minimum_income_floor_gross: [8_669.44, 0] + uc_minimum_income_floor_income_tax: [0, 0] + # 8,669.44 is above the 6,725 small profits threshold but not above the + # 12,570 lower profits threshold, so Class 2 is treated as paid; Class 4 + # starts at 12,570. + uc_minimum_income_floor_national_insurance: [0, 0] + uc_minimum_income_floor: [8_669.44, 0] + +- name: Net floor in 2022-23 deducts no Class 2 on profits treated as paid + period: 2022 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + child: + age: 3 + households: + household: + members: [parent, child] + region: NORTH_EAST + output: + # 9.50 x 16 x 52: above the 6,725 small profits threshold, not above the + # 11,908 lower profits threshold or Class 4 lower profits limit. + uc_minimum_income_floor_gross: [7_904, 0] + uc_minimum_income_floor_national_insurance: [0, 0] + uc_minimum_income_floor: [7_904, 0] + +- name: Net floor in 2023-24 deducts Class 2 above the lower profits threshold + period: 2023 + absolute_error_margin: 0.01 + input: + age: 30 + region: NORTH_EAST + output: + # 10.42 x 35 x 52. + uc_minimum_income_floor_gross: 18_964.40 + # (18,964.40 - 12,570) x 20%. + uc_minimum_income_floor_income_tax: 1_278.88 + # Class 2: 3.45 x 52 = 179.40. Class 4: (18,964.40 - 12,570) x 9% = 575.50. + uc_minimum_income_floor_national_insurance: 754.90 + uc_minimum_income_floor: 16_930.62 + +- name: Net floor in 2019-20 deducts 53 weeks of Class 2 + period: 2019 + absolute_error_margin: 0.01 + input: + age: 30 + region: NORTH_EAST + output: + # 8.21 x 35 x 52. + uc_minimum_income_floor_gross: 14_942.20 + # (14,942.20 - 12,500) x 20%. + uc_minimum_income_floor_income_tax: 488.44 + # Class 2: 2019-20 has 53 contribution weeks (Sundays 7 April 2019 to 5 + # April 2020), 3.00 x 53 = 159. Class 4: (14,942.20 - 8,632) x 9% = 567.92. + uc_minimum_income_floor_national_insurance: 726.92 + uc_minimum_income_floor: 13_726.84 + # Individual thresholds by work-related group and expected hours. Reg. 90(2): # "(a) 16 hours per week, in the case of a claimant who would otherwise fall # within section 20 ... or section 21 ...; or (b) the expected number of hours diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml index dd9caf353f..916ab75b8f 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/class_2/ni_class_2.yaml @@ -217,3 +217,23 @@ output: ni_class_4_profits: 5_964 ni_class_2: 0 + +# A full year is every contribution week in it. A contribution week starts at +# midnight between Saturday and Sunday (SI 2001/1004 reg. 1(2)), and the year's +# first week starts on the first Sunday after 5 April (HMRC NIM70200), so +# 2019-20 has 53: Sundays from 7 April 2019 to 5 April 2020. +- name: NI Class 2 - 2019-20, 53 contribution weeks. + period: 2019 + input: + self_employment_income: 10_000 + output: + # SI 2019/262 reg 3: £3.00 a week from £6,365. + ni_class_2: 3 * 53 + +- name: NI Class 2 - 2020-21, 52 contribution weeks. + period: 2020 + input: + self_employment_income: 10_000 + output: + # Sundays from 12 April 2020 to 4 April 2021. + ni_class_2: 3.05 * 52 diff --git a/policyengine_uk/tests/test_ni_class_2.py b/policyengine_uk/tests/test_ni_class_2.py index 12bdf5b84f..35ea41db17 100644 --- a/policyengine_uk/tests/test_ni_class_2.py +++ b/policyengine_uk/tests/test_ni_class_2.py @@ -2,34 +2,40 @@ STATUTE holds each tax year's figures as enacted, read from legislation.gov.uk (the amending instrument's made text and the point-in-time text of s.11), -independently of the parameter files. +independently of the parameter files. CONTRIBUTION_WEEKS holds each year's +contribution weeks, counted from the calendar: a contribution week starts at +midnight between Saturday and Sunday (SI 2001/1004 reg. 1(2)) and the year's +first week on the first Sunday after 5 April (HMRC NIM70200). The thresholds apply to relevant profits: the profits on which Class 4 is -payable under s.15, computed under Schedule 2 (s.11(3)). With no gross -receipts or losses supplied, those are self-employment profit less capital -allowances (CAA 2001 s.247). A profit of £1,000 or less is treated as covered -by the trading allowance and gives nil relevant profits; that never changes -liability, because every threshold is far above £1,000. - -Invariants, for every model year, profits >= 0 and capital allowances >= 0: - -1. Differential: ni_class_2 is 52 weeks at the statutory weekly rate when - s.11(2) makes the earner liable, and 0 otherwise. Before 2022-23 the - earner is liable on relevant profits of, or exceeding, the small profits - threshold. In 2022-23 and 2023-24 only relevant profits that exceed the - lower profits threshold are liable; relevant profits from the small - profits threshold up to it are treated as paid (s.11(5A)-(5B)), which - costs nothing. From 2024-25 s.11(2) is omitted and no one is liable. -2. ni_class_2 is either 0 or 52 x the weekly rate. +payable under s.15, computed under Schedule 2 (s.11(3)). Here the inputs are +profit, whole-pound capital allowances (CAA 2001 s.247) and a whole-pound +trading loss made in the same year (Sch 2 para 3), with no gross receipts, so +relevant profits are profit less both. A profit of £1,000 or less is treated +as covered by the trading allowance from 2017-18, when the allowance starts, +and then gives nil relevant profits; that never changes liability, because +every threshold is far above £1,000. The trading allowance with gross +receipts, and losses brought forward, are covered by the YAML cases. + +Invariants, for every model year 2015-2030, profits >= 0, capital allowances +>= 0 and in-year trading losses >= 0, all whole pounds apart from profits: + +1. Differential: ni_class_2 is the statutory weekly rate for each contribution + week in the year when s.11(2) makes the earner liable, and 0 otherwise. + Before 2022-23 the earner is liable on relevant profits of, or exceeding, + the small profits threshold. In 2022-23 and 2023-24 only relevant profits + that exceed the lower profits threshold are liable; relevant profits from + the small profits threshold up to it are treated as paid (s.11(5A)-(5B)), + which costs nothing. From 2024-25 s.11(2) is omitted and no one is liable. +2. ni_class_2 is either 0 or a full year at the weekly rate. 3. ni_class_2 is non-decreasing in relevant profits, so non-decreasing in - profit and non-increasing in capital allowances. + profit and non-increasing in capital allowances and losses. 4. Shared base: ni_class_2 is the s.11(2) rule applied to the model's own ni_class_4_profits, the base Class 4 is charged on. -The model counts 52 weeks of self-employment in a year. Profits are stored -as float32, so the reference is evaluated on the float32 value. Capital -allowances are whole pounds here, so profit less allowances is exact in -float32. +Profits are stored as float32, so the reference is evaluated on the float32 +value. Allowances and losses are whole pounds, so subtracting them from a +float32 profit is exact in float32. """ from fractions import Fraction @@ -41,8 +47,7 @@ from policyengine_uk import Simulation from policyengine_uk.system import system - -WEEKS = 52 +from policyengine_uk.utils.class_2 import class_2_contribution_weeks # Tax year starting in April of the key: (weekly rate under s.11(2), small # profits threshold, lower profits threshold or None where s.11(2) keys on the @@ -61,6 +66,10 @@ 2025: ("0", 6_845, None), # SI 2025/288 reg 3(a) 2026: ("0", 7_105, None), # SI 2026/231 reg 3(a) } +# Contribution weeks: years whose first Sunday after 5 April falls on 6 or 7 +# April hold 53 Sundays to the next 5 April (2019-20: 7 April 2019 to 5 April +# 2020; 2025-26: 6 April 2025 to 5 April 2026). Every other year has 52. +CONTRIBUTION_WEEKS = {2019: 53, 2025: 53} MODEL_YEARS = list(range(2015, 2031)) PROPERTY_SETTINGS = settings( max_examples=10, @@ -75,20 +84,25 @@ def statute(year): return STATUTE[min(year, max(STATUTE))] -def relevant_profits(profits, capital_allowances=0): - # s.11(3): the Class 4 profits, here profit less capital allowances. - exact = Fraction(float(np.float32(profits))) - capital_allowances +def weeks(year): + return CONTRIBUTION_WEEKS.get(year, 52) + + +def relevant_profits(profits, capital_allowances=0, trading_loss=0): + # s.11(3): the Class 4 profits, here profit less capital allowances and + # the year's trading loss. + exact = Fraction(float(np.float32(profits))) - capital_allowances - trading_loss return max(exact, Fraction(0)) -def statutory_class_2(year, profits, capital_allowances=0): +def statutory_class_2(year, profits, capital_allowances=0, trading_loss=0): rate, small_profits_threshold, lower_profits_threshold = statute(year) - profits = relevant_profits(profits, capital_allowances) + profits = relevant_profits(profits, capital_allowances, trading_loss) if lower_profits_threshold is None: liable = profits >= small_profits_threshold else: liable = profits > lower_profits_threshold - return WEEKS * Fraction(rate) if liable else Fraction(0) + return weeks(year) * Fraction(rate) if liable else Fraction(0) # Every threshold s.11 has used, with points either side of each. @@ -103,20 +117,25 @@ def statutory_class_2(year, profits, capital_allowances=0): ) -# Whole-pound capital allowances that move profits across each threshold. +# Whole-pound capital allowances and trading losses that move profits across +# each threshold. CAPITAL_ALLOWANCES = [0, 500, 1_000, 2_500] +TRADING_LOSSES = [0, 750] def simulate(cases): - # One person per (profits, capital allowances) case, with the same values - # in every model year, so a single simulation covers every year. + # One person per (profits, capital allowances, trading loss) case, with + # the same values in every model year, so a single simulation covers every + # year. Each year's loss is no more than its profits after allowances, so + # none is carried forward. people = { f"p{i}": { "age": {year: 40 for year in MODEL_YEARS}, "self_employment_income": {year: profits for year in MODEL_YEARS}, "capital_allowances": {year: allowances for year in MODEL_YEARS}, + "trading_loss": {year: loss for year in MODEL_YEARS}, } - for i, (profits, allowances) in enumerate(cases) + for i, (profits, allowances, loss) in enumerate(cases) } return Simulation( situation={ @@ -127,15 +146,25 @@ def simulate(cases): ) +def gross(relevant, allowances=0, loss=0): + # The profit whose relevant profits are `relevant`. + return (float(relevant) + allowances + loss, allowances, loss) + + def assert_invariants(cases): - cases = [case if isinstance(case, tuple) else (case, 0) for case in cases] + cases = [ + tuple(case) + (0,) * (3 - len(case)) + if isinstance(case, tuple) + else (case, 0, 0) + for case in cases + ] sim = simulate(cases) relevant = [relevant_profits(*case) for case in cases] order = sorted(range(len(cases)), key=lambda i: relevant[i]) for year in MODEL_YEARS: class_2 = sim.calculate("ni_class_2", year) class_4_profits = sim.calculate("ni_class_4_profits", year) - full_year = WEEKS * float(statute(year)[0]) + full_year = weeks(year) * float(statute(year)[0]) for i, case in enumerate(cases): expected = float(statutory_class_2(year, *case)) assert class_2[i] == pytest.approx(expected, abs=0.01), (year, case) @@ -160,20 +189,34 @@ def test_class_2_parameters_match_statute(year): assert class_2.lower_profits_threshold == lower_profits_threshold +@pytest.mark.parametrize("year", MODEL_YEARS) +def test_contribution_weeks_follow_the_calendar(year): + assert class_2_contribution_weeks(year) == weeks(year) + + +def test_contribution_weeks_match_hmrc_example(): + # NIM70200: "The first contribution week of the 2021 to 2022 tax year + # starts on Sunday, 11 April 2021 and the last contribution week of that + # year ends on Saturday, 9 April 2022": 364 days, 52 weeks. + assert class_2_contribution_weeks(2021) == 52 + + def test_ni_class_2_matches_statute_at_every_threshold(): rng = np.random.default_rng(1_992) spread = list(rng.uniform(0, 200_000, 200).round(2)) assert_invariants(THRESHOLD_PROFITS + spread + [60_000.0, 1_000_000.0]) -def test_ni_class_2_tests_thresholds_on_profits_after_capital_allowances(): - # Gross profits set so that profit less capital allowances lands on, and - # either side of, every threshold s.11 has used. +def test_ni_class_2_tests_thresholds_on_profits_after_deductions(): + # Profits set so that profit less capital allowances and the year's loss + # lands on, and either side of, every threshold s.11 has used. assert_invariants( [ - (relevant + allowances, allowances) + gross(relevant, allowances, loss) for relevant in THRESHOLD_PROFITS - for allowances in CAPITAL_ALLOWANCES[1:] + for allowances in CAPITAL_ALLOWANCES + for loss in TRADING_LOSSES + if allowances or loss ] ) @@ -181,22 +224,23 @@ def test_ni_class_2_tests_thresholds_on_profits_after_capital_allowances(): @PROPERTY_SETTINGS @given( st.lists( - st.tuples( + st.builds( + gross, st.one_of( + st.sampled_from(THRESHOLD_PROFITS), st.floats(0, 200_000, allow_nan=False, allow_infinity=False), st.integers(0, 200_000).map(float), - st.sampled_from(THRESHOLD_PROFITS), - ), - st.one_of( - st.sampled_from(CAPITAL_ALLOWANCES), - st.integers(0, 20_000), ), + st.one_of(st.sampled_from(CAPITAL_ALLOWANCES), st.integers(0, 20_000)), + st.one_of(st.sampled_from(TRADING_LOSSES), st.integers(0, 20_000)), ), min_size=1, max_size=24, ) ) def test_ni_class_2_properties(cases): + # Relevant profits are drawn first, so threshold points stay on their + # thresholds whatever allowances and losses are drawn with them. assert_invariants(cases) diff --git a/policyengine_uk/tests/test_ni_class_4_statute.py b/policyengine_uk/tests/test_ni_class_4_statute.py index 0f0cd2e60c..c352ef9687 100644 --- a/policyengine_uk/tests/test_ni_class_4_statute.py +++ b/policyengine_uk/tests/test_ni_class_4_statute.py @@ -75,7 +75,11 @@ 2023: ("3.45", 12_570, True), # SI 2023/236 reg 3 } YEARS = sorted(CLASS_4) -WEEKS = 52 +# A full year of Class 2 is one weekly contribution per contribution week: +# one per Sunday from 6 April to the next 5 April (SI 2001/1004 reg 1(2); +# HMRC NIM70200). 2019-20 has 53 (Sundays 7 April 2019 to 5 April 2020); the +# other years with Class 2 have 52. +CONTRIBUTION_WEEKS = {2019: 53} PROPERTY_SETTINGS = settings( max_examples=6, deadline=None, @@ -93,7 +97,8 @@ def statutory_class_2(year, profits): return Fraction(0) rate, threshold, exceed = CLASS_2[year] liable = profits > threshold if exceed else profits >= threshold - return WEEKS * Fraction(rate) if liable else Fraction(0) + weeks = CONTRIBUTION_WEEKS.get(year, 52) + return weeks * Fraction(rate) if liable else Fraction(0) def statutory_class_4(year, profits, class_1): @@ -192,15 +197,15 @@ def test_class_4_parameters_match_statute(year): # max = 3,416.29 + 273 = 3,689.29. Not binding. (2018, 60_000, 0, 153.40, 3_686.34), # 2019-20 with £2,000 of Class 1. s.15(3): 9% x (50,000 - 8,632) - # = 3,723.12. Class 2 52 x 3 = 156. Step 4 = 3,723.12 + 159 - 156 - # - 2,000 = 1,726.12 (Case 2). Step 5 = 1,726.12 / 9% = 19,179.11; - # Step 7 = 41,368 - 19,179.11 = 22,188.89; Step 8 = 443.78. - # Max = 1,726.12 + 443.78 = 2,169.90. Binding. - (2019, 50_000, 2_000, 156.00, 2_169.90), - # 2019-20 with £6,000 of Class 1. Step 4 = 3,723.12 + 159 - 156 - 6,000 + # = 3,723.12. Class 2 53 x 3 = 159 (53 contribution weeks). Step 4 + # = 3,723.12 + 159 - 159 - 2,000 = 1,723.12 (Case 2). Step 5 = 1,723.12 + # / 9% = 19,145.78; Step 7 = 41,368 - 19,145.78 = 22,222.22; Step 8 + # = 444.44. Max = 1,723.12 + 444.44 = 2,167.56. Binding. + (2019, 50_000, 2_000, 159.00, 2_167.56), + # 2019-20 with £6,000 of Class 1. Step 4 = 3,723.12 + 159 - 159 - 6,000 # < 0 (Case 3, nil). Step 7 = Step 6 = 50,000 - 8,632 = 41,368; # Step 8 = 2% x 41,368 = 827.36; Step 9 = 0. Max = 827.36. Binding. - (2019, 50_000, 6_000, 156.00, 827.36), + (2019, 50_000, 6_000, 159.00, 827.36), # 2020-21. 9% x (20,000 - 9,500) = 945. Class 2 52 x 3.05 = 158.60. (2020, 20_000, 0, 158.60, 945.00), # 2021-22 at the lower profits limit: Class 2 (profits of, or exceeding, diff --git a/policyengine_uk/tests/test_uc_minimum_income_floor_properties.py b/policyengine_uk/tests/test_uc_minimum_income_floor_properties.py index efb2dd4f19..0f7b5eeb53 100644 --- a/policyengine_uk/tests/test_uc_minimum_income_floor_properties.py +++ b/policyengine_uk/tests/test_uc_minimum_income_floor_properties.py @@ -61,9 +61,10 @@ derandomize=True, suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], ) -# 2020 has 12% Class 1 NI and the temporary UC uplift; 2026 current rates; -# 2027 the Finance Act 2026 rates. -YEARS = [2020, 2026, 2027] +# 2019 has 53 Class 2 contribution weeks; 2020 has 12% Class 1 NI and the +# temporary UC uplift; 2022 and 2023 have the Class 2 lower profits +# threshold; 2026 current rates; 2027 the Finance Act 2026 rates. +YEARS = [2019, 2020, 2022, 2023, 2026, 2027] TENURES = ["RENT_FROM_COUNCIL", "RENT_PRIVATELY", "OWNED_OUTRIGHT"] REGIONS = ["LONDON", "NORTH_EAST", "WALES", "SCOTLAND"] WORKING_AGE = st.integers(18, 64) @@ -320,6 +321,28 @@ def test_matches_closed_form_of_regulation_62(units, year): ] +# A lone parent of a child aged 3 has a 16-hour threshold, which in 2022-23 +# and 2023-24 lies between the Class 2 small profits threshold and the lower +# profits threshold: Class 2 is treated as paid and costs nothing. +LONE_PARENT_16_HOURS = [ + dict( + adults=[ + dict( + age=30, + self_employment_income=3_000.0, + employment_income=0.0, + pension_contributions=0.0, + uc_is_in_startup_period=False, + ) + ], + children=[3], + tenure="RENT_PRIVATELY", + rent=7_200.0, + region="NORTH_EAST", + ) +] + + @PROPERTY_SETTINGS @given( units=populations, @@ -328,6 +351,9 @@ def test_matches_closed_form_of_regulation_62(units, year): ) @example(units=SPA_DURING_2026, year=2026, self_employed=True) @example(units=SPA_DURING_2026, year=2026, self_employed=False) +@example(units=LONE_PARENT_16_HOURS, year=2022, self_employed=True) +@example(units=LONE_PARENT_16_HOURS, year=2023, self_employed=True) +@example(units=LONE_PARENT_16_HOURS, year=2019, self_employed=True) def test_notional_deductions_equal_tax_on_threshold_as_only_income( units, year, self_employed ): diff --git a/policyengine_uk/utils/class_2.py b/policyengine_uk/utils/class_2.py new file mode 100644 index 0000000000..32104fec3d --- /dev/null +++ b/policyengine_uk/utils/class_2.py @@ -0,0 +1,45 @@ +"""Class 2 National Insurance rules shared by ni_class_2 and by the notional +Class 2 that the Universal Credit minimum income floor deducts. + +Both charge a whole tax year of self-employment, so they share the s.11(2) +liability test and the number of contribution weeks in the year. +""" + +import datetime + +import numpy as np + + +def class_2_contribution_weeks(year: int) -> int: + """Contribution weeks in the tax year that starts on 6 April of ``year``. + + A contribution week is "a period of seven days beginning with midnight + between Saturday and Sunday" (SI 2001/1004 reg. 1(2)), and the first week + of a contribution year starts on the first Sunday after 5 April (HMRC + NIM70200). A year therefore has one week for each Sunday from 6 April to + the following 5 April: 53 in 2019-20 and 2025-26, 52 in the other years + from 2015-16 to 2026-27. NIM70200: "a person who is self-employed for the + full year will be liable for all 53 weeks." + """ + start = datetime.date(year, 4, 6) + end = datetime.date(year + 1, 4, 5) + # date.weekday() is 6 on a Sunday. + first_sunday = start + datetime.timedelta(days=(6 - start.weekday()) % 7) + return (end - first_sunday).days // 7 + 1 + + +def class_2_liable(relevant_profits, class_2): + """Whether s.11(2) SSCBA 1992 makes an earner with these relevant profits + liable to Class 2 for the year. + + From 2022-23 only relevant profits that exceed the lower profits threshold + are liable; those from the small profits threshold up to it are treated as + paid (s.11(5A)-(5B)) and cost nothing. Before then liability started at + the small profits threshold ("of, or exceeding"). ``class_2`` is the + gov.hmrc.national_insurance.class_2 parameter node for the year. + """ + return np.where( + class_2.lower_profits_threshold_applies, + relevant_profits > class_2.lower_profits_threshold, + relevant_profits >= class_2.small_profits_threshold, + ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/income/income_floor/uc_minimum_income_floor_national_insurance.py b/policyengine_uk/variables/gov/dwp/universal_credit/income/income_floor/uc_minimum_income_floor_national_insurance.py index 88bc10ec15..19025089f2 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/income/income_floor/uc_minimum_income_floor_national_insurance.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/income/income_floor/uc_minimum_income_floor_national_insurance.py @@ -1,4 +1,5 @@ from policyengine_uk.model_api import * +from policyengine_uk.utils.class_2 import class_2_contribution_weeks, class_2_liable class uc_minimum_income_floor_national_insurance(Variable): @@ -41,11 +42,13 @@ def formula(person, period, parameters): ) + class_1.rates.employee.additional * max_( 0, threshold - upper_earnings_limit ) - # Class 2 and Class 4 on profits equal to the threshold. + # Class 2 and Class 4 on profits equal to the threshold, by the same + # s.11(2) test and contribution weeks as ni_class_2: from 2022-23 + # profits up to the lower profits threshold are treated as paid. class_2 = ( - (threshold >= ni.class_2.small_profits_threshold) + class_2_liable(threshold, ni.class_2) * ni.class_2.flat_rate - * WEEKS_IN_YEAR + * class_2_contribution_weeks(period.start.year) ) class_4 = ni.class_4 class_4_amount = class_4.rates.main * max_( diff --git a/policyengine_uk/variables/gov/hmrc/national_insurance/class_2/ni_class_2.py b/policyengine_uk/variables/gov/hmrc/national_insurance/class_2/ni_class_2.py index 5ad489fd29..7737b27751 100644 --- a/policyengine_uk/variables/gov/hmrc/national_insurance/class_2/ni_class_2.py +++ b/policyengine_uk/variables/gov/hmrc/national_insurance/class_2/ni_class_2.py @@ -1,4 +1,5 @@ from policyengine_uk.model_api import * +from policyengine_uk.utils.class_2 import class_2_contribution_weeks, class_2_liable class ni_class_2(Variable): @@ -6,10 +7,11 @@ class ni_class_2(Variable): entity = Person label = "NI Class 2 contributions" documentation = ( - "Compulsory Class 2 contributions under s.11(2) SSCBA 1992, tested on " - "the earner's relevant profits: the profits on which Class 4 is " - "payable under s.15 (s.11(3)), so after capital allowances, the " - "trading allowance and Schedule 2 loss relief (ni_class_4_profits)." + "Compulsory Class 2 contributions under s.11(2) SSCBA 1992 for a full " + "year of self-employment: the weekly rate for each contribution week " + "in the tax year (53 in 2019-20, otherwise 52 to 2023-24). Liability " + "is tested on relevant profits, the profits on which Class 4 is " + "payable under s.15 (s.11(3)), as modelled in ni_class_4_profits." ) definition_period = YEAR unit = GBP @@ -20,8 +22,12 @@ class ni_class_2(Variable): href="https://www.legislation.gov.uk/ukpga/1992/4/section/11", ), dict( - title="HMRC National Insurance Manual NIM70300", - href="https://www.gov.uk/hmrc-internal-manuals/national-insurance-manual/nim70300", + title="HMRC National Insurance Manual NIM70650", + href="https://www.gov.uk/hmrc-internal-manuals/national-insurance-manual/nim70650", + ), + dict( + title="HMRC National Insurance Manual NIM70200", + href="https://www.gov.uk/hmrc-internal-manuals/national-insurance-manual/nim70200", ), ] @@ -32,14 +38,6 @@ def formula(person, period, parameters): # exceeded the lower profits limit), which s.15(3) computes under # Schedule 2. Class 2 and Class 4 therefore share one profit base. profits = person("ni_class_4_profits", period) - # Section 11(2). From 2022-23 only profits that exceed the lower - # profits threshold are liable; profits from the small profits - # threshold up to it are treated as paid (s.11(5A)-(5B)) and cost - # nothing. Before then liability started at the small profits - # threshold. The flat rate is 0 once s.11(2) is omitted in 2024-25. - liable = where( - class_2.lower_profits_threshold_applies, - profits > class_2.lower_profits_threshold, - profits >= class_2.small_profits_threshold, - ) - return liable * class_2.flat_rate * WEEKS_IN_YEAR + # The flat rate is 0 once s.11(2) is omitted in 2024-25. + weeks = class_2_contribution_weeks(period.start.year) + return class_2_liable(profits, class_2) * class_2.flat_rate * weeks From 0e9736d841db25e6708f00f58925353af74cad9d Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 07:52:08 -0400 Subject: [PATCH 8/8] Narrow the 53-week comment and the helper docstring (review r5 nits) - test_ni_class_2: a year has 53 contribution weeks when 6 April is a Sunday, or a Saturday in a 366-day year (2024-25 starts on Sunday 7 April but has 52); the carry-forward comment notes the trading allowance case. - class_2_liable: the s.11(2) threshold condition only; callers apply the rate, which handles the 2024 abolition. Co-Authored-By: Claude Opus 5.5 --- policyengine_uk/tests/test_ni_class_2.py | 13 +++++++++---- policyengine_uk/utils/class_2.py | 8 ++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/policyengine_uk/tests/test_ni_class_2.py b/policyengine_uk/tests/test_ni_class_2.py index 35ea41db17..bae597bddf 100644 --- a/policyengine_uk/tests/test_ni_class_2.py +++ b/policyengine_uk/tests/test_ni_class_2.py @@ -66,9 +66,12 @@ 2025: ("0", 6_845, None), # SI 2025/288 reg 3(a) 2026: ("0", 7_105, None), # SI 2026/231 reg 3(a) } -# Contribution weeks: years whose first Sunday after 5 April falls on 6 or 7 -# April hold 53 Sundays to the next 5 April (2019-20: 7 April 2019 to 5 April -# 2020; 2025-26: 6 April 2025 to 5 April 2026). Every other year has 52. +# Contribution weeks: one per Sunday from 6 April to the next 5 April. A year +# has 53 when 6 April is a Sunday, or when 6 April is a Saturday and the year +# has 366 days. 2019-20 runs from Sunday 7 April 2019 to Sunday 5 April 2020 +# (29 February 2020 included); 2025-26 from Sunday 6 April 2025 to Sunday 5 +# April 2026. Every other year from 2015-16 to 2030-31 has 52; 2024-25, for +# one, runs from Sunday 7 April 2024 to Sunday 30 March 2025. CONTRIBUTION_WEEKS = {2019: 53, 2025: 53} MODEL_YEARS = list(range(2015, 2031)) PROPERTY_SETTINGS = settings( @@ -127,7 +130,9 @@ def simulate(cases): # One person per (profits, capital allowances, trading loss) case, with # the same values in every model year, so a single simulation covers every # year. Each year's loss is no more than its profits after allowances, so - # none is carried forward. + # none is carried forward, except where a profit of £1,000 or less is + # relieved by the trading allowance from 2017-18: that loss carries + # forward, but those cases stay below every threshold. people = { f"p{i}": { "age": {year: 40 for year in MODEL_YEARS}, diff --git a/policyengine_uk/utils/class_2.py b/policyengine_uk/utils/class_2.py index 32104fec3d..e15c279d74 100644 --- a/policyengine_uk/utils/class_2.py +++ b/policyengine_uk/utils/class_2.py @@ -29,14 +29,18 @@ def class_2_contribution_weeks(year: int) -> int: def class_2_liable(relevant_profits, class_2): - """Whether s.11(2) SSCBA 1992 makes an earner with these relevant profits - liable to Class 2 for the year. + """Whether relevant profits meet the s.11(2) SSCBA 1992 profit condition + for the year. From 2022-23 only relevant profits that exceed the lower profits threshold are liable; those from the small profits threshold up to it are treated as paid (s.11(5A)-(5B)) and cost nothing. Before then liability started at the small profits threshold ("of, or exceeding"). ``class_2`` is the gov.hmrc.national_insurance.class_2 parameter node for the year. + + This is the threshold condition only. Callers multiply by the flat rate, + which is nil from 2024-25, when s.11(2) is omitted; the retained + thresholds then apply only to a reform that restores a rate. """ return np.where( class_2.lower_profits_threshold_applies,