Repository navigation
Conversation
…home additional_residential_property_purchased was other_residential_property_value times property_purchased, so any household that bought a home had its whole stock of other residential property charged as one additional-dwelling purchase at the higher rates; non_residential_property_purchased did the same with the non-residential stock. property_purchased now sets only the main-residence purchase. The additional-dwelling and non-residential purchase prices are inputs, nil unless set directly or imputed by a dataset, and are uprated like the stocks when a dataset supplies them. Fixes #2238 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 10, 2026
Review round 1 on #2241: the main-residence flag alone never creates an additional-dwelling purchase. A new main home bought while another dwelling is kept pays the higher rates, so it belongs in the additional-dwelling input, which holds purchases already classified as higher-rates transactions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 11, 2026
Collaborator
Author
|
PE-UK main fix landed (UK hub, 2026-10-11 21:42Z): #2349 merged as |
33 of 69 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2238.
What was wrong
additional_residential_property_purchasedwasother_residential_property_value × property_purchased.property_purchasedis the dataset's purchase flag: a seeded draw on 3.85% of households in the data build (3.3% weighted), set to match all UK housing transactions. So every household that bought a home had its whole stock of other residential property charged as a single additional-dwelling purchase at the higher rates. The variable's own documentation says "Only include the value of a single purchase."non_residential_property_purchased(defined_for = "property_purchased",adds = ["non_residential_property_value"]) did the same with the non-residential stock, so I fixed it here too.The issue's reproducer: a £600,000 main residence, £2,000,000 of other residential property, 2025. Buying the home cost £233,750 of SDLT, of which £213,750 was higher-rates SDLT on the £2m stock.
The law
SDLT is charged on the chargeable consideration for a land transaction (Finance Act 2003 s. 55). The higher rates for additional dwellings (Schedule 4ZA) apply to a purchase of a dwelling by someone who will own another dwelling and is not replacing their main residence. A stock of property already owned is not a transaction, and buying a main residence does not purchase that stock.
Changes
property_purchasednow means "bought its main residence this year". It sets onlymain_residential_property_purchased, as before.additional_residential_property_purchased(the price of one additional dwelling) andnon_residential_property_purchasedbecome inputs, nil unless set directly or by a dataset that imputes the purchases. They are listed with the property stocks indata/uprating_indices.yaml, so imputed prices are uprated the same way.This follows the replay's own construction rule (scorecard#157,
build_uk_event_registry.py): "The certified population must contain additional-home purchase values; absence yields an inert leg rather than an inferred national tax base."Tests
Examples (YAML). The issue's three cases:
Properties (
test_property_transaction_tax_purchase_properties.py, Hypothesis, households across England, Northern Ireland, Scotland and Wales, 2022-2027). Invariants:Stocks are never purchases: zeroing other residential and non-residential property leaves SDLT, LBTT, LTT and their expected values unchanged.
The main-residence flag alone never creates an additional-dwelling purchase. With that input unset, residential SDLT equals the main-rates tax on the main-residence price.
The additional-dwelling input is a purchase already classified as a higher-rates transaction. A new main home bought while another dwelling is kept, with no replacement of a main residence, pays the higher rates (Sch 4ZA), so it belongs in that input, not the main-residence price. That classification limitation predates this PR.
An additional-dwelling purchase adds exactly the higher-rates tax on its own price (nil below £40,000), and the higher rates are never below the main rates.
SDLT never falls as the additional-dwelling price rises.
Mutation check. On
main, invariants 1 and 2 fail. Invariant 2 leaves the input unset, so it exercises the old formula.Targeted suites. 159 YAML tests under stamp duty, LTT, council tax and household pass.
National impact
Data:
enhanced_frs_2024_25.h5@1.58.0, this branch againstmainat 6d89338 (this branch merged with it); the figures are unchanged from the run against e89b8ab, full microsimulation (£bn, branch minus main).stamp_duty_land_taxland_and_buildings_transaction_taxexpected_lbttland_transaction_taxgov_tax/gov_balanceWhat the figures cover:
Against HMRC. UK Stamp Tax Statistics 2024-25, England and Northern Ireland: Tables 6b and 8a.
On
main, FY2024-25 residential SDLT was £14.75bn: £6.99bn on main residences and £7.76bn at the higher rates. The higher-rates part sat on 172k households with a £104bn base.HMRC's 2024-25 figures:
So the old base charged more higher-rates SDLT to households than HMRC collects from all HRAD buyers.
After this PR households pay no higher-rates SDLT unless a dataset supplies the purchases. Individuals paid at least £4.2bn of HRAD receipts in 2024-25 (£5.44bn less at most £1.23bn from non-individual buyers). That gap stays until the data imputes additional-dwelling purchases. The issue's other suggestion was imputing one purchase price, or a purchase probability on the stock, calibrated to HMRC's HRAD statistics. That imputation and calibration belongs in policyengine-uk-data, which owns the purchase draw: PolicyEngine/policyengine-uk-data#556.
For the replay, Autumn Budget 2024's 2pp HRAD rise becomes an inert leg on data without imputed purchases, rather than 2pp of the £94.7bn inferred base.
Fiscal-event replay: Autumn Budget 2024 HRAD +2pp
Real microsimulation runs of the replay construction on the proposed certified data (
enhanced_frs_2024_25.h5@1.58.0), this branch againstmain6d89338. The construction is copied from policyengine-scorecardpipeline/build_uk_event_registry.pyon main, snapshotted 2026-10-11 (event_replay.py). Gain to the Exchequer, £bn.Forward delta: higher-rates scale 5/7/10/15/17% from 31 October 2024. The processed tree samples 30 April, so FY2024 has no effect in either run.
stamp_duty_land_taxInput support: not met on 1.58.0, so the national exposure is inert. The dataset has no
additional_residential_property_purchased, and this PR stops inferring it from the stock. The zero is that missing input, not an estimate of the measure. Read it as "not computable on this data", as the replay now does, never as an improvement on main's £2.2bn.The input belongs in the uk-data d833 batch: PolicyEngine/policyengine-uk-data#556 proposes a seeded draw calibrated to HMRC's HRAD transactions. Its design (purchase rate, price distribution) is a methodology choice for Max, queued with the batch decision.
Not in this PR (found while fixing it)
Filed as their own issues:
expected_sdltaddsproperty_sale_rate(+£0.045 per household) whereexpected_lbttandexpected_lttmultiply by it. Count corporate SDLT once and let SDLT abolition remove all of it #2235 fixes this, along with a corporate SDLT double count.expected_lbttandexpected_lttdiscount twice. Their transaction taxes already sit on the dataset's purchase draw, and the 4.5% sale rate is applied on top: in 2025, £2.21bn of LBTT becomes £0.10bn expected. expected_sdlt adds the property sale rate; expected_lbtt and expected_ltt discount purchases twice #2246.axiom: n/a: input mapping only. No SDLT, LBTT or LTT rule changes; the Schedule 4ZA conditions and rates are untouched. rulespec-uk has no FA 2003 SDLT module (rulespec-uk#63).
🤖 Generated with Claude Code
Reproducer:
policyengine_uk/tests/policy/baseline/gov/hmrc/stamp_duty/property_purchased_default.yaml::Issue 2238 - no purchase, no stamp duty,policyengine_uk/tests/policy/baseline/gov/hmrc/stamp_duty/property_purchased_default.yaml::Issue 2238 - buying the main residence does not charge the other-property stock,policyengine_uk/tests/policy/baseline/gov/hmrc/stamp_duty/property_purchased_default.yaml::Issue 2238 - an additional-dwelling purchase is charged on its own price,policyengine_uk/tests/test_property_transaction_tax_purchase_properties.py::test_property_stocks_are_never_purchases,policyengine_uk/tests/test_property_transaction_tax_purchase_properties.py::test_main_residence_flag_never_creates_an_additional_purchase,policyengine_uk/tests/test_property_transaction_tax_purchase_properties.py::test_additional_purchase_is_charged_on_its_own_price