Add map QA evidence drilldown
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-25 02:42:58 +02:00
parent d5ac9a1533
commit de9c11327c
10 changed files with 192 additions and 16 deletions
+1
View File
@@ -281,6 +281,7 @@ AI Lab run controls explicitly explain when no raster dataset is available, inst
- After an area extract, `Save area export` persists the selected FeatureCollection as a normal Export Center artifact (`vector_selection_geojson`) so the handoff remains in project export history.
- `Save as dataset` persists the same selected FeatureCollection as a derived vector dataset, selects it in the workbench and keeps it queryable through backend `vector_features` for later QA/QC or analysis.
- Once a map selection has been saved as a derived dataset, the Map workspace can run QA/QC against a selected reference dataset without switching workspaces. The action reuses the existing QA comparison endpoint and shows precision, recall, F1 and persisted quality-check status inline.
- Map selection QA results also show persisted quality-check id, matches, false positives, false negatives, mean IoU and QA warnings. `Open QA/QC evidence` hands operators to the existing QA/QC evidence drilldown for the full parameters/findings record.
- 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 or Segmentation Lab with the selected raster dataset.
+6
View File
@@ -405,6 +405,7 @@ function App(): JSX.Element {
mapSelectionQaRunning,
mapSelectionQaError,
mapSelectionQaResult,
latestMapSelectionQualityCheckId,
runMapSelectionQa,
setSelectedMapQaReferenceDatasetId,
} = useMapSelectionQa({
@@ -413,6 +414,9 @@ function App(): JSX.Element {
loadQualityChecks,
loadProjectData,
})
const openMapSelectionQualityEvidence = () => {
setActiveWorkspace('analysis')
}
const {
loadingDemoWorkflow,
demoWorkflowMessage,
@@ -845,6 +849,7 @@ function App(): JSX.Element {
mapSelectionQaRunning={mapSelectionQaRunning}
mapSelectionQaError={mapSelectionQaError}
mapSelectionQaResult={mapSelectionQaResult}
latestMapSelectionQualityCheckId={latestMapSelectionQualityCheckId}
availableMapDatasets={availableMapDatasets}
selectedFeature={selectedMapFeature}
onSelectMapArea={setSelectedMapAreaId}
@@ -861,6 +866,7 @@ function App(): JSX.Element {
onDeriveMapSelectionDataset={deriveMapSelectionDataset}
onSelectMapQaReferenceDataset={setSelectedMapQaReferenceDatasetId}
onRunMapSelectionQa={runMapSelectionQa}
onOpenMapSelectionQualityEvidence={openMapSelectionQualityEvidence}
/>
) : null}
+61 -15
View File
@@ -202,6 +202,7 @@ interface MapWorkspaceProps {
mapSelectionQaRunning: boolean
mapSelectionQaError: string | null
mapSelectionQaResult: QaComparisonResult | null
latestMapSelectionQualityCheckId: string | null
availableMapDatasets: DatasetCreateResponse[]
onSelectMapArea: (areaId: string) => void
onOpenDatasetInMap: (dataset: DatasetCreateResponse) => void
@@ -217,6 +218,7 @@ interface MapWorkspaceProps {
onDeriveMapSelectionDataset: (bbox: VectorSelectionBBox) => void
onSelectMapQaReferenceDataset: (datasetId: string) => void
onRunMapSelectionQa: () => void
onOpenMapSelectionQualityEvidence: () => void
}
export function MapWorkspace({
@@ -250,6 +252,7 @@ export function MapWorkspace({
mapSelectionQaRunning,
mapSelectionQaError,
mapSelectionQaResult,
latestMapSelectionQualityCheckId,
availableMapDatasets,
onSelectMapArea,
onOpenDatasetInMap,
@@ -265,6 +268,7 @@ export function MapWorkspace({
onDeriveMapSelectionDataset,
onSelectMapQaReferenceDataset,
onRunMapSelectionQa,
onOpenMapSelectionQualityEvidence,
}: MapWorkspaceProps): JSX.Element {
const [bboxSelectionMode, setBboxSelectionMode] = useState(false)
const [firstSelectionCorner, setFirstSelectionCorner] = useState<[number, number] | null>(null)
@@ -685,23 +689,65 @@ export function MapWorkspace({
</button>
{mapSelectionQaError ? <p className="error">{mapSelectionQaError}</p> : null}
{mapSelectionQaResult ? (
<div className="feature-extract-grid" aria-label="Map selection QA result">
<div>
<span>Precision</span>
<strong>{mapSelectionQaResult.precision ?? 'n/a'}</strong>
<div className="map-selection-qa-evidence" aria-label="Map selection QA result">
<div className="panel-title-row">
<div>
<p className="eyebrow">QA evidence</p>
<h4>Saved selection comparison</h4>
</div>
<button
className="secondary-action"
disabled={!latestMapSelectionQualityCheckId}
type="button"
onClick={onOpenMapSelectionQualityEvidence}
>
Open QA/QC evidence
</button>
</div>
<div>
<span>Recall</span>
<strong>{mapSelectionQaResult.recall ?? 'n/a'}</strong>
</div>
<div>
<span>F1</span>
<strong>{mapSelectionQaResult.f1_score ?? 'n/a'}</strong>
</div>
<div>
<span>Quality check</span>
<strong>{mapSelectionQaResult.status}</strong>
<div className="feature-extract-grid">
<div>
<span>Precision</span>
<strong>{mapSelectionQaResult.precision ?? 'n/a'}</strong>
</div>
<div>
<span>Recall</span>
<strong>{mapSelectionQaResult.recall ?? 'n/a'}</strong>
</div>
<div>
<span>F1</span>
<strong>{mapSelectionQaResult.f1_score ?? 'n/a'}</strong>
</div>
<div>
<span>Mean IoU</span>
<strong>{mapSelectionQaResult.mean_iou ?? 'n/a'}</strong>
</div>
<div>
<span>Matches</span>
<strong>{mapSelectionQaResult.matches}</strong>
</div>
<div>
<span>False positives</span>
<strong>{mapSelectionQaResult.false_positives}</strong>
</div>
<div>
<span>False negatives</span>
<strong>{mapSelectionQaResult.false_negatives}</strong>
</div>
<div>
<span>Quality check id</span>
<strong>{latestMapSelectionQualityCheckId ?? 'not persisted'}</strong>
</div>
</div>
{mapSelectionQaResult.warnings.length > 0 ? (
<div className="map-selection-qa-warnings" aria-label="Map selection QA warnings">
<span>Map selection QA warnings</span>
<ul>
{mapSelectionQaResult.warnings.map((warning) => (
<li key={warning}>{warning}</li>
))}
</ul>
</div>
) : null}
</div>
) : null}
</div>
+4
View File
@@ -20,6 +20,7 @@ export function useMapSelectionQa({
const [mapSelectionQaRunning, setMapSelectionQaRunning] = useState(false)
const [mapSelectionQaError, setMapSelectionQaError] = useState<string | null>(null)
const [mapSelectionQaResult, setMapQaResult] = useState<QaComparisonResult | null>(null)
const [latestMapSelectionQualityCheckId, setLatestMapSelectionQualityCheckId] = useState<string | null>(null)
const runMapSelectionQa = async () => {
if (!selectedProjectId) {
@@ -42,6 +43,7 @@ export function useMapSelectionQa({
setMapSelectionQaRunning(true)
setMapSelectionQaError(null)
setMapQaResult(null)
setLatestMapSelectionQualityCheckId(null)
try {
const request: QaComparisonRequest = {
candidate_dataset_id: latestSelectionDataset.id,
@@ -65,6 +67,7 @@ export function useMapSelectionQa({
return
}
setMapQaResult(parsed)
setLatestMapSelectionQualityCheckId(parsed.quality_check_id ?? null)
await loadQualityChecks(selectedProjectId)
await loadProjectData(selectedProjectId)
} catch (error) {
@@ -79,6 +82,7 @@ export function useMapSelectionQa({
mapSelectionQaRunning,
mapSelectionQaError,
mapSelectionQaResult,
latestMapSelectionQualityCheckId,
runMapSelectionQa,
setSelectedMapQaReferenceDatasetId,
}
+47 -1
View File
@@ -2690,10 +2690,56 @@ button.entity-card {
}
.map-selection-qa-surface .error,
.map-selection-qa-surface .feature-extract-grid {
.map-selection-qa-surface .map-selection-qa-evidence {
grid-column: 1 / -1;
}
.map-selection-qa-evidence {
display: grid;
gap: 0.62rem;
min-width: 0;
border: 1px solid rgba(15, 118, 110, 0.2);
border-radius: 8px;
padding: 0.68rem;
background: #ffffff;
}
.map-selection-qa-evidence .panel-title-row {
margin-bottom: 0;
}
.map-selection-qa-evidence .secondary-action {
width: fit-content;
max-width: 100%;
margin-top: 0;
}
.map-selection-qa-warnings {
display: grid;
gap: 0.36rem;
min-width: 0;
border-top: 1px solid var(--line);
padding-top: 0.58rem;
}
.map-selection-qa-warnings span {
color: var(--muted);
font-size: 0.72rem;
font-weight: 850;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.map-selection-qa-warnings ul {
display: grid;
gap: 0.28rem;
margin: 0;
padding-left: 1.1rem;
color: var(--text);
font-size: 0.82rem;
line-height: 1.35;
}
@media (max-width: 720px) {
.map-selection-qa-surface {
grid-template-columns: 1fr;
+1
View File
@@ -601,6 +601,7 @@ export interface QaComparisonResult {
unsupported_geometry: boolean
unsupported_geometries: string[]
generated_at: string
quality_check_id?: string
}
export interface MetricRead {