Repository navigation
UK: supply the engine's local_authority input from the ladder's local_authority_code, plus council tax follow-ups from policyengine-uk#1855/#1864 and uk-data#482/#484 #953
Description
Summary
Every household row in the UK build carries an ONS local authority code (local_authority_code, written by the geography ladder in uk_runtime/geography_ladder.py, April 2023 LAD vintage, 361 areas), but nothing maps it into the engine's local_authority enum, and the policyengine-uk adapter only supplies engine inputs that exist as frame columns. So the engine sees the enum default (MAIDSTONE) for every household in every Microcosm build, exactly as it does on the uk-data datasets.
This is the one council-tax gap Microcosm can close and uk-data structurally cannot: uk-data reweights one national sample per authority, so a household-level enum can never vary there; Microcosm assigns an authority per row. It matters now because:
- Compute gross council tax from LA Band D amounts and statutory band ratios policyengine-uk#1855 adds
council_tax_band_d_amount(an LA-keyed parameter read throughhousehold("local_authority")) andsimulated_council_tax; on any dataset without the input they collapse to Maidstone's Band D times the national band mix (the £61.21bn England artefact on that PR). - The bespoke Council Tax Reduction schemes in
variables/gov/local_authorities/council_tax_reduction/config.pytestlocal_authority == LocalAuthority.MERTON(and Kingston upon Thames, Newham, Oxford, Westminster). They never fire on any dataset today. - The CT-C reconciliation on WS-C deferred adjudications and the reconciliation layer #736 (rules-based liability from band, the authority's Band D level and the statutory ratios, with CTR engine-side) is unmeasurable until the engine knows each row's authority.
Review context: PolicyEngine/policyengine-uk#1855 (changes requested 2026-09-18), PolicyEngine/policyengine-uk#1864, PolicyEngine/policyengine-uk-data#482, PolicyEngine/policyengine-uk-data#484, TheAxiomFoundation/rulespec-uk#346, tracker PolicyEngine/policyengine-uk#1854.
Deliverable A: supply local_authority as an engine input
Design, following the region precedent (FRS gvtregno mapped to enum key strings such as SOUTH_EAST on the household table; council_tax_band is supplied the same way):
- Names source. The ladder sources carry codes only (no
LAD23NM-style column). Vendor a hash-pinned ONS names-and-codes lookup for the three roster vintages the crosswalk declares (ew:2023_april_lad,scotland:2019_council_area,ni:2014_lgd), the waygeography_sources.pypins its other lookups. - Key derivation. The engine enum key is a mechanical function of the ONS display name for 378 of its 379 members (uppercase, non-alphanumerics to single underscores, e.g.
Bristol, City oftoBRISTOL_CITY_OF,Na h-Eileanan SiartoNA_H_EILEANAN_SIAR). The single exception isKing's Lynn and West Norfolk(KINGS_LYNN_AND_WEST_NORFOLK, apostrophe dropped). So: mechanical rule plus a one-entry alias table, not a hand-written 361-row map. - Fail closed. A build-time test resolves every
local_area_crosswalk.jsonarea id to aLocalAuthoritymember at the pinned engine version and refuses on any miss. No silent fall-through to the enum default. - Write point. Immediately after the ladder assignment (the column is per row, so clones already differ), consistent with
region_codeby construction since authorities nest in regions. Addlocal_authoritytouk/release_input_coverage_manifest.jsonasrequired.
Dependency. The engine enum is the 2021 LAD list and lacks the six April 2023 unitaries (North Northamptonshire, West Northamptonshire, Cumberland, Westmorland and Furness, North Yorkshire, Somerset), which are exactly the codes on our roster with no member. Adding them is requested on PolicyEngine/policyengine-uk#1855; if it lands elsewhere, link it here. Until the pinned engine carries them the fail-closed test blocks the stage, which is the intended behaviour (a partial map would misprice about 1.4 million people silently). Northern Ireland's 11 districts resolve to the enum's members and correctly get zero council tax.
Acceptance.
- Every household row resolves; the test above is green at the pinned engine.
- On a build with the engine pin at or past Compute gross council tax from LA Band D amounts and statutory band ratios policyengine-uk#1855: distinct
council_tax_band_d_amountvalues per nation equal the authority count (296, 22, 32); the five bespoke CTR schemes show non-zero caseloads; the Englandsimulated_council_taxaggregate is reported next to the PR's single-authority figure as a receipt, not bound.
Other follow-ups these PRs expose
- Parity register housekeeping after Extend LA council tax to all eight bands (MHCLG Table 9, 2026-27) policyengine-uk-data#482 and Declare target parity against the N regime: v9.2 feed, CD surface on #484 merge. Both change
targets/sources/la_council_tax.pyandtests/test_la_council_tax_targets.py;uk/uk_data_target_inventory.jsonpins sha256s for both, soverify_uk_data_target_inventory_against_treerefuses until re-pinned. Rewritelocal_council_tax_band_d_rate.fence.origin(it says uk-data "did not wire them into the additive LA loss matrix"; the emission existed and was silently dropped until Declare target parity against the N regime: v9.2 feed, CD surface on #484) and cite LA Band D target is silently dropped now, and will compute as a sum-vs-level mismatch once council_tax_band_d_amount exists policyengine-uk-data#483 and Declare target parity against the N regime: v9.2 feed, CD surface on #484 in its evidence.credibility_local_council_tax_outlierscovers the same test file and inherits the drift. - Per-band amounts (PUF stage manifest declares a support_clip op that no execution path honors (dead donor_realized contract) #482): nothing to port, say so. All 296 English authorities' bands A to H are exact ninths of Band D to within half a penny, so the eight new columns carry nothing beyond the Band D fact already pinned in Chronicle (
mhclg/council_tax_levels_england_2026_27, fiscal 2025 and 2026) and the ratio parameter. Extend thelocal_council_tax_band_d_ratereason to name the eight columns so nobody ports them as targets. - Single-adult discount family candidate.
mhclg.council_taxbase.single_adult_discount_dwellings(CTB line 8, per authority, already in the pinned feed) is a count, so it is additive and a legitimate local target once Add statutory council tax discounts across Great Britain (LGFA 1992 ss.11 and 79; Welsh Regulations 2026) policyengine-uk#1864 lands and Deliverable A is in. Measurement filter: the spine'scouncil_tax_single_adult_raw == 1(FRSadulth, currently written and never consumed) or the engine'scouncil_tax_discount_rate. England first; check the Welsh CT1 and Scottish equivalents before declaring a family. - CT-C measurement conditions (tracked on WS-C deferred adjudications and the reconciliation layer #736, recorded here for the dependency). Needs an engine pin with fiscal 2025 Band D values (requested on Compute gross council tax from LA Band D amounts and statutory band ratios policyengine-uk#1855; today it carries 2026-27 only for England and Wales). Compare
simulated_council_tax_less_discountsagainst the FRScouncil_taxinput per authority, because FRS reports the billed amount (net of discounts, gross of CTR). Do not replace thecouncil_taxinput with the computed value; it feedscouncil_tax_less_benefitand the CTR stack. Note the cross-nation base mix on #1864 (discount zero outside England) until its scope is settled. - Axiom adapter naming. Add LGFA 1992 council tax charging sections (ss.5, 10, 11, 11A, 11B) to the bulk worklist TheAxiomFoundation/rulespec-uk#346 queues the LGFA 1992 charging sections; when modules are generated,
adapters/axiom.pyis where they meet the policyengine encoding. Names and entity level should trackcouncil_tax_band,local_authority,simulated_council_tax,council_tax_discount_rate(household). Nothing to do until then.
Already tracked elsewhere
- CT-B per-clone band redraw and the band-H spine support deferral: WS-E spine deferrals: known bugs and signed inconsistencies awaiting their increment #796.
- CT-C rules-based liability, OBR nation rows report-only,
council_tax_netblocked on its source: WS-C deferred adjudications and the reconciliation layer #736. - Taxbase-basis band-count family: Re-base the UK council tax stock family on the councils' taxbase returns; re-pin the Chronicle feed to df35af7 (consumer_fact.v3) (#929, closes #920) #934 (merged).
🤖 Generated with Claude Code
Activity
Supply the engine's local_authority input from the ladder's April 202…
Merge pull request #971 from PolicyEngine/uk-953-local-authority-input