From d75d8bb76b5bb8cec3b5e91200257c568a2c30d3 Mon Sep 17 00:00:00 2001 From: Vahid Ahmadi Date: Wed, 27 May 2026 10:20:52 +0200 Subject: [PATCH 1/2] Expand National Insurance docs page (#746) The existing page covered Classes 1, 2 and 4 and ended in two parameter charts. Round it out with: - Class 3 (voluntary) added to the modelling table now that ni_class_3 is exposed, - a "How PolicyEngine computes National Insurance liability" section that walks through the per-class variables and the household total, - a References block (SSCBA 1992, HMRC rates page, HMRC outturn stats). Also fix the chained boolean indexing in the rates chart cell, which was triggering a pandas UserWarning ("Boolean Series key will be reindexed"). --- changelog.d/746.md | 1 + .../gov/hmrc/national-insurance.ipynb | 55 ++++++++++++++----- 2 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 changelog.d/746.md diff --git a/changelog.d/746.md b/changelog.d/746.md new file mode 100644 index 000000000..1d14c4190 --- /dev/null +++ b/changelog.d/746.md @@ -0,0 +1 @@ +- Expand the National Insurance documentation page with a per-class computation section, add Class 3 (voluntary) to the modelling table, link the underlying variables, and add a references section; also fix the boolean-chain warning in the rates chart. diff --git a/docs/book/programs/gov/hmrc/national-insurance.ipynb b/docs/book/programs/gov/hmrc/national-insurance.ipynb index 8bdb55af3..5fc4a2a0f 100644 --- a/docs/book/programs/gov/hmrc/national-insurance.ipynb +++ b/docs/book/programs/gov/hmrc/national-insurance.ipynb @@ -133,30 +133,33 @@ "source": [ "# @title\n", "import pandas as pd\n", - "from tabulate import tabulate\n", "\n", "\n", "data = {\n", - " \"Class\": [\"Class 1\", \"Class 2\", \"Class 4\"],\n", + " \"Class\": [\"Class 1\", \"Class 2\", \"Class 3\", \"Class 4\"],\n", " \"Methodology & Basis\": [\n", " \"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", " \"Derived from self-employment income minus Class 1 employee NI.\",\n", " ],\n", " \"Thresholds & Limits\": [\n", " \"Primary Threshold, Upper Earnings Limit\",\n", " \"Small Profits Threshold\",\n", + " \"None (voluntary)\",\n", " \"Lower Profits Limit, Upper Profits Limit\",\n", " ],\n", " \"Rate Application\": [\n", " \"Main and Additional rates\",\n", " \"Flat rate\",\n", + " \"Flat rate\",\n", " \"Main and Additional rates\",\n", " ],\n", " \"Reference\": [\n", " \"Social Security Contributions and Benefits Act 1992 s. 8\",\n", - " \"Social Security and Benefits Act 1992 s. 11\",\n", - " \"Social Security and Benefits Act 1992 s. 15\",\n", + " \"Social Security Contributions and Benefits Act 1992 s. 11\",\n", + " \"Social Security Contributions and Benefits Act 1992 s. 13\",\n", + " \"Social Security Contributions and Benefits Act 1992 s. 15\",\n", " ],\n", "}\n", "\n", @@ -164,6 +167,27 @@ "df" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## How PolicyEngine computes National Insurance liability\n", + "\n", + "For 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).\n", + "- **Class 1 employer** (`ni_class_1_employer`): paid on earnings above the Secondary Threshold; surfaced through the `ni_employer` variable.\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, capped at the published Class 4 maximum.\n", + "\n", + "The household-level total is `national_insurance = ni_employee + ni_self_employed`, with `ni_employee` covering Class 1 employee contributions and `ni_self_employed` covering Class 2 + Class 4. Class 3 and the Class 1 employer charge are tracked separately so they can be summed into the government revenue aggregates without double-counting the household-side liability.\n", + "\n", + "Parameters live in `policyengine_uk/parameters/gov/hmrc/national_insurance/` and the per-class formulas in `policyengine_uk/variables/gov/hmrc/national_insurance/`.\n", + "\n", + "" + ] + }, { "cell_type": "markdown", "metadata": { @@ -1529,16 +1553,6 @@ ] }, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/var/folders/8f/pgfhysmd5ls3jnxb_5j7yy340000gn/T/ipykernel_41381/4247299465.py:2: UserWarning:\n", - "\n", - "Boolean Series key will be reindexed to match DataFrame index.\n", - "\n" - ] - }, { "data": { "application/vnd.plotly.v1+json": { @@ -2688,7 +2702,7 @@ ], "source": [ "fig = px.line(\n", - " df[df[\"Threshold\"] == False][df.Value < 1], # Don't plot the flat rate,\n", + " df[(df[\"Threshold\"] == False) & (df[\"Value\"] < 1)], # Don't plot the flat rate,\n", " x=\"Instant\",\n", " y=\"Value\",\n", " color=\"Label\",\n", @@ -2714,6 +2728,17 @@ "fig = format_fig(fig)\n", "fig" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "- [Social Security Contributions and Benefits Act 1992](https://www.legislation.gov.uk/ukpga/1992/4/contents) — primary statute defining each NI class.\n", + "- HMRC, [Rates and allowances: National Insurance contributions](https://www.gov.uk/government/publications/rates-and-allowances-national-insurance-contributions).\n", + "- HMRC, [National Insurance contributions outturn statistics](https://www.gov.uk/government/collections/income-tax-statistics-and-distributions)." + ] } ], "metadata": { From 5a4f57d9de31f59143691969c19ac3fbb0ebb3bc Mon Sep 17 00:00:00 2001 From: Vahid Ahmadi Date: Fri, 5 Jun 2026 13:26:52 +0100 Subject: [PATCH 2/2] Correct national_insurance rollup in NI docs national_insurance sums ni_class_1_employee + ni_class_2 + ni_class_3 + ni_class_4 directly (Class 3 is included), not ni_employee + ni_self_employed. The separately-tracked item is the Class 1 employer charge, not Class 3. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../gov/hmrc/national-insurance.ipynb | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/docs/book/programs/gov/hmrc/national-insurance.ipynb b/docs/book/programs/gov/hmrc/national-insurance.ipynb index 5fc4a2a0f..71841820c 100644 --- a/docs/book/programs/gov/hmrc/national-insurance.ipynb +++ b/docs/book/programs/gov/hmrc/national-insurance.ipynb @@ -170,23 +170,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "## How PolicyEngine computes National Insurance liability\n", - "\n", - "For 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).\n", - "- **Class 1 employer** (`ni_class_1_employer`): paid on earnings above the Secondary Threshold; surfaced through the `ni_employer` variable.\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, capped at the published Class 4 maximum.\n", - "\n", - "The household-level total is `national_insurance = ni_employee + ni_self_employed`, with `ni_employee` covering Class 1 employee contributions and `ni_self_employed` covering Class 2 + Class 4. Class 3 and the Class 1 employer charge are tracked separately so they can be summed into the government revenue aggregates without double-counting the household-side liability.\n", - "\n", - "Parameters live in `policyengine_uk/parameters/gov/hmrc/national_insurance/` and the per-class formulas in `policyengine_uk/variables/gov/hmrc/national_insurance/`.\n", - "\n", - "" - ] + "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).\n- **Class 1 employer** (`ni_class_1_employer`): paid on earnings above the Secondary Threshold; surfaced through the `ni_employer` variable.\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, capped at the published 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", @@ -2764,4 +2748,4 @@ }, "nbformat": 4, "nbformat_minor": 0 -} +} \ No newline at end of file