feat(iace): project-wide risk matrix (Severity × Probability)

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>
This commit is contained in:
Benjamin Admin
2026-06-11 08:54:47 +02:00
parent 901de1ca97
commit 577ceae4e6
7 changed files with 383 additions and 0 deletions
@@ -28,3 +28,22 @@ func (h *IACEHandler) GetRiskSuggestion(c *gin.Context) {
}
c.JSON(http.StatusOK, iace.BuildRiskSuggestion(hz))
}
// GetRiskMatrix handles GET /projects/:id/risk-matrix.
// Project-wide confidence-aware risk view computed on read from each hazard (no
// persistence): per-hazard risk list + a Severity×Probability aggregation grid.
// Uses the same model as the GT benchmark, so matrix numbers match the
// comparison. Lets a customer see risk for EVERY project, not only GT ones.
func (h *IACEHandler) GetRiskMatrix(c *gin.Context) {
projectID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"})
return
}
hazards, err := h.store.ListHazards(c.Request.Context(), projectID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, iace.BuildRiskMatrix(hazards))
}