diff --git a/CHANGELOG.md b/CHANGELOG.md index a41afe5c..f3beb007 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ # Changelog +## Sprint 193 End-user regional workbench simplification (2026-07-14) + +- Made the complete `Kempen Regional Workbench` the automatic fresh-session data context and removed the redundant region selector from the primary map. +- Kept all available regional datasets loaded while treating Mol, the other 27 municipalities and the complete region as spatial work-area filters. +- Replaced technical dataset names with theme/source labels and moved benchmark projects, raw source metadata, provider internals, QA evidence and model diagnostics behind advanced disclosures. +- Defaulted Detection Lab to the configured local YOLO asset, active local model profile and measured operating threshold; model quality remains explicitly review-required rather than presented as production-perfect. +- Simplified Quality, Downloads, Status and Beheer around end-user tasks without changing API contracts, migrations, persistence or inference behavior. + ## Sprint 192 Regional map state correctness (2026-07-14) - Fixed the map-first work-area selector so changing from the complete Kempen region to a municipality clears the old bbox, theme totals and result geometry before switching Areas. diff --git a/backend/tests/test_sprint103_ai_lab_run_readiness.py b/backend/tests/test_sprint103_ai_lab_run_readiness.py index 21b7a91a..4c82459c 100644 --- a/backend/tests/test_sprint103_ai_lab_run_readiness.py +++ b/backend/tests/test_sprint103_ai_lab_run_readiness.py @@ -16,11 +16,11 @@ def test_detection_lab_exposes_run_readiness_contract() -> None: assert "detectionTileManifestPath.trim().length > 0" in lab assert "detectionRunReady" in lab assert 'aria-label="Detection run readiness"' in lab - assert "Run readiness" in lab - assert "Raster dataset" in lab - assert "Model availability" in lab - assert "Tile manifest" in lab - assert "Ready to submit" in lab + assert "Wat is nog nodig?" in lab + assert "Luchtbeeld" in lab + assert "Analysemodel" in lab + assert "Beeldtegels" in lab + assert "Klaar om te starten" in lab def test_segmentation_lab_exposes_run_readiness_contract() -> None: diff --git a/backend/tests/test_sprint104_ai_lab_action_guardrails.py b/backend/tests/test_sprint104_ai_lab_action_guardrails.py index 6c25115c..92130b45 100644 --- a/backend/tests/test_sprint104_ai_lab_action_guardrails.py +++ b/backend/tests/test_sprint104_ai_lab_action_guardrails.py @@ -14,8 +14,8 @@ def test_detection_lab_distinguishes_configured_model_from_ui_runnable_action() assert "detectionModelUiRunnable" in lab assert "selectedDetectionModelId !== 'manual-fixture-detector'" in lab assert "detectionRunBlockedReason" in lab - assert "Fixture model is explicit test/demo-only" in lab - assert "Run action" in lab + assert "Het fixturemodel is alleen bedoeld voor expliciete tests" in lab + assert "Klaar om gebouwen te zoeken" in lab assert "disabled={runningDetection || !detectionRunReady}" in lab diff --git a/backend/tests/test_sprint111_qa_feature_evidence.py b/backend/tests/test_sprint111_qa_feature_evidence.py index 72aedf55..b8241de9 100644 --- a/backend/tests/test_sprint111_qa_feature_evidence.py +++ b/backend/tests/test_sprint111_qa_feature_evidence.py @@ -19,9 +19,9 @@ def test_qa_feature_evidence_contract_is_documented_and_rendered() -> None: assert field_name in api_contracts assert "Feature-level QA/QC evidence" in quality_panel - assert "Matched feature ids" in quality_panel - assert "False positive feature ids" in quality_panel - assert "False negative feature ids" in quality_panel + assert "Overeenkomende object-ID's" in quality_panel + assert "ID's van onterecht gevonden objecten" in quality_panel + assert "ID's van gemiste objecten" in quality_panel assert "evidenceLabel" in quality_panel diff --git a/backend/tests/test_sprint118_yolo_preflight_ui.py b/backend/tests/test_sprint118_yolo_preflight_ui.py index 6b374698..268f2e8d 100644 --- a/backend/tests/test_sprint118_yolo_preflight_ui.py +++ b/backend/tests/test_sprint118_yolo_preflight_ui.py @@ -47,5 +47,5 @@ def test_detection_lab_surfaces_local_model_asset_selection() -> None: assert "Local model assets" in lab assert "onSelectModelAsset" in lab assert "modelAssets={modelAssets}" in app - assert "Official reference sources" in provider_panel - assert "not AI model choices" in provider_panel + assert "Officiële referentiebronnen" in provider_panel + assert "live GRB- en OSM-koppelingen staan uit" in provider_panel diff --git a/backend/tests/test_sprint122_model_asset_activation_guardrails.py b/backend/tests/test_sprint122_model_asset_activation_guardrails.py index 1a08283d..548e4806 100644 --- a/backend/tests/test_sprint122_model_asset_activation_guardrails.py +++ b/backend/tests/test_sprint122_model_asset_activation_guardrails.py @@ -4,13 +4,15 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[2] -def test_detection_workflow_does_not_auto_select_runtime_model_assets() -> None: +def test_detection_workflow_selects_only_the_active_runtime_model_asset_automatically() -> None: hook = (ROOT / "frontend" / "src" / "hooks" / "useDetectionWorkflow.ts").read_text(encoding="utf-8") - assert "const activeAsset = assetResponse.items.find" not in hook - assert "setSelectedModelAssetId(activeAsset?.model_asset_id ?? '')" not in hook + assert "const activeAsset = assetResponse.items.find((asset) => asset.active) ?? null" in hook + assert "const selectedAssetStillAvailable" in hook + assert "activeAsset?.model_asset_id ?? ''" in hook + assert "setSelectedModelAssetId(nextAssetId)" in hook assert "setSelectedModelAssetId(assetResponse.items[0]" not in hook - assert "setSelectedModelAssetId('')" in hook + assert "getYoloPreflight({ model_asset_id: nextAssetId || null })" in hook def test_detection_lab_explains_explicit_model_asset_and_threshold_selection() -> None: @@ -18,11 +20,11 @@ def test_detection_lab_explains_explicit_model_asset_and_threshold_selection() - encoding="utf-8" ) - assert "Explicit model asset" in lab - assert "No model file is selected automatically" in lab - assert "Operator profiles" in lab + assert "Lokaal modelbestand" in lab + assert "GeoIntel kiest automatisch het actieve lokale model" in lab + assert "Gevalideerde profielen" in lab assert "DETECTION_OPERATOR_PROFILES" in lab - assert "Candidate only - not default-approved" in lab + assert "kandidaat · extra controle vereist" in lab assert "will_download_models" in lab @@ -32,5 +34,5 @@ def test_detection_run_readiness_requires_explicit_asset_when_local_assets_exist ) assert "detectionHasExplicitModelAsset" in lab - assert "Select a local model asset deliberately" in lab - assert "local model asset" in lab + assert "Kies een lokaal modelbestand onder beheer" in lab + assert "Lokale modelkeuze" in lab diff --git a/backend/tests/test_sprint123_raster_detection_handoff_operational.py b/backend/tests/test_sprint123_raster_detection_handoff_operational.py index 95833956..f327ab22 100644 --- a/backend/tests/test_sprint123_raster_detection_handoff_operational.py +++ b/backend/tests/test_sprint123_raster_detection_handoff_operational.py @@ -40,7 +40,7 @@ def test_detection_handoff_opens_ai_lab_preflights_manifest_and_keeps_asset_expl assert "setDetectionConfidenceThreshold(0.25)" in app assert "loadYoloPreflight(manifestPath).catch(() => null)" in app assert "setSelectedModelAssetId(" not in app[app.index("const useRasterTileManifestForDetection"):app.index("const {", app.index("const useRasterTileManifestForDetection"))] - assert "Linked tile manifest" in lab + assert "Gekoppelde beeldtegels" in lab assert "Tile manifest validation" in lab assert "Tile count" in lab assert "will_run_inference" in lab diff --git a/backend/tests/test_sprint155_detection_operator_profiles.py b/backend/tests/test_sprint155_detection_operator_profiles.py index ede00af2..3fb36988 100644 --- a/backend/tests/test_sprint155_detection_operator_profiles.py +++ b/backend/tests/test_sprint155_detection_operator_profiles.py @@ -34,17 +34,17 @@ def test_detection_lab_surfaces_profiles_as_deliberate_operator_actions() -> Non ) assert "DETECTION_OPERATOR_PROFILES" in lab - assert "Operator profiles" in lab + assert "Gevalideerde profielen" in lab assert "profile.displayName" in lab assert "profile.confidenceThreshold" in lab - assert "Candidate only - not default-approved" in lab - assert "default-approved" in lab - assert "Apply profile" in lab + assert "kandidaat · extra controle vereist" in lab + assert "standaardprofiel" in lab + assert "Profiel gebruiken" in lab assert "onApplyOperatorProfile(profile)" in lab assert "Recommended starting threshold: 0.25" not in lab -def test_detection_workflow_applies_profiles_without_auto_selecting_assets() -> None: +def test_detection_workflow_applies_profiles_without_selecting_the_first_arbitrary_asset() -> None: hook = (ROOT / "frontend" / "src" / "hooks" / "useDetectionWorkflow.ts").read_text(encoding="utf-8") app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") diff --git a/backend/tests/test_sprint169_long_context_name_readability.py b/backend/tests/test_sprint169_long_context_name_readability.py index e50a8497..ddab3eed 100644 --- a/backend/tests/test_sprint169_long_context_name_readability.py +++ b/backend/tests/test_sprint169_long_context_name_readability.py @@ -13,8 +13,9 @@ def test_long_context_names_remain_compact_and_inspectable() -> None: encoding="utf-8" ) - assert "const projectContextLabel = selectedProject?.name ?? 'No project'" in app - assert "const datasetContextLabel = selectedDataset?.name ?? (datasets.length > 0 ? 'Select dataset' : 'No dataset')" in app + assert "selectedProject?.name === REGIONAL_WORKSPACE_PROJECT_NAME" in app + assert "? REGIONAL_WORKSPACE_LABEL" in app + assert "const datasetContextLabel = selectedDataset ? getDatasetDisplayName(selectedDataset)" in app assert "{projectContextLabel}" in app assert "{datasetContextLabel}" in app assert "{item.value}" in status diff --git a/backend/tests/test_sprint177_mol_primary_focus.py b/backend/tests/test_sprint177_mol_primary_focus.py index f3c4d0f1..9f36a51f 100644 --- a/backend/tests/test_sprint177_mol_primary_focus.py +++ b/backend/tests/test_sprint177_mol_primary_focus.py @@ -29,7 +29,7 @@ def test_frontend_declares_mol_as_primary_operating_focus() -> None: assert "PRIMARY_FOCUS_AREA_GEOJSON" in project_hook assert "4.35,51.28" not in project_hook assert "center: PRIMARY_FOCUS_CENTER" in map_source - assert "GeoAI Workbench · Mol / Kempen" in app + assert "GeoAI-werkruimte · Mol en de Kempen" in app def test_operator_workflows_put_mol_first_and_name_future_projects() -> None: diff --git a/backend/tests/test_sprint180_premium_workbench.py b/backend/tests/test_sprint180_premium_workbench.py index 27c2a0e9..0d8be64d 100644 --- a/backend/tests/test_sprint180_premium_workbench.py +++ b/backend/tests/test_sprint180_premium_workbench.py @@ -16,8 +16,9 @@ def test_workbench_uses_grouped_navigation_and_optional_inspector() -> None: assert "import './styles/premium.css'" in app assert "const workspaceNavGroups" in app - assert "['overview', 'data', 'map']" in app + assert "['map', 'data']" in app assert "['analysis', 'ai']" in app + assert "['overview', 'system']" in app assert "const [inspectorOpen, setInspectorOpen] = useState(false)" in app assert "{inspectorOpen ? (" in app assert 'id="workbench-inspector"' in app @@ -31,12 +32,12 @@ def test_data_creation_forms_are_progressively_disclosed() -> None: dataset = read("frontend/src/components/datasets/DatasetPanel.tsx") css = read("frontend/src/styles/premium.css") - assert '
' in project - assert 'Create project' in project + assert '
' in project + assert 'Geavanceerd projectbeheer' in project assert '
' in area - assert 'Create spatial scope' in area + assert 'Eigen gebied toevoegen (GeoJSON)' in area assert '
' in dataset - assert 'Upload source data' in dataset + assert 'Eigen bronbestand toevoegen' in dataset assert "details.data-panel-form-block > summary" in css assert ".workspace-grid-data > section:nth-child(n)" in css assert "max-height: calc(100dvh - 10.5rem);" in css diff --git a/backend/tests/test_sprint189_kempen_scope.py b/backend/tests/test_sprint189_kempen_scope.py index 02163639..57cf1e65 100644 --- a/backend/tests/test_sprint189_kempen_scope.py +++ b/backend/tests/test_sprint189_kempen_scope.py @@ -129,9 +129,12 @@ def test_kempen_scope_operator_is_packaged_and_exposed_in_map_flow() -> None: assert "py_compile scripts/provision_geographic_scope.py" in readiness assert "Immutable scope dataset" in (ROOT / "scripts/provision_geographic_scope.py").read_text(encoding="utf-8") assert "Kempen (28 gemeenten)" in workspace - assert 'aria-label="Regio"' in workspace + assert 'aria-label="Regio"' not in workspace + assert 'aria-label="Ingeladen regiobereik"' in workspace + assert "Gemeente of volledige regio" in workspace assert "projects={projects}" in app - assert "onSelectProject={selectProject}" in app + map_props = app.split("", maxsplit=1)[0] + assert "onSelectProject={selectProject}" not in map_props def test_project_switches_reset_scoped_state_and_prefer_the_regional_context() -> None: @@ -144,5 +147,6 @@ def test_project_switches_reset_scoped_state_and_prefer_the_regional_context() - assert "resetProjectData()" in selected_project_branch assert "resetDatasetForProject()" in selected_project_branch assert "projectDataRequestSequence" in project_workspace + assert "REGIONAL_WORKSPACE_PROJECT_NAME" in project_workspace assert "vervoerregio|operationele grens" in map_state assert "datasets.find(isOperationalScopeBoundaryDataset)" in dataset_workflow diff --git a/backend/tests/test_sprint193_end_user_workbench.py b/backend/tests/test_sprint193_end_user_workbench.py new file mode 100644 index 00000000..ad2f4855 --- /dev/null +++ b/backend/tests/test_sprint193_end_user_workbench.py @@ -0,0 +1,55 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + + +def read(path: str) -> str: + return (ROOT / path).read_text(encoding="utf-8") + + +def test_regional_workspace_is_automatic_and_map_has_one_scope_selector() -> None: + project_hook = read("frontend/src/hooks/useProjectWorkspace.ts") + map_workspace = read("frontend/src/components/map/MapWorkspace.tsx") + + regional_check = project_hook.index("const regionalProject") + municipality_check = project_hook.index("const municipalityProject") + assert regional_check < municipality_check + assert 'aria-label="Regio"' not in map_workspace + assert 'aria-label="Ingeladen regiobereik"' in map_workspace + assert "Gemeente of volledige regio" in map_workspace + assert 'aria-label="Werkgebied"' in map_workspace + + +def test_primary_navigation_uses_end_user_language_and_keeps_management_secondary() -> None: + app = read("frontend/src/App.tsx") + + for label in ("Kaart", "Bronnen", "Kwaliteit", "Beeldanalyse", "Downloads"): + assert f"label: '{label}'" in app + assert "{ label: 'Beheer', keys: ['overview', 'system'] }" in app + assert 'className="secondary-analysis-disclosure segmentation-disclosure"' in app + + +def test_technical_projects_and_metadata_are_progressively_disclosed() -> None: + projects = read("frontend/src/components/project/ProjectPanel.tsx") + areas = read("frontend/src/components/project/AreaPanel.tsx") + datasets = read("frontend/src/components/datasets/DatasetPanel.tsx") + + assert "TECHNICAL_PROJECT_PATTERN" in projects + assert "technische test- en benchmarkruns" in projects + assert "area-catalog-disclosure" in areas + assert "dataset-technical-details" in datasets + assert "getDatasetDisplayName" in datasets + + +def test_configured_yolo_and_active_asset_are_selected_without_hiding_limitations() -> None: + hook = read("frontend/src/hooks/useDetectionWorkflow.ts") + lab = read("frontend/src/components/detection/DetectionLab.tsx") + + assert "useState('yolo-configured')" in hook + assert "useState(0.15)" in hook + assert "asset.active" in hook + assert "getYoloPreflight" in hook + assert 'aria-label="Status gebouwdetectie"' in lab + assert "resultaten blijven controleplichtig" in lab + assert "Modelkalibratie voor beheerders" in lab diff --git a/backend/tests/test_sprint20_area_map_overlay.py b/backend/tests/test_sprint20_area_map_overlay.py index bffce1e6..7f9bdf95 100644 --- a/backend/tests/test_sprint20_area_map_overlay.py +++ b/backend/tests/test_sprint20_area_map_overlay.py @@ -58,4 +58,4 @@ def test_frontend_wires_selected_area_map_overlay_contract() -> None: assert "area-fill" in geomap assert "area-line" in geomap assert "onSelectMapArea" in area_panel - assert "Show on map" in area_panel + assert "Toon op kaart" in area_panel diff --git a/backend/tests/test_sprint22_workbench_status_strip.py b/backend/tests/test_sprint22_workbench_status_strip.py index aa982046..ba10dae1 100644 --- a/backend/tests/test_sprint22_workbench_status_strip.py +++ b/backend/tests/test_sprint22_workbench_status_strip.py @@ -16,9 +16,9 @@ def test_frontend_wires_v1_workbench_status_strip() -> None: assert "qualityChecks={qualityChecks}" in app assert "activeLayerFeatureCount={mapFeatureCount}" in app assert "selectedAreaHasGeometry={Boolean(areaFeatureCollection)}" in app - assert "V1 readiness" in component - assert "Workbench status" in component - assert "Workbench has the core V1 loop populated." in component + assert "Platformstatus" in component + assert "Workbench status:" in component + assert "Kaartwerkruimte is gebruiksklaar" in component assert "workbench-status-strip" in css assert "status-tile-ready" in css diff --git a/backend/tests/test_sprint27_frontend_workflow_hooks.py b/backend/tests/test_sprint27_frontend_workflow_hooks.py index 4b03acf9..f0666aae 100644 --- a/backend/tests/test_sprint27_frontend_workflow_hooks.py +++ b/backend/tests/test_sprint27_frontend_workflow_hooks.py @@ -47,7 +47,7 @@ def test_app_still_wires_quality_results_and_export_center() -> None: assert " loadQualityChecks()}" in app - assert "Refresh QA/QC results" in quality_panel + assert "Kwaliteitsresultaten vernieuwen" in quality_panel assert "onClick={onRefresh}" in quality_panel assert " None: def test_dataset_panel_owns_upload_and_list_markup() -> None: panel = (ROOT / "frontend" / "src" / "components" / "datasets" / "DatasetPanel.tsx").read_text(encoding="utf-8") - assert "Upload dataset" in panel - assert "Refresh metadata" in panel - assert "Select / details" in panel + assert "Eigen bronbestand toevoegen" in panel + assert "Metadata vernieuwen" in panel + assert "Details en acties" in panel assert "onLoadDatasetDetails" in panel assert "onRefreshMetadata" in panel diff --git a/backend/tests/test_sprint30_workbench_components.py b/backend/tests/test_sprint30_workbench_components.py index 921528ca..fa359d6d 100644 --- a/backend/tests/test_sprint30_workbench_components.py +++ b/backend/tests/test_sprint30_workbench_components.py @@ -38,8 +38,8 @@ def test_quality_results_panel_owns_persisted_quality_check_markup() -> None: ).read_text(encoding="utf-8") assert "QualityCheckRead" in quality_panel - assert "Refresh QA/QC results" in quality_panel - assert "No persisted QA/QC results yet" in quality_panel + assert "Kwaliteitsresultaten vernieuwen" in quality_panel + assert "Nog geen bewaarde kwaliteitsresultaten" in quality_panel assert "check.metrics.map" in quality_panel assert "fetch(" not in quality_panel assert "api" not in quality_panel.lower() diff --git a/backend/tests/test_sprint50_workspace_usability_polish.py b/backend/tests/test_sprint50_workspace_usability_polish.py index 042b08f5..a5611eda 100644 --- a/backend/tests/test_sprint50_workspace_usability_polish.py +++ b/backend/tests/test_sprint50_workspace_usability_polish.py @@ -19,7 +19,7 @@ def test_data_workspace_panels_use_operator_friendly_cards_and_forms() -> None: assert "compact-form" in project_panel assert "entity-card-active" in project_panel - assert "AOI manager" in area_panel + assert "

