PolicyEngine / PolicyEngine/policyengine-uk-data

LA Band D target is silently dropped now, and will compute as a sum-vs-level mismatch once council_tax_band_d_amount exists

Open
#483 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
1
Forks
2
Avg merge
1d 14h
Merged PRs (30d)
4

Description

Summary

targets/sources/la_council_tax.py:182 emits a target whose variable is council_tax_band_d_amount. That variable does not currently exist in policyengine-uk, so the target is silently dropped. Once it exists, the target will activate and be computed incorrectly — as a sum where the target value is a level.

This is currently latent. It becomes a live calibration defect the moment policyengine-uk gains the variable and the pin here is bumped.

Current behaviour: silent drop

build_loss_matrix.py::_compute_column routes voa/council_tax/ and obr/council_tax, but has no route for the ons/council_tax_band_d/ prefix. So it falls through to _compute_simple_gbp, which does:

try:
    entity = ctx.sim.tax_benefit_system.variables[variable].entity.key
except KeyError:
    return None

KeyErrorNone → the caller continues. The logger.debug("No compute logic...") branch never fires, so there is no diagnostic either. ~360 LA targets are being discarded with no signal.

The defect that activates on merge

_compute_simple_gbp's docstring is "Sum a variable at household level", and for a household-entity variable it returns ctx.pe(variable) — the weighted sum across households.

But the target value is a level: band_d_amount is a single LA's Band D council tax, on the order of £2,500.

So the loss matrix would compare a national weighted sum (order 1e10) against ~2.5e3. That will distort calibration rather than improve it.

Related architectural note

council_tax_band_d_amount is inherently an LA-level attribute, not a household-level quantity. Note that LA analysis in this repo works by reweighting the national sample per LA (local_authority_weights.h5, one weight vector per code in local_authorities_2021.csv) rather than by tagging households with an LA — so "the Band D amount for this household" is not well-defined outside a specific LA weighting context. Whatever fix is chosen should account for that.

Options

  1. Drop the target. If Band D amount is an input to liability rather than something to calibrate towards, it may not belong in the loss matrix at all. Band counts (voa/council_tax/{code}/{band}) and the net requirement are the calibratable quantities.
  2. Add a compute route for ons/council_tax_band_d/ returning a weighted mean rather than a sum.
  3. Retarget onto total council tax revenue per LA, which is a sum and already available as total_council_tax_net in storage/la_council_tax.csv.

Option 1 or 3 look more principled than 2, but this is a judgement call for whoever owns the calibration design.

Why now

PolicyEngine/policyengine-uk#1855 adds a council_tax_band_d_amount variable. If that merges and the policyengine-uk pin here is bumped without addressing this, the defect goes live silently — it will not raise, it will just calibrate against a wrong comparison.

Found while working on the council tax deliverable tracked at PolicyEngine/policyengine-uk#1854.

🤖 Generated with Claude Code

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with targets/sources/la_council_tax.py:182 and trace the target through build_loss_matrix.py::_compute_column and _compute_simple_gbp. Review the three proposed options against the LA weighting approach and existing storage/la_council_tax.csv data. Done means a decided calibration design prevents silent target loss and avoids comparing a national sum with a Band D level when the variable becomes available.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
data-engineering
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.