Skip to content

Commit 7cdf9f5

Browse files
anth-volkclaude
andcommitted
Add Ards and North Down and the 2023 unitaries' positions to the UK list
`policyengine_api/data/local_authorities_2021.csv`, which the UK metadata's `economy_options.region` list reads, had 360 local authorities. It lacked Ards and North Down (`N09000011`), the 11th Northern Ireland district, so `local_authority/Ards and North Down` could not be selected, and it gave the four unitary authorities created on 1 April 2023 (Cumberland `E06000063`, Westmorland and Furness `E06000064`, North Yorkshire `E06000065`, Somerset `E06000066`) the shared (0, 0) placeholder position. The file now has the missing row (`N09000011,-3.0,16.0,Ards and North Down`, after `N09000010`, in the file's code order and number format), and the four unitaries take their x and y from .py 6.10.0's packaged `local_authorities_2023.csv`. Its codes, names and positions now equal that 2023 list for all 361 authorities. The file name is unchanged, to avoid renaming it everywhere it is read. The new name resolves in .py 6.10.0's installed UK region registry, whose label "Ards and North Down" belongs to `local_authority/N09000011`; the simulation API matches a `local_authority/{name}` request to the registry by code or label, ignoring case. A test checks that every offered local-authority name resolves this way. Tests written first. Before the change, 6 failed for the expected reason: the metadata and the file had 360 local authorities, not 361; `local_authority/Ards and North Down` was not offered; Northern Ireland had 10 districts, not 11; the file differed from .py's list in the missing code and the four unitaries' (0, 0) positions; and those four were still at (0, 0). The registry-resolution test passed before and after for the 360 existing names. Refs #4021 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent c2cd5c1 commit 7cdf9f5

3 files changed

Lines changed: 91 additions & 10 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
UK economy requests for Northern Ireland (`country/ni` in the Public API metadata) now send `country/northern_ireland`, the name .py's UK region registry uses, to the simulation API. The cached result keeps the `country/ni` region. The v2 catalog gives UK constituency and local-authority regions the local-area dataset .py 6.10.0 routes them to (`microcosm_uk_2024_25_dense`) and lists that dataset, instead of the national file, which has no area codes.
1+
UK economy requests for Northern Ireland (`country/ni` in the Public API metadata) now send `country/northern_ireland`, the name .py's UK region registry uses, to the simulation API. The cached result keeps the `country/ni` region. The v2 catalog gives UK constituency and local-authority regions the local-area dataset .py 6.10.0 routes them to (`microcosm_uk_2024_25_dense`) and lists that dataset, instead of the national file, which has no area codes. The UK metadata now offers Ards and North Down (`N09000011`), the 11th Northern Ireland district, which the local-authority list lacked, so it lists 361 local authorities. The four unitary authorities created in April 2023 (Cumberland, Westmorland and Furness, North Yorkshire and Somerset) now have their own hex positions instead of the shared (0, 0) placeholder. The list's codes, names and positions now match .py 6.10.0's packaged 2023 list.

‎policyengine_api/data/local_authorities_2021.csv‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ E06000059,-1.0,0.0,Dorset
5858
E06000060,5.0,5.0,Buckinghamshire
5959
E06000061,9.0,9.0,North Northamptonshire
6060
E06000062,7.0,6.0,West Northamptonshire
61-
E06000063,0.0,0.0,Cumberland
62-
E06000064,0.0,0.0,Westmorland and Furness
63-
E06000065,0.0,0.0,North Yorkshire
64-
E06000066,0.0,0.0,Somerset
61+
E06000063,4.0,18.0,Cumberland
62+
E06000064,4.0,17.0,Westmorland and Furness
63+
E06000065,8.0,17.0,North Yorkshire
64+
E06000066,0.0,1.0,Somerset
6565
E07000008,12.0,8.0,Cambridge
6666
E07000009,12.0,9.0,East Cambridgeshire
6767
E07000010,13.0,10.0,Fenland
@@ -305,6 +305,7 @@ N09000007,-5.0,15.0,Lisburn and Castlereagh
305305
N09000008,-4.0,18.0,Mid and East Antrim
306306
N09000009,-5.0,17.0,Mid Ulster
307307
N09000010,-4.0,15.0,"Newry, Mourne and Down"
308+
N09000011,-3.0,16.0,Ards and North Down
308309
S12000005,2.0,24.0,Clackmannanshire
309310
S12000006,4.0,20.0,Dumfries and Galloway
310311
S12000008,3.0,20.0,East Ayrshire

‎tests/unit/test_country.py‎

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import numpy as np
66
import pandas as pd
77
import pytest
8+
from policyengine.countries.uk.regions import build_uk_region_registry
9+
from policyengine.data.uk_geography_assets import packaged_uk_area_list_path
810
from policyengine_core.enums import EnumArray
911
from policyengine_core.model_api import Enum
1012

@@ -213,12 +215,45 @@ def test__uk_metadata_contains_local_authorities(self, uk_regions):
213215
]
214216
assert len(local_authority_regions) > 0
215217

