diff --git a/admin-compliance/app/sdk/iace/[projectId]/risikobewertung/_components/RiskModelCard.tsx b/admin-compliance/app/sdk/iace/[projectId]/risikobewertung/_components/RiskModelCard.tsx
new file mode 100644
index 00000000..ba331a89
--- /dev/null
+++ b/admin-compliance/app/sdk/iace/[projectId]/risikobewertung/_components/RiskModelCard.tsx
@@ -0,0 +1,160 @@
+'use client'
+
+import { useEffect, useState } from 'react'
+import type { HazardLite, RiskSuggestion } from '../_hooks/useRiskAssessment'
+
+function enLevel(idx: number): string {
+ if (idx >= 45) return 'kritisch'
+ if (idx >= 30) return 'hoch'
+ if (idx >= 18) return 'mittel'
+ if (idx >= 9) return 'gering'
+ return 'vernachlaessigbar'
+}
+
+function fkBand(score: number): string {
+ if (score > 400) return 'sehr hoch'
+ if (score > 200) return 'hoch'
+ if (score > 70) return 'wesentlich'
+ if (score > 20) return 'moeglich'
+ return 'gering'
+}
+
+function badgeColor(label: string): string {
+ switch (label) {
+ case 'kritisch':
+ case 'sehr hoch':
+ return 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300'
+ case 'hoch':
+ return 'bg-orange-100 text-orange-700 dark:bg-orange-900/40 dark:text-orange-300'
+ case 'mittel':
+ case 'wesentlich':
+ return 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300'
+ case 'gering':
+ case 'moeglich':
+ return 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300'
+ default:
+ return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300'
+ }
+}
+
+function Field({
+ label,
+ value,
+ step,
+ justification,
+ onChange,
+}: {
+ label: string
+ value: number
+ step?: number
+ justification: string
+ onChange: (v: number) => void
+}) {
+ return (
+
+
+
{label}
+
+ {justification}
+
+
+
onChange(parseFloat(e.target.value) || 0)}
+ className="w-16 px-2 py-1 text-sm rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-right"
+ />
+
+ )
+}
+
+function Panel({
+ title,
+ formula,
+ score,
+ badge,
+ children,
+}: {
+ title: string
+ formula: string
+ score: number
+ badge: string
+ children: React.ReactNode
+}) {
+ return (
+
+
+
{children}
+
+ {formula}
+
+ R = {Number.isInteger(score) ? score : score.toFixed(1)}
+
+
+
+ )
+}
+
+export function RiskModelCard({
+ hazard,
+ suggestion,
+}: {
+ hazard: HazardLite
+ suggestion?: RiskSuggestion
+}) {
+ const [en, setEn] = useState({ s: 0, f: 0, w: 0, p: 0 })
+ const [fk, setFk] = useState({ p: 0, e: 0, c: 0 })
+
+ useEffect(() => {
+ if (!suggestion) return
+ setEn({
+ s: suggestion.en62061.severity.value,
+ f: suggestion.en62061.frequency.value,
+ w: suggestion.en62061.probability.value,
+ p: suggestion.en62061.avoidance.value,
+ })
+ setFk({
+ p: suggestion.fine_kinney.probability.value,
+ e: suggestion.fine_kinney.exposure.value,
+ c: suggestion.fine_kinney.consequence.value,
+ })
+ }, [suggestion])
+
+ if (!suggestion) {
+ return (
+
+ {hazard.name} — keine Bewertung verfuegbar
+
+ )
+ }
+
+ const enScore = en.s * (en.f + en.w + en.p)
+ const fkScore = fk.p * fk.e * fk.c
+
+ return (
+
+
+
{hazard.name}
+
Kontaktart: {suggestion.contact_mode}
+
+
+
+ setEn({ ...en, s: v })} />
+ setEn({ ...en, f: v })} />
+ setEn({ ...en, w: v })} />
+ setEn({ ...en, p: v })} />
+
+
+ setFk({ ...fk, p: v })} />
+ setFk({ ...fk, e: v })} />
+ setFk({ ...fk, c: v })} />
+
+
+
{suggestion.note}
+
+ )
+}
diff --git a/admin-compliance/app/sdk/iace/[projectId]/risikobewertung/_hooks/useRiskAssessment.ts b/admin-compliance/app/sdk/iace/[projectId]/risikobewertung/_hooks/useRiskAssessment.ts
new file mode 100644
index 00000000..b9e323e4
--- /dev/null
+++ b/admin-compliance/app/sdk/iace/[projectId]/risikobewertung/_hooks/useRiskAssessment.ts
@@ -0,0 +1,85 @@
+'use client'
+
+import { useEffect, useState } from 'react'
+
+export interface SuggestedValue {
+ value: number
+ justification: string
+}
+
+export interface RiskSuggestion {
+ hazard_id: string
+ contact_mode: string
+ en62061: {
+ severity: SuggestedValue
+ frequency: SuggestedValue
+ probability: SuggestedValue
+ avoidance: SuggestedValue
+ score: number
+ level: string
+ formula: string
+ }
+ fine_kinney: {
+ probability: SuggestedValue
+ exposure: SuggestedValue
+ consequence: SuggestedValue
+ score: number
+ band: string
+ action: string
+ formula: string
+ }
+ note: string
+}
+
+export interface HazardLite {
+ id: string
+ name: string
+ category: string
+ scenario?: string
+}
+
+export function useRiskAssessment(projectId: string) {
+ const [hazards, setHazards] = useState([])
+ const [suggestions, setSuggestions] = useState>({})
+ 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}/hazards`)
+ const json = res.ok ? await res.json() : {}
+ const hz: HazardLite[] = json.hazards || json || []
+ if (cancelled) return
+ setHazards(hz)
+ const entries = await Promise.all(
+ hz.map(async (h) => {
+ try {
+ const r = await fetch(
+ `/api/sdk/v1/iace/projects/${projectId}/hazards/${h.id}/risk-suggestion`,
+ )
+ return r.ok ? ([h.id, (await r.json()) as RiskSuggestion] as const) : null
+ } catch {
+ return null
+ }
+ }),
+ )
+ if (cancelled) return
+ const map: Record = {}
+ for (const e of entries) if (e) map[e[0]] = e[1]
+ setSuggestions(map)
+ } catch (err) {
+ console.error('Failed to load risk assessment:', err)
+ } finally {
+ if (!cancelled) setLoading(false)
+ }
+ }
+ load()
+ return () => {
+ cancelled = true
+ }
+ }, [projectId])
+
+ return { hazards, suggestions, loading }
+}
diff --git a/admin-compliance/app/sdk/iace/[projectId]/risikobewertung/page.tsx b/admin-compliance/app/sdk/iace/[projectId]/risikobewertung/page.tsx
new file mode 100644
index 00000000..8e954d36
--- /dev/null
+++ b/admin-compliance/app/sdk/iace/[projectId]/risikobewertung/page.tsx
@@ -0,0 +1,41 @@
+'use client'
+
+import { useParams } from 'next/navigation'
+import { useRiskAssessment } from './_hooks/useRiskAssessment'
+import { RiskModelCard } from './_components/RiskModelCard'
+
+export default function RisikobewertungPage() {
+ const params = useParams<{ projectId: string }>()
+ const projectId = params.projectId
+ const { hazards, suggestions, loading } = useRiskAssessment(projectId)
+
+ return (
+
+
+
Risikobewertung
+
+ Zwei Modelle pro Gefaehrdung: EN-62061-Stil (F/W/P/S) und{' '}
+ Fine-Kinney (P/E/C, US-anerkannt). BreakPilot schlaegt begruendete
+ Werte aus oeffentlichen Datenquellen vor (ESAW/NIOSH/OSHA) — passen Sie sie nach Ihrer
+ Erfahrung bzw. Ihren eigenen Normdaten an; das Tool rechnet die Formel live aus.
+
+
+
+ {loading && (
+
Lade Gefaehrdungen…
+ )}
+
+ {!loading && hazards.length === 0 && (
+
+ Keine Gefaehrdungen vorhanden. Bitte zuerst im Hazard Log erzeugen.
+
+ )}
+
+
+ {hazards.map((h) => (
+
+ ))}
+
+
+ )
+}
diff --git a/admin-compliance/app/sdk/iace/layout.tsx b/admin-compliance/app/sdk/iace/layout.tsx
index bd302267..01ed8031 100644
--- a/admin-compliance/app/sdk/iace/layout.tsx
+++ b/admin-compliance/app/sdk/iace/layout.tsx
@@ -13,6 +13,7 @@ const IACE_NAV_ITEMS = [
{ id: 'norms', label: 'Normenrecherche', href: '/norms', icon: 'book' },
{ id: 'components', label: 'Komponenten', href: '/components', icon: 'cube' },
{ id: 'hazards', label: 'Hazard Log', href: '/hazards', icon: 'warning' },
+ { id: 'risikobewertung', label: 'Risikobewertung', href: '/risikobewertung', icon: 'activity' },
{ id: 'mitigations', label: 'Massnahmen', href: '/mitigations', icon: 'shield' },
{ id: 'clarifications', label: 'Klärungen', href: '/clarifications', icon: 'chat' },
{ id: 'verification', label: 'Verifikation', href: '/verification', icon: 'check' },