Harden raster detection manifest handoff
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-07-08 12:04:21 +02:00
parent 0ed71c6f1e
commit 46204edbf4
11 changed files with 250 additions and 9 deletions
+4
View File
@@ -83,6 +83,7 @@ function App(): JSX.Element {
jobs,
rasterPreview,
latestRasterTileManifestPath,
latestRasterTileManifest,
selectedIntersectTargetId,
selectedClipAreaId,
rasterTileSize,
@@ -279,6 +280,8 @@ function App(): JSX.Element {
setDetectionTileManifestPath(manifestPath)
setSelectedDetectionDatasetId(selectedDataset.id)
setSelectedDetectionModelId('yolo-configured')
setDetectionConfidenceThreshold(0.25)
loadYoloPreflight(manifestPath).catch(() => null)
setActiveWorkspace('ai')
}
const {
@@ -1088,6 +1091,7 @@ function App(): JSX.Element {
selectedRasterStats,
rasterPreview,
latestRasterTileManifestPath,
latestRasterTileManifest,
rasterUnavailableMessage,
selectedClipAreaId,
selectedIntersectTargetId,
@@ -5,6 +5,7 @@ import type {
RasterMetadataResponse,
RasterPreviewResponse,
RasterStatsResponse,
RasterTileHandoff,
VectorSummary,
} from '../../types'
import { RasterControls } from './RasterControls'
@@ -20,6 +21,7 @@ export interface DatasetDetailPanelProps {
selectedRasterStats: RasterStatsResponse | null
rasterPreview: RasterPreviewResponse | null
latestRasterTileManifestPath: string
latestRasterTileManifest: RasterTileHandoff | null
rasterUnavailableMessage: string | null
selectedClipAreaId: string
selectedIntersectTargetId: string
@@ -104,6 +106,7 @@ export function DatasetDetailPanel({
selectedRasterStats,
rasterPreview,
latestRasterTileManifestPath,
latestRasterTileManifest,
rasterUnavailableMessage,
selectedClipAreaId,
selectedIntersectTargetId,
@@ -178,6 +181,7 @@ export function DatasetDetailPanel({
selectedRasterStats={selectedRasterStats}
rasterPreview={rasterPreview}
latestRasterTileManifestPath={latestRasterTileManifestPath}
latestRasterTileManifest={latestRasterTileManifest}
rasterUnavailableMessage={rasterUnavailableMessage}
selectedClipAreaId={selectedClipAreaId}
rasterTileSize={rasterTileSize}
@@ -1,4 +1,4 @@
import type { AreaRead, RasterMetadataResponse, RasterPreviewResponse, RasterStatsResponse } from '../../types'
import type { AreaRead, RasterMetadataResponse, RasterPreviewResponse, RasterStatsResponse, RasterTileHandoff } from '../../types'
interface RasterControlsProps {
areas: AreaRead[]
@@ -7,6 +7,7 @@ interface RasterControlsProps {
selectedRasterStats: RasterStatsResponse | null
rasterPreview: RasterPreviewResponse | null
latestRasterTileManifestPath: string
latestRasterTileManifest: RasterTileHandoff | null
rasterUnavailableMessage: string | null
selectedClipAreaId: string
rasterTileSize: number
@@ -61,6 +62,7 @@ export function RasterControls({
selectedRasterStats,
rasterPreview,
latestRasterTileManifestPath,
latestRasterTileManifest,
rasterUnavailableMessage,
selectedClipAreaId,
rasterTileSize,
@@ -167,11 +169,41 @@ export function RasterControls({
{latestRasterTileManifestPath ||
'No tile manifest generated yet. Generate tiles before handing this raster to Detection Lab.'}
</p>
<button type="button" onClick={onUseTileManifestForDetection} disabled={!latestRasterTileManifestPath}>
Use in Detection Lab
{latestRasterTileManifest ? (
<dl className="raster-manifest-details">
<div>
<dt>Tile count</dt>
<dd>{latestRasterTileManifest.count ?? 'n/a'}</dd>
</div>
<div>
<dt>Tile size</dt>
<dd>{latestRasterTileManifest.tile_size ?? 'n/a'}</dd>
</div>
<div>
<dt>Overlap</dt>
<dd>{latestRasterTileManifest.overlap ?? 'n/a'}</dd>
</div>
<div>
<dt>Tile set</dt>
<dd>{latestRasterTileManifest.tile_set_id ?? 'n/a'}</dd>
</div>
</dl>
) : null}
<button
type="button"
aria-label="Use in Detection Lab with current manifest"
onClick={onUseTileManifestForDetection}
disabled={!latestRasterTileManifestPath}
>
Use manifest in Detection Lab
</button>
<button type="button" onClick={onUseTileManifestForSegmentation} disabled={!latestRasterTileManifestPath}>
Use in Segmentation Lab
<button
type="button"
aria-label="Use in Segmentation Lab with current manifest"
onClick={onUseTileManifestForSegmentation}
disabled={!latestRasterTileManifestPath}
>
Use manifest in Segmentation Lab
</button>
</div>
</section>
@@ -311,11 +311,20 @@ export function DetectionLab({
<span>CUDA</span>
<strong>{yoloPreflight.runtime.cuda_available === true ? 'available' : yoloPreflight.runtime.cuda_available === false ? 'not available' : 'not checked'}</strong>
</div>
<div className={yoloPreflight.checks.manifest_valid ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
<span>Tile manifest validation</span>
<strong>{yoloPreflight.checks.manifest_valid === true ? 'valid' : yoloPreflight.checks.manifest_path_set ? 'not valid' : 'not provided'}</strong>
</div>
<div className={yoloPreflight.tile_count > 0 ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
<span>Tile count</span>
<strong>{yoloPreflight.tile_count} / {yoloPreflight.max_tiles}</strong>
</div>
</div>
<div className="entity-meta">
<span>torch_version: {yoloPreflight.runtime.torch_version ?? 'n/a'}</span>
<span>ultralytics_version: {yoloPreflight.runtime.ultralytics_version ?? 'n/a'}</span>
<span>cuda_available: {String(yoloPreflight.runtime.cuda_available ?? 'unknown')}</span>
<span>will_run_inference: {String(yoloPreflight.will_run_inference)}</span>
<span>YOLO_CONFIG_DIR: {yoloPreflight.runtime.yolo_config_dir ?? 'n/a'}</span>
<span>model directory: {yoloPreflight.runtime.model_directory ?? 'n/a'}</span>
<span>model_asset_id: {yoloPreflight.model_asset_id ?? 'n/a'}</span>
@@ -437,6 +446,13 @@ export function DetectionLab({
/>
</label>
) : null}
{selectedDetectionModelId === 'yolo-configured' && detectionTileManifestPath.trim() ? (
<div className="linked-manifest-card">
<strong>Linked tile manifest</strong>
<p>{detectionTileManifestPath}</p>
<span>Refresh preflight after changing model asset or manifest path.</span>
</div>
) : null}
<button className="primary-action" type="button" onClick={onRunDetection} disabled={runningDetection || !detectionRunReady}>
Run detection
</button>
+42 -4
View File
@@ -4,6 +4,7 @@ import type {
AreaRead,
DatasetCreateResponse,
JobRead,
RasterTileHandoff,
RasterMetadataResponse,
RasterPreviewResponse,
RasterStatsResponse,
@@ -28,12 +29,39 @@ function toRasterMetadata(metadata: Record<string, unknown> | null | undefined):
}
function extractRasterTileManifestPath(job: JobRead | null | undefined): string {
const manifest = toRasterTileHandoff(job)
return manifest?.manifest_path ?? ''
}
function optionalString(value: unknown): string | null {
return typeof value === 'string' && value.trim().length > 0 ? value : null
}
function optionalNumber(value: unknown): number | null {
return typeof value === 'number' && Number.isFinite(value) ? value : null
}
function toRasterTileHandoff(job: JobRead | null | undefined): RasterTileHandoff | null {
const result = job?.result_json
if (!result) {
return ''
return null
}
const manifestPath = result.manifest_path
return typeof manifestPath === 'string' ? manifestPath : ''
if (typeof manifestPath !== 'string' || manifestPath.trim().length === 0) {
return null
}
const manifest = typeof result.manifest === 'object' && result.manifest !== null
? result.manifest as Record<string, unknown>
: {}
return {
manifest_path: manifestPath,
tile_set_id: optionalString(result.tile_set_id) ?? optionalString(manifest.tile_set_id),
tile_size: optionalNumber(result.tile_size) ?? optionalNumber(manifest.tile_size),
overlap: optionalNumber(result.overlap) ?? optionalNumber(manifest.overlap),
count: optionalNumber(result.count) ?? optionalNumber(manifest.count),
source_dataset_id: optionalString(result.source_dataset_id) ?? optionalString(manifest.source_dataset_id),
created_at: optionalString(manifest.created_at),
}
}
export function useDatasetWorkflow({
@@ -53,6 +81,7 @@ export function useDatasetWorkflow({
const [jobs, setJobs] = useState<JobRead[]>([])
const [rasterPreview, setRasterPreview] = useState<RasterPreviewResponse | null>(null)
const [latestRasterTileManifestPath, setLatestRasterTileManifestPath] = useState('')
const [latestRasterTileManifest, setLatestRasterTileManifest] = useState<RasterTileHandoff | null>(null)
const [selectedIntersectTargetId, setSelectedIntersectTargetId] = useState('')
const [selectedClipAreaId, setSelectedClipAreaId] = useState('')
const [rasterTileSize, setRasterTileSize] = useState(512)
@@ -113,7 +142,10 @@ export function useDatasetWorkflow({
const latestRasterTileJob = response.items.find(
(job) => job.job_type === 'raster.tile' && extractRasterTileManifestPath(job),
)
setLatestRasterTileManifestPath(extractRasterTileManifestPath(latestRasterTileJob))
const manifest = toRasterTileHandoff(latestRasterTileJob)
const manifestPath = manifest ? manifest.manifest_path : ''
setLatestRasterTileManifest(manifest)
setLatestRasterTileManifestPath(manifestPath)
}
const loadDatasetDetails = async (projectId: string, dataset: DatasetCreateResponse) => {
@@ -126,6 +158,7 @@ export function useDatasetWorkflow({
setDatasetContent(null)
setRasterPreview(null)
setLatestRasterTileManifestPath('')
setLatestRasterTileManifest(null)
setSelectedDatasetId(dataset.id)
setJobs([])
try {
@@ -372,10 +405,13 @@ export function useDatasetWorkflow({
overlap: rasterTileOverlap,
output_name: rasterTileOutputName || undefined,
})
const manifestPath = extractRasterTileManifestPath(job)
const manifest = toRasterTileHandoff(job)
const manifestPath = manifest?.manifest_path ?? ''
setLatestRasterTileManifest(manifest)
setLatestRasterTileManifestPath(manifestPath)
await refreshSelectedDatasetAfterJob()
if (manifestPath) {
setLatestRasterTileManifest(manifest)
setLatestRasterTileManifestPath(manifestPath)
}
setDatasetDetailError(null)
@@ -465,6 +501,7 @@ export function useDatasetWorkflow({
setDatasetContent(null)
setRasterPreview(null)
setLatestRasterTileManifestPath('')
setLatestRasterTileManifest(null)
setJobs([])
}
@@ -478,6 +515,7 @@ export function useDatasetWorkflow({
jobs,
rasterPreview,
latestRasterTileManifestPath,
latestRasterTileManifest,
selectedIntersectTargetId,
selectedClipAreaId,
rasterTileSize,
+57
View File
@@ -3702,6 +3702,63 @@ button.entity-card {
background: #ffffff;
}
.raster-manifest-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
gap: 0.42rem;
min-width: 0;
margin: 0.2rem 0 0.35rem;
}
.raster-manifest-details div {
min-width: 0;
border: 1px solid #d8e3de;
border-radius: 6px;
padding: 0.42rem 0.48rem;
background: #f8fbf9;
}
.raster-manifest-details dt {
color: var(--muted);
font-size: 0.7rem;
font-weight: 850;
text-transform: uppercase;
}
.raster-manifest-details dd {
min-width: 0;
margin: 0.12rem 0 0;
color: var(--text);
font-size: 0.86rem;
font-weight: 750;
overflow-wrap: anywhere;
}
.linked-manifest-card {
display: grid;
gap: 0.22rem;
min-width: 0;
border: 1px solid rgba(47, 125, 86, 0.24);
border-radius: 7px;
padding: 0.6rem 0.65rem;
background: #f5fbf6;
}
.linked-manifest-card strong {
color: var(--text);
font-size: 0.88rem;
}
.linked-manifest-card p,
.linked-manifest-card span {
min-width: 0;
margin: 0;
color: var(--muted);
font-size: 0.78rem;
line-height: 1.35;
overflow-wrap: anywhere;
}
.raster-readiness-item span,
.raster-manifest-handoff span {
color: var(--muted);
+10
View File
@@ -259,6 +259,16 @@ export interface RasterTileResponse {
manifest: RasterTileManifest
}
export interface RasterTileHandoff {
manifest_path: string
tile_set_id?: string | null
tile_size?: number | null
overlap?: number | null
count?: number | null
source_dataset_id?: string | null
created_at?: string | null
}
export interface VectorBBoxResponse {
dataset_id: string
bounds_json: Record<string, number> | null