diff --git a/CHANGELOG.md b/CHANGELOG.md index b485395f..115c0f01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1000,3 +1000,11 @@ Added: - Added responsive styling and regression coverage for the Map quick-action grid. - Verified locally against the live demo state that the empty map state exposes two dataset actions and opens `demo_predicted_buildings.geojson` as a 2-feature map layer. - No API contracts, migrations, product capabilities, live provider fetching or AI/model dependency changes were introduced. + +## Sprint 103 AI Lab run readiness (2026-06-24) + +- Added compact run-readiness panels to Detection Lab and Segmentation Lab. +- Detection readiness now shows raster dataset, model availability and the configured-YOLO tile manifest requirement before submitting a run. +- Segmentation readiness now shows raster dataset, model availability and tile manifest provenance state before submitting a run. +- Added regression coverage for the AI Lab readiness UI contract and styling. +- No API contracts, migrations, product capabilities, live provider fetching or AI/model dependency changes were introduced. diff --git a/backend/tests/test_sprint103_ai_lab_run_readiness.py b/backend/tests/test_sprint103_ai_lab_run_readiness.py new file mode 100644 index 00000000..21b7a91a --- /dev/null +++ b/backend/tests/test_sprint103_ai_lab_run_readiness.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + + +def test_detection_lab_exposes_run_readiness_contract() -> None: + lab = (ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text( + encoding="utf-8" + ) + + assert "selectedDetectionModel = detectionModels.find" in lab + 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 "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 + + +def test_segmentation_lab_exposes_run_readiness_contract() -> None: + lab = (ROOT / "frontend" / "src" / "components" / "segmentation" / "SegmentationLab.tsx").read_text( + encoding="utf-8" + ) + + assert "segmentationHasDataset" in lab + assert "segmentationHasTileManifest = segmentationTileManifestPath.trim().length > 0" in lab + 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 + + +def test_ai_lab_run_readiness_css_contract() -> None: + css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8") + + assert ".lab-readiness-panel" in css + assert ".lab-readiness-panel-ready" in css + assert ".lab-readiness-grid" in css + assert ".lab-readiness-item" in css + assert ".lab-readiness-item-ready" in css diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index 6d9a3c1c..41adbde3 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -3722,3 +3722,28 @@ Limitations: Next recommended pass: - After live validation, continue with the next V1 usability gap from the workbench flow rather than adding new model/provider scope. + +## Sprint 103 AI Lab run readiness (2026-06-24) + +Changed: +- Added compact run-readiness panels to Detection Lab and Segmentation Lab. +- Detection readiness now checks selected raster dataset, selected model availability and the configured-YOLO tile manifest requirement before a run is submitted. +- Segmentation readiness now checks selected raster dataset, configured segmentation model state and whether a tile manifest is present for provenance. +- Added shared AI Lab readiness styling and regression coverage in `backend/tests/test_sprint103_ai_lab_run_readiness.py`. +- Updated `CHANGELOG.md` and `docs/TODO.md`. + +Tested: +- Red step: `python -m pytest backend\tests\test_sprint103_ai_lab_run_readiness.py -q` failed while the readiness panels and CSS contracts were absent. +- `python -m pytest backend\tests\test_sprint103_ai_lab_run_readiness.py -q` (`3 passed`) +- `python -m compileall backend/app` +- `cd frontend && npm run typecheck` +- `cd frontend && npm run build` + +Open: +- Full repository validation, commit, deploy and live browser verification are still pending for this pass. + +Limitations: +- Frontend readiness guidance only; no backend API, persistence, migration, provider fetching, AI dependency or model execution behavior changed. + +Next recommended pass: +- After deploy validation, continue with V1 usability work that reduces operator confusion without expanding frozen product scope. diff --git a/docs/TODO.md b/docs/TODO.md index 17ed16a2..ff346433 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -369,3 +369,4 @@ This file now starts with the current implementation status. Older preparation/b - [x] Add raster pipeline readiness and guardrail surfaces for metadata, CRS, preview, tile manifest and clip-AOI handoff. - [x] Add useful default dataset context so Data, Map and Exports are immediately usable after project/demo load. - [x] Make raster tile handoff to Detection Lab auto-select the configured YOLO run form. +- [x] Add AI Lab run-readiness checks for Detection and Segmentation before job submission. diff --git a/frontend/src/components/detection/DetectionLab.tsx b/frontend/src/components/detection/DetectionLab.tsx index 15f357ca..98c6075d 100644 --- a/frontend/src/components/detection/DetectionLab.tsx +++ b/frontend/src/components/detection/DetectionLab.tsx @@ -84,6 +84,16 @@ export function DetectionLab({ onSelectReferenceDataset, onRunQa, }: DetectionLabProps): JSX.Element { + const selectedDetectionModel = detectionModels.find((model) => model.model_id === selectedDetectionModelId) ?? null + const detectionRequiresTileManifest = selectedDetectionModelId === 'yolo-configured' + const detectionHasDataset = selectedDetectionDatasetId.length > 0 + const detectionHasModel = selectedDetectionModel !== null + const detectionModelReady = Boolean(selectedDetectionModel?.configured) + const detectionHasTileManifest = + !detectionRequiresTileManifest || detectionTileManifestPath.trim().length > 0 + const detectionRunReady = + Boolean(selectedProjectId) && detectionHasDataset && detectionHasModel && detectionModelReady && detectionHasTileManifest + return (
@@ -143,6 +153,44 @@ export function DetectionLab({

Run detection

+
+
+
+

Run readiness

+

Checks the selected dataset, model and tile manifest before submitting a detection job.

+
+ + {detectionRunReady ? 'Ready to submit' : 'Blocked'} + +
+
+
+ Raster dataset + {detectionHasDataset ? 'Selected' : 'Select a raster dataset'} +
+
+ Model availability + + {detectionModelReady + ? `${selectedDetectionModel?.display_name ?? selectedDetectionModelId} is configured` + : selectedDetectionModel?.limitation_message ?? 'Select a configured model'} + +
+
+ Tile manifest + + {detectionRequiresTileManifest + ? detectionHasTileManifest + ? 'Provided for configured YOLO' + : 'Required for configured YOLO' + : 'Not required for this model'} + +
+
+
{rasterDatasets.length === 0 ? (
No raster datasets available for detection. diff --git a/frontend/src/components/segmentation/SegmentationLab.tsx b/frontend/src/components/segmentation/SegmentationLab.tsx index b8bbb02d..1b71d419 100644 --- a/frontend/src/components/segmentation/SegmentationLab.tsx +++ b/frontend/src/components/segmentation/SegmentationLab.tsx @@ -88,6 +88,11 @@ export function SegmentationLab({ onSelectReferenceDataset, onRunQa, }: SegmentationLabProps): JSX.Element { + const segmentationHasDataset = selectedSegmentationDatasetId.length > 0 + const segmentationHasTileManifest = segmentationTileManifestPath.trim().length > 0 + const segmentationRunReady = + Boolean(selectedProjectId) && segmentationHasDataset && selectedSegmentationModelConfigured + return (
@@ -147,6 +152,38 @@ export function SegmentationLab({

Run segmentation

+
+
+
+

Run readiness

+

Checks the selected raster and segmenter state before submitting a segmentation job.

+
+ + {segmentationRunReady ? 'Ready to submit' : 'Blocked'} + +
+
+
+ Raster dataset + {segmentationHasDataset ? 'Selected' : 'Select a raster dataset'} +
+
+ Model availability + + {selectedSegmentationModelConfigured + ? 'Selected model is configured' + : selectedSegmentationModelLimitation ?? 'Select a configured segmentation model'} + +
+
+ Tile manifest + {segmentationHasTileManifest ? 'Provided for provenance' : 'Optional for the fixture segmenter'} +
+
+
{rasterDatasets.length === 0 ? (
No raster datasets available for segmentation. diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 9fb89bcf..b18af5ab 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -2667,6 +2667,58 @@ button.entity-card { min-width: 0; } +.lab-readiness-panel { + display: grid; + gap: 0.6rem; + min-width: 0; + border-left: 4px solid #c9d8d1; + border-radius: 8px; + padding: 0.64rem 0.68rem; + background: #f8fbf9; +} + +.lab-readiness-panel-ready { + border-left-color: #2f7d56; + background: #f5fbf6; +} + +.lab-readiness-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr)); + gap: 0.5rem; + min-width: 0; +} + +.lab-readiness-item { + display: grid; + gap: 0.18rem; + min-width: 0; + border: 1px solid #d8e3de; + border-radius: 7px; + padding: 0.52rem 0.58rem; + background: #ffffff; +} + +.lab-readiness-item-ready { + border-color: #b8dcc9; + background: #fbfffc; +} + +.lab-readiness-item span { + color: var(--muted); + font-size: 0.72rem; + font-weight: 700; + text-transform: uppercase; +} + +.lab-readiness-item strong { + min-width: 0; + color: var(--text); + font-size: 0.88rem; + line-height: 1.28; + overflow-wrap: anywhere; +} + .ai-lab-shell .model-list { grid-template-columns: repeat(auto-fit, minmax(12.5rem, 1fr)); gap: 0.55rem;