diff --git a/backend/tests/test_sprint199_reviewed_accuracy_expansion.py b/backend/tests/test_sprint199_reviewed_accuracy_expansion.py new file mode 100644 index 00000000..c4e62e7b --- /dev/null +++ b/backend/tests/test_sprint199_reviewed_accuracy_expansion.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +import importlib.util +import math +from pathlib import Path +import sys + + +ROOT = Path(__file__).resolve().parents[2] + + +def load_sample_preparer(): + script_path = ROOT / "scripts" / "prepare_operator_real_data_samples.py" + spec = importlib.util.spec_from_file_location("reviewed_accuracy_samples", script_path) + assert spec is not None + assert spec.loader is not None + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +def distance_m(left, right) -> float: + radius_m = 6_371_008.8 + left_lat = math.radians(left.center_lat) + right_lat = math.radians(right.center_lat) + delta_lat = right_lat - left_lat + delta_lon = math.radians(right.center_lon - left.center_lon) + haversine = ( + math.sin(delta_lat / 2) ** 2 + + math.cos(left_lat) * math.cos(right_lat) * math.sin(delta_lon / 2) ** 2 + ) + return 2 * radius_m * math.asin(math.sqrt(haversine)) + + +def test_reviewed_accuracy_expansion_is_training_only_and_holdout_separated() -> None: + module = load_sample_preparer() + expected = { + "arendonk_center", + "dessel_center", + "meerhout_center", + "laakdal_center", + "nijlen_center", + "hulshout_center", + } + protected_holdouts = { + "turnhout", + "retie", + "westerlo", + "vosselaar_center", + "grobbendonk_center", + *module.MOL_OPERATIONAL_VALIDATION_SAMPLE_SLUGS, + } + + assert module.REVIEWED_ACCURACY_EXPANSION_SAMPLE_SLUGS == frozenset(expected) + assert expected.isdisjoint(module.DEFAULT_VALIDATION_SAMPLE_SLUGS) + assert protected_holdouts.issubset(module.DEFAULT_VALIDATION_SAMPLE_SLUGS) + + for slug in expected: + sample = module.SAMPLES[slug] + assert sample.sample_role == "reference" + assert sample.allow_empty_reference is False + assert sample.operational_zone == "reviewed_accuracy_training" + assert sample.municipality + assert module.recommended_split_for_sample(sample) == "train" + assert min( + distance_m(sample, module.SAMPLES[holdout_slug]) + for holdout_slug in protected_holdouts + ) >= 2_000 + + +def test_reviewed_accuracy_expansion_centers_are_unique() -> None: + module = load_sample_preparer() + samples = [module.SAMPLES[slug] for slug in module.REVIEWED_ACCURACY_EXPANSION_SAMPLE_SLUGS] + centers = {(sample.center_lon, sample.center_lat) for sample in samples} + municipalities = {sample.municipality for sample in samples} + + assert len(centers) == len(samples) + assert len(municipalities) == len(samples) diff --git a/scripts/prepare_operator_real_data_samples.py b/scripts/prepare_operator_real_data_samples.py index f775810f..253a339f 100644 --- a/scripts/prepare_operator_real_data_samples.py +++ b/scripts/prepare_operator_real_data_samples.py @@ -31,6 +31,16 @@ TRAINING_EXPANSION_SAMPLE_SLUGS = frozenset( SMALL_BUILDING_TRAINING_SAMPLE_SLUGS = frozenset( {"beerse_center", "rijkevorsel_center", "hoogstraten_center", "vorselaar_center"} ) +REVIEWED_ACCURACY_EXPANSION_SAMPLE_SLUGS = frozenset( + { + "arendonk_center", + "dessel_center", + "meerhout_center", + "laakdal_center", + "nijlen_center", + "hulshout_center", + } +) SMALL_BUILDING_VALIDATION_SAMPLE_SLUGS = frozenset( {"vosselaar_center", "grobbendonk_center"} ) @@ -204,6 +214,54 @@ SAMPLES: dict[str, OperatorSample] = { center_lon=4.7731, center_lat=51.2020, ), + "arendonk_center": OperatorSample( + slug="arendonk_center", + display_name="Arendonk center reviewed accuracy expansion", + center_lon=5.0864557, + center_lat=51.3202315, + municipality="Arendonk", + operational_zone="reviewed_accuracy_training", + ), + "dessel_center": OperatorSample( + slug="dessel_center", + display_name="Dessel center reviewed accuracy expansion", + center_lon=5.1128221, + center_lat=51.2390765, + municipality="Dessel", + operational_zone="reviewed_accuracy_training", + ), + "meerhout_center": OperatorSample( + slug="meerhout_center", + display_name="Meerhout center reviewed accuracy expansion", + center_lon=5.0772388, + center_lat=51.1317433, + municipality="Meerhout", + operational_zone="reviewed_accuracy_training", + ), + "laakdal_center": OperatorSample( + slug="laakdal_center", + display_name="Laakdal center reviewed accuracy expansion", + center_lon=4.9552253, + center_lat=51.0801317, + municipality="Laakdal", + operational_zone="reviewed_accuracy_training", + ), + "nijlen_center": OperatorSample( + slug="nijlen_center", + display_name="Nijlen center reviewed accuracy expansion", + center_lon=4.6702859, + center_lat=51.1610023, + municipality="Nijlen", + operational_zone="reviewed_accuracy_training", + ), + "hulshout_center": OperatorSample( + slug="hulshout_center", + display_name="Hulshout center reviewed accuracy expansion", + center_lon=4.7885461, + center_lat=51.0753923, + municipality="Hulshout", + operational_zone="reviewed_accuracy_training", + ), "vosselaar_center": OperatorSample( slug="vosselaar_center", display_name="Vosselaar center small-building validation", @@ -753,6 +811,7 @@ def main() -> int: "reference_max_features": args.reference_max_features, "default_validation_sample_slugs": sorted(DEFAULT_VALIDATION_SAMPLE_SLUGS), "training_expansion_sample_slugs": sorted(TRAINING_EXPANSION_SAMPLE_SLUGS), + "reviewed_accuracy_expansion_sample_slugs": sorted(REVIEWED_ACCURACY_EXPANSION_SAMPLE_SLUGS), "samples": samples, } manifest_path = output_dir / args.manifest_name