fix: keep latest map dataset context
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-14 13:32:47 +02:00
parent b999c54e22
commit 50e7d0163b
4 changed files with 47 additions and 9 deletions
+1
View File
@@ -15,6 +15,7 @@
- Added true drag-to-select behavior in MapLibre and exact total intersection counts alongside the bounded 1,000-feature map preview. - Added true drag-to-select behavior in MapLibre and exact total intersection counts alongside the bounded 1,000-feature map preview.
- Made the official full Mol municipality area and the largest authoritative building layer the initial map context. - Made the official full Mol municipality area and the largest authoritative building layer the initial map context.
- Added an idempotent operator provisioner for official Mol GRB roads, water and parcels through the existing API/DatasetService/PostGIS persistence flow. - Added an idempotent operator provisioner for official Mol GRB roads, water and parcels through the existing API/DatasetService/PostGIS persistence flow.
- Prevented stale asynchronous dataset-detail responses from replacing the latest selected map layer and its visible context.
## Sprint 185 Coverage-aware Mol operational benchmark (2026-07-14) ## Sprint 185 Coverage-aware Mol operational benchmark (2026-07-14)
@@ -40,6 +40,16 @@ def test_map_rectangle_drag_is_wired_to_automatic_analysis() -> None:
assert "onMapBboxSelectRef.current?.(bbox)" in geomap assert "onMapBboxSelectRef.current?.(bbox)" in geomap
def test_dataset_detail_responses_cannot_overwrite_the_latest_map_layer() -> None:
workflow = read("frontend/src/hooks/useDatasetWorkflow.ts")
assert "const datasetDetailRequestSequence = useRef(0)" in workflow
assert "const detailRequestId = ++datasetDetailRequestSequence.current" in workflow
assert "detailRequestId !== datasetDetailRequestSequence.current" in workflow
assert "detailRequestId === datasetDetailRequestSequence.current" in workflow
assert "datasetDetailRequestSequence.current += 1" in workflow
def test_selection_contract_reports_total_intersections_separately_from_preview() -> None: def test_selection_contract_reports_total_intersections_separately_from_preview() -> None:
schema = read("backend/app/schemas/operations.py") schema = read("backend/app/schemas/operations.py")
service = read("backend/app/services/vector_feature_service.py") service = read("backend/app/services/vector_feature_service.py")
+7 -4
View File
@@ -7752,15 +7752,18 @@ Changed:
- Added exact `total_feature_count` to the vector bbox-selection response while retaining the existing 1,000-feature geometry cap. - Added exact `total_feature_count` to the vector bbox-selection response while retaining the existing 1,000-feature geometry cap.
- Added `provision_mol_context_layers.py` for official GRB roads (`Wegsegment`), water (`WTZ`, `WLAS`, `WGR`) and parcels (`ADP`) clipped to NIS 13025 and imported through the public dataset API. - Added `provision_mol_context_layers.py` for official GRB roads (`Wegsegment`), water (`WTZ`, `WLAS`, `WGR`) and parcels (`ADP`) clipped to NIS 13025 and imported through the public dataset API.
Validated so far: Validated:
- Frontend typecheck and production build passed. - Frontend typecheck and production build passed.
- Focused map, orchestration and new explorer tests passed. - Focused map, orchestration and new explorer tests passed.
- Official fetch-only smoke produced 8,444 Mol road features, 3,668 water features and 32,961 parcels with complete pagination and no truncation. - Official fetch-only smoke produced 8,444 Mol road features, 3,668 water features and 32,961 parcels with complete pagination and no truncation.
- Full backend suite reached 516 tests; one legacy component-boundary guard initially failed and was resolved by moving theme API orchestration into `useMapThemeSelectionInsights`. - Full backend suite passed 517 tests; one legacy component-boundary guard initially failed and was resolved by moving theme API orchestration into `useMapThemeSelectionInsights`.
- Tower was rebuilt from `main`; live PostGIS 3.6 connectivity, Alembic head `202606120900`, required geometry tables/indexes and browser proxy health all passed.
- The official provisioner persisted 8,444 roads, 3,668 water features and 32,961 parcels alongside the existing 36,941 GRB buildings for the complete Mol municipality.
- In-app browser validation of `Volledige gemeente` reported 114.55 km2, 36,941 buildings and a building density of 322.5/km2; the bounded preview clearly reported 1,000 of 36,941 features.
- Dataset-detail loading now ignores stale asynchronous responses, preventing a slower municipality-boundary request from overwriting the currently selected building layer or its map context.
Limitations: Limitations:
- Population and forest/green remain unavailable rather than simulated until suitable authoritative sources and semantics are selected. - Population and forest/green remain unavailable rather than simulated until suitable authoritative sources and semantics are selected.
- Live Tower import and browser verification follow after the full readiness gate and deployment.
Next: Next:
- Deploy, import the three official Mol context layers, validate rectangle analysis in the in-app browser and then define authoritative population and land-cover source adapters. - Define authoritative population and land-cover source adapters, then reuse the proven municipality provisioner and bbox analysis flow for the complete Kempen.
+29 -5
View File
@@ -1,4 +1,4 @@
import { FormEvent, useEffect, useMemo, useState } from 'react' import { FormEvent, useEffect, useMemo, useRef, useState } from 'react'
import { datasetsApi, jobsApi } from '../services/api' import { datasetsApi, jobsApi } from '../services/api'
import type { import type {
AreaRead, AreaRead,
@@ -99,6 +99,7 @@ export function useDatasetWorkflow({
const [ndbiNirBand, setNdbiNirBand] = useState(4) const [ndbiNirBand, setNdbiNirBand] = useState(4)
const [loadingDatasetDetails, setLoadingDatasetDetails] = useState(false) const [loadingDatasetDetails, setLoadingDatasetDetails] = useState(false)
const [datasetDetailError, setDatasetDetailError] = useState<string | null>(null) const [datasetDetailError, setDatasetDetailError] = useState<string | null>(null)
const datasetDetailRequestSequence = useRef(0)
const [datasetForm, setDatasetForm] = useState({ const [datasetForm, setDatasetForm] = useState({
datasetType: 'vector', datasetType: 'vector',
source: 'user_upload', source: 'user_upload',
@@ -139,8 +140,11 @@ export function useDatasetWorkflow({
} }
}, [datasets, isVectorDatasetType, selectedDatasetId, selectedProjectId]) }, [datasets, isVectorDatasetType, selectedDatasetId, selectedProjectId])
const loadDatasetJobs = async (projectId: string, datasetId: string) => { const loadDatasetJobs = async (projectId: string, datasetId: string, detailRequestId?: number) => {
const response = await jobsApi.list(projectId, { dataset_id: datasetId, limit: 20, offset: 0 }) const response = await jobsApi.list(projectId, { dataset_id: datasetId, limit: 20, offset: 0 })
if (detailRequestId != null && detailRequestId !== datasetDetailRequestSequence.current) {
return
}
setJobs(response.items) setJobs(response.items)
const latestRasterTileJob = response.items.find( const latestRasterTileJob = response.items.find(
(job) => job.job_type === 'raster.tile' && extractRasterTileManifestPath(job), (job) => job.job_type === 'raster.tile' && extractRasterTileManifestPath(job),
@@ -152,6 +156,7 @@ export function useDatasetWorkflow({
} }
const loadDatasetDetails = async (projectId: string, dataset: DatasetCreateResponse) => { const loadDatasetDetails = async (projectId: string, dataset: DatasetCreateResponse) => {
const detailRequestId = ++datasetDetailRequestSequence.current
setLoadingDatasetDetails(true) setLoadingDatasetDetails(true)
setDatasetDetailError(null) setDatasetDetailError(null)
setSelectedDataset(dataset) setSelectedDataset(dataset)
@@ -167,26 +172,42 @@ export function useDatasetWorkflow({
try { try {
if (isVectorDatasetType(dataset.dataset_type)) { if (isVectorDatasetType(dataset.dataset_type)) {
const summary = await datasetsApi.vectorSummary(projectId, dataset.id) const summary = await datasetsApi.vectorSummary(projectId, dataset.id)
if (detailRequestId !== datasetDetailRequestSequence.current) {
return
}
setSelectedDatasetSummary(summary) setSelectedDatasetSummary(summary)
const featureCount = summary.feature_count ?? dataset.feature_count const featureCount = summary.feature_count ?? dataset.feature_count
if (featureCount == null || featureCount <= VECTOR_VIEWPORT_FEATURE_THRESHOLD) { if (featureCount == null || featureCount <= VECTOR_VIEWPORT_FEATURE_THRESHOLD) {
const content = await datasetsApi.getContent(projectId, dataset.id) const content = await datasetsApi.getContent(projectId, dataset.id)
if (detailRequestId !== datasetDetailRequestSequence.current) {
return
}
setDatasetContent(content) setDatasetContent(content)
} }
} else if (dataset.dataset_type === 'raster') { } else if (dataset.dataset_type === 'raster') {
try { try {
const rasterInspection = await datasetsApi.rasterInspect(projectId, dataset.id) const rasterInspection = await datasetsApi.rasterInspect(projectId, dataset.id)
if (detailRequestId !== datasetDetailRequestSequence.current) {
return
}
setSelectedRasterMetadata(toRasterMetadata(rasterInspection.metadata)) setSelectedRasterMetadata(toRasterMetadata(rasterInspection.metadata))
} catch (error) { } catch (error) {
if (detailRequestId !== datasetDetailRequestSequence.current) {
return
}
setSelectedRasterMetadata(null) setSelectedRasterMetadata(null)
setDatasetDetailError(formatError(error, 'Raster metadata unavailable')) setDatasetDetailError(formatError(error, 'Raster metadata unavailable'))
} }
} }
await loadDatasetJobs(projectId, dataset.id) await loadDatasetJobs(projectId, dataset.id, detailRequestId)
} catch (error) { } catch (error) {
setDatasetDetailError(formatError(error, 'Unable to load dataset detail')) if (detailRequestId === datasetDetailRequestSequence.current) {
setDatasetDetailError(formatError(error, 'Unable to load dataset detail'))
}
} finally { } finally {
setLoadingDatasetDetails(false) if (detailRequestId === datasetDetailRequestSequence.current) {
setLoadingDatasetDetails(false)
}
} }
} }
@@ -497,6 +518,7 @@ export function useDatasetWorkflow({
} }
const resetDatasetForProject = () => { const resetDatasetForProject = () => {
datasetDetailRequestSequence.current += 1
setSelectedDatasetId(null) setSelectedDatasetId(null)
setSelectedDataset(null) setSelectedDataset(null)
setSelectedDatasetSummary(null) setSelectedDatasetSummary(null)
@@ -507,6 +529,8 @@ export function useDatasetWorkflow({
setLatestRasterTileManifestPath('') setLatestRasterTileManifestPath('')
setLatestRasterTileManifest(null) setLatestRasterTileManifest(null)
setJobs([]) setJobs([])
setLoadingDatasetDetails(false)
setDatasetDetailError(null)
} }
return { return {