Add AI lab run readiness panels
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-24 23:47:55 +02:00
parent 299f29c94e
commit 98fff63a97
7 changed files with 222 additions and 0 deletions
+8
View File
@@ -1000,3 +1000,11 @@ Added:
- Added responsive styling and regression coverage for the Map quick-action grid. - 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. - 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. - 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.
@@ -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
+25
View File
@@ -3722,3 +3722,28 @@ Limitations:
Next recommended pass: Next recommended pass:
- After live validation, continue with the next V1 usability gap from the workbench flow rather than adding new model/provider scope. - 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.
+1
View File
@@ -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 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] 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] 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.
@@ -84,6 +84,16 @@ export function DetectionLab({
onSelectReferenceDataset, onSelectReferenceDataset,
onRunQa, onRunQa,
}: DetectionLabProps): JSX.Element { }: 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 ( return (
<section className="workspace-panel ai-lab-shell detection-lab-shell"> <section className="workspace-panel ai-lab-shell detection-lab-shell">
<div className="panel-title-row"> <div className="panel-title-row">
@@ -143,6 +153,44 @@ export function DetectionLab({
<div className="lab-block"> <div className="lab-block">
<div className="ai-lab-run-surface" aria-label="Detection run controls"> <div className="ai-lab-run-surface" aria-label="Detection run controls">
<h3>Run detection</h3> <h3>Run detection</h3>
<div
className={detectionRunReady ? 'lab-readiness-panel lab-readiness-panel-ready' : 'lab-readiness-panel'}
aria-label="Detection run readiness"
>
<div className="ai-lab-section-header">
<div>
<h3>Run readiness</h3>
<p>Checks the selected dataset, model and tile manifest before submitting a detection job.</p>
</div>
<span className={detectionRunReady ? 'status-badge status-badge-ready' : 'status-badge'}>
{detectionRunReady ? 'Ready to submit' : 'Blocked'}
</span>
</div>
<div className="lab-readiness-grid">
<div className={detectionHasDataset ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
<span>Raster dataset</span>
<strong>{detectionHasDataset ? 'Selected' : 'Select a raster dataset'}</strong>
</div>
<div className={detectionModelReady ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
<span>Model availability</span>
<strong>
{detectionModelReady
? `${selectedDetectionModel?.display_name ?? selectedDetectionModelId} is configured`
: selectedDetectionModel?.limitation_message ?? 'Select a configured model'}
</strong>
</div>
<div className={detectionHasTileManifest ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
<span>Tile manifest</span>
<strong>
{detectionRequiresTileManifest
? detectionHasTileManifest
? 'Provided for configured YOLO'
: 'Required for configured YOLO'
: 'Not required for this model'}
</strong>
</div>
</div>
</div>
{rasterDatasets.length === 0 ? ( {rasterDatasets.length === 0 ? (
<div className="result-state result-state-empty"> <div className="result-state result-state-empty">
<strong>No raster datasets available for detection.</strong> <strong>No raster datasets available for detection.</strong>
@@ -88,6 +88,11 @@ export function SegmentationLab({
onSelectReferenceDataset, onSelectReferenceDataset,
onRunQa, onRunQa,
}: SegmentationLabProps): JSX.Element { }: SegmentationLabProps): JSX.Element {
const segmentationHasDataset = selectedSegmentationDatasetId.length > 0
const segmentationHasTileManifest = segmentationTileManifestPath.trim().length > 0
const segmentationRunReady =
Boolean(selectedProjectId) && segmentationHasDataset && selectedSegmentationModelConfigured
return ( return (
<section className="workspace-panel ai-lab-shell segmentation-lab-shell"> <section className="workspace-panel ai-lab-shell segmentation-lab-shell">
<div className="panel-title-row"> <div className="panel-title-row">
@@ -147,6 +152,38 @@ export function SegmentationLab({
<div className="lab-block"> <div className="lab-block">
<div className="ai-lab-run-surface" aria-label="Segmentation run controls"> <div className="ai-lab-run-surface" aria-label="Segmentation run controls">
<h3>Run segmentation</h3> <h3>Run segmentation</h3>
<div
className={segmentationRunReady ? 'lab-readiness-panel lab-readiness-panel-ready' : 'lab-readiness-panel'}
aria-label="Segmentation run readiness"
>
<div className="ai-lab-section-header">
<div>
<h3>Run readiness</h3>
<p>Checks the selected raster and segmenter state before submitting a segmentation job.</p>
</div>
<span className={segmentationRunReady ? 'status-badge status-badge-ready' : 'status-badge'}>
{segmentationRunReady ? 'Ready to submit' : 'Blocked'}
</span>
</div>
<div className="lab-readiness-grid">
<div className={segmentationHasDataset ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
<span>Raster dataset</span>
<strong>{segmentationHasDataset ? 'Selected' : 'Select a raster dataset'}</strong>
</div>
<div className={selectedSegmentationModelConfigured ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
<span>Model availability</span>
<strong>
{selectedSegmentationModelConfigured
? 'Selected model is configured'
: selectedSegmentationModelLimitation ?? 'Select a configured segmentation model'}
</strong>
</div>
<div className={segmentationHasTileManifest ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
<span>Tile manifest</span>
<strong>{segmentationHasTileManifest ? 'Provided for provenance' : 'Optional for the fixture segmenter'}</strong>
</div>
</div>
</div>
{rasterDatasets.length === 0 ? ( {rasterDatasets.length === 0 ? (
<div className="result-state result-state-empty"> <div className="result-state result-state-empty">
<strong>No raster datasets available for segmentation.</strong> <strong>No raster datasets available for segmentation.</strong>
+52
View File
@@ -2667,6 +2667,58 @@ button.entity-card {
min-width: 0; 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 { .ai-lab-shell .model-list {
grid-template-columns: repeat(auto-fit, minmax(12.5rem, 1fr)); grid-template-columns: repeat(auto-fit, minmax(12.5rem, 1fr));
gap: 0.55rem; gap: 0.55rem;