From 3a195c3920f642848befa4429e423d991ac818be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Juaristi?= <127882282+juaristi22@users.noreply.github.com> Date: Sat, 17 Oct 2026 19:13:47 +0100 Subject: [PATCH 1/2] Restrict ordinary ATI pension uplift to relief at source --- changelog.d/2486.fixed.md | 1 + .../adjusted_total_income_pension_relief.yaml | 426 ++++++++++++++++++ .../reliefs/adjusted_total_income.py | 26 +- 3 files changed, 447 insertions(+), 6 deletions(-) create mode 100644 changelog.d/2486.fixed.md create mode 100644 policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/reliefs/adjusted_total_income_pension_relief.yaml diff --git a/changelog.d/2486.fixed.md b/changelog.d/2486.fixed.md new file mode 100644 index 0000000000..18f02a0b54 --- /dev/null +++ b/changelog.d/2486.fixed.md @@ -0,0 +1 @@ +- Limited the ordinary income-tax loss-cap pension uplift above earnings to relief-at-source contributions. diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/reliefs/adjusted_total_income_pension_relief.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/reliefs/adjusted_total_income_pension_relief.yaml new file mode 100644 index 0000000000..8760447c9f --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/reliefs/adjusted_total_income_pension_relief.yaml @@ -0,0 +1,426 @@ +# Ordinary loss-cap ATI (ITA 2007 s.24A(8)), preserving the model's own earnings base. +# FA 2004 ss.190(3),191(7): only relief at source can give the basic uplift above +# relevant earnings. Existing inputs treat gross personal contributions as RAS +# and reported employee contributions as net pay; no new scheme-input model. +# Primary 2026 control: wages2,000 + dividends250,000 - relievednetpay2,000 +# => ATI250,000; cap62,500; ANI187,500. All rates/thresholds are unchanged +# model controls, not a new rate audit. Final dividend tax is: +# (37,700-500)*.1075 + (125,140-37,700)*.3575 + (187,500-125,140)*.3935 +# =59,797.46. No eligibility, tax base, relief or final tax is supplied as input. + +- name: Net pay within earnings preserves the final tax control + period: 2026 + absolute_error_margin: 0.01 + input: + people: + taxpayer: + age: 45 + employment_income_before_lsr: 2000 + dividend_income: 250000 + trading_loss: 200000 + employee_pension_contributions_reported: 2000 + personal_pension_contributions: 0 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + benunits: + family: + members: + - taxpayer + households: + home: + members: + - taxpayer + region: LONDON + output: &id001 + income_tax: 59797.46 + employment_income: 2000 + pension_contributions_relief: 2000 + adjusted_total_income: 250000 + income_tax_relief_cap: 62500 + trade_loss_relief_against_general_income: 62500 + net_income: 189500 + adjusted_net_income: 187500 + personal_allowance: 0 + personal_pension_contributions_tax: 0 +- name: Pure net pay above earnings cannot use the RAS basic uplift + period: 2026 + absolute_error_margin: 0.01 + input: + people: + taxpayer: + age: 45 + employment_income_before_lsr: 2000 + dividend_income: 250000 + trading_loss: 200000 + employee_pension_contributions_reported: 3600 + personal_pension_contributions: 0 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + benunits: + family: + members: + - taxpayer + households: + home: + members: + - taxpayer + region: LONDON + output: *id001 +- name: Mixed payments limit the basic uplift to actual gross RAS + period: 2026 + absolute_error_margin: 0.01 + input: + people: + taxpayer: + age: 45 + employment_income_before_lsr: 2000 + dividend_income: 250000 + trading_loss: 200000 + employee_pension_contributions_reported: 3000 + personal_pension_contributions: 600 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + benunits: + family: + members: + - taxpayer + households: + home: + members: + - taxpayer + region: LONDON + output: + adjusted_total_income: 249400 + income_tax_relief_cap: 62350 + pension_contributions_relief: 2600 +- name: No member contribution leaves adjusted total income unchanged + period: 2026 + absolute_error_margin: 0.01 + input: + people: + taxpayer: + age: 45 + employment_income_before_lsr: 2000 + dividend_income: 250000 + trading_loss: 200000 + employee_pension_contributions_reported: 0 + personal_pension_contributions: 0 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + benunits: + family: + members: + - taxpayer + households: + home: + members: + - taxpayer + region: LONDON + output: + adjusted_total_income: 252000 + income_tax_relief_cap: 63000 +- name: Relief cannot exceed the member contributions actually paid + period: 2026 + absolute_error_margin: 0.01 + input: + people: + taxpayer: + age: 45 + employment_income_before_lsr: 2000 + dividend_income: 250000 + trading_loss: 200000 + employee_pension_contributions_reported: 1000 + personal_pension_contributions: 0 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + benunits: + family: + members: + - taxpayer + households: + home: + members: + - taxpayer + region: LONDON + output: + adjusted_total_income: 251000 + pension_contributions_relief: 1000 +- name: Pure RAS retains relief up to the basic amount + period: 2026 + absolute_error_margin: 0.01 + input: + people: + taxpayer: + age: 45 + employment_income_before_lsr: 2000 + dividend_income: 250000 + trading_loss: 200000 + employee_pension_contributions_reported: 0 + personal_pension_contributions: 3600 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + benunits: + family: + members: + - taxpayer + households: + home: + members: + - taxpayer + region: LONDON + output: + adjusted_total_income: 248400 + income_tax_relief_cap: 62100 + pension_contributions_relief: 3600 +- name: Mixed contributions below earnings remain fully relieved + period: 2026 + absolute_error_margin: 0.01 + input: + people: + taxpayer: + age: 45 + employment_income_before_lsr: 2000 + dividend_income: 250000 + trading_loss: 200000 + employee_pension_contributions_reported: 1000 + personal_pension_contributions: 600 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + benunits: + family: + members: + - taxpayer + households: + home: + members: + - taxpayer + region: LONDON + output: + adjusted_total_income: 250400 + pension_contributions_relief: 1600 +- name: The age 75 boundary excludes member relief + period: 2026 + absolute_error_margin: 0.01 + input: + people: + taxpayer: + age: 75 + employment_income_before_lsr: 2000 + dividend_income: 250000 + trading_loss: 200000 + employee_pension_contributions_reported: 3600 + personal_pension_contributions: 0 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + state_pension_reported: 0 + benunits: + family: + members: + - taxpayer + households: + home: + members: + - taxpayer + region: LONDON + output: + adjusted_total_income: 252000 + pension_contributions_relief: 0 +- name: Earnings above the basic amount retain ordinary net pay relief + period: 2026 + absolute_error_margin: 0.01 + input: + people: + taxpayer: + age: 45 + employment_income_before_lsr: 10000 + dividend_income: 250000 + trading_loss: 200000 + employee_pension_contributions_reported: 5000 + personal_pension_contributions: 0 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + benunits: + family: + members: + - taxpayer + households: + home: + members: + - taxpayer + region: LONDON + output: + adjusted_total_income: 255000 + pension_contributions_relief: 5000 +- name: ATI retains statutory pay in its own relevant earnings base + period: 2026 + absolute_error_margin: 0.01 + input: + people: + taxpayer: + age: 45 + employment_income_before_lsr: 1000 + dividend_income: 250000 + trading_loss: 200000 + employee_pension_contributions_reported: 0 + personal_pension_contributions: 4000 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + statutory_maternity_pay: 3000 + benunits: + family: + members: + - taxpayer + households: + home: + members: + - taxpayer + region: LONDON + output: + adjusted_total_income: 250000 + pension_contributions_relief: 3600 +- name: The basic amount remains reformable for actual RAS contributions + period: 2026 + absolute_error_margin: 0.01 + input: + people: + taxpayer: + age: 45 + employment_income_before_lsr: 2000 + dividend_income: 250000 + trading_loss: 200000 + employee_pension_contributions_reported: 0 + personal_pension_contributions: 6000 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + benunits: + family: + members: + - taxpayer + households: + home: + members: + - taxpayer + region: LONDON + gov.hmrc.income_tax.reliefs.pension_contribution.basic_amount: 5000 + output: + adjusted_total_income: 247000 + pension_contributions_relief: 5000 +- name: Relieved RAS cannot make adjusted total income negative + period: 2026 + absolute_error_margin: 0.01 + input: + people: + taxpayer: + age: 45 + employment_income_before_lsr: 0 + dividend_income: 0 + trading_loss: 0 + employee_pension_contributions_reported: 0 + personal_pension_contributions: 3600 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + benunits: + family: + members: + - taxpayer + households: + home: + members: + - taxpayer + region: LONDON + output: + adjusted_total_income: 0 + income_tax: 0 +- name: Each person keeps their own RAS and age limit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + p0: + age: 45 + employment_income_before_lsr: 2000 + dividend_income: 250000 + trading_loss: 200000 + employee_pension_contributions_reported: 3600 + personal_pension_contributions: 0 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + p1: + age: 45 + employment_income_before_lsr: 2000 + dividend_income: 250000 + trading_loss: 200000 + employee_pension_contributions_reported: 3000 + personal_pension_contributions: 600 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + p2: + age: 75 + employment_income_before_lsr: 2000 + dividend_income: 250000 + trading_loss: 200000 + employee_pension_contributions_reported: 3600 + personal_pension_contributions: 0 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + state_pension_reported: 0 + benunits: + b0: + members: + - p0 + b1: + members: + - p1 + b2: + members: + - p2 + households: + h0: + members: + - p0 + region: LONDON + h1: + members: + - p1 + region: LONDON + h2: + members: + - p2 + region: LONDON + output: + adjusted_total_income: + - 250000 + - 249400 + - 252000 + pension_contributions_relief: + - 2000 + - 2600 + - 0 +- name: ATI first and final tax first give the same corrected cash + period: 2026 + absolute_error_margin: 0.01 + input: + people: + taxpayer: + age: 45 + employment_income_before_lsr: 2000 + dividend_income: 250000 + trading_loss: 200000 + employee_pension_contributions_reported: 3600 + personal_pension_contributions: 0 + pension_contributions_via_salary_sacrifice: 0 + employer_pension_contributions: 0 + benunits: + family: + members: + - taxpayer + households: + home: + members: + - taxpayer + region: LONDON + output: + adjusted_total_income: 250000 + income_tax_relief_cap: 62500 + adjusted_net_income: 187500 + income_tax: 59797.46 diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/reliefs/adjusted_total_income.py b/policyengine_uk/variables/gov/hmrc/income_tax/reliefs/adjusted_total_income.py index 959562f123..810a99cb83 100644 --- a/policyengine_uk/variables/gov/hmrc/income_tax/reliefs/adjusted_total_income.py +++ b/policyengine_uk/variables/gov/hmrc/income_tax/reliefs/adjusted_total_income.py @@ -11,10 +11,12 @@ class adjusted_total_income(Variable): "components, with losses brought forward (loss_relief, a Step 2 " "relief the model deducts from trading profits) added back. Steps 3 " "and 4 deduct the pension contributions given relief (FA 2004 " - "ss.192-194): the person's own contributions up to the greater of " - "their relevant UK earnings (s.189(2): taxable employment income, " - "including benefits and statutory pay, and trading profits) and the " - "basic amount (s.190), none from " + "ss.192-194): the person's own contributions up to relevant UK " + "earnings (s.189(2): taxable employment income, including benefits " + "and statutory pay, and trading profits). The basic amount can " + "increase that limit only through relief at source (ss.190(3), " + "191(7)), which the model represents with gross personal pension " + "contributions; employee contributions represent net pay. None is relieved from " "age 75 (s.188(3)(a)). The annual allowance limits tax-relieved " "saving through a charge, not the relief itself, so it is not applied " "here. Step 2 adds back payroll giving, which the model does not have." @@ -30,6 +32,10 @@ class adjusted_total_income(Variable): title="Finance Act 2004 ss. 188-190", href="https://www.legislation.gov.uk/ukpga/2004/12/section/190", ), + dict( + title="Finance Act 2004 s. 191(7)", + href="https://www.legislation.gov.uk/ukpga/2004/12/section/191", + ), ] def formula(person, period, parameters): @@ -56,9 +62,17 @@ def formula(person, period, parameters): relevant_earnings = ( person("taxable_employment_income", period) + profits_before_carry_forward ) - relief_limit = max_( - p.income_tax.reliefs.pension_contribution.basic_amount, relevant_earnings + # FA 2004 ss.190(3),191(7): only actual relief-at-source contributions + # can use the basic-amount increase above relevant UK earnings. + basic_amount_increase = min_( + max_( + 0, + p.income_tax.reliefs.pension_contribution.basic_amount + - relevant_earnings, + ), + person("personal_pension_contributions", period), ) + relief_limit = relevant_earnings + basic_amount_increase under_age_limit = ( person("age", period) < p.pensions.pension_contributions_relief_age_limit ) From 818e96662607bfccf3c6516f3161808fb322d0d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Juaristi?= <127882282+juaristi22@users.noreply.github.com> Date: Tue, 20 Oct 2026 07:03:11 +0100 Subject: [PATCH 2/2] Fix statutory-pay pension relief fixture after main integration --- .../reliefs/adjusted_total_income_pension_relief.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/reliefs/adjusted_total_income_pension_relief.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/reliefs/adjusted_total_income_pension_relief.yaml index 8760447c9f..6c9ad649c2 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/reliefs/adjusted_total_income_pension_relief.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/reliefs/adjusted_total_income_pension_relief.yaml @@ -277,7 +277,7 @@ region: LONDON output: adjusted_total_income: 250000 - pension_contributions_relief: 3600 + pension_contributions_relief: 4000 - name: The basic amount remains reformable for actual RAS contributions period: 2026 absolute_error_margin: 0.01