From 5f157532470baac8a5fcbaaec5249cbc83fd1edc Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 19 Jul 2026 02:08:52 +0200 Subject: [PATCH] Prefer persisted sources in map federation --- CHANGELOG.md | 4 ++ ...t_sprint237_flanders_thematic_on_demand.py | 2 +- docs/CODEX_EXECUTION_LOG.md | 13 ++++++ ...DATA_COVERAGE_ROADMAP_BELGIUM_NORTH_SEA.md | 30 +++++++++++-- docs/TODO.md | 2 +- frontend/src/components/map/MapWorkspace.tsx | 43 ++++++++++++------- .../components/map/mapWorkspaceUtils.test.ts | 15 +++++++ .../src/components/map/mapWorkspaceUtils.ts | 8 ++++ 8 files changed, 96 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddc6b2d6..509ddceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,10 @@ reported as an NGI administrative boundary layer. - Made bounded API coverage spatially honest: a persisted provider selection is operational only for selections contained by its retained source bbox. +- Kept reusable persisted datasets ahead of on-demand catalog placeholders in + the workbench, so national Statbel 2025 population data is no longer + mislabeled as a 2019 thematic raster. Selection-bounded SPW/UrbIS results + remain available through the exact on-demand acquisition path. ## Autonomous Belgium and North Sea RC program (2026-07-17) diff --git a/backend/tests/test_sprint237_flanders_thematic_on_demand.py b/backend/tests/test_sprint237_flanders_thematic_on_demand.py index 4fb343af..65dae822 100644 --- a/backend/tests/test_sprint237_flanders_thematic_on_demand.py +++ b/backend/tests/test_sprint237_flanders_thematic_on_demand.py @@ -49,7 +49,7 @@ def test_regional_on_demand_sources_require_a_bounded_drawn_selection() -> None: assert "regionalOnDemandThemeActive" in workspace assert "regionalRasterThemeActive || regionalOnDemandThemeActive" in workspace assert "Teken een begrensde rechthoek voor deze regionale analyse." in workspace - assert "Vlaamse kaartbronnen worden begrensd opgehaald, bewaard en hergebruikt" in workspace + assert "regionale kaartbronnen worden begrensd opgehaald, bewaard en hergebruikt" in workspace def test_frontend_does_not_contact_external_map_services_directly() -> None: diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index 968790d7..be3ee9f1 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -31,6 +31,19 @@ - Bounded provider materialization now also checks its persisted `bbox_epsg4326`; one small PICC/UrbIS/GRB/raster acquisition can no longer claim that an entire region is locally loaded. +- The live governed Statbel apply materialized the reviewed 2025 `NEW` edition + as Dataset `ee0a46e5-6139-4b0a-93b5-9dabb3b3dfc7`: 20,781 statistical + sectors, 565 municipalities, four repaired geometries and a reconciled + national total of 11,825,551 inhabitants (11,817,897 spatially located plus + 7,654 explicitly unlocated). +- Live bounded PICC and UrbIS persistence proved the same service path: + 54 Walloon building geometries retained in Dataset + `0c3c0a11-c00e-4c84-87be-8ffa64302dec` and 47 Brussels building geometries + in Dataset `de53ccc0-a494-4c4e-bd0d-16d807bfefe7`. +- Corrected the map catalog precedence so reusable persisted datasets win over + on-demand placeholders. National Statbel 2025 population is now presented + as the active source, while selection-bounded PICC/UrbIS acquisitions remain + available only through an exact bounded request. - Focused backend and frontend suites passed before full release validation; full local and Tower evidence follows in the final P5 gate. diff --git a/docs/POST_RC_DATA_COVERAGE_ROADMAP_BELGIUM_NORTH_SEA.md b/docs/POST_RC_DATA_COVERAGE_ROADMAP_BELGIUM_NORTH_SEA.md index 91197269..f35377fe 100644 --- a/docs/POST_RC_DATA_COVERAGE_ROADMAP_BELGIUM_NORTH_SEA.md +++ b/docs/POST_RC_DATA_COVERAGE_ROADMAP_BELGIUM_NORTH_SEA.md @@ -68,7 +68,7 @@ applicable live bounded journey fails. ## P0 - Existing national layers become usable -**State: implemented locally; live release evidence pending under P5.** +**State: complete; live national and maritime source journeys proven.** ### Work @@ -97,7 +97,7 @@ applicable live bounded journey fails. ## P1 - National Statbel population -**State: implementation complete; reviewed live national materialization pending.** +**State: complete; reviewed 2025 national edition materialized live.** ### Work @@ -120,9 +120,17 @@ applicable live bounded journey fails. from the same authoritative Statbel edition. - Mol remains within its documented baseline tolerance. +### Live evidence + +- Reviewed dataset: `ee0a46e5-6139-4b0a-93b5-9dabb3b3dfc7`. +- 20,781 persisted sectors and all 565 municipalities are represented. +- National reconciliation: 11,825,551 inhabitants, including 7,654 explicitly + retained as unlocated rather than silently assigned to geometry. +- A bounded Mol selection returns an explicitly labelled area-weighted estimate. + ## P2 - Wallonia bounded topographic baseline -**State: implemented locally; live Wallonia journey pending under P5.** +**State: complete; bounded Wallonia acquisition and analysis proven live.** ### Governed source @@ -158,9 +166,16 @@ Initial allowlisted layers: - Provider failures and record-limit truncation fail closed. - No direct provider-to-`vector_features` write exists. +### Live evidence + +- Dataset `0c3c0a11-c00e-4c84-87be-8ffa64302dec` retained 54 valid building + geometries for the bounded Wallonia smoke selection. +- The persisted selection reported 54 buildings and 0.525363 ha without + extending operational coverage beyond the retained bbox. + ## P3 - Brussels bounded UrbIS baseline -**State: implemented locally; live Brussels journey pending under P5.** +**State: complete; bounded Brussels acquisition and analysis proven live.** ### Governed source @@ -183,6 +198,13 @@ capabilities probe before implementation is marked operational. parcels. - Catalogue-only or failed capabilities evidence remains `not_configured`. +### Live evidence + +- Dataset `de53ccc0-a494-4c4e-bd0d-16d807bfefe7` retained 47 UrbIS building + geometries for the bounded Brussels smoke selection. +- The persisted selection reported 47 buildings and 0.528894 ha without + claiming coverage outside the retained bbox. + ## P4 - Maritime and bathymetry hardening **State: complete with MDK acquisition visibly blocked by strict-TLS evidence.** diff --git a/docs/TODO.md b/docs/TODO.md index 988e72a9..20593424 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -9,7 +9,7 @@ maritieme databronnen bruikbaar in de volgende versie. - [x] P0: bestaande NGI/RBINS-lagen als kaartthema en selectieanalyse ontsluiten. - [x] P0: readiness baseren op bruikbare analysethema's, niet alleen datasetaantal. -- [ ] P1: nationale Statbel-populatieflow veilig operationaliseren. +- [x] P1: nationale Statbel-populatieflow veilig operationaliseren. - [x] P2: bounded SPW PICC voor Waalse gebouwen, wegen en hydrographie. - [x] P3: bounded UrbIS voor Brusselse gebouwen en percelen. - [x] P4: maritieme thema's harden en bathymetrie fail-closed onderzoeken. diff --git a/frontend/src/components/map/MapWorkspace.tsx b/frontend/src/components/map/MapWorkspace.tsx index fbeb0154..56c8465e 100644 --- a/frontend/src/components/map/MapWorkspace.tsx +++ b/frontend/src/components/map/MapWorkspace.tsx @@ -27,6 +27,7 @@ import { getFeatureBBox, getFeatureCollectionBBox, getFeatureGeometrySummary, + isSelectionBoundedDataset, normalizeBboxFromCorners, operationalScopeProjectLabel, parseBboxInput, @@ -411,6 +412,9 @@ function datasetCoversSelectedArea( selectedAreaName: string | null | undefined, regionalScope = false, ): boolean { + if (isSelectionBoundedDataset(dataset.source_metadata)) { + return false + } const selectedZones = selectedAreaCoverageZones(selectedAreaName) const configuredZones = dataset.source_metadata?.['coverage_zones'] if (selectedZones && Array.isArray(configuredZones) && configuredZones.length > 0) { @@ -929,19 +933,25 @@ export function MapWorkspace({ } if (flandersScopeSelected && officialMapProducts.thematic.length > 0) { for (const product of officialMapProducts.thematic) { - result[product.theme] = null + if (!result[product.theme]) { + result[product.theme] = null + } } } if (flandersScopeSelected && officialMapProducts.grb.length > 0) { for (const product of officialMapProducts.grb) { - result[product.key] = null + if (!result[product.key]) { + result[product.key] = null + } } } if (officialMapProducts.officialVector.length > 0) { for (const product of officialMapProducts.officialVector.filter((item) => productCoversZones(item.coverage_zones, selectedCoverageZones), )) { - result[product.theme] = null + if (!result[product.theme]) { + result[product.theme] = null + } } } return result @@ -1077,7 +1087,9 @@ export function MapWorkspace({ } return result }, [onDemandProductsForZones, selectedCoverageZones]) - const activeOnDemandMapProduct = onDemandProductMap.get(activeTheme.id) ?? null + const activeOnDemandMapProduct = themeDatasetMap[activeTheme.id] + ? null + : onDemandProductMap.get(activeTheme.id) ?? null const activeThemeMapStyle = DATA_THEME_MAP_STYLES[activeTheme.id] const analysisOverlayActive = mapContentMode === 'analysis' && analysisLayerAvailable && Boolean(mapFeatureCollection) const selectedOrthophotoProduct = orthophotoProducts.find((item) => item.key === selectedOrthophotoProductKey) ?? null @@ -1665,6 +1677,16 @@ export function MapWorkspace({ : [] const availableThemes: Array> = [] for (const theme of DATA_THEMES) { + const dataset = themeDatasetMap[theme.id] + if (dataset) { + availableThemes.push({ + themeId: theme.id, + dataset, + partitioned: regionalScopeSelected + && (isPartitionedRaster(dataset) || isPartitionedBathymetry(dataset)), + }) + continue + } const onDemandProducts = resolvedProducts.filter((product) => product.theme === theme.id) if (onDemandProducts.length > 0) { for (const onDemandProduct of onDemandProducts) { @@ -1679,15 +1701,6 @@ export function MapWorkspace({ } continue } - const dataset = themeDatasetMap[theme.id] - if (dataset) { - availableThemes.push({ - themeId: theme.id, - dataset, - partitioned: regionalScopeSelected - && (isPartitionedRaster(dataset) || isPartitionedBathymetry(dataset)), - }) - } } await loadThemeInsights(bbox, availableThemes, areaId) } @@ -1992,7 +2005,7 @@ export function MapWorkspace({ {officialMapProductsLoading && selectedProjectId ? ( -

