feat: CE × Compliance Crossover Engine

Automatische Erkennung von DSGVO/AI Act/CRA/NIS2/Data Act
Implikationen bei CE-Gefaehrdungen. 50 Trigger-Mappings auf
Hazard-Patterns → Compliance-Module mit Modul-Links.

- compliance_triggers.go: 50 Pattern→Regulation Mappings
- compliance_crossover.go: Engine die Projekt-Hazards gegen Trigger prueft
- iace_handler_compliance.go: GET /compliance-triggers API
- ComplianceAlerts.tsx: Frontend Alert-Panel auf Projekt-Uebersicht

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-07 15:07:22 +02:00
parent fa4fd87102
commit 56892cf7dc
6 changed files with 1004 additions and 0 deletions
@@ -0,0 +1,218 @@
'use client'
import { useState, useEffect } from 'react'
import Link from 'next/link'
interface ComplianceTrigger {
id: string
regulation: string
article: string
title: string
severity: 'high' | 'medium' | 'low'
reason: string
affected_hazard_count?: number
module_path: string
module_label: string
}
interface TriggersResponse {
triggers: ComplianceTrigger[]
total: number
}
const SEVERITY_CONFIG: Record<string, { border: string; bg: string; text: string; badge: string; icon: string }> = {
high: {
border: 'border-red-200 dark:border-red-800',
bg: 'bg-red-50 dark:bg-red-900/20',
text: 'text-red-700 dark:text-red-400',
badge: 'bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-300',
icon: 'text-red-500',
},
medium: {
border: 'border-yellow-200 dark:border-yellow-800',
bg: 'bg-yellow-50 dark:bg-yellow-900/20',
text: 'text-yellow-700 dark:text-yellow-400',
badge: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-300',
icon: 'text-yellow-500',
},
low: {
border: 'border-blue-200 dark:border-blue-800',
bg: 'bg-blue-50 dark:bg-blue-900/20',
text: 'text-blue-700 dark:text-blue-400',
badge: 'bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300',
icon: 'text-blue-500',
},
}
const SEVERITY_LABELS: Record<string, string> = {
high: 'HOCH',
medium: 'MITTEL',
low: 'NIEDRIG',
}
const REGULATION_BADGES: { key: string; label: string; activeColor: string }[] = [
{ key: 'DSGVO', label: 'DSGVO', activeColor: 'bg-red-100 text-red-800 border-red-300' },
{ key: 'AI Act', label: 'AI Act', activeColor: 'bg-orange-100 text-orange-800 border-orange-300' },
{ key: 'CRA', label: 'CRA', activeColor: 'bg-yellow-100 text-yellow-800 border-yellow-300' },
{ key: 'NIS2', label: 'NIS2', activeColor: 'bg-indigo-100 text-indigo-800 border-indigo-300' },
{ key: 'Data Act', label: 'Data Act', activeColor: 'bg-amber-100 text-amber-800 border-amber-300' },
]
function WarningIcon({ className }: { className?: string }) {
return (
<svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4.5c-.77-.833-2.694-.833-3.464 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z" />
</svg>
)
}
function ChevronIcon({ open }: { open: boolean }) {
return (
<svg className={`w-4 h-4 text-gray-400 transition-transform ${open ? 'rotate-180' : ''}`}
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
)
}
export function ComplianceAlerts({ projectId }: { projectId: string }) {
const [data, setData] = useState<TriggersResponse | null>(null)
const [loading, setLoading] = useState(true)
const [collapsed, setCollapsed] = useState(false)
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set())
useEffect(() => {
fetch(`/api/sdk/v1/iace/projects/${projectId}/compliance-triggers`)
.then((r) => (r.ok ? r.json() : null))
.then((json) => {
if (json?.triggers) setData(json)
else if (Array.isArray(json)) setData({ triggers: json, total: json.length })
})
.catch(() => {})
.finally(() => setLoading(false))
}, [projectId])
if (loading) return null
if (!data || data.triggers.length === 0) return null
const triggers = data.triggers
const activeRegulations = new Set(triggers.map((t) => t.regulation))
function toggleExpanded(id: string) {
setExpandedIds((prev) => {
const next = new Set(prev)
if (next.has(id)) next.delete(id)
else next.add(id)
return next
})
}
return (
<div className="bg-white dark:bg-gray-800 rounded-xl border border-red-200 dark:border-red-800">
{/* Header */}
<button
onClick={() => setCollapsed(!collapsed)}
className="w-full flex items-center justify-between p-6 text-left"
>
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-red-50 dark:bg-red-900/30 rounded-lg flex items-center justify-center">
<WarningIcon className="w-5 h-5 text-red-600" />
</div>
<div>
<h2 className="text-sm font-semibold text-gray-900 dark:text-white">
{triggers.length} Compliance-Hinweise erkannt
</h2>
<p className="text-xs text-gray-500">
Basierend auf den identifizierten Gefaehrdungen bestehen rechtliche Implikationen
</p>
</div>
</div>
<div className="w-8 h-8 flex items-center justify-center rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors flex-shrink-0">
<ChevronIcon open={!collapsed} />
</div>
</button>
{!collapsed && (
<div className="px-6 pb-6 space-y-4">
{/* Regulation summary badges */}
<div className="flex flex-wrap gap-2">
{REGULATION_BADGES.map((reg) => {
const active = activeRegulations.has(reg.key)
return (
<span
key={reg.key}
className={`px-2.5 py-1 text-xs font-medium rounded-full border ${
active
? reg.activeColor
: 'bg-gray-50 text-gray-400 border-gray-200 dark:bg-gray-700 dark:text-gray-500 dark:border-gray-600'
}`}
>
{reg.label}
</span>
)
})}
</div>
{/* Trigger list */}
<div className="space-y-2">
{triggers.map((trigger) => {
const sev = SEVERITY_CONFIG[trigger.severity] || SEVERITY_CONFIG.low
const isOpen = expandedIds.has(trigger.id)
return (
<div key={trigger.id} className={`rounded-lg border ${sev.border} ${sev.bg} overflow-hidden`}>
{/* Trigger header row */}
<button
onClick={() => toggleExpanded(trigger.id)}
className="w-full flex items-center gap-3 px-4 py-3 text-left"
>
<ChevronIcon open={isOpen} />
<div className="flex-1 min-w-0">
<span className="text-sm font-medium text-gray-900 dark:text-white">
{trigger.regulation} {trigger.article} {trigger.title}
</span>
</div>
<span className={`px-2 py-0.5 text-xs font-bold rounded ${sev.badge}`}>
{SEVERITY_LABELS[trigger.severity] || trigger.severity}
</span>
</button>
{/* Expanded detail */}
{isOpen && (
<div className="px-4 pb-4 pt-0 ml-7 space-y-2">
<p className="text-xs text-gray-700 dark:text-gray-300">
<span className="font-medium">Grund:</span> {trigger.reason}
</p>
{trigger.affected_hazard_count != null && trigger.affected_hazard_count > 0 && (
<p className="text-xs text-gray-500">
Betroffene Gefaehrdungen: {trigger.affected_hazard_count}
</p>
)}
<Link
href={trigger.module_path}
className={`inline-flex items-center gap-1.5 text-xs font-medium ${sev.text} hover:underline`}
>
{trigger.module_label} oeffnen
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7l5 5m0 0l-5 5m5-5H6" />
</svg>
</Link>
</div>
)}
</div>
)
})}
</div>
{/* Disclaimer */}
<div className="p-3 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 text-xs text-amber-800 dark:text-amber-300">
<strong>Hinweis:</strong> Diese Compliance-Hinweise werden automatisch aus den
Gefaehrdungen und Klassifikationen abgeleitet. Der CE-Fachmann muss die
regulatorischen Anforderungen im jeweiligen Modul verifizieren.
</div>
</div>
)}
</div>
)
}