diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb..cf47edb6a 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: minor + changes: + added: + - Salary sacrifice pension cap policy modeling (2,000 GBP cap on NI-exempt contributions) diff --git a/policyengine_uk/parameters/gov/contrib/behavioral_responses/employee_salary_sacrifice_reduction_rate.yaml b/policyengine_uk/parameters/gov/contrib/behavioral_responses/employee_salary_sacrifice_reduction_rate.yaml new file mode 100644 index 000000000..481972821 --- /dev/null +++ b/policyengine_uk/parameters/gov/contrib/behavioral_responses/employee_salary_sacrifice_reduction_rate.yaml @@ -0,0 +1,14 @@ +# The percentage by which employees reduce their salary sacrifice pension contributions in response to the cap. +# - 0 = no adjustment (employees keep high salary sacrifice and pay NI on excess) +# - 0.5 = employees reduce salary sacrifice by 50% of the excess above cap +# - 1.0 = employees fully optimize (reduce salary sacrifice to exactly the cap) +# Most employees would optimize their salary sacrifice to avoid NI charges by reducing it to the cap level. +description: The percentage by which employees reduce their salary sacrifice pension contributions in response to the salary sacrifice pension cap. +values: + 2010-01-01: 1.0 # Default: full optimization (employees reduce to cap level) +metadata: + unit: /1 + label: Employee salary sacrifice reduction rate in response to cap + reference: + - title: "Cap on UK salary sacrifice benefits is 'short-term' choice, warn experts" + href: https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0 diff --git a/policyengine_uk/parameters/gov/hmrc/national_insurance/salary_sacrifice_pension_cap.yaml b/policyengine_uk/parameters/gov/hmrc/national_insurance/salary_sacrifice_pension_cap.yaml new file mode 100644 index 000000000..5fd871c64 --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/national_insurance/salary_sacrifice_pension_cap.yaml @@ -0,0 +1,13 @@ +# The annual cap on salary sacrifice pension contributions that are exempt from NI. +# Contributions above this cap are subject to standard NI rates. +# When set to infinity (default), the scheme is inactive and all salary sacrifice remains exempt from NI. +description: The annual cap on salary sacrifice pension contributions that are exempt from employee and employer National Insurance contributions. +values: + 2010-01-01: .inf # Default: no cap (scheme inactive) +metadata: + unit: currency-GBP + label: Salary sacrifice pension NI exemption cap + period: year + reference: + - title: "Cap on UK salary sacrifice benefits is 'short-term' choice, warn experts" + href: https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0 diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employee.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employee.yaml new file mode 100644 index 000000000..1b03d184f --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employee.yaml @@ -0,0 +1,89 @@ +- name: No employee NI charge when salary sacrifice below £2,000 cap + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 40_000 + pension_contributions_via_salary_sacrifice: 1_500 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 0 + output: + salary_sacrifice_pension_ni_employee: 0 + +- name: Employee NI charge at 8% main rate for moderate earner above cap + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 45_000 + pension_contributions_via_salary_sacrifice: 2_250 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 0 + output: + salary_sacrifice_pension_ni_employee: (2_250 - 2_000) * 0.08 # £250 excess * 8% + +- name: Employee NI charge at 8% main rate for earner just below UEL + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 50_000 + pension_contributions_via_salary_sacrifice: 10_000 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 0 + output: + salary_sacrifice_pension_ni_employee: (10_000 - 2_000) * 0.08 # £8,000 excess * 8% + +- name: Employee NI charge at 2% additional rate for high earner above UEL + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 125_000 + pension_contributions_via_salary_sacrifice: 25_000 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 0 + output: + salary_sacrifice_pension_ni_employee: (25_000 - 2_000) * 0.02 # £23,000 excess * 2% + +- name: Employee NI charge at 2% for earner well above UEL + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 80_000 + pension_contributions_via_salary_sacrifice: 5_000 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 0 + output: + salary_sacrifice_pension_ni_employee: (5_000 - 2_000) * 0.02 # £3,000 excess * 2% + +- name: No employee NI charge when exactly at cap + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 60_000 + pension_contributions_via_salary_sacrifice: 2_000 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 0 + output: + salary_sacrifice_pension_ni_employee: 0 + +- name: Employee NI charge for small excess over cap + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 35_000 + pension_contributions_via_salary_sacrifice: 2_100 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 0 + output: + salary_sacrifice_pension_ni_employee: 100 * 0.08 # £100 excess * 8% + +- name: Employee optimizes salary sacrifice to cap (100% behavioral response) + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 45_000 + pension_contributions_via_salary_sacrifice: 3_000 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 1.0 + output: + salary_sacrifice_pension_ni_employee: 0 + pension_contributions_via_salary_sacrifice_adjusted: 2_000 + salary_sacrifice_returned_to_income: 1_000 diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employer.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employer.yaml new file mode 100644 index 000000000..f4b9bd74f --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employer.yaml @@ -0,0 +1,88 @@ +- name: No employer NI charge when salary sacrifice below £2,000 cap + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 40_000 + pension_contributions_via_salary_sacrifice: 1_500 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 0 + output: + salary_sacrifice_pension_ni_employer: 0 + +- name: Employer NI charge at 15% for moderate earner above cap + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 45_000 + pension_contributions_via_salary_sacrifice: 2_250 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 0 + output: + salary_sacrifice_pension_ni_employer: (2_250 - 2_000) * 0.15 # £250 excess * 15% + +- name: Employer NI charge at 15% for high earner with large excess + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 125_000 + pension_contributions_via_salary_sacrifice: 25_000 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 0 + output: + salary_sacrifice_pension_ni_employer: (25_000 - 2_000) * 0.15 # £23,000 excess * 15% + +- name: Employer NI charge for large salary sacrifice + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 80_000 + pension_contributions_via_salary_sacrifice: 15_000 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 0 + output: + salary_sacrifice_pension_ni_employer: (15_000 - 2_000) * 0.15 # £13,000 excess * 15% + +- name: No employer NI charge when exactly at cap + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 60_000 + pension_contributions_via_salary_sacrifice: 2_000 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 0 + output: + salary_sacrifice_pension_ni_employer: 0 + +- name: Employer NI charge for small excess over cap + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 35_000 + pension_contributions_via_salary_sacrifice: 2_100 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 0 + output: + salary_sacrifice_pension_ni_employer: 100 * 0.15 # £100 excess * 15% + +- name: Employer NI charge for low earner with high pension contribution + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 30_000 + pension_contributions_via_salary_sacrifice: 5_000 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 0 + output: + salary_sacrifice_pension_ni_employer: (5_000 - 2_000) * 0.15 # £3,000 excess * 15% + +- name: No employer NI charge when employee optimizes (100% behavioral response) + period: 2025 + absolute_error_margin: 0.01 + input: + employment_income: 45_000 + pension_contributions_via_salary_sacrifice: 3_000 + gov.hmrc.national_insurance.salary_sacrifice_pension_cap: 2_000 + gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate: 1.0 + output: + salary_sacrifice_pension_ni_employer: 0 + pension_contributions_via_salary_sacrifice_adjusted: 2_000 diff --git a/policyengine_uk/variables/gov/hmrc/national_insurance/national_insurance.py b/policyengine_uk/variables/gov/hmrc/national_insurance/national_insurance.py index 429a27730..d91df2967 100644 --- a/policyengine_uk/variables/gov/hmrc/national_insurance/national_insurance.py +++ b/policyengine_uk/variables/gov/hmrc/national_insurance/national_insurance.py @@ -14,4 +14,5 @@ class national_insurance(Variable): "ni_class_2", "ni_class_3", "ni_class_4", + "salary_sacrifice_pension_ni_employee", ] diff --git a/policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employee.py b/policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employee.py new file mode 100644 index 000000000..8206d1736 --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employee.py @@ -0,0 +1,49 @@ +from policyengine_uk.model_api import * + + +class salary_sacrifice_pension_ni_employee(Variable): + label = "Employee NI on salary sacrifice pension contributions above cap" + documentation = ( + "Additional employee National Insurance contributions due to " + "salary sacrifice pension contributions exceeding the £2,000 cap. " + "The excess is subject to standard NI rates: the main rate for " + "earnings below the Upper Earnings Limit, and the additional rate " + "for earnings above it." + ) + entity = Person + definition_period = YEAR + value_type = float + unit = GBP + reference = "https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0" + + def formula(person, period, parameters): + # Use adjusted salary sacrifice after behavioral response + ss_contributions = person( + "pension_contributions_via_salary_sacrifice_adjusted", period + ) + cap = parameters( + period + ).gov.hmrc.national_insurance.salary_sacrifice_pension_cap + + # If cap is infinite, scheme is inactive (no charge) + if np.isinf(cap): + return 0 + + excess = max_(ss_contributions - cap, 0) + + # Use existing NI Class 1 parameters + ni_params = parameters(period).gov.hmrc.national_insurance.class_1 + employment_income = person("employment_income", period) + upper_earnings_limit = ( + ni_params.thresholds.upper_earnings_limit * WEEKS_IN_YEAR + ) + + # Apply appropriate NI rate based on income level + # Main rate (8%) for income ≤ UEL, additional rate (2%) for income > UEL + ni_rate = where( + employment_income <= upper_earnings_limit, + ni_params.rates.employee.main, + ni_params.rates.employee.additional, + ) + + return excess * ni_rate diff --git a/policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employer.py b/policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employer.py new file mode 100644 index 000000000..ec3c01bb8 --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employer.py @@ -0,0 +1,36 @@ +from policyengine_uk.model_api import * + + +class salary_sacrifice_pension_ni_employer(Variable): + label = "Employer NI on salary sacrifice pension contributions above cap" + documentation = ( + "Additional employer National Insurance contributions due to " + "salary sacrifice pension contributions exceeding the £2,000 cap. " + "The excess is subject to the standard employer NI rate of 15%." + ) + entity = Person + definition_period = YEAR + value_type = float + unit = GBP + reference = "https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0" + + def formula(person, period, parameters): + # Use adjusted salary sacrifice after behavioral response + ss_contributions = person( + "pension_contributions_via_salary_sacrifice_adjusted", period + ) + cap = parameters( + period + ).gov.hmrc.national_insurance.salary_sacrifice_pension_cap + + # If cap is infinite, scheme is inactive (no charge) + if np.isinf(cap): + return 0 + + excess = max_(ss_contributions - cap, 0) + + # Use existing NI Class 1 employer rate parameter + ni_params = parameters(period).gov.hmrc.national_insurance.class_1 + employer_rate = ni_params.rates.employer + + return excess * employer_rate diff --git a/policyengine_uk/variables/gov/hmrc/national_insurance/total_national_insurance.py b/policyengine_uk/variables/gov/hmrc/national_insurance/total_national_insurance.py index e97222431..668f2b89c 100644 --- a/policyengine_uk/variables/gov/hmrc/national_insurance/total_national_insurance.py +++ b/policyengine_uk/variables/gov/hmrc/national_insurance/total_national_insurance.py @@ -11,4 +11,5 @@ class total_national_insurance(Variable): adds = [ "national_insurance", "ni_class_1_employer", + "salary_sacrifice_pension_ni_employer", ] diff --git a/policyengine_uk/variables/gov/hmrc/pensions/pension_contributions_via_salary_sacrifice_adjusted.py b/policyengine_uk/variables/gov/hmrc/pensions/pension_contributions_via_salary_sacrifice_adjusted.py new file mode 100644 index 000000000..436ad36df --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/pensions/pension_contributions_via_salary_sacrifice_adjusted.py @@ -0,0 +1,43 @@ +from policyengine_uk.model_api import * + + +class pension_contributions_via_salary_sacrifice_adjusted(Variable): + label = "Adjusted salary sacrifice pension contributions after behavioral response" + documentation = ( + "The actual amount of salary sacrifice pension contributions after employees " + "adjust their behavior in response to the salary sacrifice cap. When the cap " + "is active and employees face NI charges on excess contributions, they may " + "reduce their salary sacrifice to the cap level. The reduction rate is " + "controlled by the employee_salary_sacrifice_reduction_rate parameter." + ) + entity = Person + definition_period = YEAR + value_type = float + unit = GBP + reference = "https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0" + + def formula(person, period, parameters): + intended_ss = person( + "pension_contributions_via_salary_sacrifice", period + ) + cap = parameters( + period + ).gov.hmrc.national_insurance.salary_sacrifice_pension_cap + + # If cap is infinite, no adjustment needed + if np.isinf(cap): + return intended_ss + + # Calculate excess above cap + excess = max_(intended_ss - cap, 0) + + # Get behavioral response rate + reduction_rate = parameters( + period + ).gov.contrib.behavioral_responses.employee_salary_sacrifice_reduction_rate + + # Amount employee reduces their salary sacrifice + reduction = excess * reduction_rate + + # Adjusted salary sacrifice + return intended_ss - reduction diff --git a/policyengine_uk/variables/gov/hmrc/pensions/salary_sacrifice_returned_to_income.py b/policyengine_uk/variables/gov/hmrc/pensions/salary_sacrifice_returned_to_income.py new file mode 100644 index 000000000..c287fa36b --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/pensions/salary_sacrifice_returned_to_income.py @@ -0,0 +1,26 @@ +from policyengine_uk.model_api import * + + +class salary_sacrifice_returned_to_income(Variable): + label = "Amount of salary sacrifice returned to employment income" + documentation = ( + "The amount of salary sacrifice that is returned to regular employment income " + "when employees reduce their salary sacrifice in response to the pension cap. " + "This amount becomes subject to regular income tax and NI as employment income." + ) + entity = Person + definition_period = YEAR + value_type = float + unit = GBP + reference = "https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0" + + def formula(person, period, parameters): + intended_ss = person( + "pension_contributions_via_salary_sacrifice", period + ) + adjusted_ss = person( + "pension_contributions_via_salary_sacrifice_adjusted", period + ) + + # The difference is returned to employment income + return intended_ss - adjusted_ss diff --git a/policyengine_uk/variables/input/consumption/property/pension_contributions_via_salary_sacrifice.py b/policyengine_uk/variables/input/consumption/property/pension_contributions_via_salary_sacrifice.py new file mode 100644 index 000000000..822a5f58e --- /dev/null +++ b/policyengine_uk/variables/input/consumption/property/pension_contributions_via_salary_sacrifice.py @@ -0,0 +1,16 @@ +from policyengine_uk.model_api import * + + +class pension_contributions_via_salary_sacrifice(Variable): + label = "Pension contributions via salary sacrifice" + documentation = ( + "Annual amount of pension contributions made through salary sacrifice " + "arrangements, where the employee agrees to reduce their gross salary " + "in exchange for employer pension contributions" + ) + entity = Person + definition_period = YEAR + value_type = float + unit = GBP + reference = "https://datacatalogue.ukdataservice.ac.uk/datasets/dataset/630d4a8d-ba6a-82b3-f33d-c713c66efcb3" + uprating = "gov.economic_assumptions.indices.obr.average_earnings" diff --git a/policyengine_uk/variables/input/employment_income.py b/policyengine_uk/variables/input/employment_income.py index aa5640520..ad6a2666f 100644 --- a/policyengine_uk/variables/input/employment_income.py +++ b/policyengine_uk/variables/input/employment_income.py @@ -14,5 +14,6 @@ class employment_income(Variable): "employment_income_before_lsr", "employment_income_behavioral_response", "employer_ni_fixed_employer_cost_change", + "salary_sacrifice_returned_to_income", ] uprating = "gov.economic_assumptions.indices.obr.average_earnings"