fix: keep latest map dataset context
This commit is contained in:
@@ -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")
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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,28 +172,44 @@ 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) {
|
||||||
|
if (detailRequestId === datasetDetailRequestSequence.current) {
|
||||||
setDatasetDetailError(formatError(error, 'Unable to load dataset detail'))
|
setDatasetDetailError(formatError(error, 'Unable to load dataset detail'))
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
if (detailRequestId === datasetDetailRequestSequence.current) {
|
||||||
setLoadingDatasetDetails(false)
|
setLoadingDatasetDetails(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const uploadDataset = async (event: FormEvent) => {
|
const uploadDataset = async (event: FormEvent) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user