From 037f8755755e1837643809c4ac2cdc58479b4a70 Mon Sep 17 00:00:00 2001 From: Jens Date: Wed, 29 Jul 2026 17:23:36 +0200 Subject: [PATCH] Version rotated Belgian holdout corpora --- .../test_rotate_belgium_building_holdouts.py | 21 +++++++++++++++++++ scripts/rotate_belgium_building_holdouts.py | 11 +++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 backend/tests/test_rotate_belgium_building_holdouts.py diff --git a/backend/tests/test_rotate_belgium_building_holdouts.py b/backend/tests/test_rotate_belgium_building_holdouts.py new file mode 100644 index 00000000..1b353ae3 --- /dev/null +++ b/backend/tests/test_rotate_belgium_building_holdouts.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +import importlib.util +from pathlib import Path + +import pytest + + +SCRIPT = Path(__file__).parents[2] / "scripts" / "rotate_belgium_building_holdouts.py" +SPEC = importlib.util.spec_from_file_location("rotate_building_holdouts", SCRIPT) +assert SPEC and SPEC.loader +MODULE = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(MODULE) + + +def test_rotated_corpus_version_is_explicit_and_canonical() -> None: + assert MODULE.validate_dataset_version("building-be-v31-rotated-holdouts-r1") == ( + "building-be-v31-rotated-holdouts-r1" + ) + with pytest.raises(ValueError, match="canonical slug"): + MODULE.validate_dataset_version("Building BE v31") diff --git a/scripts/rotate_belgium_building_holdouts.py b/scripts/rotate_belgium_building_holdouts.py index 03ca5106..6c0bf7de 100644 --- a/scripts/rotate_belgium_building_holdouts.py +++ b/scripts/rotate_belgium_building_holdouts.py @@ -28,6 +28,13 @@ def parse_slugs(raw: str) -> set[str]: return {value.strip() for value in raw.split(",") if value.strip()} +def validate_dataset_version(value: str) -> str: + version = value.strip() + if not version or any(character not in "abcdefghijklmnopqrstuvwxyz0123456789-" for character in version): + raise ValueError("dataset version must be a lowercase canonical slug") + return version + + def write_json(path: Path, payload: dict[str, Any]) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(payload, indent=2), encoding="utf-8") @@ -64,6 +71,7 @@ def main() -> int: parser.add_argument("--test-samples", required=True) parser.add_argument("--background-samples", required=True) parser.add_argument("--internal-val-samples", required=True) + parser.add_argument("--version", required=True) parser.add_argument("--output-dir", type=Path, required=True) args = parser.parse_args() @@ -105,7 +113,7 @@ def main() -> int: raise SystemExit(f"manifest samples have no source tiles: {missing_tiles}") rotated_manifest = json.loads(json.dumps(manifest)) - rotated_manifest["dataset_version"] = "building-be-v30-rotated-holdouts-r1" + rotated_manifest["dataset_version"] = validate_dataset_version(args.version) rotated_manifest["immutable"] = True assignment = {slug: role for role, slugs in role_slugs.items() for slug in slugs} for sample in rotated_manifest["samples"]: @@ -171,6 +179,7 @@ def main() -> int: evidence = { "schema_version": 1, "status": "ok", "strategy": "fresh-former-train-holdout-rotation", "source_manifest": str(args.corpus_manifest), "source_manifest_sha256": sha256(args.corpus_manifest), + "dataset_version": rotated_manifest["dataset_version"], "source_summary_sha256": summary_hashes, "rotated_manifest": str(manifest_path), "rotated_manifest_sha256": sha256(manifest_path), "spatial_leakage_status": leakage["status"],