feat(iace): 50% display threshold — weak matches shown as separate

Matches below 50% are now split:
- GT entries → "Fehlend" tab (not matched by engine)
- Engine entries → "Engine Findings" tab (additional findings)
Only matches >= 50% shown in "Zugeordnet" tab.

Coverage score now counts only real matches (>= 50%).
"Extra" tab renamed to "Engine Findings" for clarity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-15 10:33:29 +02:00
parent 3469105d18
commit 3b7ab4cbd7
2 changed files with 20 additions and 11 deletions
@@ -14,14 +14,21 @@ type TabType = 'matched' | 'missing' | 'extra'
export function HazardComparisonTable({ matched, missing, extra }: Props) { export function HazardComparisonTable({ matched, missing, extra }: Props) {
const [tab, setTab] = useState<TabType>('matched') const [tab, setTab] = useState<TabType>('matched')
// Compute quality levels for matched pairs // Split matches: >= 50% are real matches, < 50% are weak (shown separately)
const greenCount = matched.filter(p => p.match_score >= 0.7).length const realMatched = matched.filter(p => p.match_score >= 0.5)
const yellowCount = matched.filter(p => p.match_score >= 0.4 && p.match_score < 0.7).length const weakMatched = matched.filter(p => p.match_score < 0.5)
// Weak matches: GT entries go to "missing", engine entries go to "extra"
const allMissing = [...missing, ...weakMatched.map(w => w.gt_entry)]
const allExtra = [...extra, ...weakMatched.map(w => w.engine_hazard)]
const greenCount = realMatched.filter(p => p.match_score >= 0.7).length
const yellowCount = realMatched.filter(p => p.match_score >= 0.5 && p.match_score < 0.7).length
const tabs: { id: TabType; label: string; count: number; color: string }[] = [ const tabs: { id: TabType; label: string; count: number; color: string }[] = [
{ id: 'matched', label: `Zugeordnet (${greenCount} exakt, ${yellowCount} aehnlich)`, count: matched.length, color: 'text-green-600' }, { id: 'matched', label: `Zugeordnet (${greenCount} exakt, ${yellowCount} aehnlich)`, count: realMatched.length, color: 'text-green-600' },
{ id: 'missing', label: 'Fehlend', count: missing.length, color: 'text-red-600' }, { id: 'missing', label: 'Fehlend', count: allMissing.length, color: 'text-red-600' },
{ id: 'extra', label: 'Zusaetzlich', count: extra.length, color: 'text-gray-500' }, { id: 'extra', label: 'Engine Findings', count: allExtra.length, color: 'text-blue-500' },
] ]
return ( return (
@@ -44,9 +51,9 @@ export function HazardComparisonTable({ matched, missing, extra }: Props) {
</div> </div>
<div className="overflow-x-auto"> <div className="overflow-x-auto">
{tab === 'matched' && <MatchedTable pairs={matched} />} {tab === 'matched' && <MatchedTable pairs={realMatched} />}
{tab === 'missing' && <MissingTable entries={missing} />} {tab === 'missing' && <MissingTable entries={allMissing} />}
{tab === 'extra' && <ExtraTable entries={extra} />} {tab === 'extra' && <ExtraTable entries={allExtra} />}
</div> </div>
</div> </div>
) )
@@ -12,7 +12,9 @@ export default function BenchmarkPage() {
const { result, gtLoaded, gtEntryCount, loading, error, importGT, runBenchmark } = useBenchmark(projectId) const { result, gtLoaded, gtEntryCount, loading, error, importGT, runBenchmark } = useBenchmark(projectId)
const [gtProjectId, setGtProjectId] = useState('') const [gtProjectId, setGtProjectId] = useState('')
const coveragePct = result ? Math.round(result.coverage_score * 100) : 0 // Only count matches >= 50% as real coverage
const realMatchCount = result ? (result.matched_pairs?.filter(m => m.match_score >= 0.5).length || 0) : 0
const coveragePct = result ? Math.round(realMatchCount * 100 / Math.max(result.total_gt, 1)) : 0
const measurePct = result ? Math.round(result.measure_coverage * 100) : 0 const measurePct = result ? Math.round(result.measure_coverage * 100) : 0
return ( return (
@@ -74,7 +76,7 @@ export default function BenchmarkPage() {
<ScoreCard <ScoreCard
label="Hazard Coverage" label="Hazard Coverage"
value={`${coveragePct}%`} value={`${coveragePct}%`}
sub={`${result.matched_pairs?.length || 0} / ${result.total_gt} erkannt`} sub={`${realMatchCount} / ${result.total_gt} erkannt (>= 50% Match)`}
color={coveragePct >= 80 ? 'green' : coveragePct >= 50 ? 'yellow' : 'red'} color={coveragePct >= 80 ? 'green' : coveragePct >= 50 ? 'yellow' : 'red'}
/> />
<ScoreCard <ScoreCard