Gebieden

" in area_panel assert "compact-form" in area_panel assert "entity-card-active" in area_panel assert "dataset-upload-form" in dataset_panel diff --git a/backend/tests/test_sprint51_quality_export_polish.py b/backend/tests/test_sprint51_quality_export_polish.py index c9c7a8dc..c1c11aaf 100644 --- a/backend/tests/test_sprint51_quality_export_polish.py +++ b/backend/tests/test_sprint51_quality_export_polish.py @@ -9,11 +9,11 @@ def test_quality_panel_uses_workbench_summary_and_cards() -> None: ROOT / "frontend" / "src" / "components" / "quality" / "QualityResultsPanel.tsx" ).read_text(encoding="utf-8") - assert 'className="quality-results-panel"' in quality_panel + assert "'quality-results-panel quality-results-panel-empty' : 'quality-results-panel'" in quality_panel assert 'className="quality-summary-grid"' in quality_panel assert 'className="quality-check-list"' in quality_panel assert 'className="quality-check-card"' in quality_panel - assert "No persisted QA/QC results yet" in quality_panel + assert "Nog geen bewaarde kwaliteitsresultaten" in quality_panel assert "check.metrics.map" in quality_panel assert "fetch(" not in quality_panel diff --git a/backend/tests/test_sprint53_selection_ergonomics.py b/backend/tests/test_sprint53_selection_ergonomics.py index 671ca729..4322c5cc 100644 --- a/backend/tests/test_sprint53_selection_ergonomics.py +++ b/backend/tests/test_sprint53_selection_ergonomics.py @@ -13,8 +13,8 @@ def test_dataset_panel_exposes_map_and_export_quick_actions() -> None: assert "dataset-card-active" in panel assert "onOpenDatasetInMap" in panel assert "onOpenDatasetExport" in panel - assert "Open in map" in panel - assert "Export / QA" in panel + assert "Open op kaart" in panel + assert "Downloaden" in panel assert "disabled={!(dataset.dataset_type === 'vector' || dataset.dataset_type === 'geojson')}" in panel diff --git a/backend/tests/test_sprint64_export_handoff_polish.py b/backend/tests/test_sprint64_export_handoff_polish.py index 0cd1e25d..f3ac9375 100644 --- a/backend/tests/test_sprint64_export_handoff_polish.py +++ b/backend/tests/test_sprint64_export_handoff_polish.py @@ -13,12 +13,12 @@ def test_export_center_surfaces_handoff_readiness_context() -> None: assert 'className="handoff-readiness-grid"' in export_center assert 'className="handoff-action-grid"' in export_center assert 'className="export-type-badge"' in export_center - assert "Latest handoff artifact" in export_center - assert "Project report" in export_center - assert "Detection run" in export_center - assert "Segmentation run" in export_center - assert "selectedDetectionRunId || 'none'" in export_center - assert "selectedSegmentationRunId || 'none'" in export_center + assert "Laatste bestand" in export_center + assert "Leesbaar rapport" in export_center + assert "Gebouwanalyse" in export_center + assert "Segmentatieanalyse" in export_center + assert "selectedDetectionRunId || 'geen'" in export_center + assert "selectedSegmentationRunId || 'geen'" in export_center def test_export_handoff_polish_has_responsive_styles() -> None: diff --git a/backend/tests/test_sprint68_dataset_catalog_density.py b/backend/tests/test_sprint68_dataset_catalog_density.py index d7973440..f7fc10d5 100644 --- a/backend/tests/test_sprint68_dataset_catalog_density.py +++ b/backend/tests/test_sprint68_dataset_catalog_density.py @@ -11,10 +11,10 @@ def test_dataset_panel_surfaces_role_summary_and_badges() -> None: assert "roleSummaries" in dataset_panel assert "dataset-role-summary-grid" in dataset_panel - assert "Selected" in dataset_panel - assert "Reference" in dataset_panel - assert "Candidate" in dataset_panel - assert "Source" in dataset_panel + assert "Geselecteerd" in dataset_panel + assert "Referentie" in dataset_panel + assert "Resultaat" in dataset_panel + assert "Eigen bron" in dataset_panel assert "dataset-role-badge" in dataset_panel assert "dataset-role-selected" in dataset_panel assert "dataset-role-reference" in dataset_panel @@ -28,9 +28,9 @@ def test_dataset_cards_expose_scan_friendly_source_and_crs_context() -> None: ) assert "dataset-card-kicker" in dataset_panel - assert "Source: {dataset.source_name ?? dataset.source}" in dataset_panel - assert "Layer: {dataset.reference_layer_name}" in dataset_panel - assert "CRS: {dataset.crs ?? dataset.vector_summary?.crs ?? 'n/a'}" in dataset_panel + assert "Bron: {dataset.source_name ?? dataset.source}" in dataset_panel + assert "Laag: {dataset.reference_layer_name}" in dataset_panel + assert "CRS: {dataset.crs ?? dataset.vector_summary?.crs ?? 'onbekend'}" in dataset_panel assert "dataset-card-title" in dataset_panel diff --git a/backend/tests/test_sprint69_dataset_action_polish.py b/backend/tests/test_sprint69_dataset_action_polish.py index 9ccd5b38..fefdf0ac 100644 --- a/backend/tests/test_sprint69_dataset_action_polish.py +++ b/backend/tests/test_sprint69_dataset_action_polish.py @@ -11,9 +11,9 @@ def test_dataset_panel_explains_recommended_actions() -> None: assert "datasetActionHint" in dataset_panel assert "dataset-action-hint" in dataset_panel - assert "QA reference layer" in dataset_panel - assert "QA candidate layer" in dataset_panel - assert "Open on the map or send to Export / QA." in dataset_panel + assert "Officiële laag voor kaartanalyse en kwaliteitscontrole." in dataset_panel + assert "Bewaard resultaat dat opnieuw op de kaart" in dataset_panel + assert "Ingeladen gegevensbron voor verdere analyse." in dataset_panel def test_dataset_buttons_have_scan_friendly_action_copy() -> None: @@ -22,12 +22,12 @@ def test_dataset_buttons_have_scan_friendly_action_copy() -> None: ) assert "dataset-action-grid" in dataset_panel - assert "Inspect" in dataset_panel - assert "Map" in dataset_panel - assert "Export / QA" in dataset_panel + assert "Bekijken" in dataset_panel + assert "Kaart" in dataset_panel + assert "Downloaden" in dataset_panel assert "Metadata" in dataset_panel - assert "Vector/GeoJSON only" in dataset_panel - assert "Raster metadata is automatic" in dataset_panel + assert "Enkel vectorlagen" in dataset_panel + assert "disabled={dataset.dataset_type === 'raster'}" in dataset_panel def test_dataset_action_styles_keep_buttons_dense_and_responsive() -> None: diff --git a/backend/tests/test_sprint70_quality_handoff_polish.py b/backend/tests/test_sprint70_quality_handoff_polish.py index 42a861a3..d519a250 100644 --- a/backend/tests/test_sprint70_quality_handoff_polish.py +++ b/backend/tests/test_sprint70_quality_handoff_polish.py @@ -23,11 +23,11 @@ def test_quality_panel_surfaces_candidate_reference_handoff_summary() -> None: ).read_text(encoding="utf-8") assert "quality-handoff-grid" in quality_panel - assert "Candidate layers" in quality_panel - assert "Reference layers" in quality_panel - assert "Latest comparison" in quality_panel - assert "Candidate" in quality_panel - assert "Reference" in quality_panel + assert "Te controleren lagen" in quality_panel + assert "Referentielagen" in quality_panel + assert "Laatste vergelijking" in quality_panel + assert "Te controleren:" in quality_panel + assert "referentie:" in quality_panel assert "quality-dataset-name" in quality_panel diff --git a/backend/tests/test_sprint71_quality_metric_polish.py b/backend/tests/test_sprint71_quality_metric_polish.py index aa209334..92802f6b 100644 --- a/backend/tests/test_sprint71_quality_metric_polish.py +++ b/backend/tests/test_sprint71_quality_metric_polish.py @@ -28,8 +28,8 @@ def test_quality_panel_renders_metric_evidence_cards_and_raw_metrics() -> None: assert "quality-metric-grid" in quality_panel assert "quality-metric-card" in quality_panel assert "quality-metric-card-critical" in quality_panel - assert "Metric evidence" in quality_panel - assert "Raw metrics" in quality_panel + assert "Gemeten kwaliteit" in quality_panel + assert "Alle meetwaarden" in quality_panel assert "check.metrics.map" in quality_panel diff --git a/backend/tests/test_sprint73_quality_result_filtering.py b/backend/tests/test_sprint73_quality_result_filtering.py index ce435fb6..ace1af78 100644 --- a/backend/tests/test_sprint73_quality_result_filtering.py +++ b/backend/tests/test_sprint73_quality_result_filtering.py @@ -17,12 +17,12 @@ def test_quality_panel_adds_result_filters_and_density_limit() -> None: assert "filteredQualityChecks = useMemo" in quality_panel assert "visibleQualityChecks = showAllQualityChecks ? filteredQualityChecks : filteredQualityChecks.slice(0, 8)" in quality_panel assert "quality-history-controls" in quality_panel - assert "Search QA/QC results" in quality_panel - assert "All statuses" in quality_panel - assert "All check types" in quality_panel - assert "Reset view" in quality_panel - assert "Show all QA/QC results" in quality_panel - assert "No QA/QC results match the current filters." in quality_panel + assert "Kwaliteitsresultaten zoeken" in quality_panel + assert "Alle statussen" in quality_panel + assert "Alle types" in quality_panel + assert "Filters wissen" in quality_panel + assert "Alle kwaliteitsresultaten tonen" in quality_panel + assert "Geen kwaliteitsresultaten voldoen aan de filters." in quality_panel def test_quality_filter_styles_reuse_dense_history_patterns() -> None: diff --git a/backend/tests/test_sprint84_data_workspace_density.py b/backend/tests/test_sprint84_data_workspace_density.py index 61afddf4..b6951a8e 100644 --- a/backend/tests/test_sprint84_data_workspace_density.py +++ b/backend/tests/test_sprint84_data_workspace_density.py @@ -16,12 +16,12 @@ def test_project_and_area_panels_expose_selected_summary_regions() -> None: assert "const selectedProject = projects.find" in project_panel assert 'className="data-selection-summary data-selection-summary-project"' in project_panel - assert 'className="data-panel-form-block"' in project_panel + assert 'className="data-panel-form-block technical-management-block"' in project_panel assert 'className="data-panel-list-block"' in project_panel assert "const selectedArea = areas.find" in area_panel assert 'className="data-selection-summary data-selection-summary-area"' in area_panel assert 'className="data-panel-form-block"' in area_panel - assert 'className="data-panel-list-block"' in area_panel + assert 'className="data-panel-list-block area-catalog-disclosure"' in area_panel def test_dataset_panel_separates_selected_upload_and_catalog_regions() -> None: diff --git a/backend/tests/test_sprint86_quality_workspace_density.py b/backend/tests/test_sprint86_quality_workspace_density.py index b7e8d4ab..7fff1450 100644 --- a/backend/tests/test_sprint86_quality_workspace_density.py +++ b/backend/tests/test_sprint86_quality_workspace_density.py @@ -11,7 +11,7 @@ def test_quality_results_panel_exposes_structured_surfaces() -> None: encoding="utf-8" ) - assert 'className="quality-results-panel"' in panel + assert "'quality-results-panel quality-results-panel-empty' : 'quality-results-panel'" in panel assert 'className="quality-results-shell"' in panel assert 'className="quality-summary-surface"' in panel assert 'aria-label="QA/QC result summary"' in panel @@ -22,6 +22,7 @@ def test_quality_results_panel_exposes_structured_surfaces() -> None: assert 'className="quality-result-state-stack"' in panel assert 'className="quality-history-surface"' in panel assert 'aria-label="QA/QC result history"' in panel + assert 'className="quality-advanced-disclosure"' in panel def test_quality_results_panel_preserves_existing_refresh_filter_and_history_state() -> None: @@ -34,8 +35,8 @@ def test_quality_results_panel_preserves_existing_refresh_filter_and_history_sta assert "qualityTypeFilter" in panel assert "qualitySearchQuery" in panel assert "visibleQualityChecks.map" in panel - assert "Show all QA/QC results" in panel - assert "No QA/QC results match the current filters." in panel + assert "Alle kwaliteitsresultaten tonen" in panel + assert "Geen kwaliteitsresultaten voldoen aan de filters." in panel def test_quality_workspace_density_css_contracts() -> None: diff --git a/backend/tests/test_sprint88_ai_lab_density.py b/backend/tests/test_sprint88_ai_lab_density.py index 02359c11..61322724 100644 --- a/backend/tests/test_sprint88_ai_lab_density.py +++ b/backend/tests/test_sprint88_ai_lab_density.py @@ -52,7 +52,7 @@ def test_ai_lab_preserves_existing_detection_and_segmentation_controls() -> None assert "onLoadResults" in detection assert "onRunQa" in detection assert "detectionTileManifestPath" in detection - assert "Detections loaded: {detectionItems.length}" in detection + assert "{detectionItems.length} objecten geladen" in detection assert "Compare detections to reference" in detection assert "onRunSegmentation" in segmentation diff --git a/backend/tests/test_sprint89_export_system_density.py b/backend/tests/test_sprint89_export_system_density.py index 91a1719b..cc6201eb 100644 --- a/backend/tests/test_sprint89_export_system_density.py +++ b/backend/tests/test_sprint89_export_system_density.py @@ -20,7 +20,7 @@ def test_export_center_exposes_final_handoff_surfaces() -> None: assert 'className="export-actions-surface"' in export_center assert 'aria-label="Export artifact actions"' in export_center assert 'className="export-state-stack"' in export_center - assert 'className="export-history-surface"' in export_center + assert 'className="export-history-surface export-history-disclosure"' in export_center assert 'aria-label="Export history"' in export_center diff --git a/backend/tests/test_sprint93_export_handoff_completion.py b/backend/tests/test_sprint93_export_handoff_completion.py index 57140b6d..dc21ec36 100644 --- a/backend/tests/test_sprint93_export_handoff_completion.py +++ b/backend/tests/test_sprint93_export_handoff_completion.py @@ -11,14 +11,14 @@ def test_export_center_groups_latest_handoff_artifacts_by_type() -> None: assert "latestHandoffArtifacts" in export_center assert "Latest handoff artifacts" in export_center - assert "Project report" in export_center - assert "Project metadata" in export_center - assert "Dataset GeoJSON" in export_center - assert "Detection GeoJSON" in export_center - assert "Segmentation GeoJSON" in export_center + assert "Leesbaar rapport" in export_center + assert "Werkruimtedata" in export_center + assert "Kaartlaag (GeoJSON)" in export_center + assert "Herkende gebouwen (GeoJSON)" in export_center + assert "Segmentaties (GeoJSON)" in export_center assert "latest-artifact-grid" in export_center assert "latest-artifact-card" in export_center - assert "No artifact yet" in export_center + assert "Nog niet gemaakt" in export_center def test_export_center_latest_artifact_cards_keep_existing_actions() -> None: diff --git a/backend/tests/test_sprint94_quality_drilldown.py b/backend/tests/test_sprint94_quality_drilldown.py index 15b04bf1..0fcb6b2c 100644 --- a/backend/tests/test_sprint94_quality_drilldown.py +++ b/backend/tests/test_sprint94_quality_drilldown.py @@ -12,14 +12,14 @@ def test_quality_results_panel_exposes_selected_check_drilldown() -> None: assert "selectedQualityCheckId" in panel assert "selectedQualityCheck" in panel assert "QA/QC evidence drilldown" in panel - assert "Selected check" in panel - assert "Candidate layer" in panel - assert "Reference layer" in panel - assert "Analysis run" in panel + assert "Geselecteerde controle" in panel + assert "Te controleren laag" in panel + assert "Referentielaag" in panel + assert "Analyserun" in panel assert "Job" in panel - assert "Completed" in panel - assert "Inspect latest check" in panel - assert "Inspect check" in panel + assert "Afgerond" in panel + assert "Laatste controle bekijken" in panel + assert "Controle bekijken" in panel def test_quality_results_panel_surfaces_false_positive_negative_evidence() -> None: @@ -27,9 +27,9 @@ def test_quality_results_panel_surfaces_false_positive_negative_evidence() -> No encoding="utf-8" ) - assert "False positive evidence" in panel - assert "False negative evidence" in panel - assert "Map evidence handoff" in panel + assert "Onterecht gevonden" in panel + assert "Gemiste objecten" in panel + assert "Controle op de kaart" in panel assert "metricByKey(selectedQualityCheck, 'false_positive_count')" in panel assert "metricByKey(selectedQualityCheck, 'false_negative_count')" in panel assert "selectedQualityCheck.parameters_json" in panel diff --git a/backend/tests/test_sprint96_useful_default_context.py b/backend/tests/test_sprint96_useful_default_context.py index fa9e4dd3..804864cd 100644 --- a/backend/tests/test_sprint96_useful_default_context.py +++ b/backend/tests/test_sprint96_useful_default_context.py @@ -22,8 +22,8 @@ def test_ai_labs_explain_missing_raster_input_before_disabled_runs() -> None: encoding="utf-8" ) - assert "No raster datasets available for detection." in detection_lab - assert "Upload or select a raster dataset in Data before running object detection." in detection_lab + assert "Geen luchtbeeld beschikbaar in deze werkruimte." in detection_lab + assert "Voeg onder Bronnen een gegeorefereerde GeoTIFF toe." in detection_lab assert "rasterDatasets.length === 0" in detection_lab assert "No raster datasets available for segmentation." in segmentation_lab assert "Upload or select a raster dataset in Data before running segmentation." in segmentation_lab diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index 9681a365..453b2642 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -1,3 +1,26 @@ +## Sprint 193 End-user regional workbench simplification (2026-07-14) + +Changed: +- Made `Kempen Regional Workbench` the automatic complete data context before the municipality fallback and removed the primary map's project-level region selector. +- Kept the 28 municipality Areas plus the complete region available as one spatial work-area selector over the already loaded regional datasets. +- Simplified the visible shell and moved technical project runs, raw source metadata, QA internals, provider capabilities, export history and AI diagnostics into explicit advanced disclosures. +- Defaulted Detection Lab to `yolo-configured`, threshold `0.15`, the active local asset and automatic non-mutating preflight checks. +- Added end-user workbench contract tests without changing backend API contracts, migrations, persistence or model execution. + +AI evidence: +- The configured Tower runtime reports PyTorch `2.13.0+cpu`, Ultralytics `8.4.95`, a valid local active model file and a successful model-load preflight; CUDA is not available. +- The retained active profile records precision `0.5898`, recall `0.5770` and F1 `0.5825` over seven positive AOIs with zero detections in the empty-background control. +- No automatic download or blind retraining was introduced. The 48 false-positive and 48 false-negative review decisions remain a required gate before constructing another training corpus. + +Tested before deployment: +- `bash scripts/run_readiness_check.sh` passed with `560 passed`, backend compile, one Alembic head (`202607140001`), frontend typecheck/build and live-smoke syntax validation. +- `python -m alembic upgrade head --sql` generated a 30,166-byte offline migration plan successfully; `bash -n scripts/live_migration_smoke.sh` passed. +- The false-positive review validator returned the expected `review_required` gate with all 48 selected decisions still `unreviewed`; no unreviewed QA mismatch was promoted into training data. +- Local Docker validation was unavailable because the Windows workstation has no Docker CLI; live container/PostGIS validation remains part of the Tower deployment pass. + +Next: +- Extend official population and modern land-use time series from Mol to all 28 municipalities, then expose a guided raster-import and configured-detection run over a drawn map selection. + ## Sprint 191 Regional Kempen GRB context foundation (2026-07-14) Changed: diff --git a/docs/TODO.md b/docs/TODO.md index d56aff32..9392f0f0 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -11,6 +11,9 @@ - [x] Define and provision the official 28-municipality Vervoerregio Kempen as the explicit operational regional scope. - [x] Partition and provision the complete current GRB building theme for all 28 Kempen transport-region municipalities, with resumable source artifacts and one queryable regional PostGIS dataset. - [x] Provision the remaining regional GRB context themes in bounded partitions: roads, water and parcels. +- [x] Make the complete regional workspace the automatic data context and use municipality/full-region Areas as one spatial filter instead of requiring a region dropdown. +- [x] Reduce end-user noise by moving technical projects, source metadata, QA evidence, provider internals and model diagnostics behind explicit advanced disclosures. +- [x] Default Detection Lab to the configured local YOLO asset and present measured model quality and control requirements honestly. - [ ] Extend official population and land-use time series from Mol to the approved 28-municipality regional scope. This file now starts with the current implementation status. Older preparation/backlog sections are preserved below as historical planning context and should not be treated as the live sprint board without checking `docs/CODEX_EXECUTION_LOG.md`. diff --git a/frontend/README.md b/frontend/README.md index 8a029bec..1ce42cb9 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,8 +1,12 @@ # GeoIntel Frontend (Sprint 4) -React + TypeScript + MapLibre foundation for project/area/dataset workflow. +React + TypeScript + MapLibre workbench for regional geographic analysis. -Mol is the primary operating context. On a fresh session the application opens the map-first geographic explorer, prefers the persisted `Mol Municipality Workbench`, selects the official NIS `13025` municipality boundary and activates the largest available authoritative building layer. A compact `Regio` selector switches between this workspace and the explicitly provisioned `Kempen Regional Workbench`, whose scope is the official 28-municipality Vlaamse vervoerregio. Explicit project and dataset selections remain authoritative. +The persisted `Kempen Regional Workbench` is the automatic operational data context. Its current GRB layers are loaded once for the complete official 28-municipality Vlaamse vervoerregio; the operator chooses Mol, another municipality or the complete region as a spatial work-area filter. The primary map no longer asks the user to choose a technical project or region before data becomes usable. Mol remains the primary validation and historical-data focus, while explicit project selection stays available under advanced management. + +The user-facing shell is task based: `Kaart`, `Bronnen`, `Kwaliteit`, `Beeldanalyse`, `Downloads`, `Status` and `Beheer`. Internal benchmark projects, raw dataset metadata, provider capabilities, model registry details and QA evidence remain accessible through labelled advanced disclosures instead of competing with the normal workflow. + +Detection defaults to the configured local YOLO asset and automatically selects an available raster and active model asset where possible. The model registry and preflight remain honest when PyTorch, Ultralytics, a local model file or a tile manifest is unavailable. The active building profile is operational but remains review-required: its recorded coverage-aware benchmark is approximately precision 0.590, recall 0.577 and F1 0.582 over seven positive AOIs, with zero detections in the empty-background control. Another training pass is intentionally blocked until the generated false-positive and false-negative review decisions are completed. The map-first explorer has two deliberate modes. `Latest state` selects the latest explicitly dated source snapshot without claiming an old edition is @@ -21,7 +25,7 @@ estimates clearly marked and land-cover sources show intersected hectares. The advanced workbench remains available but is not required for the primary choose-theme, draw-area, read-result flow. -The primary workflow is deliberately short: choose Mol or Kempen, choose a data theme, drag a rectangle on the MapLibre map and read the resulting PostGIS evidence. Releasing the drag runs the active theme query and every other available theme query for the same EPSG:4326 bbox. The result panel shows selection area, exact intersection totals, active-theme density, source identity and bounded feature properties. Map rendering remains capped at 1,000 features while `total_feature_count` reports the exact database count. +The primary workflow is deliberately short: choose a municipality or the complete region, choose a data theme, drag a rectangle on the MapLibre map and read the resulting PostGIS evidence. Releasing the drag runs the active theme query and every other available theme query for the same EPSG:4326 bbox. The result panel shows selection area, exact intersection totals, active-theme density, source identity and bounded feature properties. Map rendering remains capped at 1,000 features while `total_feature_count` reports the exact database count. The theme catalog currently recognizes buildings, population, forest/green, water, roads and parcels from dataset names and canonical `reference_layer_name` metadata. A theme is enabled only when a ready persisted vector dataset exists; otherwise it states `Bron nog niet ingeladen`. This prevents missing population or land-cover sources from appearing as zero-valued observations. The previous technical Map workspace remains available through `Geavanceerde werkbank` for derived datasets, QA/QC evidence and export operations. diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 63a2aa00..1061810b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -30,6 +30,8 @@ import { useQualityWorkflow } from './hooks/useQualityWorkflow' import { useSegmentationWorkflow } from './hooks/useSegmentationWorkflow' import { useWorkbenchBootstrap } from './hooks/useWorkbenchBootstrap' import { useViewportVectorLayer } from './hooks/useViewportVectorLayer' +import { getDatasetDisplayName } from './lib/datasetDisplay' +import { REGIONAL_WORKSPACE_LABEL, REGIONAL_WORKSPACE_PROJECT_NAME } from './config/primaryFocus' function isVectorDatasetType(datasetType: string): boolean { return datasetType === 'vector' || datasetType === 'geojson' @@ -38,19 +40,20 @@ function isVectorDatasetType(datasetType: string): boolean { type WorkspaceKey = 'overview' | 'data' | 'map' | 'analysis' | 'ai' | 'exports' | 'system' const workspaceNavItems: Array<{ key: WorkspaceKey; label: string; description: string }> = [ - { key: 'overview', label: 'Overview', description: 'V1 readiness and next action' }, - { key: 'data', label: 'Data', description: 'Projects, AOI and datasets' }, - { key: 'map', label: 'Map', description: 'Layer review and inspection' }, - { key: 'analysis', label: 'QA/QC', description: 'Quality checks and change detection' }, - { key: 'ai', label: 'AI Labs', description: 'Detection and segmentation' }, - { key: 'exports', label: 'Exports', description: 'Artifacts and downloads' }, - { key: 'system', label: 'System', description: 'Provider capabilities' }, + { key: 'overview', label: 'Status', description: 'Beschikbaarheid en aandachtspunten' }, + { key: 'data', label: 'Bronnen', description: 'Gebieden en ingeladen gegevens' }, + { key: 'map', label: 'Kaart', description: 'Selecteren, uitlezen en vergelijken' }, + { key: 'analysis', label: 'Kwaliteit', description: 'Resultaten controleren' }, + { key: 'ai', label: 'Beeldanalyse', description: 'Gebouwen herkennen op luchtbeelden' }, + { key: 'exports', label: 'Downloads', description: 'Resultaten bewaren en delen' }, + { key: 'system', label: 'Beheer', description: 'Databronnen en systeemstatus' }, ] const workspaceNavGroups: Array<{ label: string; keys: WorkspaceKey[] }> = [ - { label: 'Workspace', keys: ['overview', 'data', 'map'] }, - { label: 'Analyze', keys: ['analysis', 'ai'] }, - { label: 'Deliver', keys: ['exports', 'system'] }, + { label: 'Verkennen', keys: ['map', 'data'] }, + { label: 'Analyseren', keys: ['analysis', 'ai'] }, + { label: 'Afronden', keys: ['exports'] }, + { label: 'Beheer', keys: ['overview', 'system'] }, ] function App(): JSX.Element { @@ -659,43 +662,45 @@ function App(): JSX.Element { target: 'exports', }, ] - const projectContextLabel = selectedProject?.name ?? 'No project' - const areaContextLabel = selectedArea?.name ?? (areas.length > 0 ? 'Select area' : 'No AOI') - const datasetContextLabel = selectedDataset?.name ?? (datasets.length > 0 ? 'Select dataset' : 'No dataset') + const projectContextLabel = selectedProject?.name === REGIONAL_WORKSPACE_PROJECT_NAME + ? REGIONAL_WORKSPACE_LABEL + : selectedProject?.name ?? 'Geen werkruimte' + const areaContextLabel = selectedArea?.name ?? (areas.length > 0 ? 'Kies een gebied' : 'Geen gebied') + const datasetContextLabel = selectedDataset ? getDatasetDisplayName(selectedDataset) : datasets.length > 0 ? 'Kies een bron' : 'Geen bron' const layerContextLabel = mapFeatureCollection ? `${mapFeatureCount} features` : viewportVectorLayerActive - ? 'Viewport layer selected' - : 'No active layer' + ? 'Kaartlaag gekozen' + : 'Geen actieve laag' return (
- Skip to workspace + Ga naar de werkruimte

GeoIntel

-

GeoAI Workbench · Mol / Kempen

+

GeoAI-werkruimte · Mol en de Kempen

- Project + Werkruimte {projectContextLabel}
- AOI + Gebied {areaContextLabel}
- Dataset + Bron {datasetContextLabel}
- Layer + Kaartlaag {layerContextLabel}
@@ -770,7 +775,7 @@ function App(): JSX.Element { ))}
- {selectedProject ? `Active project: ${selectedProject.name}` : 'Start with a project or load the demo workflow.'} + {selectedProject ? `Actief bereik: ${projectContextLabel}` : 'De regionale gegevens worden ingeladen.'} {activeWorkspace === 'overview' ? ( @@ -784,6 +789,11 @@ function App(): JSX.Element { activeLayerFeatureCount={mapFeatureCount} selectedAreaHasGeometry={Boolean(areaFeatureCollection)} /> +
+ + Volledige workflowstatus + {workflowGuidanceComplete ? 'voltooid' : 'optionele stappen open'} +
@@ -860,6 +870,7 @@ function App(): JSX.Element {
+
) : null} @@ -958,7 +969,6 @@ function App(): JSX.Element { selectedMapDatasetId={selectedDataset && isVectorDatasetType(selectedDataset.dataset_type) ? selectedDataset.id : ''} selectedFeature={selectedMapFeature} onSelectMapArea={setSelectedMapAreaId} - onSelectProject={selectProject} onOpenDatasetInMap={openDatasetInMap} onSetAreaLayerVisible={setAreaLayerVisible} onSetAreaLayerOpacity={setAreaLayerOpacity} @@ -981,22 +991,6 @@ function App(): JSX.Element { {activeWorkspace === 'analysis' ? (
- - +
+ + Historische vectorlagen vergelijken + Geavanceerd + + +
) : null} @@ -1069,46 +1084,52 @@ function App(): JSX.Element { onApplyOperatorProfile={applyDetectionOperatorProfile} /> - loadSegmentationRuns()} - onSelectRun={setSelectedSegmentationRunId} - onSetClassFilter={setSegmentationClassFilter} - onSetMinConfidenceFilter={setSegmentationMinConfidenceFilter} - onLoadResults={() => loadSegmentationResults()} - onSelectReferenceDataset={setSegmentationReferenceDatasetId} - onRunQa={runSegmentationQa} - /> +
+ + Segmentatie van beeldvlakken + Nog niet geconfigureerd + + loadSegmentationRuns()} + onSelectRun={setSelectedSegmentationRunId} + onSetClassFilter={setSegmentationClassFilter} + onSetMinConfidenceFilter={setSegmentationMinConfidenceFilter} + onLoadResults={() => loadSegmentationResults()} + onSelectReferenceDataset={setSegmentationReferenceDatasetId} + onRunQa={runSegmentationQa} + /> +
) : null} diff --git a/frontend/src/components/WorkbenchStatusStrip.tsx b/frontend/src/components/WorkbenchStatusStrip.tsx index 63c625e9..3e0867bb 100644 --- a/frontend/src/components/WorkbenchStatusStrip.tsx +++ b/frontend/src/components/WorkbenchStatusStrip.tsx @@ -28,21 +28,21 @@ function countReferenceDatasets(datasets: DatasetCreateResponse[]): number { function nextAction(items: StatusItem[]): string { if (!items.some((item) => item.key === 'project' && item.state === 'ready')) { - return 'Create or open a project.' + return 'De regionale werkruimte kon niet worden geopend.' } if (!items.some((item) => item.key === 'area' && item.state === 'ready')) { - return 'Create an AOI or select an area with geometry.' + return 'Kies een gemeente of de volledige regio.' } if (!items.some((item) => item.key === 'datasets' && item.state === 'ready')) { - return 'Upload a vector or raster dataset, or load the offline demo workflow.' + return 'Laad een kaartlaag of luchtbeeld in.' } if (!items.some((item) => item.key === 'qa' && item.state === 'ready')) { - return 'Run QA/QC against a reference dataset when candidate and reference data are available.' + return 'Kaart en bronnen zijn klaar. Kwaliteitscontrole is beschikbaar zodra er een analyseresultaat is.' } if (!items.some((item) => item.key === 'exports' && item.state === 'ready')) { - return 'Create a metadata, report or GeoJSON export for handoff.' + return 'De analyse is klaar; maak eventueel een rapport of download.' } - return 'Workbench has the core V1 loop populated.' + return 'Alle onderdelen van de werkruimte bevatten bewaarde resultaten.' } export function WorkbenchStatusStrip({ @@ -64,63 +64,64 @@ export function WorkbenchStatusStrip({ const items: StatusItem[] = [ { key: 'project', - label: 'Project', - value: selectedProject ? selectedProject.name : 'No project', - detail: selectedProject ? selectedProject.region : 'Open or create a project', + label: 'Werkruimte', + value: selectedProject ? selectedProject.name : 'Geen werkruimte', + detail: selectedProject ? selectedProject.region : 'Niet geladen', state: selectedProject ? 'ready' : 'waiting', }, { key: 'area', - label: 'AOI', - value: `${areas.length} area${areas.length === 1 ? '' : 's'}`, - detail: selectedAreaHasGeometry ? 'selected area has map geometry' : 'no selected map geometry', + label: 'Gebieden', + value: `${areas.length} beschikbaar`, + detail: selectedAreaHasGeometry ? 'Kaartgrens beschikbaar' : 'Geen kaartgrens geselecteerd', state: selectedAreaHasGeometry ? 'ready' : areas.length > 0 ? 'warning' : 'waiting', }, { key: 'datasets', - label: 'Datasets', - value: `${readyDatasets}/${datasets.length} ready`, - detail: `${vectorDatasets} vector, ${rasterDatasets} raster, ${referenceDatasets} reference`, + label: 'Bronnen', + value: `${readyDatasets}/${datasets.length} beschikbaar`, + detail: `${vectorDatasets} kaartlagen, ${rasterDatasets} luchtbeelden, ${referenceDatasets} officiële referenties`, state: datasets.length === 0 ? 'waiting' : readyDatasets === datasets.length ? 'ready' : 'warning', }, { key: 'map', - label: 'Map', - value: `${activeLayerFeatureCount} feature${activeLayerFeatureCount === 1 ? '' : 's'}`, - detail: selectedAreaHasGeometry ? 'AOI overlay available' : 'AOI overlay missing', + label: 'Kaart', + value: `${activeLayerFeatureCount} zichtbare objecten`, + detail: selectedAreaHasGeometry ? 'Gebiedsgrens beschikbaar' : 'Gebiedsgrens ontbreekt', state: selectedAreaHasGeometry || activeLayerFeatureCount > 0 ? 'ready' : 'waiting', }, { key: 'qa', - label: 'QA/QC', - value: `${qualityChecks.length} check${qualityChecks.length === 1 ? '' : 's'}`, - detail: latestQualityCheck ? `${latestQualityCheck.check_type}: ${latestQualityCheck.status}` : 'no persisted QA/QC result', + label: 'Kwaliteit', + value: `${qualityChecks.length} controles`, + detail: latestQualityCheck ? `${latestQualityCheck.check_type}: ${latestQualityCheck.status}` : 'Nog geen controle uitgevoerd', state: qualityChecks.length > 0 ? 'ready' : 'waiting', }, { key: 'exports', - label: 'Exports', - value: `${exports.length} export${exports.length === 1 ? '' : 's'}`, - detail: latestExport ? `${latestExport.export_type}: ${latestExport.status}` : 'no handoff artifact yet', + label: 'Downloads', + value: `${exports.length} bestanden`, + detail: latestExport ? `${latestExport.export_type}: ${latestExport.status}` : 'Nog geen bestand gemaakt', state: exports.length > 0 ? 'ready' : 'waiting', }, ] const readyCount = items.filter((item) => item.state === 'ready').length + const coreReady = items.slice(0, 4).every((item) => item.state === 'ready') return (
-

V1 readiness

+

Platformstatus

Workbench status: - {readyCount === items.length ? 'Mol workbench is ready' : 'Mol workbench needs attention'} + {coreReady ? 'Kaartwerkruimte is gebruiksklaar' : 'Kaartwerkruimte vraagt aandacht'}

{nextAction(items)}

{readyCount}/{items.length} - stages ready + onderdelen gereed
diff --git a/frontend/src/components/datasets/DatasetPanel.tsx b/frontend/src/components/datasets/DatasetPanel.tsx index 1a0d621e..fdd538ce 100644 --- a/frontend/src/components/datasets/DatasetPanel.tsx +++ b/frontend/src/components/datasets/DatasetPanel.tsx @@ -1,5 +1,6 @@ import type { Dispatch, FormEvent, SetStateAction } from 'react' import type { AreaRead, DatasetCreateResponse } from '../../types' +import { getDatasetDisplayName } from '../../lib/datasetDisplay' interface DatasetFormState { datasetType: string @@ -70,25 +71,25 @@ function normalizeDatasetRole(dataset: DatasetCreateResponse): 'reference' | 'ca function datasetRoleLabel(role: string): string { if (role === 'reference') { - return 'Reference' + return 'Officiële referentie' } if (role === 'candidate') { - return 'Candidate' + return 'Analyse-resultaat' } - return 'Source' + return 'Eigen bron' } function datasetActionHint(dataset: DatasetCreateResponse, role: string): string { if (role === 'reference') { - return 'QA reference layer. Select it when comparing candidates against trusted geometry.' + return 'Officiële laag voor kaartanalyse en kwaliteitscontrole.' } if (role === 'candidate') { - return 'QA candidate layer. Open on the map or send to Export / QA.' + return 'Bewaard resultaat dat opnieuw op de kaart, in kwaliteit of als download kan worden gebruikt.' } if (dataset.dataset_type === 'raster') { - return 'Raster source. Use the raster tools in the inspector after selecting it.' + return 'Luchtbeeld of rasterbron voor kaart- en beeldanalyse.' } - return 'Source dataset. Inspect details before using it in downstream workflows.' + return 'Ingeladen gegevensbron voor verdere analyse.' } export function DatasetPanel({ @@ -111,30 +112,30 @@ export function DatasetPanel({ const roleSummaries = [ { key: 'selected', - label: 'Selected', + label: 'Geselecteerd', count: selectedDatasetId ? 1 : 0, - hint: 'Active', + hint: 'Actief', className: 'dataset-role-selected', }, { key: 'reference', - label: 'Reference', + label: 'Referentie', count: datasets.filter((dataset) => normalizeDatasetRole(dataset) === 'reference').length, - hint: 'QA truth', + hint: 'Officieel', className: 'dataset-role-reference', }, { key: 'candidate', - label: 'Candidate', + label: 'Resultaat', count: datasets.filter((dataset) => normalizeDatasetRole(dataset) === 'candidate').length, - hint: 'QA candidate', + hint: 'Afgeleid', className: 'dataset-role-candidate', }, { key: 'source', - label: 'Source', + label: 'Eigen bron', count: datasets.filter((dataset) => normalizeDatasetRole(dataset) === 'source').length, - hint: 'Uploads', + hint: 'Ingeladen', className: 'dataset-role-source', }, ] @@ -143,23 +144,23 @@ export function DatasetPanel({
-

Data catalog

-

Datasets

+

Ingeladen gegevens

+

Bronnen

- {readyDatasets}/{datasets.length} ready + {readyDatasets}/{datasets.length} beschikbaar
- Selected dataset - {selectedDataset?.name ?? 'No dataset selected'} - {selectedDataset ? `${datasetRoleLabel(normalizeDatasetRole(selectedDataset))} / ${selectedDataset.status}` : 'Inspect a dataset to load details and tools.'} + Actieve bron + {selectedDataset ? getDatasetDisplayName(selectedDataset) : 'Geen bron geselecteerd'} + {selectedDataset ? `${datasetRoleLabel(normalizeDatasetRole(selectedDataset))} · ${selectedDataset.status === 'ready' ? 'beschikbaar' : selectedDataset.status}` : 'Kies een bron om deze op de kaart te openen.'}
- Upload source data + Eigen bronbestand toevoegen
@@ -215,15 +216,15 @@ export function DatasetPanel({ ))}
- {loadingDatasets ?

Loading datasets...

: null} + {loadingDatasets ?

Bronnen laden...

: null} {datasets.length === 0 ? (
- No datasets yet -

Upload vector, GeoJSON or raster data after selecting a project.

+ Nog geen bronnen beschikbaar +

Voeg een vector-, GeoJSON- of rasterbestand toe aan deze werkruimte.

) : null}
-

Available datasets

+

Beschikbare bronnen

    {datasets.map((dataset) => { const datasetRole = normalizeDatasetRole(dataset) @@ -236,22 +237,16 @@ export function DatasetPanel({ {roleLabel} {dataset.dataset_type}
- {dataset.name} -
- Source: {dataset.source_name ?? dataset.source} - {dataset.reference_layer_name ? Layer: {dataset.reference_layer_name} : null} - CRS: {dataset.crs ?? dataset.vector_summary?.crs ?? 'n/a'} + {getDatasetDisplayName(dataset)} +
+ {(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0).toLocaleString('nl-BE')} objecten + {dataset.source_name ?? dataset.source}
- {dataset.status === 'ready' ? 'ready' : dataset.status === 'failed' ? 'failed' : 'pending'} + {dataset.status === 'ready' ? 'beschikbaar' : dataset.status === 'failed' ? 'mislukt' : 'bezig'} -
- size: {formatBytes(dataset.size_bytes)} - features: {dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 'n/a'} - bbox: {formatBounds(dataset.bounds_json ?? dataset.vector_summary?.bounds_json)} -

{datasetActionHint(dataset, datasetRole)}

+
+
+ Broninformatie +
+ Bestand: {dataset.name} + Bron: {dataset.source_name ?? dataset.source} + {dataset.reference_layer_name ? Laag: {dataset.reference_layer_name} : null} + CRS: {dataset.crs ?? dataset.vector_summary?.crs ?? 'onbekend'} +
+
+ Grootte: {formatBytes(dataset.size_bytes)} + Objecten: {dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 'onbekend'} + Begrenzing: {formatBounds(dataset.bounds_json ?? dataset.vector_summary?.bounds_json)} +
- +
) })} diff --git a/frontend/src/components/detection/DetectionLab.tsx b/frontend/src/components/detection/DetectionLab.tsx index 63a4b62c..9d4a685e 100644 --- a/frontend/src/components/detection/DetectionLab.tsx +++ b/frontend/src/components/detection/DetectionLab.tsx @@ -145,6 +145,14 @@ export function DetectionLab({ }: DetectionLabProps): JSX.Element { const selectedDetectionModel = detectionModels.find((model) => model.model_id === selectedDetectionModelId) ?? null const selectedModelAsset = modelAssets.find((asset) => asset.model_asset_id === selectedModelAssetId) ?? null + const selectedOperatorProfile = DETECTION_OPERATOR_PROFILES.find( + (profile) => profile.modelAssetId === selectedModelAssetId, + ) ?? null + const yoloRuntimeReady = Boolean( + yoloPreflight?.checks.enabled && + yoloPreflight.checks.dependencies_available && + yoloPreflight.checks.model_file_exists, + ) const detectionRequiresTileManifest = selectedDetectionModelId === 'yolo-configured' const detectionHasDataset = selectedDetectionDatasetId.length > 0 const detectionHasModel = selectedDetectionModel !== null @@ -178,19 +186,19 @@ export function DetectionLab({ detectionHasExplicitModelAsset && detectionHasTileManifest const detectionRunBlockedReason = !selectedProjectId - ? 'Select or create a project first' + ? 'De regionale werkruimte is nog niet geladen' : !detectionHasDataset - ? 'Select a raster dataset' + ? 'Kies een luchtbeeld' : !detectionHasModel - ? 'Select a detection model' + ? 'Kies een analysemodel' : selectedDetectionModelId === 'manual-fixture-detector' - ? 'Fixture model is explicit test/demo-only' + ? 'Het fixturemodel is alleen bedoeld voor expliciete tests en demo\'s' : !detectionModelReady - ? selectedDetectionModel?.limitation_message ?? 'Selected model is not configured' + ? selectedDetectionModel?.limitation_message ?? 'Het gekozen model is niet geconfigureerd' : !detectionHasExplicitModelAsset - ? 'Select a local model asset deliberately' + ? 'Kies een lokaal modelbestand onder beheer' : !detectionHasTileManifest - ? 'Provide a raster tile manifest for configured YOLO' + ? 'Maak eerst beeldtegels voor het gekozen luchtbeeld' : null const calibrationRunReady = detectionRunReady && detectionReferenceDatasetId.length > 0 && calibrationThresholdText.trim().length > 0 @@ -198,18 +206,41 @@ export function DetectionLab({
-

Object detection

-

Detection Lab

+

Analyse van luchtbeelden

+

Gebouwen herkennen

+
+
+ Actieve analyse + Gebouwdetectie +

{selectedOperatorProfile?.displayName ?? selectedModelAsset?.display_name ?? 'Lokaal YOLO-model'}

+
+
+ PyTorch-runtime + {yoloRuntimeReady ? 'Gereed' : loadingDetectionModels ? 'Controleren...' : 'Niet gereed'} +

{yoloRuntimeReady ? `${yoloPreflight?.runtime.cuda_available ? 'GPU' : 'CPU'} · lokaal model gevonden` : 'Controleer de modelconfiguratie onder beheer.'}

+
+
+ Gevalideerde kwaliteit + {selectedOperatorProfile ? `F1 ${selectedOperatorProfile.f1.toFixed(3)}` : 'Nog niet gekoppeld'} +

{selectedOperatorProfile ? `${selectedOperatorProfile.positiveSampleCount} testgebieden · resultaten blijven controleplichtig` : 'Kies het goedgekeurde lokale profiel.'}

+
+
0 ? 'ai-user-summary-card ai-user-summary-card-ready' : 'ai-user-summary-card'}> + Beschikbare luchtbeelden + {rasterDatasets.length} +

{rasterDatasets.length > 0 ? 'Klaar om een beeld te kiezen.' : 'Laad eerst een gegeorefereerd luchtbeeld in.'}

+
+
+
- Model registry - {detectionModels.length} models + Technische modelinformatie + {detectionModels.length} registraties
@@ -257,21 +288,25 @@ export function DetectionLab({
{selectedDetectionModelId === 'yolo-configured' ? ( -
+
+ + Modelkeuze voor beheerders + {selectedOperatorProfile?.displayName ?? selectedModelAsset?.display_name ?? 'Geen lokaal model'} + +
-

Explicit model asset

-

Local model assets are read-only. No model file is selected automatically. Choose an existing local model asset before submitting configured YOLO.

+

Lokaal modelbestand

+

GeoIntel kiest automatisch het actieve lokale model. Een beheerder kan hier bewust een ander reeds aanwezig, alleen-lezen modelbestand kiezen.

- {selectedModelAsset ? 'asset selected' : 'no explicit asset'} + {selectedModelAsset ? 'model gekozen' : 'geen model gekozen'}
- Operator profiles + Gevalideerde profielen

- Candidate profiles apply a local model asset and confidence threshold only after an explicit click. - Promoted profiles still require explicit operator action and do not mutate the runtime environment. + Een profiel koppelt een lokaal model aan een gemeten zekerheidsdrempel. Een andere keuze geldt alleen voor de huidige analyse en wijzigt de serverconfiguratie niet.

@@ -288,22 +323,22 @@ export function DetectionLab({
{profile.displayName} - {profile.defaultApproved ? 'default-approved' : 'Candidate only - not default-approved'} + {profile.defaultApproved ? 'standaardprofiel' : 'kandidaat · extra controle vereist'}

{profile.description}

- threshold {profile.confidenceThreshold.toFixed(2)} + drempel {profile.confidenceThreshold.toFixed(2)} precision {profile.precision.toFixed(3)} recall {profile.recall.toFixed(3)} F1 {profile.f1.toFixed(3)} - positive AOIs {profile.positiveSampleCount} - max background FP {profile.maxBackgroundDetections} + testgebieden {profile.positiveSampleCount} + max. achtergrondfouten {profile.maxBackgroundDetections}
- asset: {profile.modelAssetId} - promotionRecommendation: {profile.promotionRecommendation} - available: {profileAsset ? 'yes' : 'not mounted'} + modelbestand: {profile.modelAssetId} + beoordeling: {profile.promotionRecommendation} + beschikbaar: {profileAsset ? 'ja' : 'niet gekoppeld'}

{profile.limitationMessage}

) @@ -320,17 +355,16 @@ export function DetectionLab({
{selectedModelAsset ? (
- Selected model asset status + Status gekozen model

- {selectedModelAsset.display_name} is selected for this browser-run request. Runtime default activation - remains a separate guarded operator action backed by a promotion report. + {selectedModelAsset.display_name} wordt voor deze analyse gebruikt. De standaard serverconfiguratie blijft ongewijzigd.

) : null}
{selectedDetectionModelId === 'yolo-configured' ? (
) diff --git a/frontend/src/components/map/MapWorkspace.tsx b/frontend/src/components/map/MapWorkspace.tsx index 5247e3c0..d8ca5465 100644 --- a/frontend/src/components/map/MapWorkspace.tsx +++ b/frontend/src/components/map/MapWorkspace.tsx @@ -4,6 +4,7 @@ import type { AreaRead, DatasetCreateResponse, MapViewportState, ProjectRead, Qa import { featureCollectionBounds } from '../../lib/geojsonBounds' import { useMapThemeSelectionInsights } from '../../hooks/useMapThemeSelectionInsights' import { useTemporalComparison } from '../../hooks/useTemporalComparison' +import { getDatasetDisplayName } from '../../lib/datasetDisplay' const DEFAULT_SELECTED_FEATURE_FILENAME = 'selected-feature.geojson' const DEFAULT_AREA_SELECTION_FILENAME = 'area-selection.geojson' @@ -443,7 +444,6 @@ interface MapWorkspaceProps { availableMapDatasets: DatasetCreateResponse[] selectedMapDatasetId: string onSelectMapArea: (areaId: string) => void - onSelectProject: (projectId: string) => void onOpenDatasetInMap: (dataset: DatasetCreateResponse) => void onSetAreaLayerVisible: (visible: boolean) => void onSetAreaLayerOpacity: (opacity: number) => void @@ -512,7 +512,6 @@ export function MapWorkspace({ availableMapDatasets, selectedMapDatasetId, onSelectMapArea, - onSelectProject, onOpenDatasetInMap, onSetAreaLayerVisible, onSetAreaLayerOpacity, @@ -590,16 +589,9 @@ export function MapWorkspace({ const activeTheme = DATA_THEMES.find((theme) => theme.id === activeThemeId) ?? DATA_THEMES[0] const activeThemeMapStyle = DATA_THEME_MAP_STYLES[activeTheme.id] const activeThemeDataset = themeDatasetMap[activeTheme.id] - const operationalScopeProjects = useMemo(() => { - const scoped = projects.filter((project) => project.name === MOL_PROJECT_NAME || project.name === KEMPEN_PROJECT_NAME) - const selected = projects.find((project) => project.id === selectedProjectId) - if (selected && !scoped.some((project) => project.id === selected.id)) { - return [selected, ...scoped] - } - return scoped - }, [projects, selectedProjectId]) const activeScopeProject = projects.find((project) => project.id === selectedProjectId) ?? null const activeScopeLabel = activeScopeProject ? operationalScopeProjectLabel(activeScopeProject) : 'Werkgebied' + const municipalityAreaCount = areas.filter((area) => /^Gemeente\s/i.test(area.name)).length const activeTemporalSeriesGroups = useMemo( () => listThemeTemporalSeries(availableMapDatasets, activeTheme), [activeTheme, availableMapDatasets], @@ -964,19 +956,11 @@ export function MapWorkspace({

Dit zijn databronnen, geen AI-modellen.

- +
+ Ingeladen bereik + {activeScopeLabel} + {municipalityAreaCount > 0 ? `${municipalityAreaCount} gemeenten en de volledige regio beschikbaar` : 'Regionale gegevens worden geladen'} +
{DATA_THEMES.map((theme) => { const dataset = themeDatasetMap[theme.id] @@ -1006,7 +990,7 @@ export function MapWorkspace({ {analysisMode === 'evolution' ? activeTemporalSeriesGroup?.label ?? 'Geen tijdreeks beschikbaar' - : activeThemeDataset?.name ?? 'Geen databron beschikbaar'} + : activeThemeDataset ? getDatasetDisplayName(activeThemeDataset) : 'Geen databron beschikbaar'} {analysisMode === 'evolution' @@ -1065,9 +1049,8 @@ export function MapWorkspace({ ) : null}