Persist QA feature evidence
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user