216-
def test__uk_has_360_local_authorities(self, uk_regions):
218+
def test__uk_has_361_local_authorities(self, uk_regions):
217219
"""Verify the correct number of local authorities are loaded."""
218220
local_authority_regions = [
219221
r for r in uk_regions if r.get("type") == "local_authority"
220222
]
221-
assert len(local_authority_regions) == 360
223+
assert len(local_authority_regions) == 361
224+
225+
def test__ards_and_north_down_is_offered(self, uk_regions):
226+
"""The 11th Northern Ireland district (N09000011) is a selectable region."""
227+
assert {
228+
"name": "local_authority/Ards and North Down",
229+
"label": "Ards and North Down",
230+
"type": "local_authority",
231+
} in uk_regions
232+
233+
def test__every_local_authority_resolves_in_the_policyengine_registry(
234+
self, uk_regions
235+
):
236+
"""Each offered name matches a .py registry label, ignoring case.
237+
238+
The simulation API resolves ``local_authority/{name}`` to the registry
239+
code whose label matches the name, ignoring case.
240+
"""
241+
registry = build_uk_region_registry(
242+
include_constituencies=False, include_local_authorities=True
243+
)
244+
registry_labels = {
245+
region.label.casefold(): region.code
246+
for region in registry.get_by_type("local_authority")
247+
}
248+
offered = [
249+
region["label"]
250+
for region in uk_regions
251+
if region.get("type") == "local_authority"
252+
]
253+
assert [
254+
label for label in offered if label.casefold() not in registry_labels
255+
] == []
256+
assert registry_labels["ards and north down"] == "local_authority/N09000011"
222257

223258
def test__local_authority_regions_have_correct_name_format(self, uk_regions):
224259
"""Verify local authority region names have the correct prefix."""
@@ -295,9 +330,44 @@ def test__file_has_correct_columns(self, local_authorities_df):
295330
expected_columns = {"code", "name", "x", "y"}
296331
assert expected_columns == set(local_authorities_df.columns)
297332

298-
def test__file_has_360_local_authorities(self, local_authorities_df):
333+
def test__file_has_361_local_authorities(self, local_authorities_df):
299334
"""Verify the correct number of local authorities in file."""
300-
assert len(local_authorities_df) == 360
335+
assert len(local_authorities_df) == 361
336+
337+
def test__file_matches_the_installed_policyengine_list(self, local_authorities_df):
338+
"""Codes, names and hex positions equal .py's packaged 2023 list.
339+
340+
.py's list is read from the installed package, so a .py update that
341+
changes the area frame makes this test fail until the file follows.
342+
"""
343+
policyengine_list = pd.read_csv(packaged_uk_area_list_path("local_authority"))
344+
345+
def by_code(frame: pd.DataFrame) -> dict[str, tuple[str, float, float]]:
346+
return {
347+
row.code: (row.name, float(row.x), float(row.y))
348+
for row in frame.itertuples(index=False)
349+
}
350+
351+
assert by_code(local_authorities_df) == by_code(policyengine_list)
352+
353+
def test__april_2023_unitaries_leave_the_origin_placeholder(
354+
self, local_authorities_df
355+
):
356+
"""The four unitaries created in April 2023 have their own hex positions.
357+
358+
They used to share the (0, 0) placeholder. Another authority may still
359+
sit at (0, 0) when .py's list puts it there; the comparison with .py's
360+
list above covers every position.
361+
"""
362+
april_2023_unitaries = local_authorities_df[
363+
local_authorities_df["code"].isin(
364+
["E06000063", "E06000064", "E06000065", "E06000066"]
365+
)
366+
]
367+
assert len(april_2023_unitaries) == 4
368+
assert not (
369+
(april_2023_unitaries["x"] == 0) & (april_2023_unitaries["y"] == 0)
370+
).any()
301371

302372
def test__all_codes_are_valid_ons_codes(self, local_authorities_df):
303373
"""Verify all codes follow ONS local authority code patterns."""
@@ -321,7 +391,7 @@ def test__english_local_authorities_have_e_prefix(self, local_authorities_df):
321391
english_las = local_authorities_df[
322392
local_authorities_df["code"].str.startswith("E")
323393
]
324-
# England has 296 local authorities (majority of the 360 total)
394+
# England has 296 local authorities (majority of the 361 total)
325395
assert len(english_las) == 296
326396

327397
def test__scottish_local_authorities_have_s_prefix(self, local_authorities_df):
@@ -340,6 +410,16 @@ def test__welsh_local_authorities_have_w_prefix(self, local_authorities_df):
340410
# Wales has 22 principal areas
341411
assert len(welsh_las) == 22
342412

413+
def test__northern_irish_local_authorities_have_n_prefix(
414+
self, local_authorities_df
415+
):
416+
"""Verify all 11 Northern Ireland districts have N prefix codes."""
417+
northern_irish_las = local_authorities_df[
418+
local_authorities_df["code"].str.startswith("N")
419+
]
420+
assert len(northern_irish_las) == 11
421+
assert "N09000011" in set(northern_irish_las["code"])
422+
343423

344424
class TestSimulationCompatibility:
345425
def test__create_simulation_uses_legacy_tax_benefit_system_signature(self):

0 commit comments

Comments
 (0)