feat(iace): benchmark distance panel (Thema 1)
Surface result.distances in the benchmark module: a DistanceComparison panel showing agreement %, covered values (green), GT-only gaps (amber) and engine-only extras — mirroring the RiskComparison panel. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+69
@@ -0,0 +1,69 @@
|
||||
'use client'
|
||||
|
||||
import type { DistanceComparison as DistanceComparisonData, DistanceToken } from '../_hooks/useBenchmark'
|
||||
|
||||
function fmt(t: DistanceToken): string {
|
||||
const v = Number.isInteger(t.value) ? t.value.toString() : t.value.toString().replace('.', ',')
|
||||
return `${v} ${t.unit}`
|
||||
}
|
||||
|
||||
function TokenList({ tokens, tone }: { tokens: DistanceToken[]; tone: 'ok' | 'gap' | 'extra' }) {
|
||||
if (!tokens.length) return <span className="text-xs text-gray-400">—</span>
|
||||
const cls =
|
||||
tone === 'ok'
|
||||
? 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300'
|
||||
: tone === 'gap'
|
||||
? 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-400'
|
||||
return (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{tokens.map((t, i) => (
|
||||
<span key={i} className={`inline-block rounded px-1.5 py-0.5 text-[11px] tabular-nums ${cls}`}>
|
||||
{fmt(t)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Distance/speed dimension comparison: do the engine's mm/mm-s values match the
|
||||
* professional's (GT)? Confidence-aware tonality — green = covered, amber = gap.
|
||||
*/
|
||||
export function DistanceComparison({ data }: { data?: DistanceComparisonData }) {
|
||||
if (!data || data.gt_count === 0) return null
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4 space-y-3">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300">Abstands-/Geschwindigkeits-Vergleich (Fachmann vs. Tool)</h3>
|
||||
<p className="text-xs text-gray-500 mt-0.5">
|
||||
Deckt das Tool die konkreten mm-/mm-s-Werte des Fachmanns ab? Deterministischer Abgleich der
|
||||
Maße aus den GT-Maßnahmen gegen die vom Tool vorgeschlagenen Maßnahmen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-baseline gap-3">
|
||||
<span className="text-2xl font-bold text-purple-600 tabular-nums">{Math.round(data.agreement_pct)}%</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
{data.matched_count} von {data.gt_count} Fachmann-Maßen abgedeckt
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<dl className="grid grid-cols-1 sm:grid-cols-3 gap-3 text-xs">
|
||||
<div>
|
||||
<dt className="text-[10px] uppercase tracking-wide text-gray-400 mb-1">Abgedeckt</dt>
|
||||
<dd><TokenList tokens={data.matched} tone="ok" /></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-[10px] uppercase tracking-wide text-gray-400 mb-1">Lücken (nur Fachmann)</dt>
|
||||
<dd><TokenList tokens={data.gt_only} tone="gap" /></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-[10px] uppercase tracking-wide text-gray-400 mb-1">Nur Tool</dt>
|
||||
<dd><TokenList tokens={data.engine_only} tone="extra" /></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -66,6 +66,21 @@ export interface RiskAgreement {
|
||||
high_confidence_pct: number
|
||||
}
|
||||
|
||||
export interface DistanceToken {
|
||||
value: number
|
||||
unit: string // "mm" | "mm/s"
|
||||
raw: string
|
||||
}
|
||||
|
||||
export interface DistanceComparison {
|
||||
gt_count: number
|
||||
matched_count: number
|
||||
agreement_pct: number
|
||||
matched: DistanceToken[]
|
||||
gt_only: DistanceToken[]
|
||||
engine_only: DistanceToken[]
|
||||
}
|
||||
|
||||
export interface BenchmarkResult {
|
||||
coverage_score: number
|
||||
measure_coverage: number
|
||||
@@ -78,6 +93,7 @@ export interface BenchmarkResult {
|
||||
risk_rank_pairs: { gt_rank: number; engine_rank: number; hazard_name: string; gt_risk_score: number }[]
|
||||
risk_comparison?: RiskComparisonPair[]
|
||||
risk_agreement?: RiskAgreement
|
||||
distances?: DistanceComparison
|
||||
}
|
||||
|
||||
interface UseBenchmarkReturn {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { GTImportForm } from './_components/GTImportForm'
|
||||
import { HazardComparisonTable } from './_components/HazardComparisonTable'
|
||||
import { CategoryBreakdown } from './_components/CategoryBreakdown'
|
||||
import { RiskComparison } from './_components/RiskComparison'
|
||||
import { DistanceComparison } from './_components/DistanceComparison'
|
||||
|
||||
export default function BenchmarkPage() {
|
||||
const { projectId } = useParams<{ projectId: string }>()
|
||||
@@ -106,6 +107,9 @@ export default function BenchmarkPage() {
|
||||
{/* Risk-number comparison (tool vs professional) with traffic lights */}
|
||||
<RiskComparison pairs={result.risk_comparison} agreement={result.risk_agreement} />
|
||||
|
||||
{/* Distance/speed dimension comparison */}
|
||||
<DistanceComparison data={result.distances} />
|
||||
|
||||
{/* Hazard Comparison Table */}
|
||||
<HazardComparisonTable
|
||||
matched={result.matched_pairs || []}
|
||||
|
||||
Reference in New Issue
Block a user