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
@@ -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;