diff --git a/changelog.d/1796.fixed.md b/changelog.d/1796.fixed.md new file mode 100644 index 0000000000..8d18512306 --- /dev/null +++ b/changelog.d/1796.fixed.md @@ -0,0 +1 @@ +- Updated Welsh residential Land Transaction Tax tables for annual model years from 2023 (main rates) and 2025 (higher rates), retaining their statutory change dates. diff --git a/policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/README.md b/policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/README.md index a95cd5ccd6..033530c048 100644 --- a/policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/README.md +++ b/policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/README.md @@ -1 +1,45 @@ -# Residential +# Residential Land Transaction Tax + +The scales apply marginal rates to the consideration for each purchase. The +main and additional-property purchase inputs select the main and higher tables +respectively; the final Land Transaction Tax charge applies only in Wales. + +[WSI 2022/1027, regulation 7](https://www.legislation.gov.uk/wsi/2022/1027/regulation/7/made) +replaced the main residential table from 10 October 2022: + +| Portion of consideration | Rate | +| --- | ---: | +| Up to £225,000 | 0% | +| Over £225,000 to £400,000 | 6% | +| Over £400,000 to £750,000 | 7.5% | +| Over £750,000 to £1,500,000 | 10% | +| Over £1,500,000 | 12% | + +[WSI 2024/1311, regulation 6](https://www.legislation.gov.uk/wsi/2024/1311/regulation/6/made) +replaced the higher residential table from 11 December 2024: + +| Portion of consideration | Rate | +| --- | ---: | +| Up to £180,000 | 5% | +| Over £180,000 to £250,000 | 8.5% | +| Over £250,000 to £400,000 | 10% | +| Over £400,000 to £750,000 | 12.5% | +| Over £750,000 to £1,500,000 | 15% | +| Over £1,500,000 | 17% | + +The raw parameter files retain these statutory dates and the preceding history. +The annual model samples transactional parameters on 30 April. Therefore the +main table changes in model year 2023, while the higher table changes in model +year 2025. Model years 2022 and 2024 retain their respective April tables; +neither year blends the old and new rates. + +The annual Household purchase inputs have no transaction date, contract date, +election or subsequent-contract-event fields. They cannot select a transaction +later in the change year or model protected old contracts. Under +[the 2022 regulations, regulations 4–6](https://www.legislation.gov.uk/wsi/2022/1027/made), +qualifying earlier contracts require an election which must not increase tax. +Under [the 2024 regulations, regulations 4–5](https://www.legislation.gov.uk/wsi/2024/1311/made), +qualifying earlier contracts are protected without an election requirement. +Both provisions have conditions and exclusions; an earlier contract alone is +not sufficient in every case. These transaction-specific provisions are outside +the annual input contract. diff --git a/policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/higher_rate.yaml b/policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/higher_rate.yaml index 3729a95570..f3d47ece70 100644 --- a/policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/higher_rate.yaml +++ b/policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/higher_rate.yaml @@ -2,31 +2,37 @@ brackets: - rate: 2018-04-01: 0.03 2020-12-22: 0.04 + 2024-12-11: 0.05 threshold: 2018-04-01: 0 - rate: 2018-04-01: 0.065 2020-12-22: 0.075 + 2024-12-11: 0.085 threshold: 2018-04-01: 180000 - rate: 2018-04-01: 0.08 2020-12-22: 0.09 + 2024-12-11: 0.1 threshold: 2018-04-01: 250000 - rate: 2018-04-01: 0.105 2020-12-22: 0.115 + 2024-12-11: 0.125 threshold: 2018-04-01: 400000 - rate: 2018-04-01: 0.13 2020-12-22: 0.14 + 2024-12-11: 0.15 threshold: 2018-04-01: 750000 - rate: 2018-04-01: 0.15 2020-12-22: 0.16 + 2024-12-11: 0.17 threshold: 2018-04-01: 1500000 description: Higher residential property progressive tax scale for non-primary residences. @@ -42,4 +48,7 @@ metadata: - href: https://www.legislation.gov.uk/wsi/2020/1618/regulation/6/made title: The Land Transaction Tax (Tax Bands and Tax Rates) (Wales) (Amendment) Regulations 2020 + - href: https://www.legislation.gov.uk/wsi/2024/1311/regulation/6/made + title: The Land Transaction Tax (Tax Bands and Tax Rates) (Wales) (Amendment) + Regulations 2024, regulation 6, Table 2 threshold_unit: currency-GBP diff --git a/policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/primary.yaml b/policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/primary.yaml index 627eb0fb87..e1856312e3 100644 --- a/policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/primary.yaml +++ b/policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/primary.yaml @@ -7,14 +7,18 @@ brackets: 2018-04-01: 0.035 2020-07-27: null 2021-07-01: 0.035 + 2022-10-10: 0.06 threshold: 2018-04-01: 180000 2020-07-27: null 2021-07-01: 180000 + 2022-10-10: 225000 - rate: 2018-04-01: 0.05 + 2022-10-10: null threshold: 2018-04-01: 250000 + 2022-10-10: null - rate: 2018-04-01: 0.075 threshold: @@ -43,4 +47,7 @@ metadata: - href: https://www.legislation.gov.uk/wsi/2021/238/regulation/2/made title: The Land Transaction Tax (Temporary Variation of Rates and Bands for Residential Property Transactions) (Wales) (Amendment) Regulations 2021 + - href: https://www.legislation.gov.uk/wsi/2022/1027/regulation/7/made + title: The Land Transaction Tax (Tax Bands and Tax Rates) (Wales) (Amendment) + Regulations 2022, regulation 7, Table 1 threshold_unit: currency-GBP diff --git a/policyengine_uk/tests/policy/baseline/gov/wra/land_transaction_tax/residential_rate_changes.yaml b/policyengine_uk/tests/policy/baseline/gov/wra/land_transaction_tax/residential_rate_changes.yaml new file mode 100644 index 0000000000..c3149c6685 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/wra/land_transaction_tax/residential_rate_changes.yaml @@ -0,0 +1,166 @@ +# Literal marginal-band sums from WSI 2022/1027 reg 7 and WSI 2024/1311 reg 6. +# Annual parameters sample 30 April; these are not dated/protected-contract cases. +- name: Main purchase retains the April 2022 table + period: 2022 + input: + age: 45 + region: WALES + main_residential_property_purchased: 300_000 + additional_residential_property_purchased: 0 + non_residential_property_purchased: 0 + rent: 0 + cumulative_residential_rent: 0 + non_residential_rent: 0 + cumulative_non_residential_rent: 0 + output: + land_transaction_tax: 4_950 + +- name: Main purchase uses the new table in annual 2023 + period: 2023 + input: + age: 45 + region: WALES + main_residential_property_purchased: 300_000 + additional_residential_property_purchased: 0 + non_residential_property_purchased: 0 + rent: 0 + cumulative_residential_rent: 0 + non_residential_rent: 0 + cumulative_non_residential_rent: 0 + output: + land_transaction_tax: 4_500 + +- name: Main purchase at 400000 retains the April 2022 table + period: 2022 + input: + age: 45 + region: WALES + main_residential_property_purchased: 400_000 + additional_residential_property_purchased: 0 + non_residential_property_purchased: 0 + rent: 0 + cumulative_residential_rent: 0 + non_residential_rent: 0 + cumulative_non_residential_rent: 0 + output: + land_transaction_tax: 9_950 + +- name: New main table raises tax at the 400000 boundary + period: 2023 + input: + age: 45 + region: WALES + main_residential_property_purchased: 400_000 + additional_residential_property_purchased: 0 + non_residential_property_purchased: 0 + rent: 0 + cumulative_residential_rent: 0 + non_residential_rent: 0 + cumulative_non_residential_rent: 0 + output: + land_transaction_tax: 10_500 + +- name: Additional purchase retains the April 2024 table + period: 2024 + input: + age: 45 + region: WALES + main_residential_property_purchased: 0 + additional_residential_property_purchased: 300_000 + non_residential_property_purchased: 0 + rent: 0 + cumulative_residential_rent: 0 + non_residential_rent: 0 + cumulative_non_residential_rent: 0 + output: + land_transaction_tax: 16_950 + +- name: Additional purchase uses the new table in annual 2025 + period: 2025 + input: + age: 45 + region: WALES + main_residential_property_purchased: 0 + additional_residential_property_purchased: 300_000 + non_residential_property_purchased: 0 + rent: 0 + cumulative_residential_rent: 0 + non_residential_rent: 0 + cumulative_non_residential_rent: 0 + output: + land_transaction_tax: 19_950 + +- name: Distinct main and additional purchases use their respective tables + period: 2026 + input: + age: 45 + region: WALES + main_residential_property_purchased: 300_000 + additional_residential_property_purchased: 300_000 + non_residential_property_purchased: 0 + rent: 0 + cumulative_residential_rent: 0 + non_residential_rent: 0 + cumulative_non_residential_rent: 0 + output: + land_transaction_tax: 24_450 + +- name: Main purchase reaches every current residential band + period: 2025 + input: + age: 45 + region: WALES + main_residential_property_purchased: 2_000_000 + additional_residential_property_purchased: 0 + non_residential_property_purchased: 0 + rent: 0 + cumulative_residential_rent: 0 + non_residential_rent: 0 + cumulative_non_residential_rent: 0 + output: + land_transaction_tax: 171_750 + +- name: Additional purchase reaches every current higher band + period: 2025 + input: + age: 45 + region: WALES + main_residential_property_purchased: 0 + additional_residential_property_purchased: 2_000_000 + non_residential_property_purchased: 0 + rent: 0 + cumulative_residential_rent: 0 + non_residential_rent: 0 + cumulative_non_residential_rent: 0 + output: + land_transaction_tax: 271_200 + +- name: No purchase leaves Welsh LTT at zero + period: 2025 + input: + age: 45 + region: WALES + main_residential_property_purchased: 0 + additional_residential_property_purchased: 0 + non_residential_property_purchased: 0 + rent: 0 + cumulative_residential_rent: 0 + non_residential_rent: 0 + cumulative_non_residential_rent: 0 + output: + land_transaction_tax: 0 + +- name: English purchases do not incur Welsh LTT + period: 2025 + input: + age: 45 + region: LONDON + main_residential_property_purchased: 300_000 + additional_residential_property_purchased: 300_000 + non_residential_property_purchased: 0 + rent: 0 + cumulative_residential_rent: 0 + non_residential_rent: 0 + cumulative_non_residential_rent: 0 + output: + land_transaction_tax: 0 diff --git a/policyengine_uk/tests/test_welsh_residential_ltt_rates.py b/policyengine_uk/tests/test_welsh_residential_ltt_rates.py new file mode 100644 index 0000000000..ce9ce4e6f7 --- /dev/null +++ b/policyengine_uk/tests/test_welsh_residential_ltt_rates.py @@ -0,0 +1,147 @@ +"""Welsh residential LTT dates and the existing annual sampling convention. + +WSI 2022/1027 reg 7 replaces the main table from 10 October 2022; +WSI 2024/1311 reg 6 replaces the higher table from 11 December 2024. +Annual purchase inputs cannot select transaction dates or protected contracts. +""" + +from pathlib import Path + +import numpy as np +import pytest +import yaml +from policyengine_core.parameters import ParameterScale + +import policyengine_uk +from policyengine_uk import CountryTaxBenefitSystem + +PATH = "gov.wra.land_transaction_tax.residential" +DIRECTORY = ( + Path(policyengine_uk.__file__).parent + / "parameters/gov/wra/land_transaction_tax/residential" +) +OLD_THRESHOLDS = [0, 180_000, 250_000, 400_000, 750_000, 1_500_000] +MAIN_THRESHOLDS = [0, 225_000, 400_000, 750_000, 1_500_000] +OLD_MAIN_RATES = [0, 0.035, 0.05, 0.075, 0.1, 0.12] +MAIN_RATES = [0, 0.06, 0.075, 0.1, 0.12] +OLD_HIGHER_RATES = [0.04, 0.075, 0.09, 0.115, 0.14, 0.16] +HIGHER_RATES = [0.05, 0.085, 0.1, 0.125, 0.15, 0.17] + + +def raw_scale(name): + file = DIRECTORY / f"{name}.yaml" + data = yaml.safe_load(file.read_text()) + for bracket in data["brackets"]: + for component in ["threshold", "rate"]: + bracket[component] = { + str(date): value for date, value in bracket[component].items() + } + return ParameterScale(f"{PATH}.{name}", data=data, file_path=str(file)) + + +@pytest.mark.parametrize( + "date,thresholds,rates", + [ + ("2018-04-01", OLD_THRESHOLDS, OLD_MAIN_RATES), + ( + "2020-07-27", + [0, 250_000, 400_000, 750_000, 1_500_000], + [0, 0.05, 0.075, 0.1, 0.12], + ), + ("2021-07-01", OLD_THRESHOLDS, OLD_MAIN_RATES), + ("2022-10-09", OLD_THRESHOLDS, OLD_MAIN_RATES), + ("2022-10-10", MAIN_THRESHOLDS, MAIN_RATES), + ], +) +def test_raw_main_table_preserves_history_and_october_change(date, thresholds, rates): + raw = raw_scale("primary") + assert raw(date).thresholds == thresholds + assert raw(date).rates == rates + assert not raw.metadata.get("fiscal_year_blend", False) + assert not raw.metadata.get("preserve_calendar_dates", False) + + +@pytest.mark.parametrize( + "date,rates", + [ + ("2018-04-01", [0.03, 0.065, 0.08, 0.105, 0.13, 0.15]), + ("2020-12-21", [0.03, 0.065, 0.08, 0.105, 0.13, 0.15]), + ("2020-12-22", OLD_HIGHER_RATES), + ("2024-12-10", OLD_HIGHER_RATES), + ("2024-12-11", HIGHER_RATES), + ], +) +def test_raw_higher_table_preserves_history_and_december_change(date, rates): + raw = raw_scale("higher_rate") + assert raw(date).thresholds == OLD_THRESHOLDS + assert raw(date).rates == rates + + +@pytest.mark.parametrize( + "name,date,expected", + [ + ( + "primary", + "2022-10-10", + [0, 0, 0, 1_500, 4_500, 10_500, 18_000, 36_750, 41_750, 111_750, 171_750], + ), + ( + "higher_rate", + "2024-12-11", + [ + 0, + 9_000, + 12_825, + 14_950, + 19_950, + 29_950, + 42_450, + 73_700, + 81_200, + 186_200, + 271_200, + ], + ), + ], +) +def test_new_tables_apply_marginal_rates_across_all_bands(name, date, expected): + prices = np.array( + [ + 0, + 180_000, + 225_000, + 250_000, + 300_000, + 400_000, + 500_000, + 750_000, + 800_000, + 1_500_000, + 2_000_000, + ] + ) + np.testing.assert_allclose( + raw_scale(name)(date).calc(prices), expected, rtol=0, atol=1e-8 + ) + + +@pytest.fixture(scope="module") +def system(): + return CountryTaxBenefitSystem() + + +@pytest.mark.parametrize( + "name,year,thresholds,rates", + [ + ("primary", 2022, OLD_THRESHOLDS, OLD_MAIN_RATES), + ("primary", 2023, MAIN_THRESHOLDS, MAIN_RATES), + ("higher_rate", 2024, OLD_THRESHOLDS, OLD_HIGHER_RATES), + ("higher_rate", 2025, OLD_THRESHOLDS, HIGHER_RATES), + ], +) +def test_annual_tables_keep_april30_transactional_sampling( + system, name, year, thresholds, rates +): + scale = system.parameters.get_child(f"{PATH}.{name}")(f"{year}-01-01") + assert scale.thresholds == thresholds + assert scale.rates == rates