"""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)