From 4147ec10f66801b8271cbd9ba8478a8dd9c9631b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 4 May 2026 08:33:40 -0400 Subject: [PATCH 1/2] Clarify post-CTR council tax semantics --- docs/council_tax_ctr_pipeline.md | 36 +++++++++++++++++++ .../consumption/council_tax_less_benefit.yaml | 28 +++++++++++++++ .../consumption/council_tax_less_benefit.py | 7 ++-- .../input/consumption/property/council_tax.py | 6 +++- 4 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 docs/council_tax_ctr_pipeline.md diff --git a/docs/council_tax_ctr_pipeline.md b/docs/council_tax_ctr_pipeline.md new file mode 100644 index 000000000..a220ec733 --- /dev/null +++ b/docs/council_tax_ctr_pipeline.md @@ -0,0 +1,36 @@ +# Council Tax and CTR Pipeline + +This branch is a contingent scaffold for work that should merge after +UK-wide Council Tax Reduction (CTR) support lands in +PolicyEngine/policyengine-uk#1534. + +## Current semantics + +- `council_tax` is the household dataset's gross annual Council Tax + liability. It is an input, not yet recomputed from local authority + schedules. +- `council_tax_reduction` is the modelled annual CTR award. +- `council_tax_less_benefit` is gross Council Tax minus modelled CTR, + floored at zero. It is the right household signal for net Council Tax + receipts targets. + +## Dependency boundary + +Until #1534 is complete, downstream pipeline changes should stay +compare-only. They may add variables, targets, diagnostics, or tests, but +should not switch household net income or calibration production outputs +from FRS-reported Council Tax/CTB onto modelled CTR. + +## Draft PR sequence + +1. Clarify variable semantics and add tests for the gross-to-net Council + Tax calculation. This PR is that first step. +2. Add structural gross Council Tax variables from local authority, band, + ratios, discounts, and rates. Keep them compare-only against the + current `council_tax` input. +3. Add CTR calibration diagnostics in `policyengine-uk-data`, comparing + modelled CTR and `council_tax_less_benefit` against admin spend, + caseload, and net receipts targets where available. +4. Switch the production dataset pipeline to structural gross Council Tax + minus modelled CTR only after UK-wide CTR schemes are implemented and + diagnostics show the switch is acceptable. diff --git a/policyengine_uk/tests/policy/baseline/household/consumption/council_tax_less_benefit.yaml b/policyengine_uk/tests/policy/baseline/household/consumption/council_tax_less_benefit.yaml index ffeaf5edb..6600480e8 100644 --- a/policyengine_uk/tests/policy/baseline/household/consumption/council_tax_less_benefit.yaml +++ b/policyengine_uk/tests/policy/baseline/household/consumption/council_tax_less_benefit.yaml @@ -26,3 +26,31 @@ output: council_tax_less_benefit: 1300 hbai_household_net_income: 8700 + +- name: Council Tax after CTR is floored at zero + period: 2025 + absolute_error_margin: 0.5 + input: + people: + claimant: + age: 35 + council_tax_benefit_reported: 2_500 + employment_income: 10_000 + benunits: + benunit: + members: [claimant] + is_single_person: true + is_couple: false + is_lone_parent: false + eldest_adult_age: 35 + benefits_premiums: 0 + households: + household: + members: [claimant] + country: ENGLAND + local_authority: MAIDSTONE + council_tax_band: D + council_tax: 1_800 + savings: 0 + output: + council_tax_less_benefit: 0 diff --git a/policyengine_uk/variables/household/consumption/council_tax_less_benefit.py b/policyengine_uk/variables/household/consumption/council_tax_less_benefit.py index 3a21182d9..e28733ff7 100644 --- a/policyengine_uk/variables/household/consumption/council_tax_less_benefit.py +++ b/policyengine_uk/variables/household/consumption/council_tax_less_benefit.py @@ -2,8 +2,11 @@ class council_tax_less_benefit(Variable): - label = "Council Tax (less CTB)" - documentation = "Council Tax minus Council Tax Reduction" + label = "Council Tax after Council Tax Reduction" + documentation = ( + "Gross Council Tax liability minus modelled Council Tax Reduction, " + "floored at zero." + ) entity = Household definition_period = YEAR value_type = float diff --git a/policyengine_uk/variables/input/consumption/property/council_tax.py b/policyengine_uk/variables/input/consumption/property/council_tax.py index 7cbccebbd..8f035610b 100644 --- a/policyengine_uk/variables/input/consumption/property/council_tax.py +++ b/policyengine_uk/variables/input/consumption/property/council_tax.py @@ -5,7 +5,11 @@ class council_tax(Variable): value_type = float entity = Household label = "Council Tax" - documentation: str = "Gross amount spent on Council Tax, before discounts" + documentation = ( + "Gross annual Council Tax liability before Council Tax Reduction. " + "This is currently supplied by the household dataset rather than " + "recomputed from local authority council tax schedules." + ) definition_period = YEAR unit = GBP quantity_type = FLOW From 9a4ff3ac65ae74d6636f1b7a488350593e78e887 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 4 May 2026 08:58:41 -0400 Subject: [PATCH 2/2] Address CTR semantics review --- docs/council_tax_ctr_pipeline.md | 11 ++++++----- .../consumption/council_tax_less_benefit.yaml | 3 ++- .../household/consumption/council_tax_less_benefit.py | 5 +++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/council_tax_ctr_pipeline.md b/docs/council_tax_ctr_pipeline.md index a220ec733..5c5d28e54 100644 --- a/docs/council_tax_ctr_pipeline.md +++ b/docs/council_tax_ctr_pipeline.md @@ -9,17 +9,18 @@ PolicyEngine/policyengine-uk#1534. - `council_tax` is the household dataset's gross annual Council Tax liability. It is an input, not yet recomputed from local authority schedules. -- `council_tax_reduction` is the modelled annual CTR award. -- `council_tax_less_benefit` is gross Council Tax minus modelled CTR, - floored at zero. It is the right household signal for net Council Tax - receipts targets. +- `council_tax_reduction` is currently hybrid: it uses modelled CTR for + supported local schemes and reported CTB/CTR for unsupported schemes. +- `council_tax_less_benefit` is gross Council Tax minus that hybrid + reduction amount, floored at zero. It is the right household signal for + net Council Tax receipts targets during the transition. ## Dependency boundary Until #1534 is complete, downstream pipeline changes should stay compare-only. They may add variables, targets, diagnostics, or tests, but should not switch household net income or calibration production outputs -from FRS-reported Council Tax/CTB onto modelled CTR. +from FRS-reported or hybrid Council Tax/CTB onto fully modelled CTR. ## Draft PR sequence diff --git a/policyengine_uk/tests/policy/baseline/household/consumption/council_tax_less_benefit.yaml b/policyengine_uk/tests/policy/baseline/household/consumption/council_tax_less_benefit.yaml index 6600480e8..f3251d5a1 100644 --- a/policyengine_uk/tests/policy/baseline/household/consumption/council_tax_less_benefit.yaml +++ b/policyengine_uk/tests/policy/baseline/household/consumption/council_tax_less_benefit.yaml @@ -27,7 +27,7 @@ council_tax_less_benefit: 1300 hbai_household_net_income: 8700 -- name: Council Tax after CTR is floored at zero +- name: Council Tax after reported fallback CTR is floored at zero period: 2025 absolute_error_margin: 0.5 input: @@ -53,4 +53,5 @@ council_tax: 1_800 savings: 0 output: + council_tax_reduction: 2_500 council_tax_less_benefit: 0 diff --git a/policyengine_uk/variables/household/consumption/council_tax_less_benefit.py b/policyengine_uk/variables/household/consumption/council_tax_less_benefit.py index e28733ff7..f0d938c47 100644 --- a/policyengine_uk/variables/household/consumption/council_tax_less_benefit.py +++ b/policyengine_uk/variables/household/consumption/council_tax_less_benefit.py @@ -4,8 +4,9 @@ class council_tax_less_benefit(Variable): label = "Council Tax after Council Tax Reduction" documentation = ( - "Gross Council Tax liability minus modelled Council Tax Reduction, " - "floored at zero." + "Gross Council Tax liability minus Council Tax Reduction, floored " + "at zero. During the CTR transition, the reduction may be modelled " + "for supported local schemes or reported for unsupported schemes." ) entity = Household definition_period = YEAR