From 6c060602a8dd0c5efc8091bb144133f7cc0d1e4b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 23 May 2026 08:00:49 -0400 Subject: [PATCH] Fix PolicyBench audit issues --- changelog.d/1681.md | 1 + .../guarantee_credit/income.yaml | 1 + .../savings_starter_rate_income.yaml | 14 +++- .../is_pension_credit_eligible.yaml | 77 ++++++++++++++++--- .../pension_credit/pension_credit_income.yaml | 22 ++++++ .../capital_gains_tax/capital_gains_tax.yaml | 56 ++++++++++++-- .../charges/child_benefit_hitc.yaml | 23 ++++++ .../tests/policy/integration/income.yaml | 74 +++++++++++++++++- .../is_pension_credit_eligible.py | 12 ++- .../capital_gains_tax/capital_gains_tax.py | 24 +++++- .../add_rate_savings_income.py | 24 +++--- .../basic_rate_savings_income.py | 28 +++---- .../basic_rate_savings_income_pre_starter.py | 8 +- .../higher_rate_savings_income.py | 28 +++---- .../savings_starter_rate_income.py | 14 +++- .../liability/dividend_income_tax.py | 26 +++++-- .../liability/property_income_tax.py | 21 +++-- 17 files changed, 367 insertions(+), 86 deletions(-) create mode 100644 changelog.d/1681.md diff --git a/changelog.d/1681.md b/changelog.d/1681.md new file mode 100644 index 000000000..9cb01cec7 --- /dev/null +++ b/changelog.d/1681.md @@ -0,0 +1 @@ +- Fixed PolicyBench audit issues in Pension Credit, Capital Gains Tax, and income-tax source ordering, including mixed-age Pension Credit eligibility, property income in Pension Credit, CGT rate-band allocation, savings/dividend ordering, HICBC regression coverage, and property income-tax banding. diff --git a/policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/income.yaml b/policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/income.yaml index 818717172..fd57b6d80 100644 --- a/policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/income.yaml +++ b/policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/income.yaml @@ -14,6 +14,7 @@ values: - working_tax_credit - state_pension - private_pension_income + - property_income - pension_credit_deemed_income - esa_contrib - jsa_contrib diff --git a/policyengine_uk/tests/policy/baseline/finance/tax/income_tax/savings_starter_rate_income.yaml b/policyengine_uk/tests/policy/baseline/finance/tax/income_tax/savings_starter_rate_income.yaml index c2eb08bdc..65bd9f68b 100644 --- a/policyengine_uk/tests/policy/baseline/finance/tax/income_tax/savings_starter_rate_income.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/tax/income_tax/savings_starter_rate_income.yaml @@ -2,6 +2,8 @@ period: 2024 input: basic_rate_savings_income_pre_starter: 5_000 + taxable_savings_interest_income: 6_000 + received_allowances_savings_income: 0 earned_taxable_income: 15_000 taxable_dividend_income: 0 output: @@ -11,6 +13,8 @@ period: 2024 input: basic_rate_savings_income_pre_starter: 4_000 + taxable_savings_interest_income: 5_000 + received_allowances_savings_income: 0 earned_taxable_income: 19_000 taxable_dividend_income: 0 output: @@ -20,7 +24,9 @@ period: 2024 input: basic_rate_savings_income_pre_starter: 2_000 - earned_taxable_income: 22_000 + taxable_savings_interest_income: 3_000 + received_allowances_savings_income: 0 + earned_taxable_income: 23_000 taxable_dividend_income: 1_000 output: savings_starter_rate_income: 0 @@ -29,6 +35,8 @@ period: 2024 input: basic_rate_savings_income_pre_starter: 10_000 + taxable_savings_interest_income: 11_000 + received_allowances_savings_income: 0 earned_taxable_income: 16_000 taxable_dividend_income: 0 output: @@ -38,7 +46,9 @@ period: 2024 input: basic_rate_savings_income_pre_starter: 5_000 + taxable_savings_interest_income: 6_000 + received_allowances_savings_income: 0 earned_taxable_income: 16_000 taxable_dividend_income: 2_000 output: - savings_starter_rate_income: 4_570 \ No newline at end of file + savings_starter_rate_income: 5_000 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/is_pension_credit_eligible.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/is_pension_credit_eligible.yaml index ba1e93fe6..d2fa9efda 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/is_pension_credit_eligible.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/is_pension_credit_eligible.yaml @@ -1,22 +1,77 @@ -- name: Not pension-age, ineligible for Pension Credit (single) - period: 2022 +- name: Case 1, single adult below State Pension age is ineligible. + period: 2026 input: people: - person: - is_SP_age: false + person1: + age: 54 + benunits: + benunit: + members: [person1] + output: + is_pension_credit_eligible: false + +- name: Case 2, single adult above State Pension age is eligible. + period: 2026 + input: + people: + person1: + age: 79 + benunits: + benunit: + members: [person1] + output: + is_pension_credit_eligible: true + +- name: Case 3, couple with both adults above State Pension age is eligible. + period: 2026 + input: + people: + person1: + age: 79 + person2: + age: 70 + benunits: + benunit: + members: [person1, person2] + output: + is_pension_credit_eligible: true + +- name: Case 4, mixed-age couple without protected exception is ineligible. + period: 2026 + input: + people: + person1: + age: 79 + person2: + age: 54 + benunits: + benunit: + members: [person1, person2] output: is_pension_credit_eligible: false -- name: One member of a couple is pension age; eligible for Pension Credit - period: 2022 +- name: Case 5, child in benefit unit does not block pension-age adult eligibility. + period: 2026 input: people: - person: - is_SP_age: false - spouse: - is_SP_age: true + person1: + age: 79 + person2: + age: 10 benunits: benunit: - members: [person, spouse] + members: [person1, person2] output: is_pension_credit_eligible: true + +- name: Case 6, child-only benefit unit is ineligible. + period: 2026 + input: + people: + person1: + age: 10 + benunits: + benunit: + members: [person1] + output: + is_pension_credit_eligible: false diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_income.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_income.yaml index 956f4044e..f67f2deb8 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_income.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_income.yaml @@ -58,3 +58,25 @@ pension_credit_assessable_capital: 10_001 pension_credit_deemed_income: 52 pension_credit_income: 52 + +- name: Case 4, property income and non-main-home capital count for Pension Credit. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 79 + property_income: 10_476 + benunits: + benunit: + members: [person1] + households: + household: + members: [person1] + other_residential_property_value: 55_370 + savings: 455 + output: + pension_credit_assessable_capital: 55_825 + pension_credit_deemed_income: 4_784 + pension_credit_income: 15_260 + pension_credit: 0 diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/capital_gains_tax/capital_gains_tax.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/capital_gains_tax/capital_gains_tax.yaml index 3640d97a5..4aeef64f8 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/capital_gains_tax/capital_gains_tax.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/capital_gains_tax/capital_gains_tax.yaml @@ -28,7 +28,7 @@ adjusted_net_income: 20_000 capital_gains: 23_000 output: - capital_gains_tax: 2_230 + capital_gains_tax: 2_000 - name: Higher rate capital gains tax period: 2024 @@ -36,7 +36,7 @@ adjusted_net_income: 50_000 capital_gains: 50_000 output: - capital_gains_tax: 9_400 + capital_gains_tax: 9_373 - name: Additional rate capital gains tax period: 2024 @@ -52,7 +52,7 @@ adjusted_net_income: 35_000 capital_gains: 40_000 output: - capital_gains_tax: 7_130 + capital_gains_tax: 5_873 - name: High capital gains, low income period: 2024 @@ -60,7 +60,7 @@ adjusted_net_income: 10_000 capital_gains: 200_000 output: - capital_gains_tax: 36_630 + capital_gains_tax: 35_630 - name: Very high capital gains and income period: 2024 @@ -76,4 +76,50 @@ adjusted_net_income: 0 capital_gains: 3_000 output: - capital_gains_tax: 0 \ No newline at end of file + capital_gains_tax: 0 + +- name: Case 11, two adults each receive the annual exempt amount. + period: 2026 + input: + people: + person1: + capital_gains: 151_800 + person2: + capital_gains: 151_800 + benunit: + members: [person1, person2] + output: + capital_gains_tax: [33_450, 33_450] + +- name: Case 12, ordinary income below allowances leaves the full basic rate band for gains. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + adjusted_net_income: 9_324 + capital_gains: 228_890 + person2: + capital_gains: -124_972 + benunit: + members: [person1, person2] + output: + capital_gains_tax: [51_951.60, 0] + +- name: Case 13, Gift Aid extends the basic rate band for capital gains. + period: 2018 + input: + adjusted_net_income: 41_850 + gift_aid: 40_000 + capital_gains: 70_000 + output: + capital_gains_tax: 6_210 + +- name: Case 14, personal pension contributions extend the basic rate band for capital gains. + period: 2026 + input: + employment_income: 52_570 + personal_pension_contributions: 10_000 + capital_gains: 30_000 + output: + capital_gains_tax: 6_018 diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/charges/child_benefit_hitc.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/charges/child_benefit_hitc.yaml index 72696eb37..c4b687694 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/charges/child_benefit_hitc.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/charges/child_benefit_hitc.yaml @@ -27,3 +27,26 @@ employment_income: 60_000 output: CB_HITC: 1_000 + +- name: Case 5, high-income family pays one full Child Benefit charge. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + self_employment_income: 113_850 + person2: + age: 38 + employment_income: 35_000 + person3: + age: 8 + person4: + age: 5 + benunit: + members: [person1, person2, person3, person4] + output: + child_benefit: 2_328.16 + CB_HITC: [2_328.16, 0, 0, 0] + income_tax_pre_charges: [35_142, 4_486, 0, 0] + income_tax: [37_470.16, 4_486, 0, 0] diff --git a/policyengine_uk/tests/policy/integration/income.yaml b/policyengine_uk/tests/policy/integration/income.yaml index 56efd77d3..dc8b7ea72 100644 --- a/policyengine_uk/tests/policy/integration/income.yaml +++ b/policyengine_uk/tests/policy/integration/income.yaml @@ -22,9 +22,9 @@ output: basic_rate_savings_income_pre_starter: 10_000 savings_starter_rate_income: 0 - basic_rate_savings_income: 10_000 + basic_rate_savings_income: 9_500 savings_allowance: 500 - higher_rate_savings_income: 4_500 + higher_rate_savings_income: 5_000 taxed_savings_income: 14_500 - name: Dividends taxed at the marginal rates @@ -35,7 +35,73 @@ # Nullify Personal Allowance for testing purposes gov.hmrc.income_tax.allowances.personal_allowance.amount: 0 output: - dividend_income_tax: 6_225 + dividend_income_tax: 6_725 + +- name: Savings and dividend allowances occupy tax bands before taxable income. + period: 2027 + absolute_error_margin: 0.01 + input: + employment_income: 50_000 + savings_interest_income: 1_000 + dividend_income: 1_000 + output: + earned_taxable_income: 37_430 + savings_allowance: 500 + basic_rate_savings_income: 0 + higher_rate_savings_income: 500 + savings_income_tax: 210 + taxed_dividend_income: 500 + dividend_income_tax: 178.75 + income_tax: 7_874.75 + +- name: Personal allowance caps savings starter rate before dividends. + period: 2027 + absolute_error_margin: 0.01 + input: + employment_income: 10_000 + savings_interest_income: 5_000 + dividend_income: 5_000 + output: + received_allowances_savings_income: 2_570 + basic_rate_savings_income_pre_starter: 2_430 + savings_starter_rate_income: 2_430 + savings_allowance: 1_000 + savings_income_tax: 0 + taxed_dividend_income: 4_500 + dividend_income_tax: 483.75 + income_tax: 483.75 + +- name: Starter-rate savings applies before personal savings allowance. + period: 2027 + absolute_error_margin: 0.01 + input: + employment_income: 12_570 + savings_interest_income: 5_500 + output: + basic_rate_savings_income_pre_starter: 5_500 + savings_starter_rate_income: 5_000 + savings_allowance: 1_000 + basic_rate_savings_income: 0 + savings_income_tax: 0 + income_tax: 0 + +- name: Property income uses post-allowance rate bands. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + taxable_property_income: 75_900 + savings_interest_income: 2_900 + person2: + taxable_property_income: 46_451 + savings_interest_income: 1_525 + benunit: + members: [person1, person2] + output: + property_income_tax: [17_792, 6_776.20] + savings_income_tax: [0, 0] + income_tax: [17_792, 6_776.20] - name: Household net income can be negative period: 2020 @@ -45,4 +111,4 @@ household_tax: 2 household_benefits: 0 output: - household_net_income: -1 \ No newline at end of file + household_net_income: -1 diff --git a/policyengine_uk/variables/gov/dwp/pension_credit/is_pension_credit_eligible.py b/policyengine_uk/variables/gov/dwp/pension_credit/is_pension_credit_eligible.py index 4761c9c2c..d3c12d437 100644 --- a/policyengine_uk/variables/gov/dwp/pension_credit/is_pension_credit_eligible.py +++ b/policyengine_uk/variables/gov/dwp/pension_credit/is_pension_credit_eligible.py @@ -10,7 +10,15 @@ class is_pension_credit_eligible(Variable): reference = "https://www.legislation.gov.uk/ukpga/2002/16/section/1" def formula(benunit, period, parameters): - has_sp_age_member = benunit.any(benunit.members("is_SP_age", period)) + adult = benunit.members("is_adult", period) + adult_count = benunit.sum(adult) + all_adults_are_sp_age = ( + benunit.sum(adult & benunit.members("is_SP_age", period)) == adult_count + ) is_gc_eligible = benunit("is_guarantee_credit_eligible", period) is_sc_eligible = benunit("is_savings_credit_eligible", period) - return has_sp_age_member & (is_gc_eligible | is_sc_eligible) + return ( + (adult_count > 0) + & all_adults_are_sp_age + & (is_gc_eligible | is_sc_eligible) + ) diff --git a/policyengine_uk/variables/gov/hmrc/capital_gains_tax/capital_gains_tax.py b/policyengine_uk/variables/gov/hmrc/capital_gains_tax/capital_gains_tax.py index 0538256d7..16fd1b824 100644 --- a/policyengine_uk/variables/gov/hmrc/capital_gains_tax/capital_gains_tax.py +++ b/policyengine_uk/variables/gov/hmrc/capital_gains_tax/capital_gains_tax.py @@ -14,18 +14,34 @@ def formula(person, period, parameters): cgt = hmrc.cgt it = hmrc.income_tax - ani = person("adjusted_net_income", period) + personal_pension_band_extension = min_( + person("personal_pension_contributions", period), + person("pension_contributions_relief", period), + ) + basic_rate_band_extension = ( + person("gift_aid_grossed_up", period) + personal_pension_band_extension + ) + allowances_for_cgt_income = max_( + 0, + person("allowances", period) + - person("gift_aid", period) + - personal_pension_band_extension, + ) + taxable_income = max_( + 0, + person("adjusted_net_income", period) - allowances_for_cgt_income, + ) gains = max_(0, person("capital_gains", period)) aea = cgt.annual_exempt_amount gains_less_aea = max_(0, gains - aea) - basic_rate_limit = it.rates.uk.thresholds[1] - remaining_basic_rate_band = max_(basic_rate_limit - ani, 0) + basic_rate_limit = it.rates.uk.thresholds[1] + basic_rate_band_extension + remaining_basic_rate_band = max_(basic_rate_limit - taxable_income, 0) basic_rate_applicable_cg = min_(gains_less_aea, remaining_basic_rate_band) higher_and_add_rate_applicable_cg = max_( gains_less_aea - remaining_basic_rate_band, 0 ) - higher_rate_limit = it.rates.uk.thresholds[2] + higher_rate_limit = it.rates.uk.thresholds[2] + basic_rate_band_extension higher_rate_applicable_cg = min_( higher_and_add_rate_applicable_cg, higher_rate_limit - basic_rate_limit, diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/add_rate_savings_income.py b/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/add_rate_savings_income.py index 9ec04f79c..610b9481b 100644 --- a/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/add_rate_savings_income.py +++ b/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/add_rate_savings_income.py @@ -15,23 +15,21 @@ class add_rate_savings_income(Variable): def formula(person, period, parameters): thresholds = parameters(period).gov.hmrc.income_tax.rates.uk.thresholds other_income = person("earned_taxable_income", period) - savings_deductions = add( - person, - period, - [ - "received_allowances_savings_income", - "savings_allowance", - "savings_starter_rate_income", - ], - ) - savings_income_less_deductions = max_( + savings_after_allowances = max_( 0, - person("taxable_savings_interest_income", period) - savings_deductions, + person("taxable_savings_interest_income", period) + - person("received_allowances_savings_income", period), + ) + zero_rate_savings = min_( + savings_after_allowances, + person("savings_starter_rate_income", period) + + person("savings_allowance", period), ) + taxable_savings_start = other_income + zero_rate_savings add_rate_amount_with = clip( - other_income + savings_income_less_deductions, + other_income + savings_after_allowances, thresholds[2], inf, ) - add_rate_amount_without = clip(other_income, thresholds[2], inf) + add_rate_amount_without = clip(taxable_savings_start, thresholds[2], inf) return max_(0, add_rate_amount_with - add_rate_amount_without) diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/basic_rate_savings_income.py b/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/basic_rate_savings_income.py index bfd01b794..cea212f46 100644 --- a/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/basic_rate_savings_income.py +++ b/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/basic_rate_savings_income.py @@ -15,23 +15,25 @@ class basic_rate_savings_income(Variable): def formula(person, period, parameters): thresholds = parameters(period).gov.hmrc.income_tax.rates.uk.thresholds other_income = person("earned_taxable_income", period) - savings_deductions = add( - person, - period, - [ - "received_allowances_savings_income", - "savings_allowance", - "savings_starter_rate_income", - ], - ) - savings_income_less_deductions = max_( + savings_after_allowances = max_( 0, - person("taxable_savings_interest_income", period) - savings_deductions, + person("taxable_savings_interest_income", period) + - person("received_allowances_savings_income", period), + ) + zero_rate_savings = min_( + savings_after_allowances, + person("savings_starter_rate_income", period) + + person("savings_allowance", period), ) + taxable_savings_start = other_income + zero_rate_savings basic_rate_amount_with = clip( - other_income + savings_income_less_deductions, + other_income + savings_after_allowances, + thresholds[0], + thresholds[1], + ) + basic_rate_amount_without = clip( + taxable_savings_start, thresholds[0], thresholds[1], ) - basic_rate_amount_without = clip(other_income, thresholds[0], thresholds[1]) return max_(0, basic_rate_amount_with - basic_rate_amount_without) diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/basic_rate_savings_income_pre_starter.py b/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/basic_rate_savings_income_pre_starter.py index 63760e20a..a3f99e40c 100644 --- a/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/basic_rate_savings_income_pre_starter.py +++ b/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/basic_rate_savings_income_pre_starter.py @@ -14,9 +14,11 @@ class basic_rate_savings_income_pre_starter(Variable): def formula(person, period, parameters): thresholds = parameters(period).gov.hmrc.income_tax.rates.uk.thresholds - savings_income_total = person("taxable_savings_interest_income", period) - savings_allowance = person("savings_allowance", period) - savings_income = max_(0, savings_income_total - savings_allowance) + savings_income = max_( + 0, + person("taxable_savings_interest_income", period) + - person("received_allowances_savings_income", period), + ) other_income = person("earned_taxable_income", period) basic_rate_amount_with_savings = clip( savings_income + other_income, diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/higher_rate_savings_income.py b/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/higher_rate_savings_income.py index ee50f48c5..cfb7da1a3 100644 --- a/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/higher_rate_savings_income.py +++ b/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/higher_rate_savings_income.py @@ -15,23 +15,25 @@ class higher_rate_savings_income(Variable): def formula(person, period, parameters): thresholds = parameters(period).gov.hmrc.income_tax.rates.uk.thresholds other_income = person("earned_taxable_income", period) - savings_deductions = add( - person, - period, - [ - "received_allowances_savings_income", - "savings_allowance", - "savings_starter_rate_income", - ], - ) - savings_income_less_deductions = max_( + savings_after_allowances = max_( 0, - person("taxable_savings_interest_income", period) - savings_deductions, + person("taxable_savings_interest_income", period) + - person("received_allowances_savings_income", period), + ) + zero_rate_savings = min_( + savings_after_allowances, + person("savings_starter_rate_income", period) + + person("savings_allowance", period), ) + taxable_savings_start = other_income + zero_rate_savings higher_rate_amount_with = clip( - other_income + savings_income_less_deductions, + other_income + savings_after_allowances, + thresholds[1], + thresholds[2], + ) + higher_rate_amount_without = clip( + taxable_savings_start, thresholds[1], thresholds[2], ) - higher_rate_amount_without = clip(other_income, thresholds[1], thresholds[2]) return max_(0, higher_rate_amount_with - higher_rate_amount_without) diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/savings_starter_rate_income.py b/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/savings_starter_rate_income.py index 8f76e76a3..354b3908c 100644 --- a/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/savings_starter_rate_income.py +++ b/policyengine_uk/variables/gov/hmrc/income_tax/bracketized_savings_income/savings_starter_rate_income.py @@ -29,13 +29,19 @@ def formula(person, period, parameters): starter_rate_taper_start = max_personal_allowance + limit - savings_income = person("basic_rate_savings_income_pre_starter", period) + savings_income = min_( + person("basic_rate_savings_income_pre_starter", period), + max_( + 0, + person("taxable_savings_interest_income", period) + - person("received_allowances_savings_income", period), + ), + ) earned_taxable_income = person("earned_taxable_income", period) - dividend_income = person("taxable_dividend_income", period) - non_savings_income = earned_taxable_income + dividend_income removed_amount = max_( - 0, (non_savings_income - starter_rate_taper_start) * phase_out_rate + 0, + (earned_taxable_income - starter_rate_taper_start) * phase_out_rate, ) post_taper_max = max_(0, limit - removed_amount) diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/liability/dividend_income_tax.py b/policyengine_uk/variables/gov/hmrc/income_tax/liability/dividend_income_tax.py index a1a8e4ee0..e812a8f76 100644 --- a/policyengine_uk/variables/gov/hmrc/income_tax/liability/dividend_income_tax.py +++ b/policyengine_uk/variables/gov/hmrc/income_tax/liability/dividend_income_tax.py @@ -14,10 +14,24 @@ class dividend_income_tax(Variable): def formula(person, period, parameters): rates = parameters(period).gov.hmrc.income_tax.rates - other_income = person("earned_taxable_income", period) + person( - "taxed_savings_income", period + other_income = person("earned_taxable_income", period) + max_( + 0, + person("taxable_savings_interest_income", period) + - person("received_allowances_savings_income", period), ) - taxable_dividends = person("taxed_dividend_income", period) - tax_with_dividends = rates.dividends.calc(other_income + taxable_dividends) - tax_without_dividends = rates.dividends.calc(other_income) - return max_(0, tax_with_dividends - tax_without_dividends) + dividends_after_allowances = max_( + 0, + person("taxable_dividend_income", period) + - person("received_allowances_dividend_income", period), + ) + dividend_allowance = min_( + parameters(period).gov.hmrc.income_tax.allowances.dividend_allowance, + dividends_after_allowances, + ) + tax_with_dividends = rates.dividends.calc( + other_income + dividends_after_allowances + ) + tax_with_dividend_allowance = rates.dividends.calc( + other_income + dividend_allowance + ) + return max_(0, tax_with_dividends - tax_with_dividend_allowance) diff --git a/policyengine_uk/variables/gov/hmrc/income_tax/liability/property_income_tax.py b/policyengine_uk/variables/gov/hmrc/income_tax/liability/property_income_tax.py index aeca0c32b..0fff929ea 100644 --- a/policyengine_uk/variables/gov/hmrc/income_tax/liability/property_income_tax.py +++ b/policyengine_uk/variables/gov/hmrc/income_tax/liability/property_income_tax.py @@ -35,14 +35,23 @@ def formula(person, period, parameters): """ rates = parameters(period).gov.hmrc.income_tax.rates taxable_property = person("taxable_property_income", period) - earned_taxable = person("earned_taxable_income", period) - other_earned_taxable = max_(0, earned_taxable - taxable_property) + non_savings_after_allowances = max_( + 0, + person("adjusted_net_income", period) + - person("taxable_savings_interest_income", period) + - person("taxable_dividend_income", period) + - person("allowances", period), + ) + property_after_allowances = min_(taxable_property, non_savings_after_allowances) + other_earned_taxable = max_( + 0, non_savings_after_allowances - property_after_allowances + ) is_scottish = person("pays_scottish_income_tax", period) # Scottish taxpayers: use Scottish rates via calc method # Tax on (other earned + property) minus tax on (other earned) scottish_tax = rates.scotland.rates.calc( - other_earned_taxable + taxable_property + other_earned_taxable + property_after_allowances ) - rates.scotland.rates.calc(other_earned_taxable) # rUK taxpayers: use property-specific rates @@ -53,17 +62,17 @@ def formula(person, period, parameters): # Property income in basic rate band basic_rate_space = max_(0, basic_upper - other_earned_taxable) - property_in_basic = min_(taxable_property, basic_rate_space) + property_in_basic = min_(property_after_allowances, basic_rate_space) # Property income in higher rate band higher_start = max_(other_earned_taxable, basic_upper) higher_rate_space = max_(0, higher_upper - higher_start) - remaining_after_basic = max_(0, taxable_property - property_in_basic) + remaining_after_basic = max_(0, property_after_allowances - property_in_basic) property_in_higher = min_(remaining_after_basic, higher_rate_space) # Property income in additional rate band property_in_additional = max_( - 0, taxable_property - property_in_basic - property_in_higher + 0, property_after_allowances - property_in_basic - property_in_higher ) ruk_tax = (