feat(iace): risk as confidence range + label in benchmark tab
Report the tool's risk number as a plausible range with a confidence label instead of a false-precision point value (confidence-aware tonality — the assessment is confirmed by the DSB / safety expert). - risk_estimation.go: EstimateConfidence (hoch/mittel/niedrig from how the contact mode resolved), EstimateRiskRange (S±1 and aggregate L=F+W+P ±1, the empirically validated per-parameter accuracy), RiskLevelRange; share the riskBandLabel thresholds with EstimateRiskLevel. - risk_benchmark.go: RiskComparisonPair gains eng_risk_point/low/high + level + level_range + confidence; RiskAgreement gains high_confidence_pct. - RiskComparison.tsx: per-hazard range "low–high (level range)" + point, confidence chip, and an aggregate confidence line; types in useBenchmark.ts. - Unit tests for the range/confidence helpers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+58
-30
@@ -17,6 +17,13 @@ function ampelBand(band: string): Ampel {
|
||||
return 'green'
|
||||
}
|
||||
|
||||
// Tool confidence (how well-anchored the estimate is) → chip color.
|
||||
function ampelConfidence(c: string): Ampel {
|
||||
if (c === 'hoch') return 'green'
|
||||
if (c === 'mittel') return 'yellow'
|
||||
return 'red'
|
||||
}
|
||||
|
||||
const cellColor: Record<Ampel, string> = {
|
||||
red: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300',
|
||||
yellow: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300',
|
||||
@@ -47,18 +54,28 @@ export function RiskComparison({ pairs, agreement }: { pairs?: RiskComparisonPai
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300">Risikozahlen-Vergleich (Fachmann vs. Tool)</h3>
|
||||
<p className="text-xs text-gray-500 mt-0.5">
|
||||
R = S × (F + W + P), Ampel wie in der Excel. Fine-Kinney (P×E×C) als zweite, US-anerkannte Bewertung.
|
||||
R = S × (F + W + P), Ampel wie in der Excel. Das Tool nennt einen <strong>Schätzbereich</strong>{' '}
|
||||
(nicht einen exakten Punktwert) plus Konfidenz — die endgültige Bewertung trifft der/die Sachverständige.
|
||||
Fine-Kinney (P×E×C) als zweite, US-anerkannte Bewertung.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{agreement && agreement.n > 0 && (
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-3">
|
||||
<Stat label="Schwere S ±1" pct={agreement.severity_within1} />
|
||||
<Stat label="Haeufigkeit F ±1" pct={agreement.frequency_within1} />
|
||||
<Stat label="Wahrsch. W ±1" pct={agreement.probability_within1} />
|
||||
<Stat label="Vermeidb. P ±1" pct={agreement.avoidance_within1} />
|
||||
<Stat label="Ranking (FK)" pct={agreement.rank_concordance} />
|
||||
</div>
|
||||
<>
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-3">
|
||||
<Stat label="Schwere S ±1" pct={agreement.severity_within1} />
|
||||
<Stat label="Haeufigkeit F ±1" pct={agreement.frequency_within1} />
|
||||
<Stat label="Wahrsch. W ±1" pct={agreement.probability_within1} />
|
||||
<Stat label="Vermeidb. P ±1" pct={agreement.avoidance_within1} />
|
||||
<Stat label="Ranking (FK)" pct={agreement.rank_concordance} />
|
||||
</div>
|
||||
{typeof agreement.high_confidence_pct === 'number' && (
|
||||
<p className="text-xs text-gray-500">
|
||||
Tool-Konfidenz: <strong>{Math.round(agreement.high_confidence_pct)}%</strong> der erkannten
|
||||
Gefaehrdungen mit hoher Konfidenz (Verletzungsmechanismus eindeutig aus dem Szenario ableitbar).
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
@@ -67,31 +84,42 @@ export function RiskComparison({ pairs, agreement }: { pairs?: RiskComparisonPai
|
||||
<tr className="text-gray-500 border-b border-gray-200 dark:border-gray-700">
|
||||
<th className="text-left py-1.5 px-2">Gefaehrdung</th>
|
||||
<th className="px-1 text-center" colSpan={5}>Fachmann · S F W P <strong>R</strong></th>
|
||||
<th className="px-1 text-center border-l border-gray-200 dark:border-gray-700" colSpan={5}>Tool · S F W P <strong>R</strong> / FK</th>
|
||||
<th className="px-1 text-center border-l border-gray-200 dark:border-gray-700" colSpan={4}>Tool · S F W P</th>
|
||||
<th className="px-1 text-center border-l border-gray-200 dark:border-gray-700">Risiko (Schätzbereich) / FK</th>
|
||||
<th className="px-1 text-center border-l border-gray-200 dark:border-gray-700">Konfidenz</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{pairs.map((p, i) => {
|
||||
const engR = p.eng_severity * (p.eng_frequency + p.eng_probability + p.eng_avoidance)
|
||||
return (
|
||||
<tr key={i} className="border-b border-gray-100 dark:border-gray-700/50">
|
||||
<td className="py-1 px-2 text-gray-700 dark:text-gray-300">{p.hazard_name || '—'}</td>
|
||||
<td className="text-center text-gray-500">{p.gt_severity}</td>
|
||||
<td className="text-center text-gray-500">{p.gt_frequency}</td>
|
||||
<td className="text-center text-gray-500">{p.gt_probability}</td>
|
||||
<td className="text-center text-gray-500">{p.gt_avoidance}</td>
|
||||
<td className={`text-center font-bold rounded ${cellColor[ampelEN(p.gt_risk)]}`}>{p.gt_risk}</td>
|
||||
<td className="text-center text-gray-500 border-l border-gray-200 dark:border-gray-700">{p.eng_severity}</td>
|
||||
<td className="text-center text-gray-500">{p.eng_frequency}</td>
|
||||
<td className="text-center text-gray-500">{p.eng_probability}</td>
|
||||
<td className="text-center text-gray-500">{p.eng_avoidance}</td>
|
||||
<td className="text-center">
|
||||
<span className={`inline-block font-bold rounded px-1.5 ${cellColor[ampelEN(engR)]}`}>{engR}</span>
|
||||
<span className={`ml-1 inline-block rounded px-1 ${cellColor[ampelBand(p.fk_band)]}`} title={`Fine-Kinney ${p.fk_band}`}>FK {Math.round(p.fk_score)}</span>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
{pairs.map((p, i) => (
|
||||
<tr key={i} className="border-b border-gray-100 dark:border-gray-700/50">
|
||||
<td className="py-1 px-2 text-gray-700 dark:text-gray-300">{p.hazard_name || '—'}</td>
|
||||
<td className="text-center text-gray-500">{p.gt_severity}</td>
|
||||
<td className="text-center text-gray-500">{p.gt_frequency}</td>
|
||||
<td className="text-center text-gray-500">{p.gt_probability}</td>
|
||||
<td className="text-center text-gray-500">{p.gt_avoidance}</td>
|
||||
<td className={`text-center font-bold rounded ${cellColor[ampelEN(p.gt_risk)]}`}>{p.gt_risk}</td>
|
||||
<td className="text-center text-gray-500 border-l border-gray-200 dark:border-gray-700">{p.eng_severity}</td>
|
||||
<td className="text-center text-gray-500">{p.eng_frequency}</td>
|
||||
<td className="text-center text-gray-500">{p.eng_probability}</td>
|
||||
<td className="text-center text-gray-500">{p.eng_avoidance}</td>
|
||||
<td className="text-center border-l border-gray-200 dark:border-gray-700 whitespace-nowrap">
|
||||
<span
|
||||
className={`inline-block font-bold rounded px-1.5 ${cellColor[ampelEN(p.eng_risk_point)]}`}
|
||||
title={`Schätzbereich R ${p.eng_risk_low}–${p.eng_risk_high} (${p.eng_risk_level_range})`}
|
||||
>
|
||||
{p.eng_risk_low}–{p.eng_risk_high}
|
||||
</span>
|
||||
<span className="ml-1 text-[10px] text-gray-400">≈{p.eng_risk_point}</span>
|
||||
<span className={`ml-1 inline-block rounded px-1 ${cellColor[ampelBand(p.fk_band)]}`} title={`Fine-Kinney ${p.fk_band}`}>FK {Math.round(p.fk_score)}</span>
|
||||
<div className="text-[9px] text-gray-400 mt-0.5">{p.eng_risk_level_range}</div>
|
||||
</td>
|
||||
<td className="text-center border-l border-gray-200 dark:border-gray-700">
|
||||
<span className={`inline-block rounded px-1.5 py-0.5 text-[10px] font-medium ${cellColor[ampelConfidence(p.confidence)]}`}>
|
||||
{p.confidence}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -53,6 +53,9 @@ export interface RiskComparisonPair {
|
||||
gt_severity: number; gt_frequency: number; gt_probability: number; gt_avoidance: number; gt_risk: number
|
||||
eng_severity: number; eng_frequency: number; eng_probability: number; eng_avoidance: number
|
||||
fk_score: number; fk_band: string
|
||||
eng_risk_point: number; eng_risk_low: number; eng_risk_high: number
|
||||
eng_risk_level: string; eng_risk_level_range: string
|
||||
confidence: string // hoch | mittel | niedrig
|
||||
}
|
||||
|
||||
export interface RiskAgreement {
|
||||
@@ -60,6 +63,7 @@ export interface RiskAgreement {
|
||||
severity_within1: number; frequency_within1: number
|
||||
probability_within1: number; avoidance_within1: number
|
||||
rank_concordance: number
|
||||
high_confidence_pct: number
|
||||
}
|
||||
|
||||
export interface BenchmarkResult {
|
||||
|
||||
Reference in New Issue
Block a user