feat: add LBTT (Scotland) and LTT (Wales)#55
Conversation
Property-transaction tax now dispatches by region: - Scotland → LBTT (LBTT (Scotland) Act 2013) - Wales → LTT (LTT and Anti-avoidance of Devolved Taxes (Wales) Act 2017) - elsewhere → SDLT (Finance Act 2003 s.55, unchanged) 2025/26 residential bands per: - SSI 2015/126 (Scotland) - WSI 2018/128 (Wales) Adds: - `lbtt` and `ltt` parameter blocks in `parameters/2025_26.yaml` - `Parameters.lbtt`/`Parameters.ltt` Rust fields and Python wrapper exposure - `calculate_property_transaction_tax` dispatch function in `src/variables/wealth_taxes.rs` - New `baseline_property_transaction_tax` and `reform_property_transaction_tax` per-household microdata columns - Six Rust unit tests covering LBTT/LTT/SDLT dispatch and nil-band edges - Six YAML policy-test cases (`tests/policy/property_transaction_tax.yaml`) Stacked on #54 (YAML test harness). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4b57f83 to
d2c89be
Compare
7a9662c to
17324e5
Compare
Until now, PIP amount fields (`pip_daily_living`, `pip_mobility`) were
only populated from FRS recorded values; setting an eligibility flag on
a synthetic household built via `from_situation` produced £0 PIP, and
PIP-rate reforms had no effect even on FRS data when the recorded amount
sat outside the modelled rate.
This change adds:
- `PipParams` Rust struct (and Python wrapper class) with the four PIP
weekly rates: daily-living standard/enhanced and mobility standard/enhanced
- 2025/26 rates per gov.uk/pip/what-youll-get sourced under Welfare
Reform Act 2012 s.79 / SI 2013/377
- `pip_daily_living_amount` and `pip_mobility_amount` helpers in
`src/variables/benefits.rs` that:
- Pass through any FRS-recorded amount unchanged (preserves existing
calibration behaviour)
- Otherwise compute from the eligibility flag × the rate parameter
- Return 0 when neither holds or `params.pip` is unset
- `passthrough_benefits` now uses these helpers, so PIP from flags flows
into total_benefits and downstream household net income
Tests:
- 8 Rust unit tests covering the std/enh/recorded-override/no-flag/no-
params/reform-scaling paths
- 4 YAML policy-test cases covering the same paths end-to-end
Stacked on #55 (LBTT/LTT).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Rebased onto The region dispatch is clean — Scotland→LBTT, Wales→LTT, England/NI→SDLT (NI correctly falls through to SDLT), reusing the existing marginal-band calculation and returning 0 when a regime's params are unset. Good test coverage: per-region unit tests + nil-band edges, mirrored by YAML end-to-end cases; the Rust suite passes (141 tests). Blocker: the Welsh LTT bands are outdated — they use the pre-October-2022 main-residence schedule (£180k nil band, 3.5%/5%…). Current 2025/26 LTT main rates are 0% to £225,000, then 6% / 7.5% / 10% / 12%. A £500k Welsh home should be ~£18,000 (≈£774 annualised), not the ~£750 the test expects. Please update (This PR was rebased onto |
Summary
Addresses part of #46. Property-transaction tax now dispatches by region: Scotland → LBTT, Wales → LTT, England + NI → SDLT (unchanged). Stacked on #54.
Until now Rust applied SDLT to every household irrespective of region, materially over- or under-stating transaction-tax revenue for the ~6m UK households outside England + NI.
Numbers (verified end-to-end through the binary)
A £500,000 primary residence:
What's included
Engine changes:
Parameters.lbttandParameters.ltt(Rust + Python wrapper) — both reuseStampDutyParamssince the structure is the samecalculate_property_transaction_tax(hh, sdlt, lbtt, ltt)insrc/variables/wealth_taxes.rs— dispatches byhh.region, returns 0 when the regime that would apply is unset (e.g. no LBTT params loaded for a Scottish household)simulation.rsswaps the unconditional SDLT call for the new dispatcherParameters (
parameters/2025_26.yaml):Microdata column: new
baseline_property_transaction_taxandreform_property_transaction_taxon each household row in the microdata stdout, so reform analyses can isolate the regime-specific tax instead of inferring it from thetotal_taxdeltaTests:
wealth_taxes.rs— Scotland → LBTT, Wales → LTT, London → SDLT, missing-params fallback, plus nil-band edges (£100k Scotland and £150k Wales should pay £0)tests/policy/property_transaction_tax.yaml— same coverage end-to-end through the binaryChangelog fragment under
changelog.d/added/Verified locally
cargo test: 141 passing (135 + 6 new dispatch tests)pytest interfaces/python/tests: 75 passing (37 from previous PRs + 21 runner + 17 YAML cases)python -m policyengine_uk_compiled.yaml_tests tests/policy: 17/17Stacking
vahid/lbtt-ltt←vahid/yaml-test-harness(#54) ←vahid/parity-harness(#53) ←vahid/from-situation(#52). Once the upstream three merge, this rebases onto main automatically.Out of scope (follow-ups for #46)
Test plan
🤖 Generated with Claude Code