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>
+16 -1
View File
@@ -1618,6 +1618,7 @@ button.entity-card {
.quality-drilldown-grid,
.quality-evidence-token-grid,
.quality-feature-evidence-grid,
.quality-provenance-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
@@ -1641,6 +1642,7 @@ button.entity-card {
.quality-drilldown-grid > div,
.quality-evidence-token-grid > div,
.quality-feature-evidence-grid > div,
.quality-provenance-grid > div {
min-width: 0;
border: 1px solid var(--line);
@@ -1663,6 +1665,7 @@ button.entity-card {
.quality-handoff-grid span,
.quality-drilldown-grid span,
.quality-evidence-token-grid span,
.quality-feature-evidence-grid span,
.quality-provenance-grid span,
.quality-score-row span,
.latest-export-card span,
@@ -1703,7 +1706,8 @@ button.entity-card {
}
.quality-drilldown-grid p,
.quality-evidence-token-grid p {
.quality-evidence-token-grid p,
.quality-feature-evidence-grid p {
margin: 0.24rem 0 0;
color: var(--muted);
font-size: 0.8rem;
@@ -1711,6 +1715,17 @@ button.entity-card {
overflow-wrap: anywhere;
}
.quality-feature-evidence-grid ul {
display: grid;
gap: 0.28rem;
margin: 0.38rem 0 0;
padding-left: 1.1rem;
color: var(--text);
font-size: 0.78rem;
line-height: 1.35;
overflow-wrap: anywhere;
}
.quality-provenance-pre {
max-height: 11rem;
margin: 0.38rem 0 0;