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:
@@ -203,16 +203,92 @@ func EstimateRiskLevel(s, f, w, p int) (int, string) {
|
||||
s = 1
|
||||
}
|
||||
idx := s * (f + w + p)
|
||||
return idx, riskBandLabel(idx)
|
||||
}
|
||||
|
||||
// riskBandLabel maps a risk index (3..75) to BreakPilot's German level band.
|
||||
// Single source of truth for the thresholds, shared by EstimateRiskLevel and
|
||||
// the confidence-range derivation.
|
||||
func riskBandLabel(idx int) string {
|
||||
switch {
|
||||
case idx >= 45:
|
||||
return idx, "kritisch"
|
||||
return "kritisch"
|
||||
case idx >= 30:
|
||||
return idx, "hoch"
|
||||
return "hoch"
|
||||
case idx >= 18:
|
||||
return idx, "mittel"
|
||||
return "mittel"
|
||||
case idx >= 9:
|
||||
return idx, "gering"
|
||||
return "gering"
|
||||
default:
|
||||
return idx, "vernachlaessigbar"
|
||||
return "vernachlaessigbar"
|
||||
}
|
||||
}
|
||||
|
||||
func clampRisk1to5(x int) int {
|
||||
if x < 1 {
|
||||
return 1
|
||||
}
|
||||
if x > 5 {
|
||||
return 5
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// EstimateConfidence reports how well-anchored the tool's risk parameters are,
|
||||
// from HOW the injury mechanism (contact mode) was resolved: an explicit
|
||||
// scenario keyword → "hoch" (strong kinematic signal), a category fallback →
|
||||
// "mittel", nothing → "niedrig" (parameters fell back to neutral). This is an
|
||||
// honest signal that the point estimate is a heuristic, not a guarantee — the
|
||||
// final assessment stays with the DSB / safety expert.
|
||||
func EstimateConfidence(cats []string, scenario string) string {
|
||||
text := normalizeDE(scenario)
|
||||
for _, e := range contactModeKeywords {
|
||||
for _, kw := range e.keywords {
|
||||
if strings.Contains(text, kw) {
|
||||
return "hoch"
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, c := range cats {
|
||||
if _, ok := categoryDefaultMode[c]; ok {
|
||||
return "mittel"
|
||||
}
|
||||
}
|
||||
return "niedrig"
|
||||
}
|
||||
|
||||
// EstimateRiskRange returns the point risk index plus a plausible low/high band.
|
||||
// The band shifts severity S by ±1 and the aggregate likelihood L = F+W+P by ±1
|
||||
// (each within its domain). We move L as a whole rather than each of F/W/P
|
||||
// independently because the validation shows the per-parameter errors largely
|
||||
// cancel in the sum (W is within ±1 of the GT ~100% of the time). The result
|
||||
// communicates that the risk number is an ESTIMATE with uncertainty rather than
|
||||
// a false-precision point value — aligned with the confidence-aware tonality.
|
||||
func EstimateRiskRange(s, f, w, p int) (low, point, high int) {
|
||||
s = clampRisk1to5(s)
|
||||
l := clampRisk1to5(f) + clampRisk1to5(w) + clampRisk1to5(p) // 3..15
|
||||
clampL := func(x int) int {
|
||||
if x < 3 {
|
||||
return 3
|
||||
}
|
||||
if x > 15 {
|
||||
return 15
|
||||
}
|
||||
return x
|
||||
}
|
||||
point = s * l
|
||||
low = clampRisk1to5(s-1) * clampL(l-1)
|
||||
high = clampRisk1to5(s+1) * clampL(l+1)
|
||||
return low, point, high
|
||||
}
|
||||
|
||||
// RiskLevelRange returns the German level band for the point plus a combined
|
||||
// "low–high" range label (single label when low and high fall in the same band).
|
||||
func RiskLevelRange(low, point, high int) (level, levelRange string) {
|
||||
level = riskBandLabel(point)
|
||||
ll, lh := riskBandLabel(low), riskBandLabel(high)
|
||||
if ll == lh {
|
||||
return level, ll
|
||||
}
|
||||
return level, ll + "–" + lh // en dash
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user