Beschikbare Vlaamse kaartbronnen worden gecontroleerd…

+

Beschikbare officiële regionale kaartbronnen worden gecontroleerd…

) : null} {officialMapProductsError && selectedProjectId ? (

{officialMapProductsError}

@@ -2154,7 +2167,7 @@ export function MapWorkspace({ : regionalRasterThemeActive ? 'Teken een rechthoek; de juiste gemeentelijke rasters worden automatisch gecombineerd.' : onDemandThemeActive - ? 'Teken een rechthoek; officiële Vlaamse kaartbronnen worden begrensd opgehaald, bewaard en hergebruikt.' + ? 'Teken een rechthoek; officiële regionale kaartbronnen worden begrensd opgehaald, bewaard en hergebruikt.' : regionalBathymetryThemeActive ? 'Teken een rechthoek of analyseer Vlaanderen; alleen overlappende VHA-partities worden samengevoegd.' : 'Sleep een rechthoek of analyseer het volledige werkgebied.'} diff --git a/frontend/src/components/map/mapWorkspaceUtils.test.ts b/frontend/src/components/map/mapWorkspaceUtils.test.ts index eb7fade0..e010acad 100644 --- a/frontend/src/components/map/mapWorkspaceUtils.test.ts +++ b/frontend/src/components/map/mapWorkspaceUtils.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from 'vitest' import { bboxesEqual, + isSelectionBoundedDataset, normalizeBboxFromCorners, parseBboxInput, productCoversZones, @@ -63,4 +64,18 @@ describe('map workspace selection guards', () => { expect(productCoversZones(['wallonia'], ['flanders', 'wallonia'])).toBe(true) expect(productCoversZones(['brussels'], ['wallonia'])).toBe(false) }) + + it('distinguishes selection-bounded acquisitions from reusable regional datasets', () => { + expect(isSelectionBoundedDataset({ + geometry_clipped_to_selection: true, + bbox_epsg4326: [4.55, 50.58, 4.56, 50.59], + })).toBe(true) + expect(isSelectionBoundedDataset({ + geometry_clipped_to_selection: false, + bbox_epsg4326: [2.5, 49.5, 6.4, 51.6], + })).toBe(false) + expect(isSelectionBoundedDataset({ + geometry_clipped_to_selection: true, + })).toBe(false) + }) }) diff --git a/frontend/src/components/map/mapWorkspaceUtils.ts b/frontend/src/components/map/mapWorkspaceUtils.ts index 14897476..def2a56e 100644 --- a/frontend/src/components/map/mapWorkspaceUtils.ts +++ b/frontend/src/components/map/mapWorkspaceUtils.ts @@ -35,6 +35,14 @@ export function productCoversZones(productZones: string[], selectedZones: string return selectedZones === null || selectedZones.some((zone) => productZones.includes(zone)) } +export function isSelectionBoundedDataset( + sourceMetadata: Record | null | undefined, +): boolean { + return sourceMetadata?.['geometry_clipped_to_selection'] === true + && Array.isArray(sourceMetadata?.['bbox_epsg4326']) + && sourceMetadata['bbox_epsg4326'].length === 4 +} + export function operationalScopeProjectLabel(project: ProjectRead): string { if (project.name === MOL_PROJECT_NAME) { return 'Mol'