Files
geointel/backend/tests/test_sprint201_semantic_selection_metrics.py
T
JensandClaude Opus 5 6572e4ad5f scope frontend contracts to the feature, not to one file
93 test files read a single frontend source and asserted identifiers in it. The
MapWorkspace split showed what that costs: 24 tests went red for a move that
changed no behaviour at all. A contract belongs to the feature — a container,
its hooks, its domain layer — not to whichever file currently holds it.

232 read sites now resolve through read_feature(). The distinction that makes
this safe is direction: a *positive* contract ("this is wired") may widen,
because the identifier must still exist somewhere in the feature; a *negative*
one ("this component performs no transport") is a statement about one file, and
widening it would quietly weaken the check. The 73 single-file reads that
remain are exactly those, and a guard now enforces the rule for new tests.

Verified rather than assumed: of the 732 migrated positive assertions, 644 still
match exactly one module — as specific as before — and the other 86 already
spanned a container and its hook by nature. Two apparent misses are an artefact
of the checking regex reading an escaped newline literally.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 22:05:43 +02:00

187 lines
6.6 KiB
Python

from __future__ import annotations
from pathlib import Path
from uuid import uuid4
from app.models import Dataset
from app.schemas.operations import VectorSelectionSummary
from app.services.vector_feature_service import VectorFeatureService
from tests.frontend_contract import read_feature
ROOT = Path(__file__).parents[2]
BBOX = {"min_x": 5.0, "min_y": 51.1, "max_x": 5.2, "max_y": 51.3, "crs": "EPSG:4326"}
class ScalarQuery:
def __init__(self, value: float):
self.value = value
def filter(self, *args): # noqa: ANN002, ARG002
return self
def scalar(self):
return self.value
class SequenceScalarSession:
"""Answers scalar queries in the order the summary issues them.
The first query is the fully-covered feature count that produces the
selection-edge disclosure; ``covered_count`` defaults to the full
population, i.e. a selection that cuts nothing.
"""
def __init__(self, values: list[float], covered_count: float | None = None):
self.values = iter(([covered_count] if covered_count is not None else []) + values)
def query(self, *args): # noqa: ANN002, ARG002
return ScalarQuery(next(self.values))
def themed_dataset(theme: str, *, method: str = "feature_count") -> Dataset:
return Dataset(
id=uuid4(),
project_id=uuid4(),
name=f"regional-{theme}.geojson",
dataset_type="vector",
dataset_role="reference",
source_name="grb",
reference_layer_name=theme,
source_metadata={
"theme": theme,
"selection_aggregation": {
"method": method,
"label": theme.title(),
"unit": "objecten",
},
},
)
def test_building_selection_promotes_footprint_area_and_retains_object_count() -> None:
result = VectorFeatureService.summarize_features_by_bbox(
SequenceScalarSession([125_000.0], covered_count=40),
dataset=themed_dataset("buildings"),
bbox=BBOX,
total_feature_count=40,
)
assert result["primary_metric_key"] == "footprint_area"
assert result["metric_label"] == "Bebouwde grondoppervlakte"
assert result["metric_value"] == 12.5
assert result["metric_unit"] == "ha"
assert [(item["metric_key"], item["metric_value"]) for item in result["metrics"]] == [
("footprint_area", 12.5),
("feature_count", 40.0),
]
assert "niet de totale vloeroppervlakte" in result["warning"]
VectorSelectionSummary(**result)
def test_water_selection_reports_surface_length_and_honest_volume_limitation() -> None:
result = VectorFeatureService.summarize_features_by_bbox(
SequenceScalarSession([52_500.0, 12_750.0], covered_count=23),
dataset=themed_dataset("water"),
bbox=BBOX,
total_feature_count=23,
)
assert result["metric_value"] == 5.25
assert result["metric_unit"] == "ha"
assert [(item["metric_key"], item["metric_value"], item["metric_unit"]) for item in result["metrics"]] == [
("water_area", 5.25, "ha"),
("watercourse_length", 12.75, "km"),
("feature_count", 23.0, "objecten"),
]
assert "Watervolume is niet berekenbaar" in result["warning"]
def test_population_keeps_configured_metric_and_adds_sector_count() -> None:
dataset = themed_dataset("population", method="sum")
dataset.source_metadata["selection_aggregation"].update(
{"metric_key": "population", "property": "population_total", "label": "Inwoners", "unit": "inwoners"}
)
result = VectorFeatureService.summarize_features_by_bbox(
SequenceScalarSession([86_458.0], covered_count=733),
dataset=dataset,
bbox=BBOX,
total_feature_count=733,
)
assert result["primary_metric_key"] == "population"
assert result["metric_value"] == 86_458.0
assert result["metrics"][1] == {
"metric_key": "feature_count",
"metric_label": "Statistische sectoren",
"metric_value": 733.0,
"metric_unit": "objecten",
"aggregation_method": "feature_count",
"is_estimate": False,
"warning": None,
}
def test_station_measurement_uses_numeric_mean_without_area_extrapolation() -> None:
dataset = themed_dataset("water", method="mean")
dataset.source_name = "waterinfo"
dataset.source_metadata.update(
{
"semantic_metrics": False,
"selection_aggregation": {
"metric_key": "water_level",
"method": "mean",
"property": "annual_mean_water_level_m",
"label": "Jaargemiddelde waterstand",
"unit": "m",
"warning": "Puntmeting; geen gebiedsdekkend watervolume.",
},
}
)
result = VectorFeatureService.summarize_features_by_bbox(
SequenceScalarSession([30.455], covered_count=1),
dataset=dataset,
bbox=BBOX,
total_feature_count=1,
)
assert result["metric_value"] == 30.455
assert result["aggregation_method"] == "mean"
assert result["metric_unit"] == "m"
assert result["warning"] == "Puntmeting; geen gebiedsdekkend watervolume."
def test_regional_historical_polygons_do_not_emit_irrelevant_line_metrics() -> None:
dataset = themed_dataset("water", method="intersection_area")
dataset.source_metadata["selection_aggregation"].update(
{"metric_key": "water_area", "label": "Historische wateroppervlakte", "unit": "ha"}
)
dataset.provenance_metadata = {"operator_tool": "provision_regional_historical_landuse.py"}
result = VectorFeatureService.summarize_features_by_bbox(
SequenceScalarSession([52_500.0], covered_count=23),
dataset=dataset,
bbox=BBOX,
total_feature_count=23,
)
assert [(item["metric_key"], item["metric_unit"]) for item in result["metrics"]] == [
("water_area", "ha"),
("feature_count", "objecten"),
]
def test_future_regional_imports_persist_semantic_aggregation_configuration() -> None:
buildings = (ROOT / "scripts/provision_regional_grb_buildings.py").read_text(encoding="utf-8")
context = (ROOT / "scripts/provision_regional_grb_context.py").read_text(encoding="utf-8")
frontend = read_feature("map_workspace")
assert '"method": "intersection_area"' in buildings
assert '"label": "Bebouwde grondoppervlakte"' in buildings
assert 'metric_method="intersection_length"' in context
assert 'metric_label="Wateroppervlakte"' in context
assert 'metric_label="Perceeloppervlakte"' in context
assert 'aria-label="Aanvullende gebiedsmetingen"' in frontend
assert "activeSelectionResult.summary.warning" in frontend