577ceae4e6
Adds GET /projects/:id/risk-matrix — a confidence-aware risk view computed on read from each hazard's category/scenario/lifecycle using the SAME model as the GT benchmark (no persistence, so it never goes stale against the model; the hand-defaulted iace_hazards risk columns stay untouched). - risk_matrix.go: EstimateHazardRisk (single source of truth for S/F/W/P + range + level + confidence) and BuildRiskMatrix (per-hazard list + a 5×5 Severity×Probability aggregation grid with dominant level per cell). - Frontend: RiskMatrix grid in the Risikobewertung tab (muted colours per the confidence-aware tonality), level counts + tool-confidence summary, fed by useRiskMatrix. Shows risk for EVERY project, not only GT ones. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
55 lines
1.9 KiB
Go
55 lines
1.9 KiB
Go
package iace
|
||
|
||
import (
|
||
"testing"
|
||
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
func TestEstimateHazardRisk_Ordered(t *testing.T) {
|
||
r := EstimateHazardRisk([]string{"electrical_hazard"}, "Elektrischer Schlag am Gehaeuse", []string{"normal_operation"})
|
||
if r.Severity < 1 || r.Severity > 5 || r.Probability < 1 || r.Avoidance < 1 {
|
||
t.Fatalf("params out of range: %+v", r)
|
||
}
|
||
if r.RiskLow > r.RiskPoint || r.RiskPoint > r.RiskHigh {
|
||
t.Errorf("range not ordered: low=%d point=%d high=%d", r.RiskLow, r.RiskPoint, r.RiskHigh)
|
||
}
|
||
if r.Confidence != "hoch" { // "elektrisch" keyword → clear contact mode
|
||
t.Errorf("expected confidence hoch, got %q", r.Confidence)
|
||
}
|
||
}
|
||
|
||
func TestBuildRiskMatrix(t *testing.T) {
|
||
hazards := []Hazard{
|
||
{ID: uuid.New(), Name: "Elektrischer Schlag", Category: "electrical_hazard", Scenario: "Stromschlag am Gehaeuse", LifecyclePhase: "normal_operation"},
|
||
{ID: uuid.New(), Name: "Elektrischer Schlag 2", Category: "electrical_hazard", Scenario: "Stromschlag an Klemme", LifecyclePhase: "normal_operation"},
|
||
{ID: uuid.New(), Name: "Quetschen", Category: "mechanical_hazard", Scenario: "Quetschen der Hand", LifecyclePhase: "maintenance"},
|
||
}
|
||
m := BuildRiskMatrix(hazards)
|
||
if m.Total != 3 || len(m.Hazards) != 3 {
|
||
t.Fatalf("expected 3 hazards, got total=%d hazards=%d", m.Total, len(m.Hazards))
|
||
}
|
||
// The two identical electrical hazards land in the same Severity×Probability cell.
|
||
var sum int
|
||
for _, c := range m.Matrix {
|
||
sum += c.Count
|
||
if c.Severity < 1 || c.Probability < 1 {
|
||
t.Errorf("cell has invalid coords: %+v", c)
|
||
}
|
||
if c.DominantLevel == "" {
|
||
t.Errorf("cell missing dominant level: %+v", c)
|
||
}
|
||
}
|
||
if sum != 3 {
|
||
t.Errorf("matrix cell counts sum to %d, want 3", sum)
|
||
}
|
||
// Level counts must also total the hazard count.
|
||
lc := 0
|
||
for _, n := range m.LevelCounts {
|
||
lc += n
|
||
}
|
||
if lc != 3 {
|
||
t.Errorf("level counts sum to %d, want 3", lc)
|
||
}
|
||
}
|