Close full operational audit findings
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user