From b67dfbc881c2b9c8ce6c291be7f40078dcfd5fbd Mon Sep 17 00:00:00 2001 From: Vahid Ahmadi Date: Wed, 19 Nov 2025 12:45:45 +0100 Subject: [PATCH 1/5] Salary sacrifice pension modeling --- changelog_entry.yaml | 4 ++ .../employer_pension_reduction_rate.yaml | 17 +++++ ...alary_sacrifice_pension_cap_in_effect.yaml | 20 ++++++ .../salary_sacrifice_pension_cap.yaml | 10 +++ .../salary_sacrifice_pension_ni_employee.yaml | 62 +++++++++++++++++++ .../salary_sacrifice_pension_ni_employer.yaml | 62 +++++++++++++++++++ ...oyer_pension_contributions_after_ss_cap.py | 36 +++++++++++ .../employer_pension_reduction_from_ss_cap.py | 42 +++++++++++++ .../salary_sacrifice_pension_ni_employee.py | 52 ++++++++++++++++ .../salary_sacrifice_pension_ni_employer.py | 39 ++++++++++++ ...sion_contributions_via_salary_sacrifice.py | 16 +++++ 11 files changed, 360 insertions(+) create mode 100644 policyengine_uk/parameters/gov/contrib/behavioral_responses/employer_pension_reduction_rate.yaml create mode 100644 policyengine_uk/parameters/gov/contrib/salary_sacrifice_pension_cap_in_effect.yaml create mode 100644 policyengine_uk/parameters/gov/hmrc/national_insurance/salary_sacrifice_pension_cap.yaml create mode 100644 policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employee.yaml create mode 100644 policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employer.yaml create mode 100644 policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_contributions_after_ss_cap.py create mode 100644 policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_reduction_from_ss_cap.py create mode 100644 policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employee.py create mode 100644 policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employer.py create mode 100644 policyengine_uk/variables/input/consumption/property/pension_contributions_via_salary_sacrifice.py 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/employer_pension_reduction_rate.yaml b/policyengine_uk/parameters/gov/contrib/behavioral_responses/employer_pension_reduction_rate.yaml new file mode 100644 index 000000000..5fb51ec02 --- /dev/null +++ b/policyengine_uk/parameters/gov/contrib/behavioral_responses/employer_pension_reduction_rate.yaml @@ -0,0 +1,17 @@ +description: | + The percentage by which employers reduce their pension contributions in response + to the salary sacrifice pension cap NI charge. + - 0 = no reduction (employers absorb the cost) + - 0.5 = employers reduce contributions by 50% of the NI charge + - 1.0 = employers reduce contributions by 100% of the NI charge (full offset) + + Evidence suggests many employers would reduce contributions to partially or fully + offset the 15% NI charge they face on salary sacrifice amounts above £2,000. +values: + 2025-11-01: 0 # Default: no behavioral response (baseline scenario) +metadata: + unit: /1 + label: Employer pension contribution reduction rate in response to salary sacrifice cap + reference: + - title: "Cap on UK salary sacrifice benefits is 'short-term' choice, warn experts" + href: https://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95 diff --git a/policyengine_uk/parameters/gov/contrib/salary_sacrifice_pension_cap_in_effect.yaml b/policyengine_uk/parameters/gov/contrib/salary_sacrifice_pension_cap_in_effect.yaml new file mode 100644 index 000000000..71a374efa --- /dev/null +++ b/policyengine_uk/parameters/gov/contrib/salary_sacrifice_pension_cap_in_effect.yaml @@ -0,0 +1,20 @@ +description: | + Whether the salary sacrifice pension cap reform is in effect. + + When true (reform active): + - Salary sacrifice pension contributions above £2,000 are subject to employee and employer NI + - Employers may reduce pension contributions in response (see behavioral_responses.employer_pension_reduction_rate) + + When false (reform inactive): + - No cap on salary sacrifice pension contributions + - All salary sacrifice pension contributions remain exempt from NI (baseline/current law) + + This switch allows easy comparison between baseline and reform scenarios. +values: + 2010-01-01: false # Reform not in effect (baseline) +metadata: + unit: bool + label: Salary sacrifice pension cap reform in effect + reference: + - title: "Cap on UK salary sacrifice benefits is 'short-term' choice, warn experts" + href: https://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95 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..932ec937f --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/national_insurance/salary_sacrifice_pension_cap.yaml @@ -0,0 +1,10 @@ +description: The annual cap on salary sacrifice pension contributions that are exempt from employee and employer National Insurance contributions. Contributions above this cap are subject to standard NI rates. +values: + 2025-11-01: 2000 +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://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95 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..eae386ac4 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employee.yaml @@ -0,0 +1,62 @@ +- 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 + 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 + 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 + 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 + 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 + 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 + 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 + output: + salary_sacrifice_pension_ni_employee: 100 * 0.08 # £100 excess * 8% 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..ea2414740 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employer.yaml @@ -0,0 +1,62 @@ +- 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 + 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 + 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 + 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 + 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 + 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 + 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 + output: + salary_sacrifice_pension_ni_employer: (5_000 - 2_000) * 0.15 # £3,000 excess * 15% diff --git a/policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_contributions_after_ss_cap.py b/policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_contributions_after_ss_cap.py new file mode 100644 index 000000000..ad402c355 --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_contributions_after_ss_cap.py @@ -0,0 +1,36 @@ +from policyengine_uk.model_api import * + + +class employer_pension_contributions_after_ss_cap(Variable): + label = "Employer pension contributions after salary sacrifice cap behavioral response" + documentation = """ + Total employer pension contributions after accounting for the behavioral + response to the salary sacrifice pension cap. + + This equals: + - Original employer pension contributions + - MINUS: Reduction due to employers offsetting NI charges + + This variable models the real-world impact where employers reduce pension + contributions to offset the additional 15% NI charge they face on salary + sacrifice amounts exceeding £2,000. + """ + entity = Person + definition_period = YEAR + value_type = float + unit = GBP + reference = ( + "https://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95" + ) + + def formula(person, period): + # Original employer pension contributions + original_contributions = person( + "employer_pension_contributions", period + ) + + # Reduction due to behavioral response + reduction = person("employer_pension_reduction_from_ss_cap", period) + + # Net contributions after behavioral response + return max_(original_contributions - reduction, 0) diff --git a/policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_reduction_from_ss_cap.py b/policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_reduction_from_ss_cap.py new file mode 100644 index 000000000..f1c7341ec --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_reduction_from_ss_cap.py @@ -0,0 +1,42 @@ +from policyengine_uk.model_api import * + + +class employer_pension_reduction_from_ss_cap(Variable): + label = ( + "Employer pension contribution reduction due to salary sacrifice cap" + ) + documentation = """ + Behavioral response: Amount by which employers reduce their pension + contributions in response to the additional NI charge from the salary + sacrifice pension cap. + + This models the real-world behavior where employers offset the 15% NI + charge by reducing the pension contributions they make on behalf of employees. + + The reduction percentage is controlled by the + 'employer_pension_reduction_rate' parameter: + - 0% = employers absorb full cost (no reduction) + - 50% = employers reduce contributions by half the NI charge + - 100% = employers fully offset the NI charge + """ + entity = Person + definition_period = YEAR + value_type = float + unit = GBP + reference = ( + "https://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95" + ) + + def formula(person, period, parameters): + # Calculate the employer NI charge from salary sacrifice cap + employer_ni_charge = person( + "salary_sacrifice_pension_ni_employer", period + ) + + # Get the behavioral response rate parameter + reduction_rate = parameters( + period + ).gov.contrib.behavioral_responses.employer_pension_reduction_rate + + # Employers reduce pension contributions to offset NI costs + return employer_ni_charge * reduction_rate 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..d1e6b2e9c --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employee.py @@ -0,0 +1,52 @@ +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://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95" + ) + + def formula(person, period, parameters): + # Check if reform is in effect + reform_in_effect = parameters( + period + ).gov.contrib.salary_sacrifice_pension_cap_in_effect + if not reform_in_effect: + return 0 + + ss_contributions = person( + "pension_contributions_via_salary_sacrifice", period + ) + cap = parameters( + period + ).gov.hmrc.national_insurance.salary_sacrifice_pension_cap + 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..a938a447b --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employer.py @@ -0,0 +1,39 @@ +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://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95" + ) + + def formula(person, period, parameters): + # Check if reform is in effect + reform_in_effect = parameters( + period + ).gov.contrib.salary_sacrifice_pension_cap_in_effect + if not reform_in_effect: + return 0 + + ss_contributions = person( + "pension_contributions_via_salary_sacrifice", period + ) + cap = parameters( + period + ).gov.hmrc.national_insurance.salary_sacrifice_pension_cap + 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/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" From 7f81e4aa470df72560f4486a3d2084913f9c1c51 Mon Sep 17 00:00:00 2001 From: Vahid Ahmadi Date: Thu, 20 Nov 2025 11:31:55 +0100 Subject: [PATCH 2/5] edit logic --- ...loyee_salary_sacrifice_reduction_rate.yaml | 17 +++++++ .../employer_pension_reduction_rate.yaml | 17 ------- ...alary_sacrifice_pension_cap_in_effect.yaml | 20 --------- .../salary_sacrifice_pension_cap.yaml | 4 +- .../salary_sacrifice_pension_ni_employee.yaml | 27 +++++++++++ .../salary_sacrifice_pension_ni_employer.yaml | 26 +++++++++++ .../salary_sacrifice_pension_ni_employee.py | 15 +++---- .../salary_sacrifice_pension_ni_employer.py | 15 +++---- ...ributions_via_salary_sacrifice_adjusted.py | 45 +++++++++++++++++++ .../salary_sacrifice_returned_to_income.py | 28 ++++++++++++ 10 files changed, 159 insertions(+), 55 deletions(-) create mode 100644 policyengine_uk/parameters/gov/contrib/behavioral_responses/employee_salary_sacrifice_reduction_rate.yaml delete mode 100644 policyengine_uk/parameters/gov/contrib/behavioral_responses/employer_pension_reduction_rate.yaml delete mode 100644 policyengine_uk/parameters/gov/contrib/salary_sacrifice_pension_cap_in_effect.yaml create mode 100644 policyengine_uk/variables/gov/hmrc/pensions/pension_contributions_via_salary_sacrifice_adjusted.py create mode 100644 policyengine_uk/variables/gov/hmrc/pensions/salary_sacrifice_returned_to_income.py 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..8bd918264 --- /dev/null +++ b/policyengine_uk/parameters/gov/contrib/behavioral_responses/employee_salary_sacrifice_reduction_rate.yaml @@ -0,0 +1,17 @@ +description: | + The percentage by which employees reduce their salary sacrifice pension contributions + in response to the salary sacrifice pension 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. The excess returns to regular employment income. +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://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95 diff --git a/policyengine_uk/parameters/gov/contrib/behavioral_responses/employer_pension_reduction_rate.yaml b/policyengine_uk/parameters/gov/contrib/behavioral_responses/employer_pension_reduction_rate.yaml deleted file mode 100644 index 5fb51ec02..000000000 --- a/policyengine_uk/parameters/gov/contrib/behavioral_responses/employer_pension_reduction_rate.yaml +++ /dev/null @@ -1,17 +0,0 @@ -description: | - The percentage by which employers reduce their pension contributions in response - to the salary sacrifice pension cap NI charge. - - 0 = no reduction (employers absorb the cost) - - 0.5 = employers reduce contributions by 50% of the NI charge - - 1.0 = employers reduce contributions by 100% of the NI charge (full offset) - - Evidence suggests many employers would reduce contributions to partially or fully - offset the 15% NI charge they face on salary sacrifice amounts above £2,000. -values: - 2025-11-01: 0 # Default: no behavioral response (baseline scenario) -metadata: - unit: /1 - label: Employer pension contribution reduction rate in response to salary sacrifice cap - reference: - - title: "Cap on UK salary sacrifice benefits is 'short-term' choice, warn experts" - href: https://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95 diff --git a/policyengine_uk/parameters/gov/contrib/salary_sacrifice_pension_cap_in_effect.yaml b/policyengine_uk/parameters/gov/contrib/salary_sacrifice_pension_cap_in_effect.yaml deleted file mode 100644 index 71a374efa..000000000 --- a/policyengine_uk/parameters/gov/contrib/salary_sacrifice_pension_cap_in_effect.yaml +++ /dev/null @@ -1,20 +0,0 @@ -description: | - Whether the salary sacrifice pension cap reform is in effect. - - When true (reform active): - - Salary sacrifice pension contributions above £2,000 are subject to employee and employer NI - - Employers may reduce pension contributions in response (see behavioral_responses.employer_pension_reduction_rate) - - When false (reform inactive): - - No cap on salary sacrifice pension contributions - - All salary sacrifice pension contributions remain exempt from NI (baseline/current law) - - This switch allows easy comparison between baseline and reform scenarios. -values: - 2010-01-01: false # Reform not in effect (baseline) -metadata: - unit: bool - label: Salary sacrifice pension cap reform in effect - reference: - - title: "Cap on UK salary sacrifice benefits is 'short-term' choice, warn experts" - href: https://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95 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 index 932ec937f..13a0abf3a 100644 --- 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 @@ -1,6 +1,6 @@ -description: The annual cap on salary sacrifice pension contributions that are exempt from employee and employer National Insurance contributions. Contributions above this cap are subject to standard NI rates. +description: The annual cap on salary sacrifice pension contributions that are exempt from employee and employer National Insurance contributions. Contributions above this cap are subject to standard NI rates. When set to infinity (default), the scheme is inactive and all salary sacrifice pension contributions remain exempt from NI. values: - 2025-11-01: 2000 + 2010-01-01: .inf # Default: no cap (scheme inactive) metadata: unit: currency-GBP label: Salary sacrifice pension NI exemption cap 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 index eae386ac4..1b03d184f 100644 --- 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 @@ -4,6 +4,8 @@ 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 @@ -13,6 +15,8 @@ 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% @@ -22,6 +26,8 @@ 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% @@ -31,6 +37,8 @@ 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% @@ -40,6 +48,8 @@ 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% @@ -49,6 +59,8 @@ 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 @@ -58,5 +70,20 @@ 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 index ea2414740..f4b9bd74f 100644 --- 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 @@ -4,6 +4,8 @@ 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 @@ -13,6 +15,8 @@ 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% @@ -22,6 +26,8 @@ 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% @@ -31,6 +37,8 @@ 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% @@ -40,6 +48,8 @@ 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 @@ -49,6 +59,8 @@ 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% @@ -58,5 +70,19 @@ 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/salary_sacrifice_pension_ni_employee.py b/policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employee.py index d1e6b2e9c..e09329d6f 100644 --- 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 @@ -19,19 +19,18 @@ class salary_sacrifice_pension_ni_employee(Variable): ) def formula(person, period, parameters): - # Check if reform is in effect - reform_in_effect = parameters( - period - ).gov.contrib.salary_sacrifice_pension_cap_in_effect - if not reform_in_effect: - return 0 - + # Use adjusted salary sacrifice after behavioral response ss_contributions = person( - "pension_contributions_via_salary_sacrifice", period + "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 person("salary_sacrifice_pension_ni_employee", period) * 0 + excess = max_(ss_contributions - cap, 0) # Use existing NI Class 1 parameters 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 index a938a447b..778c69ef0 100644 --- 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 @@ -17,19 +17,18 @@ class salary_sacrifice_pension_ni_employer(Variable): ) def formula(person, period, parameters): - # Check if reform is in effect - reform_in_effect = parameters( - period - ).gov.contrib.salary_sacrifice_pension_cap_in_effect - if not reform_in_effect: - return 0 - + # Use adjusted salary sacrifice after behavioral response ss_contributions = person( - "pension_contributions_via_salary_sacrifice", period + "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 person("salary_sacrifice_pension_ni_employer", period) * 0 + excess = max_(ss_contributions - cap, 0) # Use existing NI Class 1 employer rate parameter 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..af989c48b --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/pensions/pension_contributions_via_salary_sacrifice_adjusted.py @@ -0,0 +1,45 @@ +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://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95" + ) + + 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..df7889b17 --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/pensions/salary_sacrifice_returned_to_income.py @@ -0,0 +1,28 @@ +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://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95" + ) + + 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 From c0406b26781502d5f8eeb86f99cdb64f2f0ada00 Mon Sep 17 00:00:00 2001 From: Vahid Ahmadi Date: Thu, 20 Nov 2025 11:55:50 +0100 Subject: [PATCH 3/5] edit logic --- ...loyee_salary_sacrifice_reduction_rate.yaml | 17 ++++---- .../salary_sacrifice_pension_cap.yaml | 7 +++- ...oyer_pension_contributions_after_ss_cap.py | 36 ---------------- .../employer_pension_reduction_from_ss_cap.py | 42 ------------------- .../salary_sacrifice_pension_ni_employee.py | 4 +- .../salary_sacrifice_pension_ni_employer.py | 4 +- ...ributions_via_salary_sacrifice_adjusted.py | 2 +- .../salary_sacrifice_returned_to_income.py | 2 +- .../variables/input/employment_income.py | 1 + 9 files changed, 19 insertions(+), 96 deletions(-) delete mode 100644 policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_contributions_after_ss_cap.py delete mode 100644 policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_reduction_from_ss_cap.py 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 index 8bd918264..481972821 100644 --- 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 @@ -1,12 +1,9 @@ -description: | - The percentage by which employees reduce their salary sacrifice pension contributions - in response to the salary sacrifice pension 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. The excess returns to regular employment income. +# 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: @@ -14,4 +11,4 @@ metadata: 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://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95 + 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 index 13a0abf3a..5fd871c64 100644 --- 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 @@ -1,4 +1,7 @@ -description: The annual cap on salary sacrifice pension contributions that are exempt from employee and employer National Insurance contributions. Contributions above this cap are subject to standard NI rates. When set to infinity (default), the scheme is inactive and all salary sacrifice pension contributions remain exempt from NI. +# 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: @@ -7,4 +10,4 @@ metadata: period: year reference: - title: "Cap on UK salary sacrifice benefits is 'short-term' choice, warn experts" - href: https://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95 + href: https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0 diff --git a/policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_contributions_after_ss_cap.py b/policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_contributions_after_ss_cap.py deleted file mode 100644 index ad402c355..000000000 --- a/policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_contributions_after_ss_cap.py +++ /dev/null @@ -1,36 +0,0 @@ -from policyengine_uk.model_api import * - - -class employer_pension_contributions_after_ss_cap(Variable): - label = "Employer pension contributions after salary sacrifice cap behavioral response" - documentation = """ - Total employer pension contributions after accounting for the behavioral - response to the salary sacrifice pension cap. - - This equals: - - Original employer pension contributions - - MINUS: Reduction due to employers offsetting NI charges - - This variable models the real-world impact where employers reduce pension - contributions to offset the additional 15% NI charge they face on salary - sacrifice amounts exceeding £2,000. - """ - entity = Person - definition_period = YEAR - value_type = float - unit = GBP - reference = ( - "https://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95" - ) - - def formula(person, period): - # Original employer pension contributions - original_contributions = person( - "employer_pension_contributions", period - ) - - # Reduction due to behavioral response - reduction = person("employer_pension_reduction_from_ss_cap", period) - - # Net contributions after behavioral response - return max_(original_contributions - reduction, 0) diff --git a/policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_reduction_from_ss_cap.py b/policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_reduction_from_ss_cap.py deleted file mode 100644 index f1c7341ec..000000000 --- a/policyengine_uk/variables/gov/hmrc/national_insurance/employer_pension_reduction_from_ss_cap.py +++ /dev/null @@ -1,42 +0,0 @@ -from policyengine_uk.model_api import * - - -class employer_pension_reduction_from_ss_cap(Variable): - label = ( - "Employer pension contribution reduction due to salary sacrifice cap" - ) - documentation = """ - Behavioral response: Amount by which employers reduce their pension - contributions in response to the additional NI charge from the salary - sacrifice pension cap. - - This models the real-world behavior where employers offset the 15% NI - charge by reducing the pension contributions they make on behalf of employees. - - The reduction percentage is controlled by the - 'employer_pension_reduction_rate' parameter: - - 0% = employers absorb full cost (no reduction) - - 50% = employers reduce contributions by half the NI charge - - 100% = employers fully offset the NI charge - """ - entity = Person - definition_period = YEAR - value_type = float - unit = GBP - reference = ( - "https://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95" - ) - - def formula(person, period, parameters): - # Calculate the employer NI charge from salary sacrifice cap - employer_ni_charge = person( - "salary_sacrifice_pension_ni_employer", period - ) - - # Get the behavioral response rate parameter - reduction_rate = parameters( - period - ).gov.contrib.behavioral_responses.employer_pension_reduction_rate - - # Employers reduce pension contributions to offset NI costs - return employer_ni_charge * reduction_rate 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 index e09329d6f..2b24af9b2 100644 --- 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 @@ -15,7 +15,7 @@ class salary_sacrifice_pension_ni_employee(Variable): value_type = float unit = GBP reference = ( - "https://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95" + "https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0" ) def formula(person, period, parameters): @@ -29,7 +29,7 @@ def formula(person, period, parameters): # If cap is infinite, scheme is inactive (no charge) if np.isinf(cap): - return person("salary_sacrifice_pension_ni_employee", period) * 0 + return 0 excess = max_(ss_contributions - cap, 0) 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 index 778c69ef0..f0e7e6c84 100644 --- 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 @@ -13,7 +13,7 @@ class salary_sacrifice_pension_ni_employer(Variable): value_type = float unit = GBP reference = ( - "https://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95" + "https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0" ) def formula(person, period, parameters): @@ -27,7 +27,7 @@ def formula(person, period, parameters): # If cap is infinite, scheme is inactive (no charge) if np.isinf(cap): - return person("salary_sacrifice_pension_ni_employer", period) * 0 + return 0 excess = max_(ss_contributions - cap, 0) 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 index af989c48b..adf5c7596 100644 --- 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 @@ -15,7 +15,7 @@ class pension_contributions_via_salary_sacrifice_adjusted(Variable): value_type = float unit = GBP reference = ( - "https://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95" + "https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0" ) def formula(person, period, parameters): 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 index df7889b17..a73e97094 100644 --- 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 @@ -13,7 +13,7 @@ class salary_sacrifice_returned_to_income(Variable): value_type = float unit = GBP reference = ( - "https://www.ft.com/content/11602ac1-44fc-4b58-8b17-af5e851f5c95" + "https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0" ) def formula(person, period, parameters): 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" From 508eab2f0c76806530f2326395b54759e25464f2 Mon Sep 17 00:00:00 2001 From: Vahid Ahmadi Date: Thu, 20 Nov 2025 11:57:29 +0100 Subject: [PATCH 4/5] format --- .../salary_sacrifice_pension_ni_employee.py | 4 +--- .../salary_sacrifice_pension_ni_employer.py | 4 +--- .../pension_contributions_via_salary_sacrifice_adjusted.py | 4 +--- .../gov/hmrc/pensions/salary_sacrifice_returned_to_income.py | 4 +--- 4 files changed, 4 insertions(+), 12 deletions(-) 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 index 2b24af9b2..8206d1736 100644 --- 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 @@ -14,9 +14,7 @@ class salary_sacrifice_pension_ni_employee(Variable): definition_period = YEAR value_type = float unit = GBP - reference = ( - "https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0" - ) + 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 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 index f0e7e6c84..ec3c01bb8 100644 --- 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 @@ -12,9 +12,7 @@ class salary_sacrifice_pension_ni_employer(Variable): definition_period = YEAR value_type = float unit = GBP - reference = ( - "https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0" - ) + 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 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 index adf5c7596..436ad36df 100644 --- 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 @@ -14,9 +14,7 @@ class pension_contributions_via_salary_sacrifice_adjusted(Variable): definition_period = YEAR value_type = float unit = GBP - reference = ( - "https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0" - ) + reference = "https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0" def formula(person, period, parameters): intended_ss = person( 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 index a73e97094..c287fa36b 100644 --- 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 @@ -12,9 +12,7 @@ class salary_sacrifice_returned_to_income(Variable): definition_period = YEAR value_type = float unit = GBP - reference = ( - "https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0" - ) + reference = "https://docs.google.com/document/d/1Rhrfrg7A_oZHudmA775otAn1EE4-YthgeyS9nL-PrE8/edit?tab=t.0" def formula(person, period, parameters): intended_ss = person( From 0228e7f11ac0c62f173370b9345ee56f42fee99e Mon Sep 17 00:00:00 2001 From: Vahid Ahmadi Date: Thu, 20 Nov 2025 12:06:55 +0100 Subject: [PATCH 5/5] veriable tree --- .../variables/gov/hmrc/national_insurance/national_insurance.py | 1 + .../gov/hmrc/national_insurance/total_national_insurance.py | 1 + 2 files changed, 2 insertions(+) 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/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", ]