Enable governed GRB evolution
This commit is contained in:
@@ -6,6 +6,8 @@ from types import SimpleNamespace
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from geoalchemy2.shape import from_shape
|
||||
from shapely.geometry import Polygon
|
||||
|
||||
from app.core.errors import AppError
|
||||
from app.models import Dataset, DatasetVersion
|
||||
@@ -46,6 +48,33 @@ class SequenceScalarSession:
|
||||
return ScalarQuery(next(self.values))
|
||||
|
||||
|
||||
class FeatureRowsQuery:
|
||||
def __init__(self, rows: list[object]):
|
||||
self.rows = rows
|
||||
self.row_limit: int | None = None
|
||||
|
||||
def filter(self, *args): # noqa: ANN002, ARG002
|
||||
return self
|
||||
|
||||
def order_by(self, *args): # noqa: ANN002, ARG002
|
||||
return self
|
||||
|
||||
def limit(self, value: int):
|
||||
self.row_limit = value
|
||||
return self
|
||||
|
||||
def all(self):
|
||||
return self.rows[: self.row_limit]
|
||||
|
||||
|
||||
class SequentialFeatureSession:
|
||||
def __init__(self, row_sets: list[list[object]]):
|
||||
self.row_sets = iter(row_sets)
|
||||
|
||||
def query(self, _model):
|
||||
return FeatureRowsQuery(next(self.row_sets))
|
||||
|
||||
|
||||
class VersionQuery:
|
||||
def __init__(self, latest: DatasetVersion | None):
|
||||
self.latest = latest
|
||||
@@ -104,6 +133,40 @@ def temporal_dataset(*, project_id, observed_year: int, metric_method: str = "fe
|
||||
)
|
||||
|
||||
|
||||
def governed_grb_dataset(*, project_id, observed_day: int) -> Dataset:
|
||||
dataset = temporal_dataset(project_id=project_id, observed_year=2026, metric_method="intersection_area")
|
||||
dataset.observed_at = datetime(2026, 7, observed_day, tzinfo=timezone.utc)
|
||||
dataset.source_version = f"2026-07-{observed_day:02d}"
|
||||
dataset.source_name = "grb"
|
||||
dataset.reference_layer_name = "buildings"
|
||||
dataset.temporal_series_key = "grb:buildings:kempen-transport-region"
|
||||
dataset.source_metadata = {
|
||||
"authority_level": "authoritative",
|
||||
"geometry_clipped_to_area": True,
|
||||
"collection": "GRB/GBG",
|
||||
"selection_aggregation": {
|
||||
"method": "intersection_area",
|
||||
"label": "Bebouwde grondoppervlakte",
|
||||
"unit": "ha",
|
||||
},
|
||||
}
|
||||
dataset.provenance_metadata = {
|
||||
"operator_tool": "provision_regional_grb_buildings.py",
|
||||
"reference_truncated": False,
|
||||
}
|
||||
return dataset
|
||||
|
||||
|
||||
def persisted_feature(dataset_id, source_feature_id: str | None, polygon: Polygon):
|
||||
return SimpleNamespace(
|
||||
id=uuid4(),
|
||||
dataset_id=dataset_id,
|
||||
source_feature_id=source_feature_id,
|
||||
properties_json={},
|
||||
geometry=from_shape(polygon, srid=4326),
|
||||
)
|
||||
|
||||
|
||||
def test_temporal_migration_and_models_align() -> None:
|
||||
migration = (ROOT / "backend/alembic/versions/202607140001_temporal_dataset_foundation.py").read_text(encoding="utf-8")
|
||||
for field in (
|
||||
@@ -294,6 +357,63 @@ def test_unstable_temporal_identity_returns_clear_end_user_warning() -> None:
|
||||
assert warnings == ["Wijzigingen van individuele objecten kunnen voor deze bron niet betrouwbaar worden gevolgd."]
|
||||
|
||||
|
||||
def test_governed_legacy_grb_snapshots_use_verified_official_feature_identity() -> None:
|
||||
project_id = uuid4()
|
||||
earlier = governed_grb_dataset(project_id=project_id, observed_day=14)
|
||||
later = governed_grb_dataset(project_id=project_id, observed_day=15)
|
||||
original = Polygon([(5.1, 51.1), (5.101, 51.1), (5.101, 51.101), (5.1, 51.101)])
|
||||
changed = Polygon([(5.1, 51.1), (5.102, 51.1), (5.102, 51.101), (5.1, 51.101)])
|
||||
added = Polygon([(5.11, 51.11), (5.111, 51.11), (5.111, 51.111), (5.11, 51.111)])
|
||||
session = SequentialFeatureSession(
|
||||
[
|
||||
[persisted_feature(earlier.id, "GBG.1", original)],
|
||||
[persisted_feature(later.id, "GBG.1", changed), persisted_feature(later.id, "GBG.2", added)],
|
||||
]
|
||||
)
|
||||
|
||||
changes, geojson, warnings = TemporalAnalysisService._compare_identity_features(
|
||||
session,
|
||||
earlier=earlier,
|
||||
later=later,
|
||||
bbox={"min_x": 5.0, "min_y": 51.0, "max_x": 5.2, "max_y": 51.2},
|
||||
preview_limit=100,
|
||||
)
|
||||
|
||||
assert changes.available is True
|
||||
assert changes.added_count == 1
|
||||
assert changes.removed_count == 0
|
||||
assert changes.modified_count == 1
|
||||
assert changes.unchanged_count == 0
|
||||
assert {feature["properties"]["change_type"] for feature in geojson["features"]} == {"added", "modified"}
|
||||
assert warnings == []
|
||||
|
||||
|
||||
def test_governed_grb_object_history_fails_closed_for_unverified_identity() -> None:
|
||||
project_id = uuid4()
|
||||
earlier = governed_grb_dataset(project_id=project_id, observed_day=14)
|
||||
later = governed_grb_dataset(project_id=project_id, observed_day=15)
|
||||
polygon = Polygon([(5.1, 51.1), (5.101, 51.1), (5.101, 51.101), (5.1, 51.101)])
|
||||
fallback_hash = "a" * 64
|
||||
session = SequentialFeatureSession(
|
||||
[
|
||||
[persisted_feature(earlier.id, fallback_hash, polygon)],
|
||||
[persisted_feature(later.id, fallback_hash, polygon)],
|
||||
]
|
||||
)
|
||||
|
||||
changes, geojson, warnings = TemporalAnalysisService._compare_identity_features(
|
||||
session,
|
||||
earlier=earlier,
|
||||
later=later,
|
||||
bbox={"min_x": 5.0, "min_y": 51.0, "max_x": 5.2, "max_y": 51.2},
|
||||
preview_limit=100,
|
||||
)
|
||||
|
||||
assert changes.available is False
|
||||
assert geojson["features"] == []
|
||||
assert warnings == ["De geselecteerde objecten bevatten geen volledig verifieerbare stabiele bronidentiteit."]
|
||||
|
||||
|
||||
def test_temporal_frontend_and_official_operator_contracts_exist() -> None:
|
||||
workspace = (ROOT / "frontend/src/components/map/MapWorkspace.tsx").read_text(encoding="utf-8")
|
||||
temporal_api = (ROOT / "frontend/src/services/api/temporal.ts").read_text(encoding="utf-8")
|
||||
@@ -304,6 +424,8 @@ def test_temporal_frontend_and_official_operator_contracts_exist() -> None:
|
||||
assert "Laatste toestand" in workspace
|
||||
assert "Evolutie" in workspace
|
||||
assert "Vergelijk periode" in workspace
|
||||
assert "temporalRangeLabel" in workspace
|
||||
assert "Dagelijkse GRB-edities tonen wijzigingen in de officiële registratie" in workspace
|
||||
assert "/temporal/compare" in temporal_api
|
||||
assert "Statbel" in population and "area_weighted_sum" in population
|
||||
assert '"identity_stable": False' in population
|
||||
|
||||
Reference in New Issue
Block a user