fix(map): honor raster coverage and temporal dates
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-22 00:18:55 +02:00
parent 80df5e70c9
commit 919f5879e5
9 changed files with 329 additions and 73 deletions
@@ -32,6 +32,37 @@ class TemporalAnalysisService:
"provision_regional_grb_context.py",
}
@staticmethod
def _canonical_observation_snapshots(datasets: list[Dataset]) -> list[Dataset]:
by_observation: dict[datetime, Dataset] = {}
for dataset in datasets:
if dataset.observed_at is None:
continue
current = by_observation.get(dataset.observed_at)
dataset_recency = max(
(
value.timestamp()
for value in (dataset.imported_at, dataset.updated_at, dataset.created_at)
if value is not None
),
default=0.0,
)
current_recency = max(
(
value.timestamp()
for value in (
getattr(current, "imported_at", None),
getattr(current, "updated_at", None),
getattr(current, "created_at", None),
)
if value is not None
),
default=0.0,
)
if current is None or (dataset_recency, str(dataset.id)) > (current_recency, str(current.id)):
by_observation[dataset.observed_at] = dataset
return sorted(by_observation.values(), key=lambda item: item.observed_at)
@staticmethod
def list_series(db: Session, project_id: UUID) -> list[TemporalSeriesRead]:
rows = (
@@ -49,6 +80,7 @@ class TemporalAnalysisService:
result: list[TemporalSeriesRead] = []
for key, datasets in grouped.items():
datasets = TemporalAnalysisService._canonical_observation_snapshots(datasets)
observed = [item.observed_at for item in datasets if item.observed_at is not None]
if not observed:
continue
@@ -118,6 +150,13 @@ class TemporalAnalysisService:
bbox,
selection_area.geometry,
)
def is_preclipped_to_selection_area(dataset: Dataset) -> bool:
return bool(
selection_area
and VectorFeatureService.can_use_full_area_fast_path(dataset, selection_area.id)
)
summaries: dict[UUID, dict[str, Any]] = {}
def summarize(dataset: Dataset) -> dict[str, Any]:
@@ -126,14 +165,9 @@ class TemporalAnalysisService:
return cached
kwargs: dict[str, Any] = {"dataset": dataset, "bbox": bbox}
if selection_area is not None:
kwargs["selection_geometry"] = selection_geometry
kwargs["full_dataset_area"] = (
selection_covers_full_area
and VectorFeatureService.can_use_full_area_fast_path(
dataset,
selection_area.id,
)
)
dataset_is_preclipped = is_preclipped_to_selection_area(dataset)
kwargs["selection_geometry"] = None if dataset_is_preclipped else selection_geometry
kwargs["full_dataset_area"] = selection_covers_full_area and dataset_is_preclipped
summary = VectorFeatureService.summarize_features_by_bbox(db, **kwargs)
summaries[dataset.id] = summary
return summary
@@ -164,16 +198,20 @@ class TemporalAnalysisService:
later=later,
bbox=bbox,
preview_limit=payload.preview_limit,
selection_geometry=selection_geometry,
selection_geometry=(
None
if is_preclipped_to_selection_area(earlier) and is_preclipped_to_selection_area(later)
else selection_geometry
),
earlier_full_dataset_area=(
selection_covers_full_area
and VectorFeatureService.can_use_full_area_fast_path(earlier, selection_area.id)
and is_preclipped_to_selection_area(earlier)
if selection_area is not None
else False
),
later_full_dataset_area=(
selection_covers_full_area
and VectorFeatureService.can_use_full_area_fast_path(later, selection_area.id)
and is_preclipped_to_selection_area(later)
if selection_area is not None
else False
),
@@ -298,8 +336,7 @@ class TemporalAnalysisService:
)
else:
datasets = fallback_datasets
unique = {dataset.id: dataset for dataset in datasets}
ordered = sorted(unique.values(), key=lambda item: item.observed_at or datetime.min.replace(tzinfo=timezone.utc))
ordered = TemporalAnalysisService._canonical_observation_snapshots(datasets)
observations: list[TemporalObservation] = []
for dataset in ordered:
if dataset.observed_at is None:
@@ -133,6 +133,20 @@ def temporal_dataset(*, project_id, observed_year: int, metric_method: str = "fe
)
def test_temporal_series_keeps_only_latest_snapshot_per_observation_date() -> None:
project_id = uuid4()
old = temporal_dataset(project_id=project_id, observed_year=2025)
old.imported_at = datetime(2026, 7, 19, tzinfo=timezone.utc)
latest = temporal_dataset(project_id=project_id, observed_year=2025)
latest.imported_at = datetime(2026, 7, 21, tzinfo=timezone.utc)
earlier = temporal_dataset(project_id=project_id, observed_year=2022)
earlier.imported_at = datetime(2026, 7, 21, tzinfo=timezone.utc)
canonical = TemporalAnalysisService._canonical_observation_snapshots([old, latest, earlier])
assert [dataset.id for dataset in canonical] == [earlier.id, latest.id]
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)
@@ -16,7 +16,7 @@ def test_flanders_workspace_exposes_governed_thematic_products_on_demand() -> No
assert "activeScopeProject?.name === FLANDERS_WORKSPACE_PROJECT_NAME" in workspace
assert "new Map<DataThemeId, OnDemandMapProduct>" in workspace
assert "'Op aanvraag'" in workspace
assert "'Automatisch'" in workspace
assert "theme.id === 'space_occupation'" in workspace
assert "setActiveThemeId(fallbackTheme.id)" in workspace
assert "return `referentiejaar ${observationYear}`" in workspace
@@ -391,6 +391,6 @@ def test_grb_frontend_and_contracts_use_only_the_governed_backend_path() -> None
assert "result[product.key] = null" in workspace
assert ": onDemandThemeActive\n ? null\n : mapFeatureCollection" in workspace
assert "onSetContextLayerLabel" in workspace
assert "'Op aanvraag'" in workspace
assert "'Automatisch'" in workspace
assert "/datasets/grb/acquire" in contracts
assert "geo.api.vlaanderen.be" not in workspace