From f17608a9567f939db211dd3d42c090383dc2c6ce Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Sun, 12 Apr 2026 17:13:39 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20EU=20AI=20Database=20Registration=20(Ar?= =?UTF-8?q?t.=2049)=20=E2=80=94=20Backend=20+=20Frontend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend (Go): - DB Migration 023: ai_system_registrations Tabelle - RegistrationStore: CRUD + Status-Management + Export-JSON - RegistrationHandlers: 7 Endpoints (Create, List, Get, Update, Status, Prefill, Export) - Routes in main.go: /sdk/v1/ai-registration/* Frontend (Next.js): - 6-Step Wizard: Anbieter → System → Klassifikation → Konformitaet → Trainingsdaten → Pruefung - System-Karten mit Status-Badges (Entwurf/Bereit/Eingereicht/Registriert) - JSON-Export fuer EU-Datenbank-Submission - Status-Workflow: draft → ready → submitted → registered - API Proxy Routes Co-Authored-By: Claude Opus 4.6 (1M context) --- .../api/sdk/v1/ai-registration/[id]/route.ts | 47 ++ .../app/api/sdk/v1/ai-registration/route.ts | 32 ++ .../app/sdk/ai-registration/page.tsx | 491 ++++++++++++++++++ ai-compliance-sdk/cmd/server/main.go | 14 + .../api/handlers/registration_handlers.go | 225 ++++++++ .../internal/ucca/registration_store.go | 274 ++++++++++ .../migrations/023_ai_registration_schema.sql | 65 +++ 7 files changed, 1148 insertions(+) create mode 100644 admin-compliance/app/api/sdk/v1/ai-registration/[id]/route.ts create mode 100644 admin-compliance/app/api/sdk/v1/ai-registration/route.ts create mode 100644 admin-compliance/app/sdk/ai-registration/page.tsx create mode 100644 ai-compliance-sdk/internal/api/handlers/registration_handlers.go create mode 100644 ai-compliance-sdk/internal/ucca/registration_store.go create mode 100644 ai-compliance-sdk/migrations/023_ai_registration_schema.sql diff --git a/admin-compliance/app/api/sdk/v1/ai-registration/[id]/route.ts b/admin-compliance/app/api/sdk/v1/ai-registration/[id]/route.ts new file mode 100644 index 0000000..cc82c76 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/ai-registration/[id]/route.ts @@ -0,0 +1,47 @@ +import { NextRequest, NextResponse } from 'next/server' + +const SDK_URL = process.env.SDK_URL || 'http://ai-compliance-sdk:8090' + +export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + try { + const { id } = await params + const resp = await fetch(`${SDK_URL}/sdk/v1/ai-registration/${id}`) + const data = await resp.json() + return NextResponse.json(data) + } catch (err) { + return NextResponse.json({ error: 'Failed to fetch registration' }, { status: 500 }) + } +} + +export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + try { + const { id } = await params + const tenantId = request.headers.get('x-tenant-id') || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' + const body = await request.json() + const resp = await fetch(`${SDK_URL}/sdk/v1/ai-registration/${id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json', 'X-Tenant-ID': tenantId }, + body: JSON.stringify(body), + }) + const data = await resp.json() + return NextResponse.json(data, { status: resp.status }) + } catch (err) { + return NextResponse.json({ error: 'Failed to update registration' }, { status: 500 }) + } +} + +export async function PATCH(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + try { + const { id } = await params + const body = await request.json() + const resp = await fetch(`${SDK_URL}/sdk/v1/ai-registration/${id}/status`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }) + const data = await resp.json() + return NextResponse.json(data, { status: resp.status }) + } catch (err) { + return NextResponse.json({ error: 'Failed to update status' }, { status: 500 }) + } +} diff --git a/admin-compliance/app/api/sdk/v1/ai-registration/route.ts b/admin-compliance/app/api/sdk/v1/ai-registration/route.ts new file mode 100644 index 0000000..785cff3 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/ai-registration/route.ts @@ -0,0 +1,32 @@ +import { NextRequest, NextResponse } from 'next/server' + +const SDK_URL = process.env.SDK_URL || 'http://ai-compliance-sdk:8090' + +export async function GET(request: NextRequest) { + try { + const tenantId = request.headers.get('x-tenant-id') || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' + const resp = await fetch(`${SDK_URL}/sdk/v1/ai-registration`, { + headers: { 'X-Tenant-ID': tenantId }, + }) + const data = await resp.json() + return NextResponse.json(data) + } catch (err) { + return NextResponse.json({ error: 'Failed to fetch registrations' }, { status: 500 }) + } +} + +export async function POST(request: NextRequest) { + try { + const tenantId = request.headers.get('x-tenant-id') || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' + const body = await request.json() + const resp = await fetch(`${SDK_URL}/sdk/v1/ai-registration`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-Tenant-ID': tenantId }, + body: JSON.stringify(body), + }) + const data = await resp.json() + return NextResponse.json(data, { status: resp.status }) + } catch (err) { + return NextResponse.json({ error: 'Failed to create registration' }, { status: 500 }) + } +} diff --git a/admin-compliance/app/sdk/ai-registration/page.tsx b/admin-compliance/app/sdk/ai-registration/page.tsx new file mode 100644 index 0000000..19c4971 --- /dev/null +++ b/admin-compliance/app/sdk/ai-registration/page.tsx @@ -0,0 +1,491 @@ +'use client' + +import React, { useState, useEffect } from 'react' + +interface Registration { + id: string + system_name: string + system_version: string + risk_classification: string + gpai_classification: string + registration_status: string + eu_database_id: string + provider_name: string + created_at: string +} + +const STATUS_STYLES: Record = { + draft: { bg: 'bg-gray-100', text: 'text-gray-700', label: 'Entwurf' }, + ready: { bg: 'bg-blue-100', text: 'text-blue-700', label: 'Bereit' }, + submitted: { bg: 'bg-yellow-100', text: 'text-yellow-700', label: 'Eingereicht' }, + registered: { bg: 'bg-green-100', text: 'text-green-700', label: 'Registriert' }, + update_required: { bg: 'bg-orange-100', text: 'text-orange-700', label: 'Update noetig' }, + withdrawn: { bg: 'bg-red-100', text: 'text-red-700', label: 'Zurueckgezogen' }, +} + +const RISK_STYLES: Record = { + high_risk: { bg: 'bg-red-100', text: 'text-red-700' }, + limited_risk: { bg: 'bg-yellow-100', text: 'text-yellow-700' }, + minimal_risk: { bg: 'bg-green-100', text: 'text-green-700' }, + not_classified: { bg: 'bg-gray-100', text: 'text-gray-500' }, +} + +const INITIAL_FORM = { + system_name: '', + system_version: '1.0', + system_description: '', + intended_purpose: '', + provider_name: '', + provider_legal_form: '', + provider_address: '', + provider_country: 'DE', + eu_representative_name: '', + eu_representative_contact: '', + risk_classification: 'not_classified', + annex_iii_category: '', + gpai_classification: 'none', + conformity_assessment_type: 'internal', + notified_body_name: '', + notified_body_id: '', + ce_marking: false, + training_data_summary: '', +} + +export default function AIRegistrationPage() { + const [registrations, setRegistrations] = useState([]) + const [loading, setLoading] = useState(true) + const [showWizard, setShowWizard] = useState(false) + const [wizardStep, setWizardStep] = useState(1) + const [form, setForm] = useState({ ...INITIAL_FORM }) + const [submitting, setSubmitting] = useState(false) + const [error, setError] = useState(null) + + useEffect(() => { loadRegistrations() }, []) + + async function loadRegistrations() { + try { + setLoading(true) + const resp = await fetch('/api/sdk/v1/ai-registration') + if (resp.ok) { + const data = await resp.json() + setRegistrations(data.registrations || []) + } + } catch { + setError('Fehler beim Laden') + } finally { + setLoading(false) + } + } + + async function handleSubmit() { + setSubmitting(true) + try { + const resp = await fetch('/api/sdk/v1/ai-registration', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(form), + }) + if (resp.ok) { + setShowWizard(false) + setForm({ ...INITIAL_FORM }) + setWizardStep(1) + loadRegistrations() + } else { + const data = await resp.json() + setError(data.error || 'Fehler beim Erstellen') + } + } catch { + setError('Netzwerkfehler') + } finally { + setSubmitting(false) + } + } + + async function handleExport(id: string) { + try { + const resp = await fetch(`/api/sdk/v1/ai-registration/${id}`) + if (resp.ok) { + const reg = await resp.json() + // Build export JSON client-side + const exportData = { + schema_version: '1.0', + submission_type: 'ai_system_registration', + regulation: 'EU AI Act (EU) 2024/1689', + article: 'Art. 49', + provider: { name: reg.provider_name, address: reg.provider_address, country: reg.provider_country }, + system: { name: reg.system_name, version: reg.system_version, description: reg.system_description, purpose: reg.intended_purpose }, + classification: { risk_level: reg.risk_classification, annex_iii: reg.annex_iii_category, gpai: reg.gpai_classification }, + conformity: { type: reg.conformity_assessment_type, ce_marking: reg.ce_marking }, + } + const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `eu_ai_registration_${reg.system_name.replace(/\s+/g, '_')}.json` + a.click() + URL.revokeObjectURL(url) + } + } catch { + setError('Export fehlgeschlagen') + } + } + + async function handleStatusChange(id: string, status: string) { + try { + await fetch(`/api/sdk/v1/ai-registration/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ status }), + }) + loadRegistrations() + } catch { + setError('Status-Aenderung fehlgeschlagen') + } + } + + const updateForm = (updates: Partial) => setForm(prev => ({ ...prev, ...updates })) + + const STEPS = [ + { id: 1, title: 'Anbieter', desc: 'Unternehmensangaben' }, + { id: 2, title: 'System', desc: 'KI-System Details' }, + { id: 3, title: 'Klassifikation', desc: 'Risikoeinstufung' }, + { id: 4, title: 'Konformitaet', desc: 'CE & Notified Body' }, + { id: 5, title: 'Trainingsdaten', desc: 'Datenzusammenfassung' }, + { id: 6, title: 'Pruefung', desc: 'Zusammenfassung & Export' }, + ] + + return ( +
+ {/* Header */} +
+
+

