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
+28
View File
@@ -1680,3 +1680,31 @@ DatasetService/VectorFeatureService, creates new temporal Datasets and retains
all previous snapshots. Do not add `--force` to this coordinator; a source
refetch remains a separate deliberate recovery action in the lower-level
operators.
## Safe project lifecycle cleanup
Operational validation, calibration and benchmark runs can create technical
projects. The default project API now returns active workspaces only, while
archived workspaces remain queryable with `GET /api/v1/projects?status=archived`.
Use the packaged cleanup command to archive only the strict technical-name
allowlist:
```bash
# Dry-run: inspect the number of matches without changing the database.
python scripts/archive_technical_projects.py
# Apply the exact allowlisted plan.
python scripts/archive_technical_projects.py --apply
```
Inside the all-in-one Unraid container:
```bash
docker exec geointel python /app/scripts/archive_technical_projects.py
docker exec geointel python /app/scripts/archive_technical_projects.py --apply
```
The command never deletes projects or related datasets, jobs, analyses,
quality checks and exports. It always preserves `Kempen Regional Workbench`
and `Mol Municipality Workbench`, defaults to dry-run and can print every
matched name with `--show-names`.
+11 -1
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
from typing import Literal
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query, status
@@ -18,9 +19,16 @@ def list_projects(
limit: int = Query(default=50, ge=1, le=200),
offset: int = Query(default=0, ge=0),
name: str | None = Query(default=None, min_length=1, max_length=255),
project_status: Literal["active", "archived", "all"] = Query(default="active", alias="status"),
db: Session = Depends(get_db),
):
projects, total = ProjectService.list_projects(db, limit=limit, offset=offset, name=name)
projects, total = ProjectService.list_projects(
db,
limit=limit,
offset=offset,
name=name,
project_status=project_status,
)
return envelope({"items": [ProjectRead.model_validate(item).model_dump() for item in projects], "total": total, "limit": limit, "offset": offset})
@@ -41,6 +49,8 @@ def get_project(project_id: UUID, db: Session = Depends(get_db)):
@router.patch("/{project_id}", response_model=dict)
def update_project(project_id: UUID, payload: ProjectUpdate, db: Session = Depends(get_db)):
project = ProjectService.update_project(db, project_id, payload)
if not project:
raise HTTPException(status_code=404, detail="Project not found")
return envelope(ProjectRead.model_validate(project).model_dump())
+2
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
from datetime import datetime
from typing import Literal
from uuid import UUID
from pydantic import BaseModel
@@ -16,6 +17,7 @@ class ProjectUpdate(BaseModel):
name: str | None = None
description: str | None = None
region: str | None = None
status: Literal["active", "archived"] | None = None
class ProjectRead(BaseModel):
+4
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import uuid
from typing import Literal
from sqlalchemy.orm import Session
@@ -16,8 +17,11 @@ class ProjectService:
limit: int = 50,
offset: int = 0,
name: str | None = None,
project_status: Literal["active", "archived", "all"] = "active",
) -> tuple[list[ProjectRead], int]:
query = db.query(Project).filter(Project.status != "deleted")
if project_status != "all":
query = query.filter(Project.status == project_status)
if name:
query = query.filter(Project.name == name.strip())
query = query.order_by(Project.created_at.desc())
@@ -22,7 +22,7 @@ def test_raster_tile_manifest_can_handoff_to_segmentation_lab() -> None:
assert "setSegmentationTileManifestPath(manifestPath)" in app
assert "setSelectedSegmentationDatasetId(selectedDataset.id)" in app
assert "onUseTileManifestForSegmentation" in detail_panel
assert "Use in Segmentation Lab" in raster_controls
assert "Gebruik voor segmentatie" in raster_controls
assert "disabled={!latestRasterTileManifestPath}" in raster_controls
assert "Tile manifest" in segmentation_lab
assert "Raster tile manifest path" in segmentation_lab
assert "Beeldtegelmanifest" in segmentation_lab
assert "Beeldtegelmanifest" in segmentation_lab
@@ -15,7 +15,7 @@ def test_detection_lab_exposes_run_readiness_contract() -> None:
assert "detectionRequiresTileManifest = selectedDetectionModelId === 'yolo-configured'" in lab
assert "detectionTileManifestPath.trim().length > 0" in lab
assert "detectionRunReady" in lab
assert 'aria-label="Detection run readiness"' in lab
assert 'aria-label="Startklaar voor gebouwdetectie"' in lab
assert "Wat is nog nodig?" in lab
assert "Luchtbeeld" in lab
assert "Analysemodel" in lab
@@ -33,12 +33,12 @@ def test_segmentation_lab_exposes_run_readiness_contract() -> None:
assert "segmentationRunReady" in lab
assert "selectedSegmentationModelConfigured" in lab
assert "selectedSegmentationModelLimitation" in lab
assert 'aria-label="Segmentation 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 'aria-label="Startklaar voor segmentatie"' in lab
assert "Wat is nog nodig?" in lab
assert "Rasterbestand" in lab
assert "Analysemodel" in lab
assert "Beeldtegels" in lab
assert "Klaar om te starten" in lab
def test_ai_lab_run_readiness_css_contract() -> None:
@@ -27,8 +27,8 @@ def test_segmentation_lab_distinguishes_configured_model_from_ui_runnable_action
assert "segmentationModelUiRunnable" in lab
assert "selectedSegmentationModelId !== 'fixture-segmenter'" in lab
assert "segmentationRunBlockedReason" in lab
assert "Fixture segmenter is explicit test/demo-only" in lab
assert "Run action" in lab
assert "Het fixturemodel is alleen bedoeld voor expliciete tests" in lab
assert "Analyse" in lab
assert "disabled={runningSegmentation || !segmentationRunReady}" in lab
@@ -11,13 +11,13 @@ def test_map_workspace_exposes_feature_extract_actions() -> None:
encoding="utf-8"
)
assert "Selection & extract" in map_workspace
assert "Selectie en extractie" in map_workspace
assert "downloadSelectedMapFeature" in map_workspace
assert "copySelectedMapFeatureProperties" in map_workspace
assert "selected-feature.geojson" in map_workspace
assert "Download selected GeoJSON" in map_workspace
assert "Copy selected properties" in map_workspace
assert "Clear selection" in map_workspace
assert "Geselecteerde GeoJSON downloaden" in map_workspace
assert "Eigenschappen kopiëren" in map_workspace
assert "Selectie wissen" in map_workspace
assert "featureGeometrySummary" in map_workspace
assert "featureExtractionEntries" in map_workspace
@@ -268,9 +268,9 @@ def test_frontend_exposes_map_bbox_selection_contracts() -> None:
assert "selectVectorFeatures" in api_client
assert "Area selection" in map_workspace
assert "Start map bbox" in map_workspace
assert "Run area extract" in map_workspace
assert "Download area GeoJSON" in map_workspace
assert "Teken rechthoek" in map_workspace
assert "Objecten in gebied ophalen" in map_workspace
assert "Gebiedsdownload bewaren" in map_workspace
assert "bboxSelectionMode" in geomap
assert "selection-bbox" in geomap
assert "selection-result" in geomap
@@ -258,7 +258,7 @@ def test_frontend_exposes_map_selection_export_action() -> None:
assert "area_id?: string" in exports_api
assert "exportMapSelectionGeoJson" in export_hook
assert "vector_selection" in export_hook
assert "Save area export" in map_workspace
assert "Gebiedsdownload bewaren" in map_workspace
assert "onExportMapSelection" in map_workspace
assert "selectionExportError" in map_workspace
assert "onExportMapSelection={exportMapSelectionGeoJson}" in app
@@ -1,4 +1,4 @@
from __future__ import annotations
from __future__ import annotations
import json
from pathlib import Path
@@ -220,5 +220,5 @@ def test_frontend_exposes_map_selection_derive_action() -> None:
assert "VectorSelectionDeriveRequest" in types
assert "deriveVectorSelection" in datasets_api
assert "deriveMapSelectionDataset" in app
assert "Save as dataset" in map_workspace
assert "Als resultaatlaag bewaren" in map_workspace
assert "selectionDatasetError" in map_workspace
@@ -1,4 +1,4 @@
from pathlib import Path
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
@@ -19,7 +19,7 @@ def test_map_selection_qa_shortcut_uses_existing_qa_workflow_contract() -> None:
assert "useMapSelectionQa" in app
assert "mapQaReferenceDatasets={referenceDatasets}" in app
assert "onRunMapSelectionQa={runMapSelectionQa}" in app
assert "Run QA on saved dataset" in map_workspace
assert "Bewaarde laag controleren" in map_workspace
assert "mapSelectionQaError" in map_workspace
assert "mapSelectionQaResult" in map_workspace
@@ -24,13 +24,13 @@ def test_map_qa_result_exposes_quality_check_evidence_contract():
assert "latestMapSelectionQualityCheckId={latestMapSelectionQualityCheckId}" in app
assert "onOpenMapSelectionQualityEvidence={openMapSelectionQualityEvidence}" in app
assert "QA evidence" in workspace
assert "Quality check id" in workspace
assert "Mean IoU" in workspace
assert "False positives" in workspace
assert "False negatives" in workspace
assert "Map selection QA warnings" in workspace
assert "Open QA/QC evidence" in workspace
assert "Kaartbewijs" in workspace
assert "Status bewijs" in workspace
assert "Gemiddelde overlap" in workspace
assert "Onterecht gevonden" in workspace
assert "Gemist" in workspace
assert "Aandachtspunten bij de kwaliteitscontrole" in workspace
assert "Kaartbewijs openen" in workspace
def test_map_qa_evidence_keeps_backend_contract_unchanged():
@@ -18,7 +18,7 @@ def test_qa_feature_evidence_contract_is_documented_and_rendered() -> None:
assert field_name in quality_panel
assert field_name in api_contracts
assert "Feature-level QA/QC evidence" in quality_panel
assert "Kaartbewijs per object" 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
@@ -24,24 +24,24 @@ def test_map_workspace_can_select_persisted_database_layer_and_run_query() -> No
styles = (REPO_ROOT / "frontend/src/styles/app.css").read_text(encoding="utf-8")
assert "map-database-layer-select" in map_workspace
assert "Select persisted vector layer" in map_workspace
assert "Basemap usage notice" in map_workspace
assert "Kies een bewaarde vectorlaag" in map_workspace
assert "Gebruik van de kaartondergrond" in map_workspace
assert "VITE_MAP_STYLE_URL" in map_workspace
assert "Operational GIS run" in map_workspace
assert "Guided operational GIS workflow" in map_workspace
assert "vector_features" in map_workspace
assert "Run AOI/layer query" in map_workspace
assert "Save result dataset" in map_workspace
assert "Save GeoJSON export" in map_workspace
assert "Run QA/QC" in map_workspace
assert "Run full GIS workflow" in map_workspace
assert "Operationele GIS-controle" in map_workspace
assert "Begeleide operationele GIS-werkstroom" in map_workspace
assert "Bewaarde databankobjecten" in map_workspace
assert "Werkgebied of laag doorzoeken" in map_workspace
assert "Resultaatlaag bewaren" in map_workspace
assert "GeoJSON-download bewaren" in map_workspace
assert "Kwaliteit controleren" in map_workspace
assert "Volledige GIS-werkstroom uitvoeren" in map_workspace
assert "runFullGisWorkflow" in map_workspace
assert "fullWorkflowStatus" in map_workspace
assert "Query, save, QA and export" in map_workspace
assert "Selecteren, bewaren, controleren en downloaden" in map_workspace
assert "fullWorkflowMode" in map_workspace
assert "Create new dataset/export" in map_workspace
assert "Reuse latest saved dataset for QA" in map_workspace
assert "Reusing latest saved dataset for QA/QC" in map_workspace
assert "Nieuwe resultaatlaag en download maken" in map_workspace
assert "Laatste resultaatlaag opnieuw controleren" in map_workspace
assert "Het laatste bewaarde resultaat is opnieuw gebruikt en gecontroleerd." in map_workspace
assert "latestSelectionDataset" in map_workspace
assert "selectedMapDatasetId=" in app_shell
assert ".basemap-policy-notice" in styles
@@ -5,15 +5,18 @@ ROOT = Path(__file__).resolve().parents[2]
def test_detection_lab_surfaces_yolo_runtime_preflight() -> None:
lab = (ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(
encoding="utf-8"
lab = "\n".join(
(
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(encoding="utf-8"),
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionModelManagement.tsx").read_text(encoding="utf-8"),
)
)
hook = (ROOT / "frontend" / "src" / "hooks" / "useDetectionWorkflow.ts").read_text(encoding="utf-8")
api = (ROOT / "frontend" / "src" / "services" / "api" / "detection.ts").read_text(encoding="utf-8")
types = (ROOT / "frontend" / "src" / "types.ts").read_text(encoding="utf-8")
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
assert "YOLO runtime preflight" in lab
assert "Technische YOLO-runtimecontrole" in lab
assert "torch_version" in lab
assert "ultralytics_version" in lab
assert "cuda_available" in lab
@@ -26,8 +29,11 @@ def test_detection_lab_surfaces_yolo_runtime_preflight() -> None:
def test_detection_lab_surfaces_local_model_asset_selection() -> None:
lab = (ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(
encoding="utf-8"
lab = "\n".join(
(
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(encoding="utf-8"),
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionModelManagement.tsx").read_text(encoding="utf-8"),
)
)
hook = (ROOT / "frontend" / "src" / "hooks" / "useDetectionWorkflow.ts").read_text(encoding="utf-8")
api = (ROOT / "frontend" / "src" / "services" / "api" / "detection.ts").read_text(encoding="utf-8")
@@ -44,7 +50,7 @@ def test_detection_lab_surfaces_local_model_asset_selection() -> None:
assert "modelAssets" in hook
assert "selectedModelAssetId" in hook
assert "model_asset_id: selectedModelAssetId || null" in hook
assert "Local model assets" in lab
assert "Lokaal modelbestand" in lab
assert "onSelectModelAsset" in lab
assert "modelAssets={modelAssets}" in app
assert "Officiële referentiebronnen" in provider_panel
@@ -16,21 +16,27 @@ def test_detection_workflow_selects_only_the_active_runtime_model_asset_automati
def test_detection_lab_explains_explicit_model_asset_and_threshold_selection() -> None:
lab = (ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(
encoding="utf-8"
lab = "\n".join(
(
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(encoding="utf-8"),
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionModelManagement.tsx").read_text(encoding="utf-8"),
)
)
assert "Lokaal modelbestand" in lab
assert "GeoIntel kiest automatisch het actieve lokale model" in lab
assert "Gevalideerde profielen" in lab
assert "Gevalideerde YOLO-profielen" in lab
assert "DETECTION_OPERATOR_PROFILES" in lab
assert "kandidaat · extra controle vereist" in lab
assert "kandidaat, extra controle vereist" in lab
assert "will_download_models" in lab
def test_detection_run_readiness_requires_explicit_asset_when_local_assets_exist() -> None:
lab = (ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(
encoding="utf-8"
lab = "\n".join(
(
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(encoding="utf-8"),
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionModelManagement.tsx").read_text(encoding="utf-8"),
)
)
assert "detectionHasExplicitModelAsset" in lab
@@ -21,17 +21,20 @@ def test_raster_controls_show_manifest_details_and_ai_handoff_action() -> None:
)
assert "latestRasterTileManifest" in controls
assert "Tile count" in controls
assert "Tile size" in controls
assert "Aantal tegels" in controls
assert "Tegelgrootte" in controls
assert "Overlap" in controls
assert "Use manifest in Detection Lab" in controls
assert "Use manifest in Segmentation Lab" in controls
assert "Gebruik voor gebouwdetectie" in controls
assert "Gebruik voor segmentatie" in controls
def test_detection_handoff_opens_ai_lab_preflights_manifest_and_keeps_asset_explicit() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
lab = (ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(
encoding="utf-8"
lab = "\n".join(
(
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(encoding="utf-8"),
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionModelManagement.tsx").read_text(encoding="utf-8"),
)
)
assert "setDetectionTileManifestPath(manifestPath)" in app
@@ -41,6 +44,6 @@ def test_detection_handoff_opens_ai_lab_preflights_manifest_and_keeps_asset_expl
assert "loadYoloPreflight(manifestPath).catch(() => null)" in app
assert "setSelectedModelAssetId(" not in app[app.index("const useRasterTileManifestForDetection"):app.index("const {", app.index("const useRasterTileManifestForDetection"))]
assert "Gekoppelde beeldtegels" in lab
assert "Tile manifest validation" in lab
assert "Tile count" in lab
assert "will_run_inference" in lab
assert "Gekoppelde beeldtegels" in lab
assert "Aantal beeldtegels" in lab
assert "yoloPreflight.tile_count" in lab
@@ -22,7 +22,7 @@ def test_detection_lab_exposes_persisted_threshold_calibration_comparison() -> N
assert "Minste foutieve meldingen" in source
assert "Drempel" in source
assert "Precisie" in source
assert "Recall" in source
assert "Herkenningsgraad" in source
assert "F1" in source
assert "Fout positief" in source
assert "Fout negatief" in source
@@ -33,11 +33,11 @@ def test_detection_lab_has_guided_threshold_calibration_runner() -> None:
assert "Configured YOLO calibration requires a tile manifest" in hook_source
assert "Select a local model asset before calibration" in hook_source
assert "Guided calibration runner" in lab_source
assert "This runs real configured YOLO jobs" in lab_source
assert "Threshold set" in lab_source
assert "Run calibration sweep" in lab_source
assert "Calibration run progress" in lab_source
assert "Modelkalibratie voor beheerders" in lab_source
assert "Voert het lokale model en een kwaliteitscontrole uit" in lab_source
assert "Zekerheidsdrempels" in lab_source
assert "Drempels vergelijken" in lab_source
assert "Voortgang modelkalibratie" in lab_source
assert "detectionCalibrationRows.map" in lab_source
assert "runningDetectionCalibration" in lab_source
assert "detectionCalibrationError" in lab_source
@@ -14,7 +14,7 @@ def test_guided_calibration_rows_link_to_existing_qa_evidence_map() -> None:
todo_source = todo.read_text(encoding="utf-8")
assert "onOpenCalibrationEvidence" in lab_source
assert "Open evidence map" in lab_source
assert "Toon kaartbewijs" in lab_source
assert "disabled={!row.quality_check_id || row.status !== 'success'}" in lab_source
assert "onOpenCalibrationEvidence(row.quality_check_id)" in lab_source
assert "Evidence" in lab_source
@@ -16,7 +16,7 @@ def test_guided_calibration_runner_exports_review_summary() -> None:
assert "downloadJsonFile('detection-calibration-summary.json'" in lab_source
assert "evidence_geojson_url" in lab_source
assert "/api/v1/projects/${projectId}/quality-checks/${row.quality_check_id}/evidence/geojson" in lab_source
assert "Download calibration summary" in lab_source
assert "Samenvatting downloaden" in lab_source
assert "disabled={detectionCalibrationRows.length === 0 || !selectedProjectId}" in lab_source
assert "calibration_thresholds" in lab_source
assert "quality_check_ids" in lab_source
@@ -31,15 +31,18 @@ def test_detection_operator_profiles_define_explicit_yolo_candidates_and_promote
def test_detection_lab_surfaces_profiles_as_deliberate_operator_actions() -> None:
lab = (ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(
encoding="utf-8"
lab = "\n".join(
(
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(encoding="utf-8"),
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionModelManagement.tsx").read_text(encoding="utf-8"),
)
)
assert "DETECTION_OPERATOR_PROFILES" in lab
assert "Gevalideerde profielen" in lab
assert "Gevalideerde YOLO-profielen" in lab
assert "profile.displayName" in lab
assert "profile.confidenceThreshold" in lab
assert "kandidaat · extra controle vereist" in lab
assert "kandidaat, extra controle vereist" in lab
assert "standaardprofiel" in lab
assert "Profiel gebruiken" in lab
assert "onApplyOperatorProfile(profile)" in lab
@@ -99,7 +99,7 @@ def test_detection_results_table_uses_bounded_local_pagination() -> None:
assert "DETECTION_PAGE_SIZE_OPTIONS" in lab
assert "visibleDetectionItems" in lab
assert "detectionItems.slice" in lab
assert "Detection result pagination" in lab
assert "Paginering van gevonden objecten" in lab
assert "Vorige resultatenpagina" in lab
assert "Volgende resultatenpagina" in lab
assert "formatSourceTilePath(detection.source_tile_path)" in lab
@@ -60,13 +60,18 @@ def test_map_prioritizes_controls_map_and_collapsed_diagnostics() -> None:
def test_ai_workspaces_prioritize_runs_and_collapse_registry_detail() -> None:
detection = read("frontend/src/components/detection/DetectionLab.tsx")
detection = "\n".join(
(
read("frontend/src/components/detection/DetectionLab.tsx"),
read("frontend/src/components/detection/DetectionModelManagement.tsx"),
)
)
segmentation = read("frontend/src/components/segmentation/SegmentationLab.tsx")
css = read("frontend/src/styles/premium.css")
assert '<details className="ai-lab-model-surface" aria-label="Detection model capabilities">' in detection
assert '<details className="ai-lab-model-surface" aria-label="YOLO runtime preflight">' in detection
assert '<details className="ai-lab-model-surface" aria-label="Segmentation model capabilities">' in segmentation
assert '<details className="ai-lab-model-surface" aria-label="Technische modelmogelijkheden">' in detection
assert '<details className="ai-lab-model-surface" aria-label="Technische YOLO-runtimecontrole">' in detection
assert '<details className="ai-lab-model-surface" aria-label="Technische informatie over segmentatiemodellen">' in segmentation
assert ".ai-lab-shell > .lab-block {\n order: 2;" in css
assert ".ai-lab-shell > .ai-lab-model-surface {\n order: 7;" in css
assert "details.ai-lab-model-surface > summary" in css
@@ -11,7 +11,7 @@ def test_map_source_mode_keeps_database_layers_distinct_from_analysis_results()
assert "const [mapContentMode, setMapContentMode]" in app
assert "mapContentMode === 'analysis' && analysisMapLayerAvailable" in app
assert "setMapContentMode('dataset')" in app
assert 'aria-label="Map content source"' in workspace
assert 'aria-label="Bron van de kaartinhoud"' in workspace
assert "onSetMapContentMode('dataset')" in workspace
assert "onSetMapContentMode('analysis')" in workspace
assert "disabled={!analysisLayerAvailable}" in workspace
+6 -1
View File
@@ -121,7 +121,12 @@ def test_scope_api_pagination_respects_canonical_limit() -> None:
def test_kempen_scope_operator_is_packaged_and_exposed_in_map_flow() -> None:
dockerfile = (ROOT / "deploy/unraid/Dockerfile.all-in-one").read_text(encoding="utf-8")
readiness = (ROOT / "scripts/run_readiness_check.sh").read_text(encoding="utf-8")
workspace = (ROOT / "frontend/src/components/map/MapWorkspace.tsx").read_text(encoding="utf-8")
workspace = "\n".join(
(
(ROOT / "frontend/src/components/map/MapWorkspace.tsx").read_text(encoding="utf-8"),
(ROOT / "frontend/src/components/map/mapWorkspaceUtils.ts").read_text(encoding="utf-8"),
)
)
app = (ROOT / "frontend/src/App.tsx").read_text(encoding="utf-8")
assert "COPY scripts/geographic_scopes.py" in dockerfile
+3 -3
View File
@@ -28,7 +28,7 @@ def test_app_wires_map_workbench_component() -> None:
assert "selectedMapFeature" in app
assert "<MapWorkspace" in app
assert "onSelectMapFeature={setSelectedMapFeature}" in app
assert "Active layer" in component
assert "Layer opacity" in component
assert "Feature inspector" in component
assert "Actieve kaartlaag" in component
assert "Dekking van de kaartlaag" in component
assert "Objectinspectie" in component
assert "onFeatureSelect={onSelectMapFeature}" in component
@@ -54,7 +54,7 @@ def test_frontend_wires_selected_area_map_overlay_contract() -> None:
assert "areaFeatureCollection" in app
assert "areaFeatureCollection={areaFeatureCollection}" in app
assert "areaData={areaFeatureCollection}" in map_workspace
assert "AOI" in map_workspace
assert "Werkgebied" in map_workspace
assert "area-fill" in geomap
assert "area-line" in geomap
assert "onSelectMapArea" in area_panel
@@ -231,7 +231,7 @@ def test_source_freshness_operator_and_ui_contract_are_read_only() -> None:
script = (root / "scripts" / "audit_source_freshness.py").read_text(encoding="utf-8")
dockerfile = (root / "deploy" / "unraid" / "Dockerfile.all-in-one").read_text(encoding="utf-8")
readiness = (root / "scripts" / "run_readiness_check.sh").read_text(encoding="utf-8")
app = (root / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
app = (root / "frontend" / "src" / "components" / "overview" / "OverviewWorkspace.tsx").read_text(encoding="utf-8")
api = (root / "frontend" / "src" / "services" / "api" / "datasets.ts").read_text(encoding="utf-8")
assert "Request(endpoint" in script
@@ -8,12 +8,13 @@ ROOT = Path(__file__).resolve().parents[2]
def test_frontend_wires_v1_workbench_status_strip() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
overview = (ROOT / "frontend" / "src" / "components" / "overview" / "OverviewWorkspace.tsx").read_text(encoding="utf-8")
component = (ROOT / "frontend" / "src" / "components" / "WorkbenchStatusStrip.tsx").read_text(encoding="utf-8")
css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
assert "WorkbenchStatusStrip" in app
assert "selectedProject={selectedProject}" in app
assert "qualityChecks={qualityChecks}" in app
assert "WorkbenchStatusStrip" in overview
assert "selectedProject={selectedProject}" in overview
assert "qualityChecks={qualityChecks}" in overview
assert "activeLayerFeatureCount={mapFeatureCount}" in app
assert "selectedAreaHasGeometry={Boolean(areaFeatureCollection)}" in app
assert "Platformstatus" in component
@@ -248,8 +248,8 @@ def test_project_list_supports_exact_canonical_workspace_lookup(monkeypatch) ->
project_id = uuid4()
captured: dict = {}
def fake_list(_db, *, limit, offset, name):
captured.update(limit=limit, offset=offset, name=name)
def fake_list(_db, *, limit, offset, name, project_status):
captured.update(limit=limit, offset=offset, name=name, project_status=project_status)
return [
ProjectRead(
id=project_id,
@@ -271,6 +271,7 @@ def test_project_list_supports_exact_canonical_workspace_lookup(monkeypatch) ->
"limit": 1,
"offset": 0,
"name": "Kempen Regional Workbench",
"project_status": "active",
}
@@ -0,0 +1,149 @@
from __future__ import annotations
import importlib.util
from pathlib import Path
import sys
from types import SimpleNamespace
from uuid import uuid4
import pytest
from fastapi import HTTPException
from fastapi.testclient import TestClient
from app.api.routes import projects as project_routes
from app.main import app
from app.schemas.project import ProjectRead, ProjectUpdate
from app.services.project_service import ProjectService
ROOT = Path(__file__).resolve().parents[2]
def load_cleanup_module():
script = ROOT / "scripts" / "archive_technical_projects.py"
spec = importlib.util.spec_from_file_location("archive_technical_projects_test", script)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
def test_project_list_defaults_to_active_and_can_request_archived(monkeypatch) -> None:
captured: list[str] = []
def fake_list(_db, *, limit, offset, name, project_status):
del limit, offset, name
captured.append(project_status)
return [
ProjectRead(
id=uuid4(),
name=f"{project_status} project",
region="Kempen",
status=project_status if project_status != "all" else "active",
)
], 1
monkeypatch.setattr(ProjectService, "list_projects", fake_list)
client = TestClient(app)
assert client.get("/api/v1/projects").status_code == 200
assert client.get("/api/v1/projects", params={"status": "archived"}).status_code == 200
assert captured == ["active", "archived"]
def test_project_update_schema_allows_only_active_or_archived() -> None:
from pydantic import ValidationError
from app.schemas.project import ProjectUpdate
assert ProjectUpdate(status="archived").status == "archived"
try:
ProjectUpdate(status="deleted")
except ValidationError:
pass
else:
raise AssertionError("ProjectUpdate must not expose deleted as an ordinary lifecycle state")
def test_project_update_returns_404_when_project_is_missing(monkeypatch) -> None:
monkeypatch.setattr(ProjectService, "update_project", lambda *_args, **_kwargs: None)
with pytest.raises(HTTPException) as exc_info:
project_routes.update_project(uuid4(), ProjectUpdate(status="archived"), db=SimpleNamespace())
assert exc_info.value.status_code == 404
def test_cleanup_allowlist_preserves_real_workspaces() -> None:
module = load_cleanup_module()
assert module.is_technical_project_name("GeoIntel Detection Quality Matrix 42")
assert module.is_technical_project_name("GeoIntel hard-negative Mol 20260709")
assert module.is_technical_project_name("GeoIntel Detection Calibration 0.15 20260709T090018Z")
assert module.is_technical_project_name("GeoIntel Real Data Validation 20260707T000620Z")
assert module.is_technical_project_name("GeoIntel Operational YOLO Geel Smoke 20260711T133656Z")
assert module.is_technical_project_name("GeoIntel Demo - Building QA")
assert not module.is_technical_project_name("Kempen Regional Workbench")
assert not module.is_technical_project_name("Mol Municipality Workbench")
assert not module.is_technical_project_name("Vrij project van een gebruiker")
def test_cleanup_plan_selects_active_allowlisted_projects_only() -> None:
module = load_cleanup_module()
rows = [
SimpleNamespace(
id=uuid4(),
name="GeoIntel Detection Quality Matrix 1",
status="active",
),
SimpleNamespace(
id=uuid4(),
name="Kempen Regional Workbench",
status="active",
),
]
class Query:
def filter(self, *_args):
return self
def order_by(self, *_args):
return self
def all(self):
return rows
class Session:
def query(self, _model):
return Query()
plan = module.build_archive_plan(Session())
assert plan.count == 1
assert plan.names == ("GeoIntel Detection Quality Matrix 1",)
def test_cleanup_script_is_packaged_and_readiness_checked() -> None:
dockerfile = (ROOT / "deploy/unraid/Dockerfile.all-in-one").read_text(encoding="utf-8")
readiness = (ROOT / "scripts/run_readiness_check.sh").read_text(encoding="utf-8")
assert "COPY scripts/archive_technical_projects.py" in dockerfile
assert "py_compile scripts/archive_technical_projects.py" in readiness
def test_frontend_lifecycle_and_component_boundaries_are_wired() -> None:
app_source = (ROOT / "frontend/src/App.tsx").read_text(encoding="utf-8")
project_panel = (ROOT / "frontend/src/components/project/ProjectPanel.tsx").read_text(encoding="utf-8")
detection_lab = (ROOT / "frontend/src/components/detection/DetectionLab.tsx").read_text(encoding="utf-8")
map_workspace = (ROOT / "frontend/src/components/map/MapWorkspace.tsx").read_text(encoding="utf-8")
premium_css = (ROOT / "frontend/src/styles/premium.css").read_text(encoding="utf-8")
assert "OverviewWorkspace" in app_source
assert "onArchiveProject={archiveProject}" in app_source
assert "DetectionModelManagement" in detection_lab
assert "from './mapWorkspaceUtils'" in map_workspace
assert "Werkruimte archiveren" in project_panel
assert "PROTECTED_PROJECT_NAMES" in project_panel
assert ".technical-inline-details" in premium_css
assert ".workspace-grid-ai" in premium_css
@@ -19,8 +19,8 @@ def test_app_uses_dataset_presentational_components() -> None:
assert "from '../datasets/DatasetDetailPanel'" in inspector
assert "<DatasetDetailPanel {...datasetDetailProps} />" in inspector
assert "<form onSubmit={uploadDataset}" not in app
assert "<h3>Raster operations</h3>" not in app
assert "<h3>Vector operations</h3>" not in app
assert "<h3>Rasterbewerkingen</h3>" not in app
assert "<h3>Vectorbewerkingen</h3>" not in app
def test_dataset_panel_owns_upload_and_list_markup() -> None:
@@ -46,9 +46,9 @@ def test_dataset_detail_panel_composes_raster_and_vector_controls() -> None:
assert "<RasterControls" in detail_panel
assert "<VectorControls" in detail_panel
assert "Jobs" in detail_panel
assert "Raster operations" in raster_controls
assert "Compute NDVI" in raster_controls
assert "Generate tiles" in raster_controls
assert "Vector operations" in vector_controls
assert "Run intersect" in vector_controls
assert "Technische verwerking" in detail_panel
assert "Rasterbewerkingen" in raster_controls
assert "NDVI berekenen" in raster_controls
assert "Beeldtegels maken" in raster_controls
assert "Vectorbewerkingen" in vector_controls
assert "Overlap berekenen" in vector_controls
@@ -53,9 +53,9 @@ def test_map_workspace_owns_map_controls_and_feature_inspector_markup() -> None:
assert "GeoMap" in map_workspace
assert "map-toolbar" in map_workspace
assert "Area" in map_workspace
assert "AOI" in map_workspace
assert "Active layer" in map_workspace
assert "Feature inspector" in map_workspace
assert "Werkgebied" in map_workspace
assert "Actieve kaartlaag" in map_workspace
assert "Objectinspectie" in map_workspace
assert "onFeatureSelect={onSelectMapFeature}" in map_workspace
assert "fetch(" not in map_workspace
assert "api" not in map_workspace.lower()
@@ -31,8 +31,11 @@ def test_map_and_ai_workspaces_use_task_blocks_not_raw_stacks() -> None:
map_workspace = (ROOT / "frontend" / "src" / "components" / "map" / "MapWorkspace.tsx").read_text(
encoding="utf-8"
)
detection_lab = (ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(
encoding="utf-8"
detection_lab = "\n".join(
(
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(encoding="utf-8"),
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionModelManagement.tsx").read_text(encoding="utf-8"),
)
)
segmentation_lab = (
ROOT / "frontend" / "src" / "components" / "segmentation" / "SegmentationLab.tsx"
@@ -41,7 +44,7 @@ def test_map_and_ai_workspaces_use_task_blocks_not_raw_stacks() -> None:
assert "map-toolbar" in map_workspace
assert "layer-control-card" in map_workspace
assert "Spatial review" in map_workspace
assert "Kwaliteit controleren" in map_workspace
assert "model-list" in detection_lab
assert "lab-block" in detection_lab
assert "lab-form-grid" in detection_lab
@@ -27,10 +27,10 @@ def test_workbench_inspector_exposes_context_dataset_quality_and_ai_tabs() -> No
assert 'data-testid="workbench-inspector-panel"' in inspector
assert "data-testid={`inspector-tab-${tab.key}`}" in inspector
assert "<DatasetDetailPanel {...datasetDetailProps} />" in inspector
assert "Latest QA/QC" in inspector
assert "Latest export" in inspector
assert "Detection run" in inspector
assert "Segmentation run" in inspector
assert "Laatste kwaliteitscontrole" in inspector
assert "Laatste download" in inspector
assert "Gebouwdetectie" in inspector
assert "Segmentatie" in inspector
assert "fetch(" not in inspector
@@ -63,9 +63,9 @@ def test_inspector_exposes_navigation_actions_without_api_calls() -> None:
assert "onOpenQualityWorkspace" in inspector
assert "onOpenExportsWorkspace" in inspector
assert "onOpenAiWorkspace" in inspector
assert "Data catalog" in inspector
assert "Map layer" in inspector
assert "Open QA/QC" in inspector
assert "Open exports" in inspector
assert "Open AI Labs" in inspector
assert "Gegevenscatalogus" in inspector
assert "Kaartlaag" in inspector
assert "Kwaliteit openen" in inspector
assert "Downloads openen" in inspector
assert "Beeldanalyse openen" in inspector
assert "fetch(" not in inspector
@@ -28,5 +28,5 @@ def test_map_workspace_has_clear_empty_result_layer_guidance() -> None:
encoding="utf-8"
)
assert "No active vector or result layer" in map_workspace
assert "Open a dataset, detection run, segmentation run or change result to draw it here." in map_workspace
assert "Geen actieve vector- of resultaatlaag" in map_workspace
assert "Open een databron, beeldanalyse, segmentatie of veranderingsresultaat om het hier te tekenen." in map_workspace
@@ -12,9 +12,9 @@ def test_map_empty_state_surfaces_ready_vector_dataset_actions() -> None:
assert "availableMapDatasets" in map_workspace
assert "onOpenDatasetInMap" in map_workspace
assert "map-empty-action-grid" in map_workspace
assert "Open a ready vector dataset" in map_workspace
assert "Open in map" in map_workspace
assert "No ready vector datasets available yet" in map_workspace
assert "Open een beschikbare vectorlaag" in map_workspace
assert "Open op kaart" in map_workspace
assert "Nog geen gebruiksklare vectorlagen beschikbaar" in map_workspace
def test_app_passes_available_vector_datasets_to_map_workspace() -> None:
@@ -21,8 +21,11 @@ def test_ai_labs_mobile_density_css_contracts() -> None:
def test_detection_and_segmentation_keep_ai_lab_workflow_markup() -> None:
detection_lab = (ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(
encoding="utf-8"
detection_lab = "\n".join(
(
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(encoding="utf-8"),
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionModelManagement.tsx").read_text(encoding="utf-8"),
)
)
segmentation_lab = (
ROOT / "frontend" / "src" / "components" / "segmentation" / "SegmentationLab.tsx"
@@ -21,12 +21,12 @@ def test_global_focus_visible_contracts_are_defined() -> None:
def test_primary_navigation_has_keyboard_labels() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
assert 'aria-label={`Open ${item.label} workspace: ${item.description}`}' in app
assert 'aria-label={`Switch to ${item.label} workspace`}' in app
assert 'aria-label="Open data setup workspace"' in app
assert 'aria-label="Inspect map workspace"' in app
assert 'aria-label="Review QA/QC workspace"' in app
assert 'aria-label="Manage exports workspace"' in app
assert 'aria-label={`Open ${item.label}: ${item.description}`}' in app
assert 'aria-label={`Ga naar ${item.label}`}' in app
assert "label: 'Bronnen'" in app
assert "label: 'Kaart'" in app
assert "label: 'Kwaliteit'" in app
assert "label: 'Downloads'" in app
def test_inspector_tabs_are_bound_to_tab_panels() -> None:
@@ -30,8 +30,8 @@ def test_raster_controls_expose_readable_operation_groups() -> None:
assert 'className="dataset-tool-label"' in raster_controls
assert 'className="dataset-tool-action-row"' in raster_controls
assert 'className="dataset-tool-error"' in raster_controls
assert "Target CRS for the derived raster artifact." in raster_controls
assert "Tile size must be > 0." in raster_controls
assert "Coördinatenstelsel van het afgeleide raster." in raster_controls
assert "De tegelgrootte moet groter zijn dan nul." in raster_controls
def test_vector_controls_expose_readable_operation_groups() -> None:
@@ -45,5 +45,5 @@ def test_vector_controls_expose_readable_operation_groups() -> None:
assert 'className="dataset-tool-field"' in vector_controls
assert 'className="dataset-tool-label"' in vector_controls
assert 'className="dataset-tool-action-row"' in vector_controls
assert "Clip features to the selected project area." in vector_controls
assert "Intersect with another persisted vector dataset." in vector_controls
assert "Beperk objecten tot het gekozen werkgebied." in vector_controls
assert "Bereken de overlap met een andere bewaarde vectorlaag." in vector_controls
@@ -11,7 +11,7 @@ def test_workbench_shell_has_skip_link_and_main_focus_target() -> None:
assert 'className="skip-link"' in app
assert 'href="#workspace-main"' in app
assert 'nav aria-label="Primary workspaces"' in app
assert 'nav aria-label="Hoofdonderdelen"' in app
assert 'id="workspace-main"' in app
assert "tabIndex={-1}" in app
@@ -7,12 +7,12 @@ ROOT = Path(__file__).resolve().parents[2]
def test_overview_uses_named_hierarchy_regions() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
app = (ROOT / "frontend" / "src" / "components" / "overview" / "OverviewWorkspace.tsx").read_text(encoding="utf-8")
assert 'className="overview-action-copy"' in app
assert 'className="quick-action-grid overview-quick-actions"' in app
assert 'className="quick-action-button"' in app
assert 'aria-label="Recommended next workbench actions"' in app
assert 'aria-label="Aanbevolen acties"' in app
def test_status_strip_has_compact_section_surface_contracts() -> None:
@@ -13,9 +13,9 @@ def test_map_workspace_exposes_structured_surfaces() -> None:
assert 'className="map-workspace-shell"' in map_workspace
assert 'className="map-context-summary"' in map_workspace
assert 'aria-label="Map layer status"' in map_workspace
assert 'aria-label="Status van de kaartlagen"' in map_workspace
assert 'className="map-control-surface"' in map_workspace
assert 'aria-label="Map workspace controls"' in map_workspace
assert 'aria-label="Bediening van de kaartwerkruimte"' in map_workspace
assert 'className="map-frame-surface"' in map_workspace
assert 'className="map-inspection-surface"' in map_workspace
@@ -26,7 +26,7 @@ def test_map_workspace_summary_uses_current_layer_and_selection_state() -> None:
)
assert "const selectedMapArea = areas.find" in map_workspace
assert "selectedMapArea?.name ?? 'No area selected'" in map_workspace
assert "selectedMapArea?.name ?? 'Geen gebied geselecteerd'" in map_workspace
assert "mapLayerLabel" in map_workspace
assert "mapLayerSourceLabel" in map_workspace
assert "mapFeatureCount" in map_workspace
@@ -14,14 +14,14 @@ def test_quality_results_panel_exposes_structured_surfaces() -> None:
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
assert 'aria-label="Samenvatting kwaliteitsresultaten"' in panel
assert 'className="quality-evidence-surface"' in panel
assert 'aria-label="QA/QC dataset evidence"' in panel
assert 'aria-label="Databronnen van de kwaliteitscontrole"' in panel
assert 'className="quality-control-surface"' in panel
assert 'aria-label="QA/QC refresh and filters"' in panel
assert 'aria-label="Kwaliteitsresultaten vernieuwen en filteren"' in panel
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 'aria-label="Geschiedenis van kwaliteitsresultaten"' in panel
assert 'className="quality-advanced-disclosure"' in panel
@@ -14,10 +14,10 @@ def test_change_detection_panel_exposes_structured_surfaces() -> None:
assert 'className="panel change-detection-shell"' in panel
assert 'className="panel-header change-detection-heading"' in panel
assert 'className="change-detection-input-surface"' in panel
assert 'aria-label="Change detection input controls"' in panel
assert 'aria-label="Instellingen voor veranderingsanalyse"' in panel
assert 'className="change-detection-state-stack"' in panel
assert 'className="change-detection-result-surface"' in panel
assert 'aria-label="Change detection result summary"' in panel
assert 'aria-label="Samenvatting veranderingsanalyse"' in panel
assert 'className="change-detection-warning-surface"' in panel
@@ -31,8 +31,8 @@ def test_change_detection_panel_preserves_existing_compare_controls() -> None:
assert "onIouThresholdChange" in panel
assert "onIncludeUnchangedChange" in panel
assert "onRun" in panel
assert "Compare vectors" in panel
assert "Upload at least two vector datasets to compare." in panel
assert "Kaartlagen vergelijken" in panel
assert "Voeg minstens twee vectorlagen toe om periodes te vergelijken." in panel
assert "result.added_count" in panel
assert "result.removed_count" in panel
assert "result.unchanged_count" in panel
+14 -11
View File
@@ -7,18 +7,21 @@ ROOT = Path(__file__).resolve().parents[2]
def test_detection_lab_exposes_structured_surfaces() -> None:
lab = (ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(
encoding="utf-8"
lab = "\n".join(
(
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(encoding="utf-8"),
(ROOT / "frontend" / "src" / "components" / "detection" / "DetectionModelManagement.tsx").read_text(encoding="utf-8"),
)
)
assert 'className="workspace-panel ai-lab-shell detection-lab-shell"' in lab
assert 'className="ai-lab-model-surface"' in lab
assert 'aria-label="Detection model capabilities"' in lab
assert 'aria-label="Technische modelmogelijkheden"' in lab
assert 'className="ai-lab-run-surface"' in lab
assert 'aria-label="Detection run controls"' in lab
assert 'aria-label="Gebouwdetectie starten"' in lab
assert 'className="ai-lab-state-stack"' in lab
assert 'className="ai-lab-results-surface"' in lab
assert 'aria-label="Detection results"' in lab
assert 'aria-label="Resultaten van de beeldanalyse"' in lab
assert 'className="ai-lab-qa-surface"' in lab
assert 'aria-label="Kwaliteitscontrole gebouwdetectie"' in lab
@@ -30,14 +33,14 @@ def test_segmentation_lab_exposes_structured_surfaces() -> None:
assert 'className="workspace-panel ai-lab-shell segmentation-lab-shell"' in lab
assert 'className="ai-lab-model-surface"' in lab
assert 'aria-label="Segmentation model capabilities"' in lab
assert 'aria-label="Technische informatie over segmentatiemodellen"' in lab
assert 'className="ai-lab-run-surface"' in lab
assert 'aria-label="Segmentation run controls"' in lab
assert 'aria-label="Segmentatie starten"' in lab
assert 'className="ai-lab-state-stack"' in lab
assert 'className="ai-lab-results-surface"' in lab
assert 'aria-label="Segmentation results"' in lab
assert 'aria-label="Segmentatieresultaten"' in lab
assert 'className="ai-lab-qa-surface"' in lab
assert 'aria-label="Segmentation QA controls and results"' in lab
assert 'aria-label="Kwaliteitscontrole voor segmentatie"' in lab
def test_ai_lab_preserves_existing_detection_and_segmentation_controls() -> None:
@@ -59,8 +62,8 @@ def test_ai_lab_preserves_existing_detection_and_segmentation_controls() -> None
assert "onLoadResults" in segmentation
assert "onRunQa" in segmentation
assert "selectedSegmentationModelConfigured" in segmentation
assert "Segmentations loaded: {segmentationItems.length}" in segmentation
assert "Compare segmentations to reference" in segmentation
assert "{segmentationItems.length} vlakken geladen" in segmentation
assert "Vergelijk met referentielaag" in segmentation
def test_ai_lab_density_css_contracts() -> None:
@@ -7,28 +7,29 @@ ROOT = Path(__file__).resolve().parents[2]
def test_overview_exposes_end_to_end_workflow_guidance() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
overview = (ROOT / "frontend" / "src" / "components" / "overview" / "OverviewWorkspace.tsx").read_text(encoding="utf-8")
assert 'aria-label="V1 workflow guidance"' in app
assert 'className="workflow-guidance-panel"' in app
assert 'className="workflow-guidance-steps"' in app
assert "workflowGuidanceSteps.map" in app
assert "Project & AOI" in app
assert "Data" in app
assert "Map" in app
assert "QA / AI" in app
assert "Export" in app
assert 'aria-label="Voortgang van de werkstroom"' in overview
assert 'className="workflow-guidance-panel"' in overview
assert 'className="workflow-guidance-steps"' in overview
assert "steps.map" in overview
assert "Werkruimte en gebied" in overview
assert "Databronnen" in overview
assert "Kaart" in overview
assert "Controle en analyse" in overview
assert "Downloads" in overview
def test_workflow_guidance_routes_to_existing_workspaces() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
overview = (ROOT / "frontend" / "src" / "components" / "overview" / "OverviewWorkspace.tsx").read_text(encoding="utf-8")
assert "target: 'data'" in app
assert "target: 'map'" in app
assert "target: 'analysis'" in app
assert "target: 'exports'" in app
assert "onOpenAiWorkspace={() => setActiveWorkspace('ai')}" in app
assert "onClick={() => openWorkflowGuidanceStep(step.target)}" in app
assert "target: 'data'" in overview
assert "target: 'map'" in overview
assert "target: 'analysis'" in overview
assert "target: 'exports'" in overview
assert "onOpenWorkspace={openWorkflowGuidanceStep}" in app
assert "onClick={() => onOpenWorkspace(step.target)}" in overview
assert "setActiveWorkspace(target)" in app
@@ -44,21 +45,23 @@ def test_workflow_guidance_has_responsive_contracts() -> None:
def test_workflow_guidance_complete_state_and_map_copy_are_precise() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
overview = (ROOT / "frontend" / "src" / "components" / "overview" / "OverviewWorkspace.tsx").read_text(encoding="utf-8")
assert "workflowGuidanceComplete" in app
assert "Ready for handoff" in app
assert "layer feature" in app
assert "+ AOI" in app
assert "AOI loaded" in app
assert "workflowComplete" in overview
assert "Klaar om te delen" in overview
assert "objecten op de kaart" in overview
assert "selectedAreaHasGeometry" in overview
assert "Werkgebied ingeladen" in overview
def test_workflow_guidance_map_and_export_steps_reuse_dataset_context() -> None:
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
overview = (ROOT / "frontend" / "src" / "components" / "overview" / "OverviewWorkspace.tsx").read_text(encoding="utf-8")
assert "openWorkflowGuidanceStep" in app
assert "target === 'map'" in app
assert "openDatasetInMap(availableMapDatasets[0])" in app
assert "target === 'exports'" in app
assert "openDatasetExport(availableMapDatasets[0])" in app
assert "onClick={() => openWorkflowGuidanceStep(step.target)}" in app
assert "onOpenWorkspace={openWorkflowGuidanceStep}" in app
assert "onClick={() => onOpenWorkspace(step.target)}" in overview
@@ -11,7 +11,7 @@ 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 "Details van het kaartbewijs" in panel
assert "Geselecteerde controle" in panel
assert "Te controleren laag" in panel
assert "Referentielaag" in panel
@@ -11,13 +11,13 @@ def test_raster_controls_expose_pipeline_readiness_and_handoff() -> None:
assert "rasterReadinessItems" in raster_controls
assert "rasterGuardrailItems" in raster_controls
assert "Raster pipeline readiness" in raster_controls
assert "Metadata profile" in raster_controls
assert "CRS readiness" in raster_controls
assert "Preview artifact" in raster_controls
assert "Tile manifest handoff" in raster_controls
assert "Clip AOI" in raster_controls
assert "Processing guardrails" in raster_controls
assert "Gereedheid van rasterverwerking" in raster_controls
assert "Bestandsprofiel" in raster_controls
assert "Coördinatenstelsel" in raster_controls
assert "Voorbeeldweergave" in raster_controls
assert "Laatste beeldtegelmanifest" in raster_controls
assert "Werkgebied voor begrenzing" in raster_controls
assert "Veiligheidscontroles" in raster_controls
assert "selectedRasterMetadata?.crs" in raster_controls
assert "rasterPreview ?" in raster_controls
assert "isRasterTileInputValid" in raster_controls
@@ -27,6 +27,6 @@ def test_ai_labs_explain_missing_raster_input_before_disabled_runs() -> None:
assert 'aria-label="Luchtbeeld toevoegen"' in detection_lab
assert "onUploadRaster" 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
assert "Geen rasterbestand beschikbaar voor segmentatie." in segmentation_lab
assert "Voeg eerst een rasterbestand toe onder Bronnen." in segmentation_lab
assert "rasterDatasets.length === 0" in segmentation_lab
@@ -20,6 +20,6 @@ def test_raster_tile_manifest_is_visible_and_can_handoff_to_detection_lab() -> N
assert "setSelectedDetectionModelId('yolo-configured')" in app
assert "latestRasterTileManifestPath" in detail_panel
assert "onUseTileManifestForDetection" in detail_panel
assert "Latest tile manifest" in raster_controls
assert "Use in Detection Lab" in raster_controls
assert "Laatste beeldtegelmanifest" in raster_controls
assert "Gebruik voor gebouwdetectie" in raster_controls
assert "disabled={!latestRasterTileManifestPath}" in raster_controls