Files
geointel/scripts/geographic_scopes.py
T
Codex 50897c3473
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s
Federate official Belgium data sources
2026-07-19 01:34:39 +02:00

143 lines
5.1 KiB
Python

"""Canonical operator scope definitions for GeoIntel.
These definitions describe administrative/policy scopes only. They do not
claim that a policy boundary equals a cultural or physical landscape region.
"""
from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True)
class ScopeMember:
name: str
nis_code: str
@dataclass(frozen=True)
class GeographicScope:
key: str
display_name: str
project_name: str
project_region: str
area_name: str
authority_name: str
authority_url: str
scope_type: str
limitation_message: str
members: tuple[ScopeMember, ...]
all_municipalities: bool = False
@property
def nis_codes(self) -> tuple[str, ...]:
return tuple(member.nis_code for member in self.members)
MOL_SCOPE = GeographicScope(
key="mol",
display_name="Mol",
project_name="Mol Municipality Workbench",
project_region="Mol, Kempen",
area_name="Gemeente Mol - officiële grens",
authority_name="Digitaal Vlaanderen VRBG",
authority_url="https://geo.api.vlaanderen.be/VRBG/ogc/features/v1/collections/Refgem/items",
scope_type="municipality",
limitation_message="Officiële gemeentegrens; dit is geen perceelsgrens.",
members=(ScopeMember("Mol", "13025"),),
)
KEMPEN_TRANSPORT_REGION_SCOPE = GeographicScope(
key="kempen-transport-region",
display_name="Kempen (28 gemeenten)",
project_name="Kempen Regional Workbench",
project_region="Vervoerregio Kempen, Vlaanderen",
area_name="Vervoerregio Kempen - officiële operationele grens",
authority_name="Vlaamse overheid - Vervoerregio Kempen",
authority_url=(
"https://www.vlaanderen.be/mobiliteitsprofessionals/personenvervoer/"
"basisbereikbaarheid/mobiliteitsuitdagingen-regionaal-aanpakken/"
"vervoerregios/over-de-vervoerregio-kempen"
),
scope_type="transport_region",
limitation_message=(
"Operationele beleidsgrens van de Vlaamse vervoerregio Kempen; "
"geen claim over de ruimere culturele, landschappelijke of historische Kempen."
),
members=(
ScopeMember("Arendonk", "13001"),
ScopeMember("Baarle-Hertog", "13002"),
ScopeMember("Balen", "13003"),
ScopeMember("Beerse", "13004"),
ScopeMember("Dessel", "13006"),
ScopeMember("Geel", "13008"),
ScopeMember("Grobbendonk", "13010"),
ScopeMember("Herentals", "13011"),
ScopeMember("Herenthout", "13012"),
ScopeMember("Herselt", "13013"),
ScopeMember("Hoogstraten", "13014"),
ScopeMember("Hulshout", "13016"),
ScopeMember("Kasterlee", "13017"),
ScopeMember("Laakdal", "13053"),
ScopeMember("Lille", "13019"),
ScopeMember("Meerhout", "13021"),
ScopeMember("Merksplas", "13023"),
ScopeMember("Mol", "13025"),
ScopeMember("Nijlen", "12026"),
ScopeMember("Olen", "13029"),
ScopeMember("Oud-Turnhout", "13031"),
ScopeMember("Ravels", "13035"),
ScopeMember("Retie", "13036"),
ScopeMember("Rijkevorsel", "13037"),
ScopeMember("Turnhout", "13040"),
ScopeMember("Vorselaar", "13044"),
ScopeMember("Vosselaar", "13046"),
ScopeMember("Westerlo", "13049"),
),
)
BELGIUM_SCOPE = GeographicScope(
key="belgium",
display_name="België",
project_name="Belgium and North Sea Workbench",
project_region="Belgium and Belgian North Sea",
area_name="Belgium land",
authority_name="National Geographic Institute (NGI), AdminVector",
authority_url="https://www.geo.be/catalog/details/fb1e2993-2020-428c-9188-eb5f75e284b9",
scope_type="country",
limitation_message=(
"De nationale Statbel-sectorlaag dekt het Belgische landgebied. "
"Belgische maritieme zones bevatten geen statistische bevolkingssectoren."
),
members=(),
all_municipalities=True,
)
GEOGRAPHIC_SCOPES = {
scope.key: scope
for scope in (MOL_SCOPE, KEMPEN_TRANSPORT_REGION_SCOPE, BELGIUM_SCOPE)
}
def validate_scope(scope: GeographicScope) -> None:
if not scope.key or not scope.project_name or not scope.area_name:
raise ValueError("Geographic scope identity fields must not be empty")
if len(scope.members) == 0 and not scope.all_municipalities:
raise ValueError(f"Geographic scope {scope.key} has no members")
if scope.all_municipalities and scope.scope_type != "country":
raise ValueError(f"Geographic scope {scope.key} can include all municipalities only for a country scope")
names = [member.name.casefold() for member in scope.members]
codes = [member.nis_code for member in scope.members]
if len(names) != len(set(names)):
raise ValueError(f"Geographic scope {scope.key} contains duplicate municipality names")
if len(codes) != len(set(codes)):
raise ValueError(f"Geographic scope {scope.key} contains duplicate NIS codes")
if any(len(code) != 5 or not code.isdigit() for code in codes):
raise ValueError(f"Geographic scope {scope.key} contains an invalid NIS code")
for _scope in GEOGRAPHIC_SCOPES.values():
validate_scope(_scope)