EU AI Database Registrierung

+

Art. 49 KI-Verordnung (EU) 2024/1689 — Registrierung von Hochrisiko-KI-Systemen

+
+ +
+ + {error && ( +
+ {error} + +
+ )} + + {/* Stats */} +
+ {['draft', 'ready', 'submitted', 'registered'].map(status => { + const count = registrations.filter(r => r.registration_status === status).length + const style = STATUS_STYLES[status] + return ( +
+
{count}
+
{style.label}
+
+ ) + })} +
+ + {/* Registrations List */} + {loading ? ( +
Lade...
+ ) : registrations.length === 0 ? ( +
+

Noch keine Registrierungen

+

Erstelle eine neue Registrierung fuer dein Hochrisiko-KI-System.

+
+ ) : ( +
+ {registrations.map(reg => { + const status = STATUS_STYLES[reg.registration_status] || STATUS_STYLES.draft + const risk = RISK_STYLES[reg.risk_classification] || RISK_STYLES.not_classified + return ( +
+
+
+
+

{reg.system_name}

+ v{reg.system_version} + {status.label} + {reg.risk_classification.replace('_', ' ')} + {reg.gpai_classification !== 'none' && ( + GPAI: {reg.gpai_classification} + )} +
+
+ {reg.provider_name && {reg.provider_name} · } + {reg.eu_database_id && EU-ID: {reg.eu_database_id} · } + {new Date(reg.created_at).toLocaleDateString('de-DE')} +
+
+
+ + {reg.registration_status === 'draft' && ( + + )} + {reg.registration_status === 'ready' && ( + + )} +
+
+
+ ) + })} +
+ )} + + {/* Wizard Modal */} + {showWizard && ( +
+
+
+
+

Neue EU AI Registrierung

+ +
+ {/* Step Indicator */} +
+ {STEPS.map(step => ( + + ))} +
+
+ +
+ {/* Step 1: Provider */} + {wizardStep === 1 && ( + <> +

Anbieter-Informationen

+

Angaben zum Anbieter des KI-Systems gemaess Art. 49 KI-VO.

+
+
+ + updateForm({ provider_name: e.target.value })} + className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" placeholder="Acme GmbH" /> +
+
+ + updateForm({ provider_legal_form: e.target.value })} + className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" placeholder="GmbH" /> +
+
+
+ + updateForm({ provider_address: e.target.value })} + className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" placeholder="Musterstr. 1, 20095 Hamburg" /> +
+
+
+ + +
+
+ + updateForm({ eu_representative_name: e.target.value })} + className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" placeholder="Optional" /> +
+
+ + )} + + {/* Step 2: System */} + {wizardStep === 2 && ( + <> +

KI-System Details

+
+
+ + updateForm({ system_name: e.target.value })} + className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" placeholder="z.B. HR Copilot" /> +
+
+ + updateForm({ system_version: e.target.value })} + className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" placeholder="1.0" /> +
+
+
+ +