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
@@ -0,0 +1,64 @@
'use client'
import { useEffect, useState } from 'react'
export interface HazardRiskDetail {
hazard_id: string
name: string
category: string
zone: string
severity: number
frequency: number
probability: number
avoidance: number
risk_point: number
risk_low: number
risk_high: number
level: string
level_range: string
confidence: string
}
export interface RiskMatrixCell {
severity: number
probability: number
count: number
dominant_level: string
hazard_ids: string[]
}
export interface RiskMatrixData {
hazards: HazardRiskDetail[]
matrix: RiskMatrixCell[]
level_counts: Record<string, number>
total: number
high_confidence_pct: number
}
/** Loads the project-wide confidence-aware risk matrix (computed server-side). */
export function useRiskMatrix(projectId: string) {
const [data, setData] = useState<RiskMatrixData | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
let cancelled = false
async function load() {
setLoading(true)
try {
const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/risk-matrix`)
const json = res.ok ? ((await res.json()) as RiskMatrixData) : null
if (!cancelled) setData(json)
} catch (err) {
console.error('Failed to load risk matrix:', err)
} finally {
if (!cancelled) setLoading(false)
}
}
load()
return () => {
cancelled = true
}
}, [projectId])
return { data, loading }
}