Add QA evidence map overlay
This commit is contained in:
@@ -205,7 +205,12 @@ function App(): JSX.Element {
|
||||
qaError,
|
||||
qualityChecks,
|
||||
qualityChecksError,
|
||||
qualityEvidenceGeoJson,
|
||||
qualityEvidenceLoading,
|
||||
qualityEvidenceError,
|
||||
loadQualityChecks,
|
||||
loadQualityEvidenceGeoJson,
|
||||
clearQualityEvidenceGeoJson,
|
||||
runQaComparison,
|
||||
setQaCandidateDatasetId,
|
||||
setQaReferenceDatasetId,
|
||||
@@ -417,6 +422,13 @@ function App(): JSX.Element {
|
||||
const openMapSelectionQualityEvidence = () => {
|
||||
setActiveWorkspace('analysis')
|
||||
}
|
||||
const openQualityEvidenceOnMap = async (qualityCheckId: string) => {
|
||||
const result = await loadQualityEvidenceGeoJson(qualityCheckId)
|
||||
if (result) {
|
||||
setMapLayerVisible(true)
|
||||
setActiveWorkspace('map')
|
||||
}
|
||||
}
|
||||
const {
|
||||
loadingDemoWorkflow,
|
||||
demoWorkflowMessage,
|
||||
@@ -824,6 +836,11 @@ function App(): JSX.Element {
|
||||
selectedMapAreaId={selectedMapAreaId}
|
||||
areaFeatureCollection={areaFeatureCollection}
|
||||
mapFeatureCollection={mapFeatureCollection}
|
||||
qualityEvidenceGeoJson={qualityEvidenceGeoJson?.geojson ?? null}
|
||||
qualityEvidenceFeatureCount={qualityEvidenceGeoJson?.feature_count ?? 0}
|
||||
qualityEvidenceLoading={qualityEvidenceLoading}
|
||||
qualityEvidenceError={qualityEvidenceError}
|
||||
qualityEvidenceWarnings={qualityEvidenceGeoJson?.warnings ?? []}
|
||||
mapLayerLabel={mapLayerLabel}
|
||||
mapLayerSourceLabel={mapLayerSourceLabel}
|
||||
mapLayerProvenance={mapLayerProvenance}
|
||||
@@ -867,6 +884,7 @@ function App(): JSX.Element {
|
||||
onSelectMapQaReferenceDataset={setSelectedMapQaReferenceDatasetId}
|
||||
onRunMapSelectionQa={runMapSelectionQa}
|
||||
onOpenMapSelectionQualityEvidence={openMapSelectionQualityEvidence}
|
||||
onClearQualityEvidence={clearQualityEvidenceGeoJson}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -895,6 +913,9 @@ function App(): JSX.Element {
|
||||
candidateDatasets={candidateDatasets}
|
||||
referenceDatasets={referenceDatasets}
|
||||
onRefresh={() => loadQualityChecks()}
|
||||
onOpenEvidenceMap={openQualityEvidenceOnMap}
|
||||
evidenceLoading={qualityEvidenceLoading}
|
||||
evidenceError={qualityEvidenceError}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -7,6 +7,7 @@ interface GeoMapProps {
|
||||
areaData?: GeoJSON.FeatureCollection | null
|
||||
selectedFeature?: GeoJSON.Feature | null
|
||||
selectionData?: GeoJSON.FeatureCollection | null
|
||||
qaEvidenceData?: GeoJSON.FeatureCollection | null
|
||||
selectionBbox?: { min_x: number; min_y: number; max_x: number; max_y: number } | null
|
||||
bboxSelectionMode?: boolean
|
||||
visible?: boolean
|
||||
@@ -97,6 +98,7 @@ function GeoMap({
|
||||
areaData = null,
|
||||
selectedFeature = null,
|
||||
selectionData = null,
|
||||
qaEvidenceData = null,
|
||||
selectionBbox = null,
|
||||
bboxSelectionMode = false,
|
||||
visible = true,
|
||||
@@ -148,7 +150,18 @@ function GeoMap({
|
||||
onMapCoordinateSelectRef.current?.([event.lngLat.lng, event.lngLat.lat])
|
||||
return
|
||||
}
|
||||
const layers = ['dataset-fill', 'dataset-line', 'area-fill', 'area-line'].filter((layerId) => map.getLayer(layerId))
|
||||
const layers = [
|
||||
'qa-evidence-fill',
|
||||
'qa-evidence-line',
|
||||
'qa-evidence-circle',
|
||||
'selection-result-fill',
|
||||
'selection-result-line',
|
||||
'selection-result-circle',
|
||||
'dataset-fill',
|
||||
'dataset-line',
|
||||
'area-fill',
|
||||
'area-line',
|
||||
].filter((layerId) => map.getLayer(layerId))
|
||||
if (layers.length === 0) {
|
||||
onFeatureSelectRef.current?.(null)
|
||||
return
|
||||
@@ -463,6 +476,74 @@ function GeoMap({
|
||||
})
|
||||
}, [selectionData, mapStyleReady])
|
||||
|
||||
useEffect(() => {
|
||||
const map = mapRef.current
|
||||
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
|
||||
return
|
||||
}
|
||||
|
||||
const evidenceCollection = qaEvidenceData ?? EMPTY_FEATURE_COLLECTION
|
||||
if (map.getSource('qa-evidence')) {
|
||||
;(map.getSource('qa-evidence') as maplibregl.GeoJSONSource).setData(evidenceCollection)
|
||||
return
|
||||
}
|
||||
|
||||
map.addSource('qa-evidence', { type: 'geojson', data: evidenceCollection })
|
||||
const evidenceColor = [
|
||||
'match',
|
||||
['get', 'qa_evidence_role'],
|
||||
'match_candidate',
|
||||
'#2563eb',
|
||||
'match_reference',
|
||||
'#0f766e',
|
||||
'false_positive',
|
||||
'#dc2626',
|
||||
'false_negative',
|
||||
'#d97706',
|
||||
'#475569',
|
||||
] as maplibregl.ExpressionSpecification
|
||||
map.addLayer({
|
||||
id: 'qa-evidence-fill',
|
||||
type: 'fill',
|
||||
source: 'qa-evidence',
|
||||
filter: ['match', ['geometry-type'], ['Polygon', 'MultiPolygon'], true, false],
|
||||
paint: {
|
||||
'fill-color': evidenceColor,
|
||||
'fill-opacity': 0.28,
|
||||
},
|
||||
})
|
||||
map.addLayer({
|
||||
id: 'qa-evidence-line',
|
||||
type: 'line',
|
||||
source: 'qa-evidence',
|
||||
filter: ['match', ['geometry-type'], ['Polygon', 'MultiPolygon', 'LineString', 'MultiLineString'], true, false],
|
||||
paint: {
|
||||
'line-color': evidenceColor,
|
||||
'line-width': [
|
||||
'match',
|
||||
['get', 'qa_evidence_role'],
|
||||
'false_positive',
|
||||
4,
|
||||
'false_negative',
|
||||
4,
|
||||
3,
|
||||
],
|
||||
},
|
||||
})
|
||||
map.addLayer({
|
||||
id: 'qa-evidence-circle',
|
||||
type: 'circle',
|
||||
source: 'qa-evidence',
|
||||
filter: ['match', ['geometry-type'], ['Point', 'MultiPoint'], true, false],
|
||||
paint: {
|
||||
'circle-color': evidenceColor,
|
||||
'circle-radius': 7,
|
||||
'circle-stroke-color': '#ffffff',
|
||||
'circle-stroke-width': 2,
|
||||
},
|
||||
})
|
||||
}, [qaEvidenceData, mapStyleReady])
|
||||
|
||||
return <div className="map-container" ref={containerRef} />
|
||||
}
|
||||
|
||||
|
||||
@@ -176,6 +176,11 @@ interface MapWorkspaceProps {
|
||||
selectedMapAreaId: string
|
||||
areaFeatureCollection: GeoJSON.FeatureCollection | null
|
||||
mapFeatureCollection: GeoJSON.FeatureCollection | null
|
||||
qualityEvidenceGeoJson?: GeoJSON.FeatureCollection | null
|
||||
qualityEvidenceFeatureCount?: number
|
||||
qualityEvidenceLoading?: boolean
|
||||
qualityEvidenceError?: string | null
|
||||
qualityEvidenceWarnings?: string[]
|
||||
mapLayerLabel: string
|
||||
mapLayerSourceLabel: string
|
||||
mapLayerProvenance: string
|
||||
@@ -219,6 +224,7 @@ interface MapWorkspaceProps {
|
||||
onSelectMapQaReferenceDataset: (datasetId: string) => void
|
||||
onRunMapSelectionQa: () => void
|
||||
onOpenMapSelectionQualityEvidence: () => void
|
||||
onClearQualityEvidence?: () => void
|
||||
}
|
||||
|
||||
export function MapWorkspace({
|
||||
@@ -226,6 +232,11 @@ export function MapWorkspace({
|
||||
selectedMapAreaId,
|
||||
areaFeatureCollection,
|
||||
mapFeatureCollection,
|
||||
qualityEvidenceGeoJson = null,
|
||||
qualityEvidenceFeatureCount = 0,
|
||||
qualityEvidenceLoading = false,
|
||||
qualityEvidenceError = null,
|
||||
qualityEvidenceWarnings = [],
|
||||
mapLayerLabel,
|
||||
mapLayerSourceLabel,
|
||||
mapLayerProvenance,
|
||||
@@ -269,6 +280,7 @@ export function MapWorkspace({
|
||||
onSelectMapQaReferenceDataset,
|
||||
onRunMapSelectionQa,
|
||||
onOpenMapSelectionQualityEvidence,
|
||||
onClearQualityEvidence,
|
||||
}: MapWorkspaceProps): JSX.Element {
|
||||
const [bboxSelectionMode, setBboxSelectionMode] = useState(false)
|
||||
const [firstSelectionCorner, setFirstSelectionCorner] = useState<[number, number] | null>(null)
|
||||
@@ -398,6 +410,11 @@ export function MapWorkspace({
|
||||
<strong>{mapFeatureCollection ? `${mapFeatureCount} rendered features` : 'No layer rendered'}</strong>
|
||||
<small>{mapLayerProvenance}</small>
|
||||
</div>
|
||||
<div>
|
||||
<span>QA/QC evidence</span>
|
||||
<strong>{qualityEvidenceGeoJson ? `${qualityEvidenceFeatureCount} evidence features` : 'No evidence overlay'}</strong>
|
||||
<small>{qualityEvidenceLoading ? 'Loading persisted evidence' : 'Matches, false positives and false negatives'}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="map-control-surface" aria-label="Map workspace controls">
|
||||
@@ -483,9 +500,37 @@ export function MapWorkspace({
|
||||
<span>Draw state</span>
|
||||
<strong>{mapFeatureCollection ? `${mapFeatureCount} rendered features` : 'No active vector or result layer'}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>QA evidence overlay</span>
|
||||
<strong>{qualityEvidenceGeoJson ? `${qualityEvidenceFeatureCount} rendered` : 'off'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{qualityEvidenceGeoJson || qualityEvidenceError || qualityEvidenceWarnings.length > 0 ? (
|
||||
<div className="qa-evidence-map-status" aria-label="QA/QC evidence map overlay status">
|
||||
<div>
|
||||
<span>QA/QC evidence overlay</span>
|
||||
<strong>{qualityEvidenceGeoJson ? `${qualityEvidenceFeatureCount} persisted features` : 'Not loaded'}</strong>
|
||||
{qualityEvidenceError ? <p className="error">{qualityEvidenceError}</p> : null}
|
||||
{qualityEvidenceWarnings.length > 0 ? (
|
||||
<p className="muted">{qualityEvidenceWarnings.length} evidence id{qualityEvidenceWarnings.length === 1 ? '' : 's'} could not be resolved.</p>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="qa-evidence-legend" aria-label="QA/QC evidence overlay legend">
|
||||
<span><i className="qa-evidence-swatch qa-evidence-swatch-match-candidate" /> Match candidate</span>
|
||||
<span><i className="qa-evidence-swatch qa-evidence-swatch-match-reference" /> Match reference</span>
|
||||
<span><i className="qa-evidence-swatch qa-evidence-swatch-false-positive" /> False positive</span>
|
||||
<span><i className="qa-evidence-swatch qa-evidence-swatch-false-negative" /> False negative</span>
|
||||
</div>
|
||||
{onClearQualityEvidence ? (
|
||||
<button className="secondary-action" type="button" onClick={onClearQualityEvidence}>
|
||||
Clear QA evidence
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!mapFeatureCollection ? (
|
||||
<div className="empty-state map-empty-state">
|
||||
<strong>No active vector or result layer</strong>
|
||||
@@ -514,6 +559,7 @@ export function MapWorkspace({
|
||||
areaData={areaFeatureCollection}
|
||||
selectedFeature={selectedFeature}
|
||||
selectionData={mapSelectionResult?.geojson ?? null}
|
||||
qaEvidenceData={qualityEvidenceGeoJson}
|
||||
selectionBbox={mapSelectionBbox}
|
||||
bboxSelectionMode={bboxSelectionMode}
|
||||
visible={mapLayerVisible}
|
||||
|
||||
@@ -17,6 +17,9 @@ interface QualityResultsPanelProps {
|
||||
candidateDatasets: DatasetCreateResponse[]
|
||||
referenceDatasets: DatasetCreateResponse[]
|
||||
onRefresh: () => void
|
||||
onOpenEvidenceMap?: (qualityCheckId: string) => void
|
||||
evidenceLoading?: boolean
|
||||
evidenceError?: string | null
|
||||
}
|
||||
|
||||
function qualityMetricLabel(metricKey: string): string {
|
||||
@@ -95,6 +98,9 @@ export function QualityResultsPanel({
|
||||
candidateDatasets,
|
||||
referenceDatasets,
|
||||
onRefresh,
|
||||
onOpenEvidenceMap,
|
||||
evidenceLoading = false,
|
||||
evidenceError = null,
|
||||
}: QualityResultsPanelProps): JSX.Element {
|
||||
const [showAllQualityChecks, setShowAllQualityChecks] = useState(false)
|
||||
const [qualityStatusFilter, setQualityStatusFilter] = useState('all')
|
||||
@@ -204,7 +210,21 @@ export function QualityResultsPanel({
|
||||
>
|
||||
Inspect latest check
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="secondary-action"
|
||||
onClick={() => latestCheck?.id && onOpenEvidenceMap?.(latestCheck.id)}
|
||||
disabled={!latestCheck || evidenceLoading || !onOpenEvidenceMap}
|
||||
>
|
||||
{evidenceLoading ? 'Loading map evidence...' : 'Show latest on map'}
|
||||
</button>
|
||||
</div>
|
||||
{evidenceError ? (
|
||||
<div className="result-state result-state-error">
|
||||
<strong>QA/QC evidence overlay could not be loaded.</strong>
|
||||
<p>{evidenceError}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedQualityCheck ? (
|
||||
<>
|
||||
<div className="quality-drilldown-grid">
|
||||
@@ -254,6 +274,14 @@ export function QualityResultsPanel({
|
||||
<span>Map evidence handoff</span>
|
||||
<strong>{selectedCandidateName} / {selectedReferenceName}</strong>
|
||||
<p>Use the candidate and reference datasets as map layers for spatial review.</p>
|
||||
<button
|
||||
type="button"
|
||||
className="secondary-action"
|
||||
onClick={() => onOpenEvidenceMap?.(selectedQualityCheck.id)}
|
||||
disabled={evidenceLoading || !onOpenEvidenceMap}
|
||||
>
|
||||
{evidenceLoading ? 'Loading overlay...' : 'Show evidence overlay'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="quality-feature-evidence-grid" aria-label="Feature-level QA/QC evidence">
|
||||
@@ -440,6 +468,14 @@ export function QualityResultsPanel({
|
||||
<button type="button" className="secondary-action" onClick={() => setSelectedQualityCheckId(check.id)}>
|
||||
Inspect check
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="secondary-action"
|
||||
onClick={() => onOpenEvidenceMap?.(check.id)}
|
||||
disabled={evidenceLoading || !onOpenEvidenceMap}
|
||||
>
|
||||
Show evidence on map
|
||||
</button>
|
||||
</div>
|
||||
<div className="quality-score-row">
|
||||
<div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { qaApi } from '../services/api'
|
||||
import type { JobRead, QaComparisonRequest, QaComparisonResult, QualityCheckRead } from '../types'
|
||||
import type { JobRead, QaComparisonRequest, QaComparisonResult, QualityCheckRead, QualityEvidenceGeoJsonResponse } from '../types'
|
||||
import { formatError } from '../lib/formatError'
|
||||
|
||||
interface QualityWorkflowOptions {
|
||||
@@ -18,6 +18,9 @@ export function useQualityWorkflow({ selectedProjectId, loadProjectData }: Quali
|
||||
const [qaError, setQaError] = useState<string | null>(null)
|
||||
const [qualityChecks, setQualityChecks] = useState<QualityCheckRead[]>([])
|
||||
const [qualityChecksError, setQualityChecksError] = useState<string | null>(null)
|
||||
const [qualityEvidenceGeoJson, setQualityEvidenceGeoJson] = useState<QualityEvidenceGeoJsonResponse | null>(null)
|
||||
const [qualityEvidenceLoading, setQualityEvidenceLoading] = useState(false)
|
||||
const [qualityEvidenceError, setQualityEvidenceError] = useState<string | null>(null)
|
||||
|
||||
const loadQualityChecks = async (projectId = selectedProjectId): Promise<QualityCheckRead[] | void> => {
|
||||
if (!projectId) {
|
||||
@@ -34,6 +37,30 @@ export function useQualityWorkflow({ selectedProjectId, loadProjectData }: Quali
|
||||
}
|
||||
}
|
||||
|
||||
const loadQualityEvidenceGeoJson = async (qualityCheckId: string, projectId = selectedProjectId): Promise<QualityEvidenceGeoJsonResponse | null> => {
|
||||
if (!projectId) {
|
||||
setQualityEvidenceError('Select a project first')
|
||||
return null
|
||||
}
|
||||
setQualityEvidenceLoading(true)
|
||||
setQualityEvidenceError(null)
|
||||
try {
|
||||
const response = await qaApi.getQualityEvidenceGeoJson(projectId, qualityCheckId)
|
||||
setQualityEvidenceGeoJson(response)
|
||||
return response
|
||||
} catch (error) {
|
||||
setQualityEvidenceError(formatError(error, 'Failed to load QA/QC evidence overlay'))
|
||||
return null
|
||||
} finally {
|
||||
setQualityEvidenceLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const clearQualityEvidenceGeoJson = () => {
|
||||
setQualityEvidenceGeoJson(null)
|
||||
setQualityEvidenceError(null)
|
||||
}
|
||||
|
||||
const runQaComparison = async () => {
|
||||
if (!selectedProjectId) {
|
||||
setQaError('Select a project first')
|
||||
@@ -102,7 +129,12 @@ export function useQualityWorkflow({ selectedProjectId, loadProjectData }: Quali
|
||||
qaError,
|
||||
qualityChecks,
|
||||
qualityChecksError,
|
||||
qualityEvidenceGeoJson,
|
||||
qualityEvidenceLoading,
|
||||
qualityEvidenceError,
|
||||
loadQualityChecks,
|
||||
loadQualityEvidenceGeoJson,
|
||||
clearQualityEvidenceGeoJson,
|
||||
runQaComparison,
|
||||
setQaCandidateDatasetId,
|
||||
setQaReferenceDatasetId,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { apiGet, apiPost } from './client'
|
||||
import type { QaComparisonRequest, JobRead, QualityCheckListResponse } from '../../types'
|
||||
import type { QaComparisonRequest, JobRead, QualityCheckListResponse, QualityEvidenceGeoJsonResponse } from '../../types'
|
||||
|
||||
export const qaApi = {
|
||||
runQa: (payload: QaComparisonRequest): Promise<JobRead> =>
|
||||
apiPost<JobRead>('/api/v1/qa/detections-vs-reference', payload),
|
||||
listQualityChecks: (projectId: string): Promise<QualityCheckListResponse> =>
|
||||
apiGet<QualityCheckListResponse>(`/api/v1/projects/${projectId}/quality-checks`),
|
||||
getQualityEvidenceGeoJson: (projectId: string, qualityCheckId: string): Promise<QualityEvidenceGeoJsonResponse> =>
|
||||
apiGet<QualityEvidenceGeoJsonResponse>(`/api/v1/projects/${projectId}/quality-checks/${qualityCheckId}/evidence/geojson`),
|
||||
}
|
||||
|
||||
@@ -2564,11 +2564,81 @@ button.entity-card {
|
||||
|
||||
.layer-provenance-rail {
|
||||
display: grid;
|
||||
grid-template-columns: 0.85fr minmax(0, 1.5fr) 0.85fr;
|
||||
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
|
||||
gap: 0.65rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.qa-evidence-map-status {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.6fr) auto;
|
||||
gap: 0.65rem;
|
||||
align-items: center;
|
||||
border: 1px solid #bfdbfe;
|
||||
border-left: 4px solid #2563eb;
|
||||
border-radius: 8px;
|
||||
padding: 0.72rem;
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
.qa-evidence-map-status span,
|
||||
.qa-evidence-map-status strong {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.qa-evidence-map-status > div:first-child span {
|
||||
color: #1d4ed8;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 850;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.qa-evidence-map-status > div:first-child strong {
|
||||
margin-top: 0.18rem;
|
||||
color: #172554;
|
||||
}
|
||||
|
||||
.qa-evidence-legend {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
|
||||
gap: 0.38rem;
|
||||
}
|
||||
|
||||
.qa-evidence-legend span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.38rem;
|
||||
color: #1e3a8a;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.qa-evidence-swatch {
|
||||
display: inline-block;
|
||||
width: 0.72rem;
|
||||
height: 0.72rem;
|
||||
border-radius: 999px;
|
||||
border: 2px solid #ffffff;
|
||||
box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.18);
|
||||
}
|
||||
|
||||
.qa-evidence-swatch-match-candidate {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.qa-evidence-swatch-match-reference {
|
||||
background: #0f766e;
|
||||
}
|
||||
|
||||
.qa-evidence-swatch-false-positive {
|
||||
background: #dc2626;
|
||||
}
|
||||
|
||||
.qa-evidence-swatch-false-negative {
|
||||
background: #d97706;
|
||||
}
|
||||
|
||||
.layer-provenance-rail > div,
|
||||
.feature-property-chip {
|
||||
min-width: 0;
|
||||
|
||||
@@ -600,6 +600,9 @@ export interface QaComparisonResult {
|
||||
iou_threshold: number
|
||||
unsupported_geometry: boolean
|
||||
unsupported_geometries: string[]
|
||||
match_evidence?: Record<string, unknown>[]
|
||||
false_positive_evidence?: Record<string, unknown>[]
|
||||
false_negative_evidence?: Record<string, unknown>[]
|
||||
generated_at: string
|
||||
quality_check_id?: string
|
||||
}
|
||||
@@ -640,6 +643,17 @@ export interface QualityCheckListResponse {
|
||||
offset: number
|
||||
}
|
||||
|
||||
export interface QualityEvidenceGeoJsonResponse {
|
||||
quality_check_id: string
|
||||
project_id: string
|
||||
candidate_dataset_id?: string | null
|
||||
reference_dataset_id: string
|
||||
analysis_run_id?: string | null
|
||||
feature_count: number
|
||||
warnings: string[]
|
||||
geojson: GeoJSON.FeatureCollection
|
||||
}
|
||||
|
||||
export type ExportKind = 'dataset' | 'detection_run' | 'segmentation_run' | 'vector_selection'
|
||||
|
||||
export interface ExportRead {
|
||||
|
||||
Reference in New Issue
Block a user