Harden raster pipeline readiness UI
This commit is contained in:
@@ -7,6 +7,14 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
## Sprint 95 raster pipeline hardening (2026-06-22)
|
||||
|
||||
- Added a raster pipeline readiness surface to the dataset inspector.
|
||||
- Surfaced metadata profile, CRS readiness, preview artifact, tile manifest handoff and clip AOI state before raster operations.
|
||||
- Added processing guardrails for missing metadata, missing CRS, missing preview, invalid tile parameters, unavailable rasters and missing clip areas.
|
||||
- Added responsive styling and regression coverage for the raster readiness/handoff structure.
|
||||
- No API contracts, migrations, provider fetching or AI/model behavior changed.
|
||||
|
||||
## Sprint 94 QA/QC evidence drilldown (2026-06-22)
|
||||
|
||||
- Added a selected QA/QC evidence drilldown to the Quality Results panel.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def test_raster_controls_expose_pipeline_readiness_and_handoff() -> None:
|
||||
raster_controls = (ROOT / "frontend" / "src" / "components" / "datasets" / "RasterControls.tsx").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
assert "rasterReadinessItems" in raster_controls
|
||||
assert "rasterGuardrailItems" in raster_controls
|
||||
assert "Raster pipeline readiness" in raster_controls
|
||||
assert "Metadata profile" in raster_controls
|
||||
assert "CRS readiness" in raster_controls
|
||||
assert "Preview artifact" in raster_controls
|
||||
assert "Tile manifest handoff" in raster_controls
|
||||
assert "Clip AOI" in raster_controls
|
||||
assert "Processing guardrails" in raster_controls
|
||||
assert "selectedRasterMetadata?.crs" in raster_controls
|
||||
assert "rasterPreview ?" in raster_controls
|
||||
assert "isRasterTileInputValid" in raster_controls
|
||||
|
||||
|
||||
def test_raster_pipeline_readiness_css_contracts() -> None:
|
||||
css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
|
||||
|
||||
assert ".raster-readiness-surface" in css
|
||||
assert ".raster-readiness-grid" in css
|
||||
assert ".raster-readiness-item" in css
|
||||
assert ".raster-readiness-state" in css
|
||||
assert ".raster-guardrail-surface" in css
|
||||
assert ".raster-guardrail-list" in css
|
||||
assert ".raster-manifest-handoff" in css
|
||||
assert "grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));" in css
|
||||
@@ -3406,3 +3406,36 @@ Limitations:
|
||||
|
||||
Next recommended pass:
|
||||
- After deploy and live validation, continue with QA map overlay affordances or raster pipeline hardening.
|
||||
|
||||
## Sprint 95 raster pipeline hardening (2026-06-22)
|
||||
|
||||
Changed:
|
||||
- Added a `Raster pipeline readiness` surface to `frontend/src/components/datasets/RasterControls.tsx`.
|
||||
- Surfaced metadata profile, CRS readiness, preview artifact, tile manifest handoff and clip AOI state before raster operations.
|
||||
- Added `Processing guardrails` for missing selected dataset, unavailable raster processing, missing metadata, missing CRS, missing preview, invalid tile parameters and missing clip areas.
|
||||
- Added responsive raster readiness, guardrail and manifest-handoff styles to `frontend/src/styles/app.css`.
|
||||
- Added `backend/tests/test_sprint95_raster_pipeline_hardening.py`.
|
||||
- Updated `CHANGELOG.md`, `docs/TODO.md` and `frontend/README.md`.
|
||||
|
||||
Tested:
|
||||
- Red step: `python -m pytest backend\tests\test_sprint95_raster_pipeline_hardening.py -q` failed on missing readiness/handoff component structure and CSS.
|
||||
- `python -m pytest backend\tests\test_sprint95_raster_pipeline_hardening.py -q` (`2 passed`)
|
||||
- `python -m pytest backend\tests\test_sprint80_operation_form_readability.py backend\tests\test_sprint77_inspector_mobile_polish.py -q` (`5 passed`)
|
||||
- `python -m compileall backend/app`
|
||||
- `cd backend && python -m pytest -q` (`316 passed`)
|
||||
- `cd frontend && npm run typecheck`
|
||||
- `cd frontend && npm run build`
|
||||
- `bash scripts/run_readiness_check.sh` (`316 passed`; frontend typecheck/build passed; Alembic head `202606120900`; live smoke syntax passed)
|
||||
- `cd backend && python -m alembic heads` (`202606120900 (head)`)
|
||||
- `cd backend && python -m alembic upgrade head --sql`
|
||||
- `bash -n scripts/live_migration_smoke.sh`
|
||||
|
||||
Open:
|
||||
- Deploy and live browser validation are still pending for this pass.
|
||||
|
||||
Limitations:
|
||||
- Frontend raster inspector hardening only; no API contract, persistence, migration, provider fetching or AI/model behavior changes.
|
||||
- The panel explains the existing tile manifest handoff but does not create a new manifest picker or new detection/segmentation behavior.
|
||||
|
||||
Next recommended pass:
|
||||
- After deploy and live validation, add a raster fixture/demo upload smoke or continue with map evidence overlay affordances for QA/QC.
|
||||
|
||||
@@ -366,3 +366,4 @@ This file now starts with the current implementation status. Older preparation/b
|
||||
- [x] Make Overview workflow rail Map/Export clicks preserve useful dataset context.
|
||||
- [x] Add latest handoff artifact cards to the Export Center for report, metadata and GeoJSON outputs.
|
||||
- [x] Add QA/QC evidence drilldown for selected checks, false-positive/negative evidence and provenance JSON.
|
||||
- [x] Add raster pipeline readiness and guardrail surfaces for metadata, CRS, preview, tile manifest and clip-AOI handoff.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user