Persist QA feature evidence
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:57:50 +02:00
parent 49e58cd1c7
commit 368c1ad738
15 changed files with 425 additions and 81 deletions
@@ -46,6 +46,20 @@ function metricByKey(check: QualityCheckRead | null, metricKey: string): MetricR
return check?.metrics.find((metric) => metric.metric_key === metricKey)
}
function findingEvidenceList(check: QualityCheckRead | null, key: string): Record<string, unknown>[] {
const value = check?.findings_json?.[key]
return Array.isArray(value) ? value.filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === 'object' && !Array.isArray(item)) : []
}
function evidenceLabel(item: Record<string, unknown>): string {
const candidate = item['candidate_feature_id']
const reference = item['reference_feature_id']
const iou = item['iou']
const pairLabel = [candidate ? `Candidate ${candidate}` : null, reference ? `Reference ${reference}` : null].filter(Boolean).join(' / ')
const iouValue = Number(iou)
return iou === null || iou === undefined || !Number.isFinite(iouValue) ? pairLabel || JSON.stringify(item) : `${pairLabel || 'Match'} / IoU ${iouValue.toFixed(3)}`
}
function formatQualityTimestamp(value?: string | null): string {
return value || 'n/a'
}
@@ -108,6 +122,9 @@ export function QualityResultsPanel({
const selectedReferenceName = selectedQualityCheck
? datasetNameById.get(selectedQualityCheck.reference_dataset_id) ?? selectedQualityCheck.reference_dataset_id
: 'n/a'
const selectedMatchEvidence = findingEvidenceList(selectedQualityCheck, 'match_evidence')
const selectedFalsePositiveEvidence = findingEvidenceList(selectedQualityCheck, 'false_positive_evidence')
const selectedFalseNegativeEvidence = findingEvidenceList(selectedQualityCheck, 'false_negative_evidence')
const qualityStatuses = useMemo(() => Array.from(new Set(qualityChecks.map((check) => check.status))).sort(), [qualityChecks])
const qualityTypes = useMemo(() => Array.from(new Set(qualityChecks.map((check) => check.check_type))).sort(), [qualityChecks])
const filteredQualityChecks = useMemo(
@@ -239,6 +256,44 @@ export function QualityResultsPanel({
<p>Use the candidate and reference datasets as map layers for spatial review.</p>
</div>
</div>
<div className="quality-feature-evidence-grid" aria-label="Feature-level QA/QC evidence">
<div>
<span>Matched feature ids</span>
{selectedMatchEvidence.length > 0 ? (
<ul>
{selectedMatchEvidence.slice(0, 6).map((item, index) => (
<li key={`${evidenceLabel(item)}-${index}`}>{evidenceLabel(item)}</li>
))}
</ul>
) : (
<p>No matched feature ids persisted for this check.</p>
)}
</div>
<div>
<span>False positive feature ids</span>
{selectedFalsePositiveEvidence.length > 0 ? (
<ul>
{selectedFalsePositiveEvidence.slice(0, 6).map((item, index) => (
<li key={`${evidenceLabel(item)}-${index}`}>{evidenceLabel(item)}</li>
))}
</ul>
) : (
<p>No false-positive feature ids persisted for this check.</p>
)}
</div>
<div>
<span>False negative feature ids</span>
{selectedFalseNegativeEvidence.length > 0 ? (
<ul>
{selectedFalseNegativeEvidence.slice(0, 6).map((item, index) => (
<li key={`${evidenceLabel(item)}-${index}`}>{evidenceLabel(item)}</li>
))}
</ul>
) : (
<p>No false-negative feature ids persisted for this check.</p>
)}
</div>
</div>
<div className="quality-provenance-grid">
<div>
<span>Parameters</span>