8bb90d73e5
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>
161 lines
6.4 KiB
TypeScript
161 lines
6.4 KiB
TypeScript
'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>
|
|
)
|
|
}
|