Skip to content

Add council_tax_benefit to net income tree (matches HBAI methodology)#1668

Merged
MaxGhenis merged 1 commit into
mainfrom
fix/abolish-council-tax-net
May 11, 2026
Merged

Add council_tax_benefit to net income tree (matches HBAI methodology)#1668
MaxGhenis merged 1 commit into
mainfrom
fix/abolish-council-tax-net

Conversation

@MaxGhenis
Copy link
Copy Markdown
Collaborator

@MaxGhenis MaxGhenis commented May 11, 2026

Summary

council_tax_benefit (CTR) was completely absent from PolicyEngine UK's net income tree: not in household_benefits.adds, not in hbai_household_net_income.adds, not in pre_budget_change_household_benefits.adds, and not in gov_spending.adds. The council_tax_benefit variable existed (adds = ["council_tax_benefit_reported"]) but nothing consumed it.

Meanwhile council_tax (gross billing, before CTR) is in household_tax.adds, so the model deducted the gross billed amount from household_net_income and never added CTR back. This diverges from DWP HBAI methodology, which counts CTR as benefit income and treats council tax as a deduction at the gross amount — net effect: income measured at gross-CT-minus-CTR (i.e. net CT).

Bug consequences before the fix

Baseline (no reform):

  • CTR-recipient households' HBAI net income is understated by their CTR amount.
  • All four poverty rates are overstated (more CTR-recipients tipped below the threshold than DWP would measure).

Under any reform touching council tax (e.g. gov.contrib.abolish_council_tax):

  • Household net income rises by gross council tax, not net.
  • gov_balance falls by gross council tax revenue, not net.
  • The CTR-shaped gap (~£4bn aggregate at 2026-27 UK levels) appears as an unintended cash transfer in any abolition-style reform.

Fix

  • Add council_tax_benefit to household_benefits.adds, hbai_household_net_income.adds, pre_budget_change_household_benefits.adds, and gov_spending.adds.
  • The existing if abolish_council_tax: [b for b in benefits if b != "council_tax_benefit"] filters in household_benefits.formula and pre_budget_change_household_benefits.formula become meaningful (no-ops before).
  • Apply the same filter pattern to hbai_household_net_income.formula (uses its own adds list) and a fresh gov_spending.formula (previously had no formula — just an adds list).

After the fix:

  • Baseline HBAI net income for CTR-recipient households reflects net out-of-pocket council tax, matching DWP.
  • Any reform abolishing council tax: household net income rises by net council tax; government balance falls by net council tax revenue.

Effect on baseline UK poverty rates (2026-27)

Direct policyengine_uk.Microsimulation on the Enhanced FRS 2023-24 dataset, calibrated to 2026-27:

Measure Pre-fix Post-fix Δ
Absolute BHC poverty 11.35% 11.01% −0.34 pp
Absolute AHC poverty 14.51% 14.25% −0.26 pp
Relative BHC poverty 14.83% 14.68% −0.15 pp
Relative AHC poverty 19.55% 18.55% −1.00 pp

Relative AHC moves most because AHC subtracts housing costs (the threshold is a fixed share of median AHC income, which itself shifts when CTR is added to the income tree). All four baselines fall under the fix because the CTR-recipient households previously misclassified as below the poverty line now have their CTR counted as income.

Anyone running historical poverty comparisons should expect baseline numbers to shift under this PR. Reform-state values for abolish_council_tax reforms are unchanged (CT and CTR both vanish from the income equation regardless of which side they sit on) — only the baseline shifts, which means the change under reform shifts too, by the size of the baseline correction.

Test plan

  • test_abolish_council_tax_removes_budget_impact (zero-CTR case, unchanged behaviour) — passes.
  • New test_abolish_council_tax_nets_out_council_tax_benefit (CTR-recipient case: gross CT £2,000, CTR £800, expected refund £1,200) — passes; would have failed pre-fix.
  • Full pytest suite locally with HUGGING_FACE_TOKEN — 140 passed, 1 skipped, no regressions.

Motivating analysis

Council-tax-to-LVT replacement scoring in PolicyEngine/uk-land-value-tax#8.

The `gov.contrib.abolish_council_tax` switch removed gross council
tax from `household_tax` and `gov_tax` but left `council_tax_benefit`
untouched. CTR was not in any of the household benefit aggregates
(`household_benefits`, `hbai_household_net_income`, `pre_budget_
change_household_benefits`) or in `gov_spending`, so CTR never
flowed through net income or the government balance in the first
place. Abolition therefore refunded the full gross amount to every
household — overstating CTR-recipient households' real-world out-
of-pocket saving by their CTR amount, ~£4bn aggregate.

Add `council_tax_benefit` to the four aggregates. The existing
`if abolish_council_tax: [b for b in benefits if b != "council_
tax_benefit"]` filters become meaningful (no-ops before). Applied
the same filter pattern to `hbai_household_net_income.formula` and
a fresh `gov_spending.formula`.

