Skip to content

Commit fd39838

Browse files
dr5hnclaude
andauthored
feat(postcodes/FK,GS,IO,PN,SH,TC,NR): import 7 singleton-territory postcodes (#1039) (#1526)
Adds the fixed single-postcode entries for seven small territories that each use a single Royal Mail-style postcode (UK Crown Dependencies / Overseas Territories) or a national postal singleton. Why --- Closes the FK, GS, IO, PN, SH, TC, NR gaps on issue #1039. These territories use a single fixed postcode each (per Royal Mail and each national post's published convention). Coverage -------- - FK Falkland Islands 1 code (FIQQ 1ZZ) - GS South Georgia/S. Sandwich 1 code (SIQQ 1ZZ) - IO British Indian Ocean Territory 1 code (BBND 1ZZ) - PN Pitcairn Islands 1 code (PCRN 1ZZ) - SH Saint Helena (+ Ascension 3 codes (STHL 1ZZ, ASCN 1ZZ, and Tristan da Cunha) TDCU 1ZZ) - TC Turks and Caicos Islands 1 code (TKCA 1ZZ) - NR Nauru 1 code (NRU68) Country-only ship (no state_id) — these countries either have a single state or no formal sub-state postcode mapping. Regex fixes ----------- Several countries had countries.json regex entries that were either: - Plain literal strings without ^...$ anchors (FK, GS, IO, PN, NR had `FIQQ 1ZZ` etc. as raw values, would match ANY substring) - A no-space variant (SH had `^(STHL1ZZ)$` vs canonical 'STHL 1ZZ' with space) Updated to anchored regexes that accept both the spaced and unspaced forms (e.g. `^(FIQQ\s?1ZZ)$`). License ------- Postcode assignments are public Royal Mail / national-post conventions; no formal license required. Each row: source: "wikipedia-singleton-territory" Validation ---------- - python3 -m py_compile passes - 100% regex match against updated regexes - No state_id (country-only ships) - No auto-managed fields (id, created_at, updated_at, flag) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1a9e5a8 commit fd39838

9 files changed

Lines changed: 241 additions & 8 deletions

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#!/usr/bin/env python3
2+
"""Singleton-postcode UK overseas territories importer for issue #1039.
3+
4+
Source data
5+
-----------
6+
The following territories use a single fixed Royal Mail-style
7+
postcode each, assigned by the British postal system:
8+
9+
Country iso2 postcode ship to
10+
Falkland Islands FK FIQQ 1ZZ
11+
South Georgia (S. Sand.) GS SIQQ 1ZZ
12+
BIOT (Diego Garcia, ...) IO BBND 1ZZ
13+
Pitcairn Islands PN PCRN 1ZZ
14+
Saint Helena (Island) SH STHL 1ZZ
15+
Ascension Island SH ASCN 1ZZ (same SH country)
16+
Tristan da Cunha SH TDCU 1ZZ (same SH country)
17+
Turks and Caicos Islands TC TKCA 1ZZ
18+
Nauru NR NRU68
19+
20+
Source: Royal Mail / Wikipedia public references for each Crown
21+
Dependency or Overseas Territory's postcode assignment.
22+
23+
What this script does
24+
---------------------
25+
Emits 7 contributions/postcodes/<iso2>.json files (SH gets all
26+
three Atlantic Saint-Helena-overseas postcodes in one file).
27+
28+
State FK
29+
--------
30+
Country-only ship for all (these countries have either a single
31+
state or no formal sub-state postcode mapping).
32+
33+
License & attribution
34+
---------------------
35+
Postcode assignments are public Royal Mail / national-post conventions;
36+
no formal license required. Each row carries
37+
``source: "wikipedia-singleton-territory"`` for export-time provenance.
38+
39+
Usage
40+
-----
41+
python3 bin/scripts/sync/import_singleton_territory_postcodes.py
42+
"""
43+
44+
from __future__ import annotations
45+
46+
import argparse
47+
import json
48+
import re
49+
import sys
50+
from pathlib import Path
51+
from typing import Dict, List
52+
53+
54+
# iso2 -> list of (postcode, locality_name) tuples
55+
TERRITORIES: Dict[str, List[tuple]] = {
56+
"FK": [("FIQQ 1ZZ", "Falkland Islands")],
57+
"GS": [("SIQQ 1ZZ", "South Georgia and the South Sandwich Islands")],
58+
"IO": [("BBND 1ZZ", "British Indian Ocean Territory")],
59+
"PN": [("PCRN 1ZZ", "Pitcairn Islands")],
60+
"SH": [
61+
("STHL 1ZZ", "Saint Helena"),
62+
("ASCN 1ZZ", "Ascension Island"),
63+
("TDCU 1ZZ", "Tristan da Cunha"),
64+
],
65+
"TC": [("TKCA 1ZZ", "Turks and Caicos Islands")],
66+
"NR": [("NRU68", "Nauru")],
67+
}
68+
69+
70+
def main() -> int:
71+
parser = argparse.ArgumentParser(description=__doc__)
72+
parser.add_argument("--dry-run", action="store_true")
73+
args = parser.parse_args()
74+
75+
project_root = Path(__file__).resolve().parents[3]
76+
countries = json.load(
77+
(project_root / "contributions/countries/countries.json").open(encoding="utf-8")
78+
)
79+
countries_by_iso2 = {c["iso2"]: c for c in countries}
80+
81+
written: List[str] = []
82+
for iso2, entries in TERRITORIES.items():
83+
country = countries_by_iso2.get(iso2)
84+
if country is None:
85+
print(f"WARN: {iso2} not in countries.json", file=sys.stderr)
86+
continue
87+
regex = re.compile(country.get("postal_code_regex") or ".*")
88+
89+
records: List[dict] = []
90+
for code, locality in entries:
91+
if not regex.match(code):
92+
print(
93+
f" WARN: {iso2}/{code!r} fails regex {regex.pattern!r}",
94+
file=sys.stderr,
95+
)
96+
continue
97+
record: Dict[str, object] = {
98+
"code": code,
99+
"country_id": int(country["id"]),
100+
"country_code": iso2,
101+
"locality_name": locality,
102+
"type": "full",
103+
"source": "wikipedia-singleton-territory",
104+
}
105+
records.append(record)
106+
107+
if args.dry_run:
108+
print(f" {iso2}: would write {len(records)} record(s)")
109+
continue
110+
111+
target = project_root / f"contributions/postcodes/{iso2}.json"
112+
target.parent.mkdir(parents=True, exist_ok=True)
113+
if target.exists():
114+
with target.open(encoding="utf-8") as f:
115+
existing = json.load(f)
116+
existing_seen = {
117+
(r["code"], (r.get("locality_name") or "").lower())
118+
for r in existing
119+
}
120+
merged = list(existing)
121+
for r in records:
122+
key = (r["code"], (r.get("locality_name") or "").lower())
123+
if key not in existing_seen:
124+
merged.append(r)
125+
existing_seen.add(key)
126+
merged.sort(key=lambda r: (r["code"], r.get("locality_name", "")))
127+
else:
128+
merged = sorted(
129+
records, key=lambda r: (r["code"], r.get("locality_name", ""))
130+
)
131+
132+
with target.open("w", encoding="utf-8") as f:
133+
json.dump(merged, f, ensure_ascii=False, indent=2)
134+
f.write("\n")
135+
size_kb = target.stat().st_size / 1024
136+
print(
137+
f" [OK] {target.relative_to(project_root)} "
138+
f"({len(merged)} record(s), {size_kb:.1f} KB)"
139+
)
140+
written.append(iso2)
141+
142+
print(f"\nShipped: {len(written)} territories: {', '.join(written)}")
143+
return 0
144+
145+
146+
if __name__ == "__main__":
147+
raise SystemExit(main())

contributions/countries/countries.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2272,7 +2272,7 @@
22722272
"nationality": "BIOT",
22732273
"area_sq_km": 60.0,
22742274
"postal_code_format": "BBND 1ZZ",
2275-
"postal_code_regex": "BBND 1ZZ",
2275+
"postal_code_regex": "^(BBND\\s?1ZZ)$",
22762276
"timezones": [
22772277
{
22782278
"zoneName": "Indian/Chagos",
@@ -4928,7 +4928,7 @@
49284928
"nationality": "Falkland Island",
49294929
"area_sq_km": 12173.0,
49304930
"postal_code_format": "FIQQ 1ZZ",
4931-
"postal_code_regex": "FIQQ 1ZZ",
4931+
"postal_code_regex": "^(FIQQ\\s?1ZZ)$",
49324932
"timezones": [
49334933
{
49344934
"zoneName": "Atlantic/Stanley",
@@ -10253,7 +10253,7 @@
1025310253
"nationality": "Nauruan",
1025410254
"area_sq_km": 21.0,
1025510255
"postal_code_format": "NRU68",
10256-
"postal_code_regex": "NRU68",
10256+
"postal_code_regex": "^(NRU68)$",
1025710257
"timezones": [
1025810258
{
1025910259
"zoneName": "Pacific/Nauru",
@@ -11641,7 +11641,7 @@
1164111641
"nationality": "Pitcairn Island",
1164211642
"area_sq_km": 47.0,
1164311643
"postal_code_format": "PCRN 1ZZ",
11644-
"postal_code_regex": "PCRN 1ZZ",
11644+
"postal_code_regex": "^(PCRN\\s?1ZZ)$",
1164511645
"timezones": [
1164611646
{
1164711647
"zoneName": "Pacific/Pitcairn",
@@ -12386,8 +12386,8 @@
1238612386
"subregion_id": 3,
1238712387
"nationality": "Saint Helenian",
1238812388
"area_sq_km": 410.0,
12389-
"postal_code_format": "STHL 1ZZ",
12390-
"postal_code_regex": "^(STHL1ZZ)$",
12389+
"postal_code_format": "STHL 1ZZ|ASCN 1ZZ|TDCU 1ZZ",
12390+
"postal_code_regex": "^((?:STHL|ASCN|TDCU)\\s?1ZZ)$",
1239112391
"timezones": [
1239212392
{
1239312393
"zoneName": "Atlantic/St_Helena",
@@ -13689,7 +13689,7 @@
1368913689
"nationality": "South Georgia or South Sandwich Islands",
1369013690
"area_sq_km": 3903.0,
1369113691
"postal_code_format": "SIQQ 1ZZ",
13692-
"postal_code_regex": "SIQQ 1ZZ",
13692+
"postal_code_regex": "^(SIQQ\\s?1ZZ)$",
1369313693
"timezones": [
1369413694
{
1369513695
"zoneName": "Atlantic/South_Georgia",
@@ -15067,7 +15067,7 @@
1506715067
"nationality": "Turks and Caicos Island",
1506815068
"area_sq_km": 430.0,
1506915069
"postal_code_format": "TKCA 1ZZ",
15070-
"postal_code_regex": "^(TKCA 1ZZ)$",
15070+
"postal_code_regex": "^(TKCA\\s?1ZZ)$",
1507115071
"timezones": [
1507215072
{
1507315073
"zoneName": "America/Grand_Turk",

contributions/postcodes/FK.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[
2+
{
3+
"code": "FIQQ 1ZZ",
4+
"country_id": 71,
5+
"country_code": "FK",
6+
"locality_name": "Falkland Islands",
7+
"type": "full",
8+
"source": "wikipedia-singleton-territory"
9+
}
10+
]

contributions/postcodes/GS.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[
2+
{
3+
"code": "SIQQ 1ZZ",
4+
"country_id": 205,
5+
"country_code": "GS",
6+
"locality_name": "South Georgia and the South Sandwich Islands",
7+
"type": "full",
8+
"source": "wikipedia-singleton-territory"
9+
}
10+
]

contributions/postcodes/IO.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[
2+
{
3+
"code": "BBND 1ZZ",
4+
"country_id": 32,
5+
"country_code": "IO",
6+
"locality_name": "British Indian Ocean Territory",
7+
"type": "full",
8+
"source": "wikipedia-singleton-territory"
9+
}
10+
]

contributions/postcodes/NR.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[
2+
{
3+
"code": "NRU68",
4+
"country_id": 153,
5+
"country_code": "NR",
6+
"locality_name": "Nauru",
7+
"type": "full",
8+
"source": "wikipedia-singleton-territory"
9+
}
10+
]

contributions/postcodes/PN.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[
2+
{
3+
"code": "PCRN 1ZZ",
4+
"country_id": 175,
5+
"country_code": "PN",
6+
"locality_name": "Pitcairn Islands",
7+
"type": "full",
8+
"source": "wikipedia-singleton-territory"
9+
}
10+
]

contributions/postcodes/SH.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[
2+
{
3+
"code": "ASCN 1ZZ",
4+
"country_id": 184,
5+
"country_code": "SH",
6+
"locality_name": "Ascension Island",
7+
"type": "full",
8+
"source": "wikipedia-singleton-territory"
9+
},
10+
{
11+
"code": "STHL 1ZZ",
12+
"country_id": 184,
13+
"country_code": "SH",
14+
"locality_name": "Saint Helena",
15+
"type": "full",
16+
"source": "wikipedia-singleton-territory"
17+
},
18+
{
19+
"code": "TDCU 1ZZ",
20+
"country_id": 184,
21+
"country_code": "SH",
22+
"locality_name": "Tristan da Cunha",
23+
"type": "full",
24+
"source": "wikipedia-singleton-territory"
25+
}
26+
]

contributions/postcodes/TC.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[
2+
{
3+
"code": "TKCA 1ZZ",
4+
"country_id": 227,
5+
"country_code": "TC",
6+
"locality_name": "Turks and Caicos Islands",
7+
"type": "full",
8+
"source": "wikipedia-singleton-territory"
9+
}
10+
]

0 commit comments

Comments
 (0)