fix: isolate regional project context
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
- Added a central operator scope registry with current municipality names and NIS codes, including Mol `13025` and Nijlen `12026`.
|
- Added a central operator scope registry with current municipality names and NIS codes, including Mol `13025` and Nijlen `12026`.
|
||||||
- Added an explicit, idempotent VRBG scope provisioner that creates one regional boundary, 28 member boundaries, one regional Area, 28 municipality Areas and canonical source datasets through the existing API.
|
- Added an explicit, idempotent VRBG scope provisioner that creates one regional boundary, 28 member boundaries, one regional Area, 28 municipality Areas and canonical source datasets through the existing API.
|
||||||
- Added a compact Mol/Kempen region selector to the map-first explorer and made its heading and full-area action scope-neutral.
|
- Added a compact Mol/Kempen region selector to the map-first explorer and made its heading and full-area action scope-neutral.
|
||||||
|
- Made region switches clear all project-bound state, ignore stale project-data responses and prefer the regional boundary/AOI, preventing Mol context from leaking into the Kempen workbench.
|
||||||
- Kept thematic regional ingestion separate from the boundary pass so large GRB/WCS sources can be partitioned and validated without hidden startup fetches or truncated datasets.
|
- Kept thematic regional ingestion separate from the boundary pass so large GRB/WCS sources can be partitioned and validated without hidden startup fetches or truncated datasets.
|
||||||
|
|
||||||
## Sprint 188 Official modern Mol land-use series (2026-07-14)
|
## Sprint 188 Official modern Mol land-use series (2026-07-14)
|
||||||
|
|||||||
@@ -132,3 +132,17 @@ def test_kempen_scope_operator_is_packaged_and_exposed_in_map_flow() -> None:
|
|||||||
assert 'aria-label="Regio"' in workspace
|
assert 'aria-label="Regio"' in workspace
|
||||||
assert "projects={projects}" in app
|
assert "projects={projects}" in app
|
||||||
assert "onSelectProject={selectProject}" in app
|
assert "onSelectProject={selectProject}" in app
|
||||||
|
|
||||||
|
|
||||||
|
def test_project_switches_reset_scoped_state_and_prefer_the_regional_context() -> None:
|
||||||
|
bootstrap = (ROOT / "frontend/src/hooks/useWorkbenchBootstrap.ts").read_text(encoding="utf-8")
|
||||||
|
project_workspace = (ROOT / "frontend/src/hooks/useProjectWorkspace.ts").read_text(encoding="utf-8")
|
||||||
|
map_state = (ROOT / "frontend/src/hooks/useMapWorkspaceState.ts").read_text(encoding="utf-8")
|
||||||
|
dataset_workflow = (ROOT / "frontend/src/hooks/useDatasetWorkflow.ts").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
selected_project_branch = bootstrap.split("if (!selectedProjectId)", maxsplit=1)[1]
|
||||||
|
assert "resetProjectData()" in selected_project_branch
|
||||||
|
assert "resetDatasetForProject()" in selected_project_branch
|
||||||
|
assert "projectDataRequestSequence" in project_workspace
|
||||||
|
assert "vervoerregio|operationele grens" in map_state
|
||||||
|
assert "datasets.find(isOperationalScopeBoundaryDataset)" in dataset_workflow
|
||||||
|
|||||||
@@ -30,6 +30,14 @@ function toRasterMetadata(metadata: Record<string, unknown> | null | undefined):
|
|||||||
return metadata as unknown as RasterMetadataResponse
|
return metadata as unknown as RasterMetadataResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isOperationalScopeBoundaryDataset(dataset: DatasetCreateResponse): boolean {
|
||||||
|
return (
|
||||||
|
dataset.status === 'ready' &&
|
||||||
|
(dataset.dataset_type === 'vector' || dataset.dataset_type === 'geojson') &&
|
||||||
|
dataset.source_metadata?.layer_type === 'regional_boundary'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function extractRasterTileManifestPath(job: JobRead | null | undefined): string {
|
function extractRasterTileManifestPath(job: JobRead | null | undefined): string {
|
||||||
const manifest = toRasterTileHandoff(job)
|
const manifest = toRasterTileHandoff(job)
|
||||||
return manifest?.manifest_path ?? ''
|
return manifest?.manifest_path ?? ''
|
||||||
@@ -131,6 +139,7 @@ export function useDatasetWorkflow({
|
|||||||
}
|
}
|
||||||
// Auto-open the first usable dataset so Data, Map and Exports start with real context.
|
// Auto-open the first usable dataset so Data, Map and Exports start with real context.
|
||||||
const defaultDataset =
|
const defaultDataset =
|
||||||
|
datasets.find(isOperationalScopeBoundaryDataset) ??
|
||||||
datasets.find(isPrimaryFocusMunicipalityBoundaryDataset) ??
|
datasets.find(isPrimaryFocusMunicipalityBoundaryDataset) ??
|
||||||
datasets.find((dataset) => isVectorDatasetType(dataset.dataset_type) && dataset.status === 'ready') ??
|
datasets.find((dataset) => isVectorDatasetType(dataset.dataset_type) && dataset.status === 'ready') ??
|
||||||
datasets.find((dataset) => dataset.status === 'ready') ??
|
datasets.find((dataset) => dataset.status === 'ready') ??
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ export function useMapWorkspaceState({
|
|||||||
if (areas.length === 0) {
|
if (areas.length === 0) {
|
||||||
setSelectedMapAreaId('')
|
setSelectedMapAreaId('')
|
||||||
} else if (!selectedMapAreaId || !areas.some((area) => area.id === selectedMapAreaId)) {
|
} else if (!selectedMapAreaId || !areas.some((area) => area.id === selectedMapAreaId)) {
|
||||||
const municipalityArea = areas.find((area) => /gemeente mol|municipality/i.test(area.name))
|
const operationalScopeArea = areas.find((area) => /vervoerregio|operationele grens/i.test(area.name))
|
||||||
if (municipalityArea) {
|
if (operationalScopeArea) {
|
||||||
setSelectedMapAreaId(municipalityArea.id)
|
setSelectedMapAreaId(operationalScopeArea.id)
|
||||||
} else {
|
} else {
|
||||||
const largestArea = [...areas].sort((left, right) => (right.area_m2 ?? 0) - (left.area_m2 ?? 0))[0]
|
const largestArea = [...areas].sort((left, right) => (right.area_m2 ?? 0) - (left.area_m2 ?? 0))[0]
|
||||||
if (largestArea?.area_m2) {
|
if (largestArea?.area_m2) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FormEvent, useMemo, useState } from 'react'
|
import { FormEvent, useMemo, useRef, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
PRIMARY_FOCUS_AREA_GEOJSON,
|
PRIMARY_FOCUS_AREA_GEOJSON,
|
||||||
PRIMARY_FOCUS_AREA_NAME,
|
PRIMARY_FOCUS_AREA_NAME,
|
||||||
@@ -46,6 +46,7 @@ export function useProjectWorkspace() {
|
|||||||
const [loadingAreas, setLoadingAreas] = useState(false)
|
const [loadingAreas, setLoadingAreas] = useState(false)
|
||||||
const [loadingDatasets, setLoadingDatasets] = useState(false)
|
const [loadingDatasets, setLoadingDatasets] = useState(false)
|
||||||
const [errorMessage, setErrorMessage] = useState<string | null>(null)
|
const [errorMessage, setErrorMessage] = useState<string | null>(null)
|
||||||
|
const projectDataRequestSequence = useRef(0)
|
||||||
|
|
||||||
const [projectForm, setProjectForm] = useState<ProjectCreate>({
|
const [projectForm, setProjectForm] = useState<ProjectCreate>({
|
||||||
name: '',
|
name: '',
|
||||||
@@ -145,20 +146,28 @@ export function useProjectWorkspace() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadProjectData = async (projectId: string) => {
|
const loadProjectData = async (projectId: string) => {
|
||||||
|
const requestId = ++projectDataRequestSequence.current
|
||||||
setLoadingAreas(true)
|
setLoadingAreas(true)
|
||||||
setLoadingDatasets(true)
|
setLoadingDatasets(true)
|
||||||
setErrorMessage(null)
|
setErrorMessage(null)
|
||||||
try {
|
try {
|
||||||
const projectData = await fetchProjectData(projectId)
|
const projectData = await fetchProjectData(projectId)
|
||||||
|
if (requestId !== projectDataRequestSequence.current) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
setAreas(projectData.areas)
|
setAreas(projectData.areas)
|
||||||
setDatasets(projectData.datasets)
|
setDatasets(projectData.datasets)
|
||||||
return projectData
|
return projectData
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setErrorMessage(error instanceof Error ? error.message : 'Failed to load project data')
|
if (requestId === projectDataRequestSequence.current) {
|
||||||
|
setErrorMessage(error instanceof Error ? error.message : 'Failed to load project data')
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingAreas(false)
|
if (requestId === projectDataRequestSequence.current) {
|
||||||
setLoadingDatasets(false)
|
setLoadingAreas(false)
|
||||||
|
setLoadingDatasets(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,6 +218,7 @@ export function useProjectWorkspace() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resetProjectData = () => {
|
const resetProjectData = () => {
|
||||||
|
projectDataRequestSequence.current += 1
|
||||||
setAreas([])
|
setAreas([])
|
||||||
setDatasets([])
|
setDatasets([])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,11 @@ export function useWorkbenchBootstrap({
|
|||||||
resetExportsForProject()
|
resetExportsForProject()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
resetProjectData()
|
||||||
|
resetDatasetForProject()
|
||||||
|
resetDetectionForProject()
|
||||||
|
resetSegmentationForProject()
|
||||||
|
resetExportsForProject()
|
||||||
loadProjectData(selectedProjectId).catch(() => null)
|
loadProjectData(selectedProjectId).catch(() => null)
|
||||||
loadDetectionRuns(selectedProjectId).catch(() => null)
|
loadDetectionRuns(selectedProjectId).catch(() => null)
|
||||||
loadSegmentationRuns(selectedProjectId).catch(() => null)
|
loadSegmentationRuns(selectedProjectId).catch(() => null)
|
||||||
|
|||||||
Reference in New Issue
Block a user