feat(iace): benchmark system + erklaerteil + dedup-fix
Build + Deploy / build-admin-compliance (push) Successful in 2m7s
Build + Deploy / build-backend-compliance (push) Successful in 3m34s
Build + Deploy / build-ai-sdk (push) Successful in 1m6s
Build + Deploy / build-developer-portal (push) Successful in 1m7s
Build + Deploy / build-tts (push) Successful in 1m58s
Build + Deploy / build-document-crawler (push) Successful in 57s
Build + Deploy / build-dsms-gateway (push) Successful in 34s
Build + Deploy / build-dsms-node (push) Successful in 29s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 17s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m28s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 42s
CI / test-python-backend (push) Successful in 37s
CI / test-python-document-crawler (push) Successful in 27s
CI / test-python-dsms-gateway (push) Successful in 22s
CI / validate-canonical-controls (push) Successful in 15s
Build + Deploy / trigger-orca (push) Successful in 3m10s
Build + Deploy / build-admin-compliance (push) Successful in 2m7s
Build + Deploy / build-backend-compliance (push) Successful in 3m34s
Build + Deploy / build-ai-sdk (push) Successful in 1m6s
Build + Deploy / build-developer-portal (push) Successful in 1m7s
Build + Deploy / build-tts (push) Successful in 1m58s
Build + Deploy / build-document-crawler (push) Successful in 57s
Build + Deploy / build-dsms-gateway (push) Successful in 34s
Build + Deploy / build-dsms-node (push) Successful in 29s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 17s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m28s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 42s
CI / test-python-backend (push) Successful in 37s
CI / test-python-document-crawler (push) Successful in 27s
CI / test-python-dsms-gateway (push) Successful in 22s
CI / validate-canonical-controls (push) Successful in 15s
Build + Deploy / trigger-orca (push) Successful in 3m10s
- Erklaerteil-Template fuer Risikobeurteilungen (risk_assessment_template.go) in PDF-Export, Markdown-Export und Frontend ReportPrintView eingebaut - Ground Truth Benchmark-System: Datenmodell, Fuzzy-Matching-Engine, 3 API Endpoints (import-gt, benchmark, benchmark/summary) - Frontend Benchmark-Tab mit Score-Cards, Kategorie-Breakdown, Hazard-Vergleichstabelle (Zugeordnet/Fehlend/Extra), Business Impact - Erster Benchmark: 13.3% Coverage (Baseline) gegen 60 GT-Eintraege - Dedup-Fix: seenCat[cat] -> seenCatZone[cat+zone] erlaubt mehrere Gefaehrdungen pro Kategorie an verschiedenen Gefahrenstellen - Komponenten-spezifische Hazard-Namen und Zone-basierte Zuordnung Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { useBenchmark } from './_hooks/useBenchmark'
|
||||
import { GTImportForm } from './_components/GTImportForm'
|
||||
import { HazardComparisonTable } from './_components/HazardComparisonTable'
|
||||
import { CategoryBreakdown } from './_components/CategoryBreakdown'
|
||||
|
||||
export default function BenchmarkPage() {
|
||||
const { projectId } = useParams<{ projectId: string }>()
|
||||
const { result, gtLoaded, gtEntryCount, loading, error, importGT, runBenchmark } = useBenchmark(projectId)
|
||||
const [gtProjectId, setGtProjectId] = useState('')
|
||||
|
||||
const coveragePct = result ? Math.round(result.coverage_score * 100) : 0
|
||||
const measurePct = result ? Math.round(result.measure_coverage * 100) : 0
|
||||
|
||||
return (
|
||||
<div className="space-y-6 max-w-[1200px]">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="text-lg font-bold text-gray-900 dark:text-white">Ground Truth Benchmark</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
Vergleich der Engine-Ergebnisse mit einer professionellen Risikobeurteilung
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="px-4 py-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg text-sm text-red-600">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* GT Import or Cross-Project Reference */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<GTImportForm onImport={importGT} loading={loading} />
|
||||
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">Benchmark ausfuehren</h3>
|
||||
<p className="text-xs text-gray-500 mb-3">
|
||||
GT aus diesem Projekt verwenden, oder eine Projekt-ID mit importierter GT angeben.
|
||||
</p>
|
||||
|
||||
<div className="space-y-2">
|
||||
<input
|
||||
type="text"
|
||||
value={gtProjectId}
|
||||
onChange={(e) => setGtProjectId(e.target.value)}
|
||||
placeholder="GT-Projekt-ID (optional — leer = dieses Projekt)"
|
||||
className="w-full text-xs border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 bg-gray-50 dark:bg-gray-900"
|
||||
/>
|
||||
<button
|
||||
onClick={() => runBenchmark(gtProjectId || undefined)}
|
||||
disabled={loading}
|
||||
className="w-full px-4 py-2 text-sm font-medium bg-purple-600 hover:bg-purple-700 disabled:bg-gray-300 text-white rounded-md transition-colors"
|
||||
>
|
||||
{loading ? 'Vergleiche...' : 'Benchmark starten'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{gtLoaded && !result && (
|
||||
<div className="mt-3 px-3 py-2 bg-blue-50 dark:bg-blue-900/20 rounded text-xs text-blue-600">
|
||||
{gtEntryCount} GT-Eintraege geladen. Klicke "Benchmark starten".
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Results */}
|
||||
{result && (
|
||||
<>
|
||||
{/* Score Cards */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<ScoreCard
|
||||
label="Hazard Coverage"
|
||||
value={`${coveragePct}%`}
|
||||
sub={`${result.matched_pairs?.length || 0} / ${result.total_gt} erkannt`}
|
||||
color={coveragePct >= 80 ? 'green' : coveragePct >= 50 ? 'yellow' : 'red'}
|
||||
/>
|
||||
<ScoreCard
|
||||
label="Massnahmen-Coverage"
|
||||
value={`${measurePct}%`}
|
||||
sub="der zugeordneten Gefaehrdungen"
|
||||
color={measurePct >= 80 ? 'green' : measurePct >= 50 ? 'yellow' : 'red'}
|
||||
/>
|
||||
<ScoreCard
|
||||
label="GT Eintraege"
|
||||
value={String(result.total_gt)}
|
||||
sub="professionelle Beurteilung"
|
||||
color="gray"
|
||||
/>
|
||||
<ScoreCard
|
||||
label="Engine Eintraege"
|
||||
value={String(result.total_engine)}
|
||||
sub={`${result.extra_in_engine?.length || 0} zusaetzlich`}
|
||||
color="gray"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Category Breakdown */}
|
||||
<CategoryBreakdown breakdown={result.category_breakdown || []} />
|
||||
|
||||
{/* Hazard Comparison Table */}
|
||||
<HazardComparisonTable
|
||||
matched={result.matched_pairs || []}
|
||||
missing={result.missing_from_engine || []}
|
||||
extra={result.extra_in_engine || []}
|
||||
/>
|
||||
|
||||
{/* Business Impact */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">Business Impact</h3>
|
||||
<div className="grid grid-cols-3 gap-4 text-center">
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-gray-900 dark:text-white">2,5 Tage</div>
|
||||
<div className="text-xs text-gray-500">Manueller Aufwand</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-purple-600">
|
||||
{(coveragePct / 100 * 2.5).toFixed(1)} Tage
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">Eingespart bei {coveragePct}% Coverage</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-green-600">
|
||||
{Math.round(coveragePct / 100 * 2.5 * 8 * 100)} EUR
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">Einsparung (100 EUR/h)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ScoreCard({ label, value, sub, color }: {
|
||||
label: string; value: string; sub: string
|
||||
color: 'green' | 'yellow' | 'red' | 'gray'
|
||||
}) {
|
||||
const colors = {
|
||||
green: 'border-green-200 dark:border-green-800',
|
||||
yellow: 'border-yellow-200 dark:border-yellow-800',
|
||||
red: 'border-red-200 dark:border-red-800',
|
||||
gray: 'border-gray-200 dark:border-gray-700',
|
||||
}
|
||||
const textColors = {
|
||||
green: 'text-green-600', yellow: 'text-yellow-600',
|
||||
red: 'text-red-600', gray: 'text-gray-900 dark:text-white',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`bg-white dark:bg-gray-800 rounded-lg border-2 ${colors[color]} p-4 text-center`}>
|
||||
<div className={`text-2xl font-bold ${textColors[color]}`}>{value}</div>
|
||||
<div className="text-xs font-medium text-gray-700 dark:text-gray-300 mt-1">{label}</div>
|
||||
<div className="text-[10px] text-gray-400 mt-0.5">{sub}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user