Harden raster pipeline readiness UI
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-22 03:17:41 +02:00
parent 58fa07f430
commit 330b67766d
7 changed files with 257 additions and 10 deletions
+2
View File
@@ -26,6 +26,8 @@ Primary workbench navigation, overview shortcuts, inspector tabs and dataset act
Raster and vector operation panels use structured group headings, compact helper text, field grids, action rows and inline error blocks so dense GIS controls remain scannable in the inspector.
Raster controls now start with a pipeline readiness surface for metadata profile, CRS readiness, preview artifact, tile manifest handoff and clip AOI state. Processing guardrails call out missing metadata, CRS, preview, invalid tile settings or unavailable raster processing before users run downstream GIS/AI operations.
QA/QC, exports and AI lab result panels use shared loading, error, empty and ready state cards. This keeps model registry failures, empty histories and result counts visually consistent across the workbench.
Detection Lab and Segmentation Lab now share the same AI workspace hierarchy: model capabilities, run controls, persisted results and QA controls are separated into focused surfaces. Existing run, filter, result loading and QA callbacks remain unchanged, but the screens are denser and easier to scan on desktop and mobile.
@@ -93,18 +93,106 @@ export function RasterControls({
onRunRasterNdwi,
onRunRasterNdbi,
}: RasterControlsProps) {
const previewState = rasterPreview ? 'ready' : 'not generated'
const rasterReadinessItems = [
{
label: 'Metadata profile',
state: selectedRasterMetadata ? 'ready' : 'inspect needed',
detail: selectedRasterMetadata
? `${selectedRasterMetadata.driver} | ${selectedRasterMetadata.width} x ${selectedRasterMetadata.height} | ${selectedRasterMetadata.band_count} bands`
: 'Run metadata inspection before derived raster operations.',
},
{
label: 'CRS readiness',
state: selectedRasterMetadata?.crs ? 'ready' : 'missing',
detail: selectedRasterMetadata?.crs ?? 'Raster CRS is required for safe map handoff and clipping.',
},
{
label: 'Preview artifact',
state: previewState,
detail: rasterPreview
? `${rasterPreview.preview.path} (${rasterPreview.preview.width ?? 'n/a'} x ${rasterPreview.preview.height ?? 'n/a'})`
: 'Generate a preview to confirm visual orientation before AI runs.',
},
{
label: 'Tile manifest handoff',
state: isRasterTileInputValid ? 'input valid' : 'input blocked',
detail: isRasterTileInputValid
? 'Tile generation will create the manifest path used by detection and segmentation requests.'
: 'Tile size must be positive and overlap must stay below tile size.',
},
{
label: 'Clip AOI',
state: areas.length > 0 ? 'available' : 'missing',
detail: areas.length > 0 ? `${areas.length} project area${areas.length === 1 ? '' : 's'} available.` : 'Create an area before clipping.',
},
]
const rasterGuardrailItems = [
!selectedDatasetId ? 'Select a raster dataset before running raster operations.' : null,
rasterUnavailableMessage ? `Raster unavailable: ${rasterUnavailableMessage}` : null,
selectedDatasetId && !selectedRasterMetadata ? 'Inspect metadata before reprojecting, clipping, tiling or computing indices.' : null,
selectedRasterMetadata && !selectedRasterMetadata?.crs ? 'CRS is missing; geospatial handoff should be fixed before downstream QA.' : null,
!rasterPreview ? 'Preview is not generated yet; create it before visual review.' : null,
!isRasterTileInputValid ? 'Tile settings are invalid; update tile size and overlap before generating a manifest.' : null,
areas.length === 0 ? 'No project area exists yet, so raster clipping is disabled.' : null,
].filter((item): item is string => Boolean(item))
return (
<div className="dataset-tool-panel raster-tool-panel">
<p>Raster driver: {selectedRasterMetadata?.driver ?? 'n/a'}</p>
<p>Raster size: {selectedRasterMetadata ? `${selectedRasterMetadata.width} x ${selectedRasterMetadata.height}` : 'n/a'}</p>
<p>Raster checksum: {selectedRasterMetadata?.checksum_sha256 ?? 'n/a'}</p>
<p>
Profile: CRS {selectedRasterMetadata?.crs ?? 'n/a'} | bands {selectedRasterMetadata?.band_count ?? 'n/a'} | dtype {
(selectedRasterMetadata?.dtype as string[] | undefined)?.join(', ') ?? 'n/a'}
</p>
<p>Bounds: {formatRasterBounds(selectedRasterMetadata?.bounds)}</p>
<p>Resolution: {selectedRasterMetadata?.resolution ? selectedRasterMetadata.resolution.join(', ') : 'n/a'}</p>
{rasterUnavailableMessage ? <p className="error">Raster unavailable: {rasterUnavailableMessage}</p> : null}
<section className="raster-readiness-surface" aria-label="Raster pipeline readiness">
<div className="panel-title-row">
<div>
<h3>Raster pipeline readiness</h3>
<p>Operational state for metadata, preview, clipping and tile-manifest handoff.</p>
</div>
</div>
<div className="raster-readiness-grid">
{rasterReadinessItems.map((item) => (
<div className="raster-readiness-item" key={item.label}>
<span>{item.label}</span>
<strong>{item.state}</strong>
<p>{item.detail}</p>
</div>
))}
</div>
<div className="raster-manifest-handoff">
<span>Tile manifest handoff</span>
<p>
Generated tile manifests remain backend artifacts and are referenced by path in detection/segmentation requests; this panel only prepares
and explains the handoff.
</p>
</div>
</section>
<section className="raster-guardrail-surface" aria-label="Processing guardrails">
<div className="panel-title-row">
<div>
<h3>Processing guardrails</h3>
<p>Checks that protect downstream GIS and AI operations from ambiguous raster state.</p>
</div>
</div>
{rasterGuardrailItems.length > 0 ? (
<ul className="raster-guardrail-list">
{rasterGuardrailItems.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
) : (
<p className="raster-readiness-state">Raster controls are ready for safe operation.</p>
)}
</section>
<div className="dataset-tool-group">
<h4 className="dataset-tool-heading">Raster metadata</h4>
<p>Raster driver: {selectedRasterMetadata?.driver ?? 'n/a'}</p>
<p>Raster size: {selectedRasterMetadata ? `${selectedRasterMetadata.width} x ${selectedRasterMetadata.height}` : 'n/a'}</p>
<p>Raster checksum: {selectedRasterMetadata?.checksum_sha256 ?? 'n/a'}</p>
<p>
Profile: CRS {selectedRasterMetadata?.crs ?? 'n/a'} | bands {selectedRasterMetadata?.band_count ?? 'n/a'} | dtype {
(selectedRasterMetadata?.dtype as string[] | undefined)?.join(', ') ?? 'n/a'}
</p>
<p>Bounds: {formatRasterBounds(selectedRasterMetadata?.bounds)}</p>
<p>Resolution: {selectedRasterMetadata?.resolution ? selectedRasterMetadata.resolution.join(', ') : 'n/a'}</p>
{rasterUnavailableMessage ? <p className="error">Raster unavailable: {rasterUnavailableMessage}</p> : null}
</div>
<h3>Raster operations</h3>
<p>Available operations: inspect, stats, reproject, preview, clip by selected area, tile generation.</p>
<p>Preview: {rasterPreview?.preview.path ?? 'not generated'}</p>
+79
View File
@@ -2887,6 +2887,85 @@ button.entity-card {
gap: 0.62rem;
}
.raster-readiness-surface,
.raster-guardrail-surface {
display: grid;
min-width: 0;
gap: 0.65rem;
border: 1px solid rgba(31, 92, 71, 0.18);
border-radius: 8px;
padding: 0.72rem;
background: linear-gradient(180deg, #f8fbfa 0%, #ffffff 100%);
}
.raster-readiness-surface .panel-title-row,
.raster-guardrail-surface .panel-title-row {
align-items: start;
gap: 0.55rem;
margin-bottom: 0;
}
.raster-readiness-surface h3,
.raster-guardrail-surface h3 {
margin: 0;
font-size: 0.95rem;
line-height: 1.2;
}
.raster-readiness-surface p,
.raster-guardrail-surface p {
margin: 0;
color: var(--muted);
font-size: 0.82rem;
line-height: 1.38;
overflow-wrap: anywhere;
}
.raster-readiness-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
gap: 0.5rem;
}
.raster-readiness-item,
.raster-manifest-handoff {
display: grid;
min-width: 0;
gap: 0.22rem;
border: 1px solid var(--line);
border-radius: 8px;
padding: 0.58rem;
background: #ffffff;
}
.raster-readiness-item span,
.raster-manifest-handoff span {
color: var(--muted);
font-size: 0.74rem;
font-weight: 850;
line-height: 1.2;
text-transform: uppercase;
}
.raster-readiness-item strong,
.raster-readiness-state {
color: var(--text);
font-size: 0.86rem;
font-weight: 850;
line-height: 1.25;
}
.raster-guardrail-list {
display: grid;
gap: 0.4rem;
margin: 0;
padding-left: 1.05rem;
color: var(--text);
font-size: 0.84rem;
line-height: 1.38;
overflow-wrap: anywhere;
}
.dataset-tool-group {
display: grid;
min-width: 0;