Close full operational audit findings
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-17 14:19:02 +02:00
parent cc36dabcbe
commit 78a5f0b0b2
81 changed files with 2248 additions and 1467 deletions
+36 -190
View File
@@ -10,11 +10,10 @@ import { ExportCenter } from './components/exports/ExportCenter'
import { ExportPreview } from './components/exports/ExportPreview'
import { WorkbenchInspector } from './components/inspector/WorkbenchInspector'
import { MapWorkspace } from './components/map/MapWorkspace'
import { OverviewWorkspace, type WorkspaceKey } from './components/overview/OverviewWorkspace'
import { AreaPanel } from './components/project/AreaPanel'
import { ProjectPanel } from './components/project/ProjectPanel'
import { QualityResultsPanel } from './components/quality/QualityResultsPanel'
import { WorkbenchStatusStrip } from './components/WorkbenchStatusStrip'
import { SourceFreshnessPanel } from './components/status/SourceFreshnessPanel'
import type { DatasetCreateResponse } from './types'
import { ProviderPanel } from './components/providers/ProviderPanel'
import { SegmentationLab } from './components/segmentation/SegmentationLab'
@@ -43,8 +42,6 @@ function isVectorDatasetType(datasetType: string): boolean {
return datasetType === 'vector' || datasetType === 'geojson'
}
type WorkspaceKey = 'overview' | 'data' | 'map' | 'assistant' | 'analysis' | 'ai' | 'exports' | 'system'
const workspaceNavItems: Array<{ key: WorkspaceKey; label: string; description: string }> = [
{ key: 'overview', label: 'Status', description: 'Beschikbaarheid en aandachtspunten' },
{ key: 'data', label: 'Bronnen', description: 'Gebieden en ingeladen gegevens' },
@@ -83,6 +80,7 @@ function App(): JSX.Element {
loadingProjects,
loadingAreas,
loadingDatasets,
archivingProjectId,
errorMessage,
projectForm,
areaForm,
@@ -90,6 +88,7 @@ function App(): JSX.Element {
loadProjectData,
createProject,
createArea,
archiveProject,
resetProjectData,
setSelectedProjectId,
setErrorMessage,
@@ -586,24 +585,24 @@ function App(): JSX.Element {
return 'Detectierun'
}
if ((datasetMapContent || viewportVectorLayer.enabled) && selectedDataset) {
return `${selectedDataset.dataset_type}-dataset`
return selectedDataset.dataset_type === 'raster' ? 'Rasterdatabron' : 'Vectordatabron'
}
return 'Geen actieve gegevens- of analyselaag'
}, [analysisMapLayerActive, changeDetectionResult?.geojson, datasetMapContent, detectionGeoJson, segmentationGeoJson, selectedDataset, viewportVectorLayer.enabled])
const mapLayerProvenance = useMemo(() => {
if (analysisMapLayerActive && changeDetectionResult?.geojson) {
return `source ${changeSourceDatasetId || 'n/a'} -> target ${changeTargetDatasetId || 'n/a'}`
return 'Vergelijking van twee bewaarde kaartlagen'
}
if (analysisMapLayerActive && segmentationGeoJson) {
return selectedSegmentationRunId ? `analysis run ${selectedSegmentationRunId}` : 'segmentation results loaded'
return selectedSegmentationRunId ? 'Bewaarde segmentatieronde' : 'Segmentatieresultaten geladen'
}
if (analysisMapLayerActive && detectionGeoJson) {
return selectedDetectionRunId ? `analysis run ${selectedDetectionRunId}` : 'detection results loaded'
return selectedDetectionRunId ? 'Bewaarde detectieronde' : 'Detectieresultaten geladen'
}
if ((datasetMapContent || viewportVectorLayer.enabled) && selectedDataset) {
return `${selectedDataset.dataset_role ?? 'source'} / ${selectedDataset.source_name ?? selectedDataset.source}`
return `Bewaarde ${selectedDataset.dataset_role === 'reference' ? 'referentielaag' : 'databron'}`
}
return 'Open a dataset, detection run, segmentation run or change result to draw it here.'
return 'Open een databron, detectieronde, segmentatieronde of veranderingsresultaat om het hier te tekenen.'
}, [
analysisMapLayerActive,
changeDetectionResult?.geojson,
@@ -658,73 +657,11 @@ function App(): JSX.Element {
}
setActiveWorkspace(target)
}
const hasAnalysisOutput = qualityChecks.length > 0 || Boolean(changeDetectionResult) || detectionItems.length > 0 || segmentationItems.length > 0
const hasMapContext = mapFeatureCount > 0 || areaFeatureCount > 0
const workflowGuidanceComplete = Boolean(selectedProjectId) && datasets.length > 0 && hasMapContext && hasAnalysisOutput && exports.length > 0
const recommendedWorkflowTarget: WorkspaceKey = !selectedProjectId
? 'data'
: datasets.length === 0
? 'data'
: !hasMapContext
? 'map'
: !hasAnalysisOutput
? 'analysis'
: exports.length === 0
? 'exports'
: 'exports'
const workflowGuidanceSteps: Array<{
step: string
title: string
detail: string
status: string
ready: boolean
target: WorkspaceKey
}> = [
{
step: '1',
title: 'Project & AOI',
detail: selectedProjectId ? `${areas.length} AOI record${areas.length === 1 ? '' : 's'} available` : 'Create or load a project context',
status: selectedProjectId ? 'ready' : 'next',
ready: Boolean(selectedProjectId),
target: 'data',
},
{
step: '2',
title: 'Data',
detail: datasets.length > 0 ? `${datasets.length} dataset${datasets.length === 1 ? '' : 's'} loaded` : 'Upload source and reference datasets',
status: datasets.length > 0 ? 'ready' : 'waiting',
ready: datasets.length > 0,
target: 'data',
},
{
step: '3',
title: 'Map',
detail: hasMapContext
? mapFeatureCount > 0
? `${mapFeatureCount} layer feature${mapFeatureCount === 1 ? '' : 's'}${areaFeatureCount > 0 ? ' + AOI' : ''}`
: 'AOI loaded'
: 'Inspect AOI and selected layer',
status: hasMapContext ? 'ready' : 'waiting',
ready: hasMapContext,
target: 'map',
},
{
step: '4',
title: 'QA / AI',
detail: hasAnalysisOutput ? 'QA, change, detection or segmentation output exists' : 'Run checks after data is ready',
status: hasAnalysisOutput ? 'ready' : 'waiting',
ready: hasAnalysisOutput,
target: 'analysis',
},
{
step: '5',
title: 'Export',
detail: exports.length > 0 ? `${exports.length} export artifact${exports.length === 1 ? '' : 's'} recorded` : 'Package validated project outputs',
status: exports.length > 0 ? 'ready' : 'waiting',
ready: exports.length > 0,
target: 'exports',
},
]
const hasAnalysisOutput =
qualityChecks.length > 0
|| Boolean(changeDetectionResult)
|| detectionItems.length > 0
|| segmentationItems.length > 0
const projectContextLabel = selectedProject?.name === REGIONAL_WORKSPACE_PROJECT_NAME
? REGIONAL_WORKSPACE_LABEL
: selectedProject?.name ?? 'Geen werkruimte'
@@ -759,7 +696,7 @@ function App(): JSX.Element {
<p>GeoAI-werkruimte · Mol en de Kempen</p>
</div>
</div>
<div className="context-bar" aria-label="Active workbench context">
<div className="context-bar" aria-label="Actieve werkcontext">
<div>
<span>Werkruimte</span>
<strong title={projectContextLabel}>{projectContextLabel}</strong>
@@ -782,8 +719,8 @@ function App(): JSX.Element {
{errorMessage ? <p className="error">{errorMessage}</p> : null}
<div className="workbench-layout">
<aside className="workbench-sidebar" aria-label="Workbench navigation">
<nav aria-label="Primary workspaces">
<aside className="workbench-sidebar" aria-label="Navigatie van de werkruimte">
<nav aria-label="Hoofdonderdelen">
{workspaceNavGroups.map((group) => (
<div className="nav-group" key={group.label}>
<p className="nav-section-label">{group.label}</p>
@@ -799,7 +736,7 @@ function App(): JSX.Element {
className={item.key === activeWorkspace ? 'nav-item nav-item-active' : 'nav-item'}
onClick={() => setActiveWorkspace(item.key)}
aria-current={item.key === activeWorkspace ? 'page' : undefined}
aria-label={`Open ${item.label} workspace: ${item.description}`}
aria-label={`Open ${item.label}: ${item.description}`}
data-testid={`workspace-nav-${item.key}`}
>
<span>{item.label}</span>
@@ -833,7 +770,7 @@ function App(): JSX.Element {
) : null}
</div>
</div> : null}
<div className="workspace-command-bar" aria-label="Workspace shortcuts">
<div className="workspace-command-bar" aria-label="Snelkoppelingen">
<div className="workspace-nav-cluster">
{workspaceNavItems.slice(0, 5).map((item) => (
<button
@@ -842,7 +779,7 @@ function App(): JSX.Element {
className={item.key === activeWorkspace ? 'command-chip command-chip-active' : 'command-chip'}
onClick={() => setActiveWorkspace(item.key)}
aria-pressed={item.key === activeWorkspace}
aria-label={`Switch to ${item.label} workspace`}
aria-label={`Ga naar ${item.label}`}
>
{item.label}
</button>
@@ -852,112 +789,19 @@ function App(): JSX.Element {
</div>
{activeWorkspace === 'overview' ? (
<div className="workspace-stack">
<WorkbenchStatusStrip
selectedProject={selectedProject}
areas={areas}
datasets={datasets}
qualityChecks={qualityChecks}
exports={exports}
activeLayerFeatureCount={mapFeatureCount}
selectedAreaHasGeometry={Boolean(areaFeatureCollection)}
/>
<SourceFreshnessPanel
report={sourceFreshness.report}
loading={sourceFreshness.loading}
error={sourceFreshness.error}
onRefresh={() => { void sourceFreshness.refresh() }}
catalogReport={sourceFreshness.catalogReport}
catalogLoading={sourceFreshness.catalogLoading}
catalogError={sourceFreshness.catalogError}
onProbeCatalogs={(force) => { void sourceFreshness.probeCatalogs(force) }}
grbRefreshPlan={sourceFreshness.grbRefreshPlan}
grbRefreshPlanLoading={sourceFreshness.grbRefreshPlanLoading}
grbRefreshPlanError={sourceFreshness.grbRefreshPlanError}
/>
<details className="status-details-disclosure">
<summary>
<span>Volledige workflowstatus</span>
<strong>{workflowGuidanceComplete ? 'voltooid' : 'optionele stappen open'}</strong>
</summary>
<section className="workflow-guidance-panel" aria-label="V1 workflow guidance">
<div className="panel-title-row">
<div>
<p className="eyebrow">Workbench flow</p>
<h2>Project to export path</h2>
</div>
<span className="status-badge">
{workflowGuidanceComplete
? 'Ready for handoff'
: `Next: ${workspaceNavItems.find((item) => item.key === recommendedWorkflowTarget)?.label ?? 'Overview'}`}
</span>
</div>
<div className="workflow-guidance-steps">
{workflowGuidanceSteps.map((step) => (
<button
key={`${step.step}-${step.title}`}
type="button"
className={
step.target === recommendedWorkflowTarget && !step.ready
? 'workflow-guidance-step workflow-guidance-step-active'
: step.ready
? 'workflow-guidance-step workflow-guidance-step-ready'
: 'workflow-guidance-step'
}
onClick={() => openWorkflowGuidanceStep(step.target)}
aria-label={`Open ${step.title} step`}
>
<span className="workflow-step-status">{step.status}</span>
<strong>
{step.step}. {step.title}
</strong>
<small>{step.detail}</small>
</button>
))}
</div>
</section>
<section className="overview-actions">
<div className="overview-action-copy">
<p className="eyebrow">Quick access</p>
<h2>Continue working</h2>
</div>
<div className="quick-action-grid overview-quick-actions" aria-label="Recommended next workbench actions">
<button
type="button"
className="quick-action-button"
onClick={() => setActiveWorkspace('data')}
aria-label="Open data setup workspace"
>
Open data setup
</button>
<button
type="button"
className="quick-action-button"
onClick={() => setActiveWorkspace('map')}
aria-label="Inspect map workspace"
>
Inspect map
</button>
<button
type="button"
className="quick-action-button"
onClick={() => setActiveWorkspace('analysis')}
aria-label="Review QA/QC workspace"
>
Review QA/QC
</button>
<button
type="button"
className="quick-action-button"
onClick={() => setActiveWorkspace('exports')}
aria-label="Manage exports workspace"
>
Manage exports
</button>
</div>
</section>
</details>
</div>
<OverviewWorkspace
selectedProject={selectedProject}
selectedProjectId={selectedProjectId}
areas={areas}
datasets={datasets}
qualityChecks={qualityChecks}
exports={exports}
activeLayerFeatureCount={mapFeatureCount}
selectedAreaHasGeometry={Boolean(areaFeatureCollection)}
hasAnalysisOutput={hasAnalysisOutput}
sourceFreshness={sourceFreshness}
onOpenWorkspace={openWorkflowGuidanceStep}
/>
) : null}
{activeWorkspace === 'data' ? (
@@ -967,12 +811,14 @@ function App(): JSX.Element {
projects={projects}
selectedProjectId={selectedProjectId}
loadingProjects={loadingProjects}
archivingProjectId={archivingProjectId}
projectForm={projectForm}
loadingDemoWorkflow={loadingDemoWorkflow}
demoWorkflowMessage={demoWorkflowMessage}
onCreateProject={createProject}
onUpdateProjectForm={setProjectForm}
onSelectProject={selectProject}
onArchiveProject={archiveProject}
onLoadDemoWorkflow={loadDemoWorkflow}
/>
@@ -1287,7 +1133,7 @@ function App(): JSX.Element {
</main>
{inspectorOpen ? (
<aside className="workbench-inspector" id="workbench-inspector" aria-label="Current selection inspector">
<aside className="workbench-inspector" id="workbench-inspector" aria-label="Details van de huidige selectie">
<WorkbenchInspector
selectedProject={selectedProject}
selectedArea={selectedArea}