extract and test the four-step GIS workflow
Extracted for its error paths, not its size. Every step can fail or return nothing, and each outcome has to leave the operator with a status that says where the chain stopped — a workflow reporting "afgerond" after a step produced nothing tells them a result exists when it does not. Inline in the component, none of that was exercised; it now has fourteen tests covering each stopping point, both rejection kinds, the reuse path and the preconditions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ MAP_WORKSPACE_SOURCES = (
|
|||||||
"components/map/mapWorkspaceUtils.ts",
|
"components/map/mapWorkspaceUtils.ts",
|
||||||
"hooks/useMapImageOverlays.ts",
|
"hooks/useMapImageOverlays.ts",
|
||||||
"hooks/useMapRectangleSelection.ts",
|
"hooks/useMapRectangleSelection.ts",
|
||||||
|
"hooks/useFullGisWorkflow.ts",
|
||||||
"components/map/MapExplorerView.tsx",
|
"components/map/MapExplorerView.tsx",
|
||||||
"components/map/MapAdvancedWorkbench.tsx",
|
"components/map/MapAdvancedWorkbench.tsx",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from tests.frontend_contract import read_map_workspace
|
||||||
|
|
||||||
|
|
||||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||||
@@ -19,7 +20,7 @@ def test_map_uses_road_basemap_with_attribution_and_env_override() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_map_workspace_can_select_persisted_database_layer_and_run_query() -> None:
|
def test_map_workspace_can_select_persisted_database_layer_and_run_query() -> None:
|
||||||
map_workspace = (REPO_ROOT / "frontend/src/components/map/MapWorkspace.tsx").read_text(encoding="utf-8")
|
map_workspace = read_map_workspace()
|
||||||
app_shell = (REPO_ROOT / "frontend/src/App.tsx").read_text(encoding="utf-8")
|
app_shell = (REPO_ROOT / "frontend/src/App.tsx").read_text(encoding="utf-8")
|
||||||
styles = (REPO_ROOT / "frontend/src/styles/app.css").read_text(encoding="utf-8")
|
styles = (REPO_ROOT / "frontend/src/styles/app.css").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useOfficialMapProducts } from '../../hooks/useOfficialMapProducts'
|
|||||||
import { useTemporalComparison } from '../../hooks/useTemporalComparison'
|
import { useTemporalComparison } from '../../hooks/useTemporalComparison'
|
||||||
import { isValueRampRasterSource, useMapImageOverlays } from '../../hooks/useMapImageOverlays'
|
import { isValueRampRasterSource, useMapImageOverlays } from '../../hooks/useMapImageOverlays'
|
||||||
import { useMapRectangleSelection } from '../../hooks/useMapRectangleSelection'
|
import { useMapRectangleSelection } from '../../hooks/useMapRectangleSelection'
|
||||||
|
import { useFullGisWorkflow } from '../../hooks/useFullGisWorkflow'
|
||||||
import { getDatasetDisplayName, getDatasetSourceDisplayName } from '../../lib/datasetDisplay'
|
import { getDatasetDisplayName, getDatasetSourceDisplayName } from '../../lib/datasetDisplay'
|
||||||
import { TemporalTrendChart } from './TemporalTrendChart'
|
import { TemporalTrendChart } from './TemporalTrendChart'
|
||||||
import { MunicipalitySearch } from './MunicipalitySearch'
|
import { MunicipalitySearch } from './MunicipalitySearch'
|
||||||
@@ -324,10 +325,6 @@ export function MapWorkspace({
|
|||||||
bboxInput,
|
bboxInput,
|
||||||
setBboxInput,
|
setBboxInput,
|
||||||
} = rectangle
|
} = rectangle
|
||||||
const [fullWorkflowRunning, setFullWorkflowRunning] = useState(false)
|
|
||||||
const [fullWorkflowStatus, setFullWorkflowStatus] = useState('Klaar om de volledige GIS-werkstroom uit te voeren.')
|
|
||||||
const [fullWorkflowError, setFullWorkflowError] = useState<string | null>(null)
|
|
||||||
const [fullWorkflowMode, setFullWorkflowMode] = useState<'new' | 'reuse'>('new')
|
|
||||||
const [mapAnalysisDurationMs, setMapAnalysisDurationMs] = useState<number | null>(null)
|
const [mapAnalysisDurationMs, setMapAnalysisDurationMs] = useState<number | null>(null)
|
||||||
const mapAnalysisRequestSequence = useRef(0)
|
const mapAnalysisRequestSequence = useRef(0)
|
||||||
const regionalScopeSelected = Boolean(selectedMapArea && !isMunicipalityAreaName(selectedMapArea.name))
|
const regionalScopeSelected = Boolean(selectedMapArea && !isMunicipalityAreaName(selectedMapArea.name))
|
||||||
@@ -1463,75 +1460,26 @@ export function MapWorkspace({
|
|||||||
void analyzeSelection(bbox, areaIdForSelection(bbox))
|
void analyzeSelection(bbox, areaIdForSelection(bbox))
|
||||||
}
|
}
|
||||||
|
|
||||||
const runFullGisWorkflow = async () => {
|
const fullWorkflow = useFullGisWorkflow({
|
||||||
const bbox = currentSelectionBbox ?? selectedAreaBbox ?? activeLayerBbox
|
selectedDataset: selectedMapDataset,
|
||||||
if (fullWorkflowMode === 'reuse') {
|
latestSelectionDataset,
|
||||||
if (!latestSelectionDataset) {
|
qaReferenceDatasetId: selectedMapQaReferenceDatasetId,
|
||||||
setFullWorkflowError('Bewaar eerst een kaartselectie voordat je het laatste resultaat opnieuw gebruikt.')
|
resolveBbox: () => currentSelectionBbox ?? selectedAreaBbox ?? activeLayerBbox,
|
||||||
return
|
resolveAreaId: areaIdForSelection,
|
||||||
}
|
setSelectionBbox,
|
||||||
if (!selectedMapQaReferenceDatasetId) {
|
onRunSelectionExtract: onRunMapSelectionExtract,
|
||||||
setFullWorkflowError('Kies eerst een referentielaag voor de kwaliteitscontrole.')
|
onDeriveDataset: onDeriveMapSelectionDataset,
|
||||||
return
|
onExportSelection: onExportMapSelection,
|
||||||
}
|
onRunQa: onRunMapSelectionQa,
|
||||||
setFullWorkflowRunning(true)
|
})
|
||||||
setFullWorkflowError(null)
|
const {
|
||||||
try {
|
running: fullWorkflowRunning,
|
||||||
setFullWorkflowStatus('Laatste bewaarde resultaatlaag opnieuw controleren...')
|
status: fullWorkflowStatus,
|
||||||
const qaResult = await onRunMapSelectionQa(latestSelectionDataset)
|
error: fullWorkflowError,
|
||||||
setFullWorkflowStatus(qaResult ? 'Het laatste bewaarde resultaat is opnieuw gebruikt en gecontroleerd.' : 'Het laatste resultaat is gebruikt, maar de kwaliteitscontrole is niet afgerond.')
|
mode: fullWorkflowMode,
|
||||||
} catch (error) {
|
setMode: setFullWorkflowMode,
|
||||||
setFullWorkflowError(error instanceof Error ? error.message : 'Full GIS workflow failed.')
|
run: runFullGisWorkflow,
|
||||||
setFullWorkflowStatus('De werkstroom is gestopt.')
|
} = fullWorkflow
|
||||||
} finally {
|
|
||||||
setFullWorkflowRunning(false)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!selectedMapDataset || !bbox) {
|
|
||||||
setFullWorkflowError('Kies een kaartlaag en een werkgebied of laagbegrenzing.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setFullWorkflowRunning(true)
|
|
||||||
setFullWorkflowError(null)
|
|
||||||
try {
|
|
||||||
setFullWorkflowStatus('1/4 Bewaarde kaartobjecten selecteren...')
|
|
||||||
setSelectionBbox(bbox)
|
|
||||||
const selectionAreaId = areaIdForSelection(bbox)
|
|
||||||
const selection = await onRunMapSelectionExtract(bbox, selectionAreaId)
|
|
||||||
if (!selection) {
|
|
||||||
setFullWorkflowError('De ruimtelijke selectie kon niet worden afgerond.')
|
|
||||||
setFullWorkflowStatus('Stopped at query.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setFullWorkflowStatus('2/4 Saving derived result dataset...')
|
|
||||||
const derived = await onDeriveMapSelectionDataset(bbox, selectionAreaId)
|
|
||||||
if (!derived) {
|
|
||||||
setFullWorkflowError('De afgeleide resultaatlaag kon niet worden aangemaakt.')
|
|
||||||
setFullWorkflowStatus('Stopped at dataset save.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setFullWorkflowStatus('3/4 Saving GeoJSON export artifact...')
|
|
||||||
await onExportMapSelection(bbox, selectionAreaId)
|
|
||||||
|
|
||||||
if (selectedMapQaReferenceDatasetId) {
|
|
||||||
setFullWorkflowStatus('4/4 Kwaliteit vergelijken met de gekozen referentielaag...')
|
|
||||||
const qaResult = await onRunMapSelectionQa(derived)
|
|
||||||
setFullWorkflowStatus(qaResult ? 'De volledige GIS-werkstroom en kwaliteitscontrole zijn afgerond.' : 'Resultaat en download zijn gereed; de kwaliteitscontrole is niet afgerond.')
|
|
||||||
} else {
|
|
||||||
setFullWorkflowStatus('Resultaat en download zijn gereed. Kies een referentielaag om de kwaliteit te controleren.')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setFullWorkflowError(error instanceof Error ? error.message : 'Full GIS workflow failed.')
|
|
||||||
setFullWorkflowStatus('De werkstroom is gestopt.')
|
|
||||||
} finally {
|
|
||||||
setFullWorkflowRunning(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!advancedMode) {
|
if (!advancedMode) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
import { act, renderHook, waitFor } from '@testing-library/react'
|
||||||
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
import { IDLE_STATUS, useFullGisWorkflow } from './useFullGisWorkflow'
|
||||||
|
import type { DatasetCreateResponse, VectorSelectionBBox } from '../types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The workflow's error paths are what matter. A chain that reports "afgerond"
|
||||||
|
* after a step returned nothing tells an operator a result exists when it does
|
||||||
|
* not — the one mistake this panel must not make.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const BBOX: VectorSelectionBBox = { min_x: 5.0, min_y: 51.1, max_x: 5.2, max_y: 51.3, crs: 'EPSG:4326' }
|
||||||
|
const DATASET = { id: 'derived-1', name: 'derived.geojson' } as DatasetCreateResponse
|
||||||
|
|
||||||
|
function setup(overrides: Partial<Parameters<typeof useFullGisWorkflow>[0]> = {}) {
|
||||||
|
const options = {
|
||||||
|
selectedDataset: { id: 'source-1' } as DatasetCreateResponse,
|
||||||
|
latestSelectionDataset: DATASET,
|
||||||
|
qaReferenceDatasetId: 'reference-1',
|
||||||
|
resolveBbox: () => BBOX,
|
||||||
|
resolveAreaId: () => 'area-1',
|
||||||
|
setSelectionBbox: vi.fn(),
|
||||||
|
onRunSelectionExtract: vi.fn().mockResolvedValue({ feature_count: 3 }),
|
||||||
|
onDeriveDataset: vi.fn().mockResolvedValue(DATASET),
|
||||||
|
onExportSelection: vi.fn().mockResolvedValue({}),
|
||||||
|
onRunQa: vi.fn().mockResolvedValue({ f1_score: 0.9 }),
|
||||||
|
...overrides,
|
||||||
|
}
|
||||||
|
return { options, view: renderHook(() => useFullGisWorkflow(options)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('the happy path', () => {
|
||||||
|
it('walks all four steps and reports completion', async () => {
|
||||||
|
const { options, view } = setup()
|
||||||
|
|
||||||
|
await act(async () => void (await view.result.current.run()))
|
||||||
|
|
||||||
|
expect(options.onRunSelectionExtract).toHaveBeenCalledWith(BBOX, 'area-1')
|
||||||
|
expect(options.onDeriveDataset).toHaveBeenCalledWith(BBOX, 'area-1')
|
||||||
|
expect(options.onExportSelection).toHaveBeenCalledWith(BBOX, 'area-1')
|
||||||
|
expect(options.onRunQa).toHaveBeenCalledWith(DATASET)
|
||||||
|
expect(view.result.current.status).toContain('afgerond')
|
||||||
|
expect(view.result.current.error).toBeNull()
|
||||||
|
expect(view.result.current.running).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('starts idle', () => {
|
||||||
|
const { view } = setup()
|
||||||
|
|
||||||
|
expect(view.result.current.status).toBe(IDLE_STATUS)
|
||||||
|
expect(view.result.current.running).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('a step that produces nothing stops the chain', () => {
|
||||||
|
it('stops at the selection and does not derive a dataset', async () => {
|
||||||
|
const { options, view } = setup({ onRunSelectionExtract: vi.fn().mockResolvedValue(null) })
|
||||||
|
|
||||||
|
await act(async () => void (await view.result.current.run()))
|
||||||
|
|
||||||
|
expect(view.result.current.status).toBe('Stopped at query.')
|
||||||
|
expect(view.result.current.error).toContain('ruimtelijke selectie')
|
||||||
|
expect(options.onDeriveDataset).not.toHaveBeenCalled()
|
||||||
|
expect(options.onExportSelection).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('stops at the derived dataset and does not export or check quality', async () => {
|
||||||
|
const { options, view } = setup({ onDeriveDataset: vi.fn().mockResolvedValue(null) })
|
||||||
|
|
||||||
|
await act(async () => void (await view.result.current.run()))
|
||||||
|
|
||||||
|
expect(view.result.current.status).toBe('Stopped at dataset save.')
|
||||||
|
expect(options.onExportSelection).not.toHaveBeenCalled()
|
||||||
|
expect(options.onRunQa).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('reports an unfinished quality check without claiming completion', async () => {
|
||||||
|
const { view } = setup({ onRunQa: vi.fn().mockResolvedValue(null) })
|
||||||
|
|
||||||
|
await act(async () => void (await view.result.current.run()))
|
||||||
|
|
||||||
|
expect(view.result.current.status).toContain('niet afgerond')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('a thrown error', () => {
|
||||||
|
it('surfaces the message and marks the workflow stopped', async () => {
|
||||||
|
const { view } = setup({ onDeriveDataset: vi.fn().mockRejectedValue(new Error('PostGIS unavailable')) })
|
||||||
|
|
||||||
|
await act(async () => void (await view.result.current.run()))
|
||||||
|
|
||||||
|
expect(view.result.current.error).toBe('PostGIS unavailable')
|
||||||
|
expect(view.result.current.status).toBe('De werkstroom is gestopt.')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('always releases the running flag', async () => {
|
||||||
|
const { view } = setup({ onRunSelectionExtract: vi.fn().mockRejectedValue(new Error('boom')) })
|
||||||
|
|
||||||
|
await act(async () => void (await view.result.current.run()))
|
||||||
|
|
||||||
|
await waitFor(() => expect(view.result.current.running).toBe(false))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not present a non-Error rejection as a real message', async () => {
|
||||||
|
const { view } = setup({ onExportSelection: vi.fn().mockRejectedValue('a string') })
|
||||||
|
|
||||||
|
await act(async () => void (await view.result.current.run()))
|
||||||
|
|
||||||
|
expect(view.result.current.error).toBe('Full GIS workflow failed.')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('preconditions', () => {
|
||||||
|
it('refuses to start without a selection', async () => {
|
||||||
|
const { options, view } = setup({ resolveBbox: () => null })
|
||||||
|
|
||||||
|
await act(async () => void (await view.result.current.run()))
|
||||||
|
|
||||||
|
expect(view.result.current.error).toContain('werkgebied')
|
||||||
|
expect(options.onRunSelectionExtract).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('refuses to start without a map layer', async () => {
|
||||||
|
const { options, view } = setup({ selectedDataset: null })
|
||||||
|
|
||||||
|
await act(async () => void (await view.result.current.run()))
|
||||||
|
|
||||||
|
expect(options.onRunSelectionExtract).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('reports a quality check that was skipped for lack of a reference', async () => {
|
||||||
|
const { options, view } = setup({ qaReferenceDatasetId: null })
|
||||||
|
|
||||||
|
await act(async () => void (await view.result.current.run()))
|
||||||
|
|
||||||
|
expect(options.onRunQa).not.toHaveBeenCalled()
|
||||||
|
expect(view.result.current.status).toContain('Kies een referentielaag')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('reuse mode', () => {
|
||||||
|
it('checks the last saved result without redoing the selection', async () => {
|
||||||
|
const { options, view } = setup()
|
||||||
|
|
||||||
|
act(() => view.result.current.setMode('reuse'))
|
||||||
|
await act(async () => void (await view.result.current.run()))
|
||||||
|
|
||||||
|
expect(options.onRunSelectionExtract).not.toHaveBeenCalled()
|
||||||
|
expect(options.onRunQa).toHaveBeenCalledWith(DATASET)
|
||||||
|
expect(view.result.current.status).toContain('opnieuw gebruikt')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('refuses without a saved selection', async () => {
|
||||||
|
const { options, view } = setup({ latestSelectionDataset: null })
|
||||||
|
|
||||||
|
act(() => view.result.current.setMode('reuse'))
|
||||||
|
await act(async () => void (await view.result.current.run()))
|
||||||
|
|
||||||
|
expect(view.result.current.error).toContain('Bewaar eerst')
|
||||||
|
expect(options.onRunQa).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('refuses without a reference layer', async () => {
|
||||||
|
const { options, view } = setup({ qaReferenceDatasetId: null })
|
||||||
|
|
||||||
|
act(() => view.result.current.setMode('reuse'))
|
||||||
|
await act(async () => void (await view.result.current.run()))
|
||||||
|
|
||||||
|
expect(view.result.current.error).toContain('referentielaag')
|
||||||
|
expect(options.onRunQa).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
import { useCallback, useState } from 'react'
|
||||||
|
|
||||||
|
import type { DatasetCreateResponse, VectorSelectionBBox } from '../types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The four-step GIS workflow: select, derive, export, check quality.
|
||||||
|
*
|
||||||
|
* Extracted for its error paths. Every step can fail or return nothing, and
|
||||||
|
* each outcome has to leave the operator with a status that says where the
|
||||||
|
* chain stopped — a workflow that silently reports "done" after a failed step
|
||||||
|
* is the one mistake this panel must not make. Inline in a 3.300-line
|
||||||
|
* component none of that was exercised.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type FullWorkflowMode = 'new' | 'reuse'
|
||||||
|
|
||||||
|
export const IDLE_STATUS = 'Klaar om de volledige GIS-werkstroom uit te voeren.'
|
||||||
|
const STOPPED_STATUS = 'De werkstroom is gestopt.'
|
||||||
|
const GENERIC_FAILURE = 'Full GIS workflow failed.'
|
||||||
|
|
||||||
|
interface FullGisWorkflowOptions {
|
||||||
|
selectedDataset: DatasetCreateResponse | null | undefined
|
||||||
|
latestSelectionDataset: DatasetCreateResponse | null | undefined
|
||||||
|
qaReferenceDatasetId: string | null | undefined
|
||||||
|
resolveBbox: () => VectorSelectionBBox | null
|
||||||
|
resolveAreaId: (bbox: VectorSelectionBBox) => string | undefined
|
||||||
|
setSelectionBbox: (bbox: VectorSelectionBBox) => void
|
||||||
|
onRunSelectionExtract: (bbox: VectorSelectionBBox, areaId?: string) => Promise<unknown>
|
||||||
|
onDeriveDataset: (bbox: VectorSelectionBBox, areaId?: string) => Promise<DatasetCreateResponse | null>
|
||||||
|
onExportSelection: (bbox: VectorSelectionBBox, areaId?: string) => Promise<unknown>
|
||||||
|
onRunQa: (dataset: DatasetCreateResponse) => Promise<unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FullGisWorkflow {
|
||||||
|
running: boolean
|
||||||
|
status: string
|
||||||
|
error: string | null
|
||||||
|
mode: FullWorkflowMode
|
||||||
|
setMode: (mode: FullWorkflowMode) => void
|
||||||
|
run: () => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFullGisWorkflow(options: FullGisWorkflowOptions): FullGisWorkflow {
|
||||||
|
const [running, setRunning] = useState(false)
|
||||||
|
const [status, setStatus] = useState(IDLE_STATUS)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const [mode, setMode] = useState<FullWorkflowMode>('new')
|
||||||
|
|
||||||
|
const {
|
||||||
|
selectedDataset,
|
||||||
|
latestSelectionDataset,
|
||||||
|
qaReferenceDatasetId,
|
||||||
|
resolveBbox,
|
||||||
|
resolveAreaId,
|
||||||
|
setSelectionBbox,
|
||||||
|
onRunSelectionExtract,
|
||||||
|
onDeriveDataset,
|
||||||
|
onExportSelection,
|
||||||
|
onRunQa,
|
||||||
|
} = options
|
||||||
|
|
||||||
|
const runReuse = useCallback(async () => {
|
||||||
|
if (!latestSelectionDataset) {
|
||||||
|
setError('Bewaar eerst een kaartselectie voordat je het laatste resultaat opnieuw gebruikt.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!qaReferenceDatasetId) {
|
||||||
|
setError('Kies eerst een referentielaag voor de kwaliteitscontrole.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setRunning(true)
|
||||||
|
setError(null)
|
||||||
|
try {
|
||||||
|
setStatus('Laatste bewaarde resultaatlaag opnieuw controleren...')
|
||||||
|
const qaResult = await onRunQa(latestSelectionDataset)
|
||||||
|
setStatus(
|
||||||
|
qaResult
|
||||||
|
? 'Het laatste bewaarde resultaat is opnieuw gebruikt en gecontroleerd.'
|
||||||
|
: 'Het laatste resultaat is gebruikt, maar de kwaliteitscontrole is niet afgerond.',
|
||||||
|
)
|
||||||
|
} catch (thrown) {
|
||||||
|
setError(thrown instanceof Error ? thrown.message : GENERIC_FAILURE)
|
||||||
|
setStatus(STOPPED_STATUS)
|
||||||
|
} finally {
|
||||||
|
setRunning(false)
|
||||||
|
}
|
||||||
|
}, [latestSelectionDataset, onRunQa, qaReferenceDatasetId])
|
||||||
|
|
||||||
|
const runNew = useCallback(async () => {
|
||||||
|
const bbox = resolveBbox()
|
||||||
|
if (!selectedDataset || !bbox) {
|
||||||
|
setError('Kies een kaartlaag en een werkgebied of laagbegrenzing.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setRunning(true)
|
||||||
|
setError(null)
|
||||||
|
try {
|
||||||
|
setStatus('1/4 Bewaarde kaartobjecten selecteren...')
|
||||||
|
setSelectionBbox(bbox)
|
||||||
|
const areaId = resolveAreaId(bbox)
|
||||||
|
|
||||||
|
// Each step reports where the chain stopped. A step that returns nothing
|
||||||
|
// is a failure, not a reason to continue to the next one.
|
||||||
|
const selection = await onRunSelectionExtract(bbox, areaId)
|
||||||
|
if (!selection) {
|
||||||
|
setError('De ruimtelijke selectie kon niet worden afgerond.')
|
||||||
|
setStatus('Stopped at query.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus('2/4 Saving derived result dataset...')
|
||||||
|
const derived = await onDeriveDataset(bbox, areaId)
|
||||||
|
if (!derived) {
|
||||||
|
setError('De afgeleide resultaatlaag kon niet worden aangemaakt.')
|
||||||
|
setStatus('Stopped at dataset save.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus('3/4 Saving GeoJSON export artifact...')
|
||||||
|
await onExportSelection(bbox, areaId)
|
||||||
|
|
||||||
|
if (!qaReferenceDatasetId) {
|
||||||
|
setStatus('Resultaat en download zijn gereed. Kies een referentielaag om de kwaliteit te controleren.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setStatus('4/4 Kwaliteit vergelijken met de gekozen referentielaag...')
|
||||||
|
const qaResult = await onRunQa(derived)
|
||||||
|
setStatus(
|
||||||
|
qaResult
|
||||||
|
? 'De volledige GIS-werkstroom en kwaliteitscontrole zijn afgerond.'
|
||||||
|
: 'Resultaat en download zijn gereed; de kwaliteitscontrole is niet afgerond.',
|
||||||
|
)
|
||||||
|
} catch (thrown) {
|
||||||
|
setError(thrown instanceof Error ? thrown.message : GENERIC_FAILURE)
|
||||||
|
setStatus(STOPPED_STATUS)
|
||||||
|
} finally {
|
||||||
|
setRunning(false)
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
onDeriveDataset,
|
||||||
|
onExportSelection,
|
||||||
|
onRunQa,
|
||||||
|
onRunSelectionExtract,
|
||||||
|
qaReferenceDatasetId,
|
||||||
|
resolveAreaId,
|
||||||
|
resolveBbox,
|
||||||
|
selectedDataset,
|
||||||
|
setSelectionBbox,
|
||||||
|
])
|
||||||
|
|
||||||
|
const run = useCallback(
|
||||||
|
() => (mode === 'reuse' ? runReuse() : runNew()),
|
||||||
|
[mode, runNew, runReuse],
|
||||||
|
)
|
||||||
|
|
||||||
|
return { running, status, error, mode, setMode, run }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user