Conversation
council_tax is policyengine-uk's gross liability, but the FRS CTANNUAL is "Annual CT amount after discounts/reduction" (DWP DV summary 2024-25), so every reported CTR recipient entered the model with its own reduction already taken off, and full-CTR recipients with a zero bill. derive_council_tax adds CTREBAMT back for CTREB = 1 households, imputes missing bills from non-recipients only, and in Scotland nets water and sewerage off with the 2024-25 gross charges (CWATAMT1, CSEWAMT1; CSEWAMT is blank that year), at 65% of gross for recipients, matching the Water Charges Reduction Scheme maximum. Earlier releases keep the previous Scottish netting. Reported CTR is a household amount held on the reference person, and policyengine-uk reads any positive value as a claim. The stage-2 QRF spread it over people on SPI-synthetic rows, rising with income, so it is now set to zero there instead of imputed. Fixes #496 Fixes #497 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… no-CTREB fallback Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…groups Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
25 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 #496
Fixes #497
Draft. This changes the published dataset, so merging and releasing it is a release decision for Max. No dataset was built, uploaded or released from this branch.
What changes
council_taxis the bill after discounts and before council tax reduction (CTR) (council_tax is the FRS bill net of council tax reduction, but the model uses it as the gross liability #496). A newderive_council_taxindatasets/frs.pyreplaces the inline council tax block increate_frs:council_tax_benefit_reportedis removed fromFRS_ONLY_PERSON_VARIABLESand listed in a newSPI_DONOR_ZEROED_PERSON_VARIABLES.impute_frs_only_variablessets it to 0 on the target (SPI-donor) rows.hypothesisas a dev extra (with theuv.lockrefresh, which also updates the lock's stale self-version), and.hypothesis/in.gitignore.axiom: n/a: dataset construction change in uk-data, no policy rule encoded.
Evidence for #496
DWP's definition. The FRS 2024-25 derived-variable summary (UKDS SN 9563,
9563_dv_summary_2425.xlsx, sheetDV 2425, row 118) defines CTANNUAL as "Annual CT amount after discounts/reduction". It says the DV is calculated using "... GVTREGNO, CTBAND, GROSSCT, WHYNOCT, CTREB and CTREBAMT", and CTREB and CTREBAMT are the reported reduction. policyengine-uk documentscouncil_taxas "Gross annual Council Tax liability before Council Tax Reduction", andcouncil_tax_reduction_maximum_eligible_liabilityreturns it unchanged.The add-back identity. Raw FRS, gross4-weighted. Recipients have CTANNUAL ≥ 0 and CTREBAMT > 0. Each ratio is their mean divided by the mean CTANNUAL of non-recipients in the same (region, band, single-adult) cell.
The identity holds in the older interview-based years too. Their retired question asked for the amount "after deducting any discounts or reduction". So the add-back applies to every vintage that has CTREB and CTREBAMT. Only the Scottish gross water DVs are limited to 2024-25 onwards.
Scottish water and sewerage. All measured on raw FRS 2024-25 Scottish households, gross4-weighted.
Effect on the base FRS 2024-25. Raw household file, gross4-weighted survey-year £. This is the same code path
create_frsnow calls. It is not the calibrated EFRS.council_taxbeforeNon-recipients with a bill outside Scotland are unchanged, record for record. The Scottish non-recipient fall is the sewerage that the old netting missed.
Calibration. The OBR council tax targets use
council_tax_less_benefit, which iscouncil_taxminus CTR. For reported recipients,council_taxwas already net of CTR, so CTR came off twice. With a gross bill it comes off once. Scottishcouncil_tax_less_benefitalso falls by the sewerage, so the Scottish council tax fit will move on the next build.Evidence for #497
The FRS build puts reported CTR (household CTREBAMT) on the household reference person only. The stage-2 QRF is person-level and predicted it for every person on SPI-donor rows:
The imputed receipt also runs the wrong way with income. Measured on the 1.56.16 file (base-year values, stored
household_weight). Household market income is the sum of employment, self-employment, private pension, savings interest, dividend and property income. Each group includes its capital-gains clones.policyengine-uk does not treat this as noise:
would_claim_council_tax_reductionisclaims_all_entitled_benefits | council_tax_benefit_reported > 0;council_tax_reduction_scheme_supportedis false,council_tax_benefitis the reported amount.#497 gives the 2026 effect: synthetic rows hold £0.44bn of England's £1.35bn working-age CTR.
Why zero rather than an HRP-only household imputation. The stage-2 model predicts from person-level age, gender, region and personal incomes. A credible CTR imputation would need a separate household-level model (household income, tenure, household composition) and its own validation. Zero is the conservative fix: synthetic households can still get CTR through the model's own eligibility and take-up.
The cost is that zero also removes imputed receipt from low-income synthetic rows: £0.16bn (base year, stored weights) in the under-£20k bands, out of £0.72bn on synthetic rows. If that proves too low, the household-level imputation in #497's first option is the follow-up.
Downstream: policyengine-uk
The consumer is PolicyEngine/policyengine-uk#1966, the Scotland and Wales working-age CTR rules. Its diagnostic run is a real 2026 microsimulation. It used a diagnostic copy of the 1.56.16 EFRS with reported CTR added back to FRS-original rows'
council_tax, not a dataset rebuilt from this branch. It gave:(Source: the CTR shortfall investigation of 2026-09-30,
impact/branch_v1.jsonandimpact/branch_v1_grossbill.json.)This branch will not reproduce those numbers exactly:
A rebuilt dataset is needed for the release numbers.
Invariants (stated and tested)
For every household table:
council_taxis finite, non-negative and has one value per household.council_tax_benefit_reportedis 0.Invariants 1-4 are Hypothesis property tests in
test_council_tax_gross_bill.py, alongside example tests for each case in the issue.Differential check (one-off, on the licensed raw files, aggregates only; not committed because the data are licensed). With CTREB and CTREBAMT removed,
derive_council_taxequals the old inline block exactly:Mutation check. Each of these regressions fails at least one test:
max;Tests
Local, Python 3.13 with
uv sync --frozen --all-extras:test_council_tax_gross_bill.py: 12 passed. Two of these are Hypothesis properties, at 300 and 200 examples.test_frs_only_imputation.py: 6 passed, including the new SPI zeroing test.test_legacy_benefit_proxies.py: 14 passed. The end-to-endcreate_frsfixture now also checks that a full reduction leaves the gross bill incouncil_tax.TESTING=1 pytest, no built datasets locally): 530 passed, 113 skipped, 1 xfailed and 4 failed..h5datasets.test_obr_nic_signal.pyand two OBR income tax tests intest_target_registry.py. They fail because the live OBR download returns a non-zip file (the HMRC salary sacrifice CSV also returns 410 Gone).origin/main(b45c373), so they are not caused by this PR.ruff format --check .passes with ruff 0.15.5 (locked) and 0.16.9 (latest, as CI installs it).Not done here
water_and_sewerage_chargesis still zero for Scotland in 2024-25. The sewerage netted out ofcouncil_taxhere does not land there yet, as was already true of the water charge netted since Net Scottish Water charges out of FRS council tax (fixes ~25% Scottish overstatement) #449.council_tax_benefit_reported= 0, as before.council_taxis a predictor in the WAS wealth QRF (imputations/wealth.py), so recipients' predictor rises to the gross bill. Separately, and predating this PR, the WAS side of that predictor looks mismatched:CTAmtR8("Council Tax Amount") is mostly a per-instalment amount. In round 8 its median is £174, 0.1 ofDVCTaxAmtAnnualR8("Household Annual Council Tax paid"), whose median is £1,520.uv.lock(and.gitignore, for Calibrate pension-age Housing Benefit to DWP's GB figures #490) may conflict with Drop ons/council_tax_band_d/{code} target emission (#483) #484 and Calibrate pension-age Housing Benefit to DWP's GB figures #490. A relock resolves that.🤖 Generated with Claude Code