Now abolition: household net income rises by net council tax, and
government balance falls by net council tax revenue.

Added regression test for a CTR-recipient household.
MaxGhenis added a commit to PolicyEngine/uk-land-value-tax that referenced this pull request May 11, 2026
PolicyEngine/policyengine-uk#1668 fixes a long-standing accounting
issue where `gov.contrib.abolish_council_tax` refunded gross council
tax to households rather than net, because `council_tax_benefit` was
not in any of the benefit aggregates feeding `household_net_income`.

After installing the dev branch of pe-uk locally and re-running the
pipeline, the household-side change drops by the aggregate CTR
amount (~£4bn). Distributional results shift, mostly at the bottom:

  D1 average net change:  +£774  ->  +£522   (CTR no longer windfalls
                                              into baseline-low net
                                              income)
  D9 average loss:        -£980  ->  -£1,011
  D10 average loss:       -£941  ->  -£967
  Aggregate winners:      72%    ->  68%
  BHC poverty change:     -0.84  ->  -0.57 pp
  Income Gini change:     +0.13% ->  +0.58%

Wealth-decile pattern shifts modestly: top wealth decile loss
£5,533 -> £5,539, range of gains across deciles 2-7 narrows from
£509-£1,537 to £483-£1,454.

Article updates:
- Lead bullet 1 reflects new D1/D9/D10 numbers
- Bullet 3 reflects new poverty + Gini
- Table 1 (rate sensitivity) regenerated
- Table 2 (decile impact) regenerated
- Land-distribution prose updated (new D2/D3/D4 inversion pattern)
- Decile prose updated
- Wealth-decile prose updated
- Within-decile (Figure 6 follow-up) updated
- CTR paragraph reframed: abolition retires CTR alongside CT (the
  model now correctly handles this); a future variant could preserve
  CTR-equivalent transfers, which would lift bottom-decile gains
- Charts re-rendered: 6 SVGs + 6 PNG screenshots

CTR-recipient households still gain on average from the reform
(savings net of CTR loss are positive at modest LVT bills) but the
bottom-decile windfall shrinks meaningfully, as expected once the
gross-vs-net accounting bug is fixed.
@MaxGhenis MaxGhenis changed the title Fix abolish_council_tax to refund net council tax, not gross Add council_tax_benefit to net income tree (matches HBAI methodology) May 11, 2026
@MaxGhenis MaxGhenis merged commit e65ddcd into main May 11, 2026
9 checks passed
@MaxGhenis MaxGhenis deleted the fix/abolish-council-tax-net branch May 11, 2026 12:50
Copy link
Copy Markdown
Collaborator

@vahid-ahmadi vahid-ahmadi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Traced the math and the fix is structurally correct: CTR was orphaned (variable existed, no aggregate consumed it), so baseline hbai_household_net_income deducted gross CT and added zero CTR back, and gov_balance showed gross CT revenue with no offsetting CTR spending. Adding CTR to the four aggregates and applying the abolish_council_tax filter consistently gives the right deltas on both sides.

Spot checks:

  • Reform delta with CTR=£800, gross CT=£2,000: hbai_net_income +£1,200, gov_balance −£1,200. Matches the new test.
  • Zero-CTR case (existing test, yaml parametric tests in hbai_council_tax_lvt.yaml): unchanged, since CTR was effectively absent from baseline anyway.
  • BenUnit→Household aggregation via add(...) is consistent with how child_benefit (also BenUnit-scoped) is handled in these same lists, so no member-projection double-count.
  • household_benefits.formula uprating: CTR will now be uprated by uprating.non_sp, which is consistent with how other non-SP benefits are treated.

Worth flagging (non-blocking):

  1. Baseline poverty rates shift materially (PR description shows −1.00 pp on relative AHC). Cached baselines, dashboards, and any published comparisons downstream of policyengine_uk will need refreshing. A minor version bump rather than patch seems warranted given the user-facing effect.
  2. gov_spending gains a formula for the first time. Functionally equivalent to the adds-only path when abolish_council_tax is false, but worth being aware of for any future reform that touches gov_spending.
  3. Calibration sanity check (for whoever owns calibration, not blocking this PR): the change adds roughly £4 bn of CTR to baseline household_benefits and gov_spending. If calibration targets a national benefit-spending total, double-check CTR isn't already being absorbed via housing_benefit or the UC housing element.

Pre-existing nit (not introduced here): pre_budget_change_household_benefits.adds is missing several items present in household_benefits.adds (e.g., iidb, maternity_allowance). Out of scope for this PR.

MaxGhenis added a commit to PolicyEngine/uk-land-value-tax that referenced this pull request May 11, 2026
The CTR-in-net-income fix shipped as PolicyEngine/policyengine-uk#1668
and auto-bumped the version from 2.88.14 to 2.88.15. The analysis
JSON and charts are bit-identical to the pre-merge run (same source,
just a version label change upstream), so this is a footnote-only
update — no recomputation required.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants