From ae3d3dc6348c72b72d1a3e04bedfa2883c51f442 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 19 Jul 2026 03:31:46 +0200 Subject: [PATCH] Accept official Statbel geometry date format --- ...test_sprint227_statbel_population_preflight.py | 8 ++++++++ scripts/statbel_population_preflight.py | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/backend/tests/test_sprint227_statbel_population_preflight.py b/backend/tests/test_sprint227_statbel_population_preflight.py index 6ad625cb..3f2e1b85 100644 --- a/backend/tests/test_sprint227_statbel_population_preflight.py +++ b/backend/tests/test_sprint227_statbel_population_preflight.py @@ -266,6 +266,13 @@ def test_preflight_reports_bounded_topology_repairs(tmp_path: Path) -> None: assert result.geometry.payload["features"][0]["geometry"]["type"] == "Polygon" +def test_preflight_accepts_official_slash_geometry_date(tmp_path: Path) -> None: + result = validate(tmp_path, geometry_content=geometry_archive(date="2025/01/01")) + + assert len(result.geometry.payload["features"]) == 3 + assert result.manifest["import_eligible"] is True + + @pytest.mark.parametrize( ("population_kwargs", "error_code"), [ @@ -292,6 +299,7 @@ def test_preflight_rejects_population_contract_breaks( [ ({"crs": "EPSG:4326"}, "STATBEL_GEOMETRY_CRS_REJECTED"), ({"date": "2026-01-01"}, "STATBEL_GEOMETRY_DATE_MISMATCH"), + ({"date": "2025/13/01"}, "STATBEL_GEOMETRY_DATE_MISMATCH"), ({"municipality_mismatch": True}, "STATBEL_JOIN_MUNICIPALITY_MISMATCH"), ], ) diff --git a/scripts/statbel_population_preflight.py b/scripts/statbel_population_preflight.py index 97174bcb..67591f67 100644 --- a/scripts/statbel_population_preflight.py +++ b/scripts/statbel_population_preflight.py @@ -59,6 +59,7 @@ GEOMETRY_MEMBER_PATTERN = re.compile( r"^sh_statbel_statistical_sectors_(?:31370_)?(20[0-9]{2})0101\.geojson$", re.IGNORECASE, ) +GEOMETRY_DATE_PATTERN = re.compile(r"^(20[0-9]{2})([-/])([0-9]{2})\2([0-9]{2})$") POPULATION_SOURCE_PATTERN = re.compile( r"^/sites/default/files/files/opendata/bevolking/sectoren/" r"OPENDATA_SECTOREN_(20[0-9]{2})(?:_(NEW|OLD))?\.zip$", @@ -123,6 +124,18 @@ def _layout_from_variant(variant: str | None) -> str: return variant.lower() +def _normalize_geometry_date(value: Any) -> str | None: + raw_value = str(value or "").strip() + match = GEOMETRY_DATE_PATTERN.fullmatch(raw_value) + if not match: + return None + normalized = f"{match.group(1)}-{match.group(3)}-{match.group(4)}" + try: + return datetime.strptime(normalized, "%Y-%m-%d").date().isoformat() + except ValueError: + return None + + def _validate_source_url(url: str, pattern: re.Pattern[str], *, code: str) -> re.Match[str]: parsed = urlsplit(url) match = pattern.fullmatch(parsed.path) @@ -428,7 +441,7 @@ def parse_geometry_archive(content: bytes, year: int) -> GeometryArchiveData: "Geometry artifact contains duplicate sector codes.", sector_code=sector_code, ) - if str(properties.get("dt_situation") or "") != expected_date: + if _normalize_geometry_date(properties.get("dt_situation")) != expected_date: _fail( "STATBEL_GEOMETRY_DATE_MISMATCH", "Geometry situation date does not match the population reference year.",