diff --git a/CHANGELOG.md b/CHANGELOG.md index a32e1518..a6b557b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ # Changelog +## Sprint 99 raster tile Detection Lab handoff (2026-06-23) + +- Surfaced the latest persisted `raster.tile` manifest path in the raster dataset inspector. +- Added a direct Detection Lab handoff action that fills the selected raster dataset and tile manifest path from the existing raster tile job result. +- Preserved existing raster, detection and segmentation API contracts; no AI inference, provider fetching, migrations or backend route changes were introduced. +- Added regression coverage for the frontend handoff wiring. + ## Sprint 98 demo raster workflow smoke (2026-06-23) - Added `scripts/verify_demo_raster_workflow.sh` to validate the browser-facing demo raster happy path: inspect, preview, stats and tile manifest generation. diff --git a/backend/tests/test_sprint99_raster_ui_handoff.py b/backend/tests/test_sprint99_raster_ui_handoff.py new file mode 100644 index 00000000..9445219d --- /dev/null +++ b/backend/tests/test_sprint99_raster_ui_handoff.py @@ -0,0 +1,24 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + + +def test_raster_tile_manifest_is_visible_and_can_handoff_to_detection_lab() -> None: + app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") + hook = (ROOT / "frontend" / "src" / "hooks" / "useDatasetWorkflow.ts").read_text(encoding="utf-8") + detail_panel = (ROOT / "frontend" / "src" / "components" / "datasets" / "DatasetDetailPanel.tsx").read_text(encoding="utf-8") + raster_controls = (ROOT / "frontend" / "src" / "components" / "datasets" / "RasterControls.tsx").read_text(encoding="utf-8") + + assert "latestRasterTileManifestPath" in hook + assert "extractRasterTileManifestPath" in hook + assert "setLatestRasterTileManifestPath(manifestPath)" in hook + assert "latestRasterTileManifestPath," in app + assert "onUseTileManifestForDetection: useRasterTileManifestForDetection" in app + assert "setDetectionTileManifestPath(manifestPath)" in app + assert "setSelectedDetectionDatasetId(selectedDataset.id)" 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 "disabled={!latestRasterTileManifestPath}" in raster_controls diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index 66da1c69..edb4491a 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -1,3 +1,32 @@ +## Sprint 99 raster tile Detection Lab handoff (2026-06-23) + +Changed: +- Added latest raster tile manifest tracking to `frontend/src/hooks/useDatasetWorkflow.ts` from persisted `raster.tile` job results and direct tile generation responses. +- Surfaced the latest manifest path in `frontend/src/components/datasets/RasterControls.tsx`. +- Added a `Use in Detection Lab` handoff that fills the selected raster dataset and tile manifest path in the existing Detection Lab state. +- Updated `frontend/src/components/datasets/DatasetDetailPanel.tsx`, `frontend/src/App.tsx`, `frontend/README.md` and `CHANGELOG.md`. +- Added `backend/tests/test_sprint99_raster_ui_handoff.py`. + +Validation: +- RED: `python -m pytest backend\tests\test_sprint99_raster_ui_handoff.py -q` failed before implementation because the raster tile manifest state and handoff wiring did not exist. +- `python -m pytest backend\tests\test_sprint99_raster_ui_handoff.py -q` passed. +- `cd frontend && npm run typecheck` passed. +- `python -m pytest backend\tests\test_sprint99_raster_ui_handoff.py backend\tests\test_sprint95_raster_pipeline_hardening.py backend\tests\test_sprint28_dataset_workflow_hook.py -q` passed: 6 tests. +- `cd frontend && npm run build` passed. +- `python -m compileall backend/app` passed. +- `python -m pytest -q` from `backend/` passed: 322 tests. +- `bash scripts/run_readiness_check.sh` passed. +- `cd backend && python -m alembic heads` passed: `202606120900 (head)`. +- `cd backend && python -m alembic upgrade head --sql` passed. +- `bash -n scripts/live_migration_smoke.sh` passed. + +Limitations: +- Frontend handoff only; no API contract, migration, provider fetching, AI inference or model behavior changes. +- The Detection Lab still follows the existing model availability and explicit-run constraints. + +Next recommended pass: +- Run full readiness, redeploy, then validate the demo raster tile flow and Detection Lab prefill live on `http://192.168.10.150:1202`. + ## Sprint 98 demo raster workflow smoke (2026-06-23) Changed: diff --git a/frontend/README.md b/frontend/README.md index 5b4c71b2..6d4322b0 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -277,6 +277,7 @@ AI Lab run controls explicitly explain when no raster dataset is available, inst - When the Map workspace has no active result layer, it lists ready vector/GeoJSON datasets as direct quick actions so populated demo projects can jump straight from the empty state to map inspection. - The Data catalog shows a compact selected/reference/candidate/source summary and scan-friendly badges. Persisted `reference` datasets are shown as Reference, non-reference vector/GeoJSON layers are shown as QA Candidates for workbench scanning, and raster/other uploads remain Source. - Dataset cards explain the recommended next action and use compact two-line action buttons for inspect, map, export/QA and metadata refresh. Disabled actions keep a visible reason, such as `Vector/GeoJSON only`. +- Raster controls show the latest generated tile manifest path from persisted `raster.tile` jobs and can hand that path directly to Detection Lab with the selected raster dataset. - The QA/QC workspace shows candidate/reference handoff cards and resolves persisted quality-check dataset IDs back to dataset names when the datasets are loaded in the current project context. - The QA/QC workspace includes a selected-check evidence drilldown with candidate/reference provenance, false-positive/negative metric evidence, map handoff context and parameters/findings JSON. diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index bc87dc4e..ccb73835 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -79,6 +79,7 @@ function App(): JSX.Element { datasetContent, jobs, rasterPreview, + latestRasterTileManifestPath, selectedIntersectTargetId, selectedClipAreaId, rasterTileSize, @@ -254,6 +255,15 @@ function App(): JSX.Element { loadProjectData, loadQualityChecks, }) + const useRasterTileManifestForDetection = () => { + const manifestPath = latestRasterTileManifestPath.trim() + if (!manifestPath || !selectedDataset || selectedDataset.dataset_type !== 'raster') { + return + } + setDetectionTileManifestPath(manifestPath) + setSelectedDetectionDatasetId(selectedDataset.id) + setActiveWorkspace('ai') + } const { segmentationModels, loadingSegmentationModels, @@ -949,6 +959,7 @@ function App(): JSX.Element { selectedRasterMetadata, selectedRasterStats, rasterPreview, + latestRasterTileManifestPath, rasterUnavailableMessage, selectedClipAreaId, selectedIntersectTargetId, @@ -987,6 +998,7 @@ function App(): JSX.Element { onRunRasterReproject: runRasterReproject, onRunRasterClip: runRasterClip, onRunRasterTile: runRasterTile, + onUseTileManifestForDetection: useRasterTileManifestForDetection, onRunRasterNdvi: runRasterNdvi, onRunRasterNdwi: runRasterNdwi, onRunRasterNdbi: runRasterNdbi, diff --git a/frontend/src/components/datasets/DatasetDetailPanel.tsx b/frontend/src/components/datasets/DatasetDetailPanel.tsx index 835bcc1b..ba7365f6 100644 --- a/frontend/src/components/datasets/DatasetDetailPanel.tsx +++ b/frontend/src/components/datasets/DatasetDetailPanel.tsx @@ -19,6 +19,7 @@ export interface DatasetDetailPanelProps { selectedRasterMetadata: RasterMetadataResponse | null selectedRasterStats: RasterStatsResponse | null rasterPreview: RasterPreviewResponse | null + latestRasterTileManifestPath: string rasterUnavailableMessage: string | null selectedClipAreaId: string selectedIntersectTargetId: string @@ -57,6 +58,7 @@ export interface DatasetDetailPanelProps { onRunRasterReproject: () => void onRunRasterClip: () => void onRunRasterTile: () => void + onUseTileManifestForDetection: () => void onRunRasterNdvi: () => void onRunRasterNdwi: () => void onRunRasterNdbi: () => void @@ -100,6 +102,7 @@ export function DatasetDetailPanel({ selectedRasterMetadata, selectedRasterStats, rasterPreview, + latestRasterTileManifestPath, rasterUnavailableMessage, selectedClipAreaId, selectedIntersectTargetId, @@ -138,6 +141,7 @@ export function DatasetDetailPanel({ onRunRasterReproject, onRunRasterClip, onRunRasterTile, + onUseTileManifestForDetection, onRunRasterNdvi, onRunRasterNdwi, onRunRasterNdbi, @@ -171,6 +175,7 @@ export function DatasetDetailPanel({ selectedRasterMetadata={selectedRasterMetadata} selectedRasterStats={selectedRasterStats} rasterPreview={rasterPreview} + latestRasterTileManifestPath={latestRasterTileManifestPath} rasterUnavailableMessage={rasterUnavailableMessage} selectedClipAreaId={selectedClipAreaId} rasterTileSize={rasterTileSize} @@ -203,6 +208,7 @@ export function DatasetDetailPanel({ onRunRasterReproject={onRunRasterReproject} onRunRasterClip={onRunRasterClip} onRunRasterTile={onRunRasterTile} + onUseTileManifestForDetection={onUseTileManifestForDetection} onRunRasterNdvi={onRunRasterNdvi} onRunRasterNdwi={onRunRasterNdwi} onRunRasterNdbi={onRunRasterNdbi} diff --git a/frontend/src/components/datasets/RasterControls.tsx b/frontend/src/components/datasets/RasterControls.tsx index 7bafc944..f7d57142 100644 --- a/frontend/src/components/datasets/RasterControls.tsx +++ b/frontend/src/components/datasets/RasterControls.tsx @@ -6,6 +6,7 @@ interface RasterControlsProps { selectedRasterMetadata: RasterMetadataResponse | null selectedRasterStats: RasterStatsResponse | null rasterPreview: RasterPreviewResponse | null + latestRasterTileManifestPath: string rasterUnavailableMessage: string | null selectedClipAreaId: string rasterTileSize: number @@ -38,6 +39,7 @@ interface RasterControlsProps { onRunRasterReproject: () => void onRunRasterClip: () => void onRunRasterTile: () => void + onUseTileManifestForDetection: () => void onRunRasterNdvi: () => void onRunRasterNdwi: () => void onRunRasterNdbi: () => void @@ -57,6 +59,7 @@ export function RasterControls({ selectedRasterMetadata, selectedRasterStats, rasterPreview, + latestRasterTileManifestPath, rasterUnavailableMessage, selectedClipAreaId, rasterTileSize, @@ -89,6 +92,7 @@ export function RasterControls({ onRunRasterReproject, onRunRasterClip, onRunRasterTile, + onUseTileManifestForDetection, onRunRasterNdvi, onRunRasterNdwi, onRunRasterNdbi, @@ -156,11 +160,14 @@ export function RasterControls({ ))}
- Generated tile manifests remain backend artifacts and are referenced by path in detection/segmentation requests; this panel only prepares - and explains the handoff. + {latestRasterTileManifestPath || + 'No tile manifest generated yet. Generate tiles before handing this raster to Detection Lab.'}
+