[split-required] Split 58 monoliths across Python, Go, TypeScript (Phases 1-3)

Phase 1 — Python (klausur-service): 5 monoliths → 36 files
- dsfa_corpus_ingestion.py (1,828 LOC → 5 files)
- cv_ocr_engines.py (2,102 LOC → 7 files)
- cv_layout.py (3,653 LOC → 10 files)
- vocab_worksheet_api.py (2,783 LOC → 8 files)
- grid_build_core.py (1,958 LOC → 6 files)

Phase 2 — Go (edu-search-service, school-service): 8 monoliths → 19 files
- staff_crawler.go (1,402 → 4), policy/store.go (1,168 → 3)
- policy_handlers.go (700 → 2), repository.go (684 → 2)
- search.go (592 → 2), ai_extraction_handlers.go (554 → 2)
- seed_data.go (591 → 2), grade_service.go (646 → 2)

Phase 3 — TypeScript (admin-lehrer): 45 monoliths → 220+ files
- sdk/types.ts (2,108 → 16 domain files)
- ai/rag/page.tsx (2,686 → 14 files)
- 22 page.tsx files split into _components/ + _hooks/
- 11 component files split into sub-components
- 10 SDK data catalogs added to loc-exceptions
- Deleted dead backup index_original.ts (4,899 LOC)

All original public APIs preserved via re-export facades.
Zero new errors: Python imports verified, Go builds clean,
TypeScript tsc --noEmit shows only pre-existing errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-24 17:28:57 +02:00
parent 9ba420fa91
commit b681ddb131
251 changed files with 30016 additions and 25037 deletions

View File

@@ -0,0 +1,515 @@
/**
* Module Registry Data - All backend module definitions
*
* This file contains the pure data array of module definitions.
* Helper functions are in module-registry.ts.
*/
import type { BackendModule } from './module-registry'
export const MODULE_REGISTRY: BackendModule[] = [
// ===========================================
// COMPLIANCE MODULES
// ===========================================
{
id: 'consent-documents',
name: 'Consent Dokumente',
description: 'Verwaltung rechtlicher Dokumente (AGB, Datenschutz, etc.)',
category: 'compliance',
backend: {
service: 'consent-service',
port: 8081,
basePath: '/api/consent/admin',
endpoints: [
{ path: '/documents', method: 'GET', description: 'Liste aller Dokumente' },
{ path: '/documents', method: 'POST', description: 'Dokument erstellen' },
{ path: '/documents/{id}', method: 'GET', description: 'Dokument Details' },
{ path: '/documents/{id}', method: 'PUT', description: 'Dokument aktualisieren' },
{ path: '/documents/{id}', method: 'DELETE', description: 'Dokument loeschen' },
]
},
frontend: {
adminV2Page: '/sdk/consent-management',
oldAdminPage: '/admin/consent',
status: 'connected'
},
priority: 'critical'
},
{
id: 'consent-versions',
name: 'Dokument-Versionierung',
description: 'Versionsverwaltung und Freigabe-Workflow fuer rechtliche Dokumente',
category: 'compliance',
backend: {
service: 'consent-service',
port: 8081,
basePath: '/api/consent/admin',
endpoints: [
{ path: '/documents/{id}/versions', method: 'GET', description: 'Versionen eines Dokuments' },
{ path: '/versions', method: 'POST', description: 'Neue Version erstellen' },
{ path: '/versions/{id}', method: 'PUT', description: 'Version aktualisieren' },
{ path: '/versions/{id}', method: 'DELETE', description: 'Version loeschen' },
{ path: '/versions/{id}/submit-review', method: 'POST', description: 'Zur Pruefung einreichen' },
{ path: '/versions/{id}/approve', method: 'POST', description: 'Version genehmigen' },
{ path: '/versions/{id}/reject', method: 'POST', description: 'Version ablehnen' },
{ path: '/versions/{id}/publish', method: 'POST', description: 'Version veroeffentlichen' },
{ path: '/versions/{id}/approval-history', method: 'GET', description: 'Genehmigungsverlauf' },
{ path: '/versions/upload-word', method: 'POST', description: 'Word-Dokument importieren' },
]
},
frontend: {
adminV2Page: '/sdk/workflow',
oldAdminPage: '/admin/consent (Versions Tab)',
status: 'connected'
},
dependencies: ['consent-documents'],
priority: 'critical'
},
{
id: 'consent-user',
name: 'Nutzer-Einwilligungen',
description: 'Tracking von Nutzer-Einwilligungen fuer DSGVO-Compliance',
category: 'compliance',
backend: {
service: 'consent-service',
port: 8081,
basePath: '/api/consent',
endpoints: [
{ path: '/status', method: 'GET', description: 'Einwilligungsstatus pruefen' },
{ path: '/give', method: 'POST', description: 'Einwilligung erteilen' },
{ path: '/withdraw', method: 'POST', description: 'Einwilligung widerrufen' },
{ path: '/history', method: 'GET', description: 'Einwilligungshistorie' },
]
},
frontend: {
adminV2Page: '/sdk/einwilligungen',
oldAdminPage: '/admin/consent (Users Tab)',
status: 'connected',
},
priority: 'critical',
},
{
id: 'dsr-requests',
name: 'Datenschutzanfragen (DSR)',
description: 'DSGVO Art. 15-21 Anfragen verwalten',
category: 'compliance',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/dsr',
endpoints: [
{ path: '/requests', method: 'GET', description: 'Alle DSR-Anfragen' },
{ path: '/requests', method: 'POST', description: 'Neue Anfrage erstellen' },
{ path: '/requests/{id}', method: 'GET', description: 'Anfrage-Details' },
{ path: '/requests/{id}/process', method: 'POST', description: 'Anfrage bearbeiten' },
{ path: '/requests/{id}/export', method: 'GET', description: 'Daten exportieren' },
]
},
frontend: {
adminV2Page: '/sdk/dsr',
oldAdminPage: '/admin/dsr',
status: 'connected'
},
priority: 'high',
},
{
id: 'dsms',
name: 'Datenschutz-Management-System',
description: 'Zentrales DSMS fuer Dokumentation und Compliance',
category: 'compliance',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/dsms',
endpoints: [
{ path: '/documents', method: 'GET', description: 'DSMS-Dokumente' },
{ path: '/processes', method: 'GET', description: 'Verarbeitungsverzeichnis' },
{ path: '/toms', method: 'GET', description: 'TOM-Katalog' },
{ path: '/audits', method: 'GET', description: 'Audit-Historie' },
]
},
frontend: {
adminV2Page: '/sdk/dsms',
oldAdminPage: '/admin/dsms',
status: 'connected'
},
priority: 'medium'
},
{
id: 'cookie-categories',
name: 'Cookie-Kategorien',
description: 'Verwaltung von Cookie-Kategorien fuer Consent Banner',
category: 'compliance',
backend: {
service: 'consent-service',
port: 8081,
basePath: '/api/consent/admin',
endpoints: [
{ path: '/cookies/categories', method: 'GET', description: 'Alle Cookie-Kategorien' },
{ path: '/cookies/categories', method: 'POST', description: 'Kategorie erstellen' },
{ path: '/cookies/categories/{id}', method: 'PUT', description: 'Kategorie aktualisieren' },
{ path: '/cookies/categories/{id}', method: 'DELETE', description: 'Kategorie loeschen' },
]
},
frontend: {
adminV2Page: undefined,
oldAdminPage: '/admin/consent (Cookies Tab)',
status: 'not-connected'
},
priority: 'medium',
notes: 'Cookie-Kategorien Tab im alten Admin vorhanden'
},
// ===========================================
// AI MODULES
// ===========================================
{
id: 'ai-agents',
name: 'AI Agents',
description: 'Multi-Agent System Verwaltung und Monitoring',
category: 'ai',
backend: {
service: 'voice-service',
port: 8088,
basePath: '/api/v1/agents',
endpoints: [
{ path: '/sessions', method: 'GET', description: 'Agent-Sessions' },
{ path: '/statistics', method: 'GET', description: 'Agent-Statistiken' },
{ path: '/{agentId}', method: 'GET', description: 'Agent-Details' },
{ path: '/{agentId}/soul', method: 'GET', description: 'SOUL-Konfiguration' },
]
},
frontend: {
adminV2Page: '/ai/agents',
oldAdminPage: undefined,
status: 'connected'
},
priority: 'high',
notes: 'Neues Multi-Agent System'
},
{
id: 'ai-quality',
name: 'AI Quality (BQAS)',
description: 'KI-Qualitaetssicherung und Evaluierung',
category: 'ai',
backend: {
service: 'voice-service',
port: 8088,
basePath: '/api/bqas',
endpoints: [
{ path: '/evaluate', method: 'POST', description: 'Antwort evaluieren' },
{ path: '/metrics', method: 'GET', description: 'Qualitaetsmetriken' },
]
},
frontend: {
adminV2Page: '/ai/quality',
oldAdminPage: '/admin/quality',
status: 'connected'
},
priority: 'high'
},
{
id: 'magic-help',
name: 'Magic Help (TrOCR)',
description: 'Handschrifterkennung mit TrOCR und LoRA Fine-Tuning',
category: 'ai',
backend: {
service: 'klausur-service',
port: 8086,
basePath: '/api/klausur/trocr',
endpoints: [
{ path: '/status', method: 'GET', description: 'TrOCR Status' },
{ path: '/extract', method: 'POST', description: 'Text aus Bild extrahieren' },
{ path: '/training/examples', method: 'GET', description: 'Trainingsbeispiele' },
{ path: '/training/add', method: 'POST', description: 'Trainingsbeispiel hinzufuegen' },
{ path: '/training/fine-tune', method: 'POST', description: 'Fine-Tuning starten' },
]
},
frontend: {
adminV2Page: '/ai/magic-help',
oldAdminPage: '/admin/magic-help',
status: 'connected'
},
priority: 'medium',
notes: 'Lokale Handschrifterkennung mit Privacy-by-Design'
},
{
id: 'klausur-korrektur',
name: 'Klausur-Korrektur',
description: 'KI-gestuetzte Abitur-Korrektur mit EH-Vorschlaegen',
category: 'ai',
backend: {
service: 'klausur-service',
port: 8086,
basePath: '/api/v1',
endpoints: [
{ path: '/klausuren', method: 'GET', description: 'Alle Klausuren' },
{ path: '/klausuren', method: 'POST', description: 'Klausur erstellen' },
{ path: '/klausuren/{id}/students', method: 'GET', description: 'Studentenarbeiten' },
{ path: '/students/{id}/annotations', method: 'GET', description: 'Anmerkungen' },
{ path: '/students/{id}/gutachten/generate', method: 'POST', description: 'Gutachten generieren' },
]
},
frontend: {
adminV2Page: '/ai/klausur-korrektur',
oldAdminPage: '/admin/klausur-korrektur',
status: 'not-connected'
},
priority: 'high',
notes: 'Komplexes Modul mit eigenem Backend-Service'
},
{
id: 'ocr-labeling',
name: 'OCR-Labeling',
description: 'Handschrift-Training und Label-Verwaltung',
category: 'ai',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/ocr',
endpoints: [
{ path: '/samples', method: 'GET', description: 'Training-Samples' },
{ path: '/labels', method: 'GET', description: 'Label-Kategorien' },
{ path: '/train', method: 'POST', description: 'Training starten' },
]
},
frontend: {
adminV2Page: '/ai/ocr-labeling',
oldAdminPage: '/admin/ocr-labeling',
status: 'not-connected'
},
priority: 'medium'
},
{
id: 'rag-management',
name: 'RAG & Daten',
description: 'Retrieval Augmented Generation und Training Data',
category: 'ai',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/rag',
endpoints: [
{ path: '/documents', method: 'GET', description: 'RAG-Dokumente' },
{ path: '/collections', method: 'GET', description: 'Vector-Collections' },
{ path: '/query', method: 'POST', description: 'RAG-Abfrage' },
]
},
frontend: {
adminV2Page: '/ai/rag',
oldAdminPage: '/admin/rag',
status: 'connected'
},
priority: 'medium'
},
// ===========================================
// INFRASTRUCTURE MODULES
// ===========================================
{
id: 'gpu-infrastructure',
name: 'GPU Infrastruktur',
description: 'vast.ai GPU-Management und Monitoring',
category: 'infrastructure',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/gpu',
endpoints: [
{ path: '/instances', method: 'GET', description: 'GPU-Instanzen' },
{ path: '/instances', method: 'POST', description: 'Instanz erstellen' },
{ path: '/usage', method: 'GET', description: 'Nutzungsstatistiken' },
]
},
frontend: {
adminV2Page: '/infrastructure/gpu',
oldAdminPage: '/admin/gpu',
status: 'connected'
},
priority: 'medium'
},
{
id: 'security-dashboard',
name: 'Security Dashboard',
description: 'DevSecOps Dashboard und Vulnerability Scans',
category: 'infrastructure',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/security',
endpoints: [
{ path: '/scans', method: 'GET', description: 'Security-Scans' },
{ path: '/vulnerabilities', method: 'GET', description: 'Schwachstellen' },
{ path: '/compliance', method: 'GET', description: 'Compliance-Status' },
]
},
frontend: {
adminV2Page: '/infrastructure/security',
oldAdminPage: '/admin/security',
status: 'connected'
},
priority: 'high'
},
{
id: 'sbom',
name: 'SBOM',
description: 'Software Bill of Materials',
category: 'infrastructure',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/sbom',
endpoints: [
{ path: '/components', method: 'GET', description: 'Komponenten-Liste' },
{ path: '/licenses', method: 'GET', description: 'Lizenz-Uebersicht' },
{ path: '/export', method: 'GET', description: 'SBOM exportieren' },
]
},
frontend: {
adminV2Page: '/infrastructure/sbom',
oldAdminPage: '/admin/sbom',
status: 'connected'
},
priority: 'medium'
},
{
id: 'middleware',
name: 'Middleware Manager',
description: 'Verwaltung und Monitoring der Backend-Middleware',
category: 'infrastructure',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/middleware',
endpoints: [
{ path: '/status', method: 'GET', description: 'Middleware-Status' },
{ path: '/config', method: 'GET', description: 'Konfiguration' },
]
},
frontend: {
adminV2Page: '/infrastructure/middleware',
oldAdminPage: '/admin/middleware',
status: 'connected'
},
priority: 'medium'
},
{
id: 'ci-cd',
name: 'CI/CD Pipeline',
description: 'Build-Pipeline und Deployment-Management',
category: 'infrastructure',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/builds',
endpoints: [
{ path: '/pipelines', method: 'GET', description: 'Pipeline-Status' },
{ path: '/builds', method: 'GET', description: 'Build-Historie' },
]
},
frontend: {
adminV2Page: '/infrastructure/ci-cd',
oldAdminPage: '/admin/builds',
status: 'connected'
},
priority: 'medium'
},
// ===========================================
// EDUCATION MODULES
// ===========================================
{
id: 'edu-search',
name: 'Bildungssuche',
description: 'Suche nach Bildungsinhalten und Ressourcen',
category: 'education',
backend: {
service: 'edu-search-service',
port: 8089,
basePath: '/api/edu',
endpoints: [
{ path: '/search', method: 'GET', description: 'Bildungssuche' },
{ path: '/resources', method: 'GET', description: 'Ressourcen' },
]
},
frontend: {
adminV2Page: '/education/edu-search',
oldAdminPage: '/admin/edu-search',
status: 'connected'
},
priority: 'medium'
},
// ===========================================
// COMMUNICATION MODULES
// ===========================================
{
id: 'alerts',
name: 'Alerts & Benachrichtigungen',
description: 'System-Benachrichtigungen und Alerts',
category: 'communication',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/alerts',
endpoints: [
{ path: '/notifications', method: 'GET', description: 'Benachrichtigungen' },
{ path: '/alerts', method: 'GET', description: 'Aktive Alerts' },
]
},
frontend: {
adminV2Page: '/communication/alerts',
oldAdminPage: '/admin/alerts',
status: 'connected'
},
priority: 'medium'
},
{
id: 'unified-inbox',
name: 'Unified Inbox',
description: 'E-Mail-Konten und KI-Analyse',
category: 'communication',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/mail',
endpoints: [
{ path: '/accounts', method: 'GET', description: 'E-Mail-Konten' },
{ path: '/messages', method: 'GET', description: 'Nachrichten' },
{ path: '/analyze', method: 'POST', description: 'KI-Analyse' },
]
},
frontend: {
adminV2Page: '/communication/mail',
oldAdminPage: '/admin/mail',
status: 'connected'
},
priority: 'low'
},
// ===========================================
// DEVELOPMENT MODULES
// ===========================================
{
id: 'voice-service',
name: 'Voice Service',
description: 'Voice-First Interface',
category: 'development',
backend: {
service: 'voice-service',
port: 8088,
basePath: '/api/voice',
endpoints: [
{ path: '/transcribe', method: 'POST', description: 'Sprache transkribieren' },
{ path: '/synthesize', method: 'POST', description: 'Text zu Sprache' },
]
},
frontend: {
adminV2Page: '/development/voice',
oldAdminPage: '/admin/voice',
status: 'not-connected'
},
priority: 'low'
},
]

View File

@@ -6,6 +6,9 @@
* - Backend service and endpoints
* - Frontend pages that use it
* - Connection status (connected, partial, not connected)
*
* Module definitions live in ./module-registry-data.ts
* This file exports the type, re-exports the data, and provides helper functions.
*/
export interface BackendModule {
@@ -33,512 +36,11 @@ export interface BackendModule {
notes?: string
}
export const MODULE_REGISTRY: BackendModule[] = [
// ===========================================
// COMPLIANCE MODULES
// ===========================================
{
id: 'consent-documents',
name: 'Consent Dokumente',
description: 'Verwaltung rechtlicher Dokumente (AGB, Datenschutz, etc.)',
category: 'compliance',
backend: {
service: 'consent-service',
port: 8081,
basePath: '/api/consent/admin',
endpoints: [
{ path: '/documents', method: 'GET', description: 'Liste aller Dokumente' },
{ path: '/documents', method: 'POST', description: 'Dokument erstellen' },
{ path: '/documents/{id}', method: 'GET', description: 'Dokument Details' },
{ path: '/documents/{id}', method: 'PUT', description: 'Dokument aktualisieren' },
{ path: '/documents/{id}', method: 'DELETE', description: 'Dokument loeschen' },
]
},
frontend: {
adminV2Page: '/sdk/consent-management',
oldAdminPage: '/admin/consent',
status: 'connected'
},
priority: 'critical'
},
{
id: 'consent-versions',
name: 'Dokument-Versionierung',
description: 'Versionsverwaltung und Freigabe-Workflow fuer rechtliche Dokumente',
category: 'compliance',
backend: {
service: 'consent-service',
port: 8081,
basePath: '/api/consent/admin',
endpoints: [
{ path: '/documents/{id}/versions', method: 'GET', description: 'Versionen eines Dokuments' },
{ path: '/versions', method: 'POST', description: 'Neue Version erstellen' },
{ path: '/versions/{id}', method: 'PUT', description: 'Version aktualisieren' },
{ path: '/versions/{id}', method: 'DELETE', description: 'Version loeschen' },
{ path: '/versions/{id}/submit-review', method: 'POST', description: 'Zur Pruefung einreichen' },
{ path: '/versions/{id}/approve', method: 'POST', description: 'Version genehmigen' },
{ path: '/versions/{id}/reject', method: 'POST', description: 'Version ablehnen' },
{ path: '/versions/{id}/publish', method: 'POST', description: 'Version veroeffentlichen' },
{ path: '/versions/{id}/approval-history', method: 'GET', description: 'Genehmigungsverlauf' },
{ path: '/versions/upload-word', method: 'POST', description: 'Word-Dokument importieren' },
]
},
frontend: {
adminV2Page: '/sdk/workflow',
oldAdminPage: '/admin/consent (Versions Tab)',
status: 'connected'
},
dependencies: ['consent-documents'],
priority: 'critical'
},
{
id: 'consent-user',
name: 'Nutzer-Einwilligungen',
description: 'Tracking von Nutzer-Einwilligungen fuer DSGVO-Compliance',
category: 'compliance',
backend: {
service: 'consent-service',
port: 8081,
basePath: '/api/consent',
endpoints: [
{ path: '/status', method: 'GET', description: 'Einwilligungsstatus pruefen' },
{ path: '/give', method: 'POST', description: 'Einwilligung erteilen' },
{ path: '/withdraw', method: 'POST', description: 'Einwilligung widerrufen' },
{ path: '/history', method: 'GET', description: 'Einwilligungshistorie' },
]
},
frontend: {
adminV2Page: '/sdk/einwilligungen',
oldAdminPage: '/admin/consent (Users Tab)',
status: 'connected',
},
priority: 'critical',
},
{
id: 'dsr-requests',
name: 'Datenschutzanfragen (DSR)',
description: 'DSGVO Art. 15-21 Anfragen verwalten',
category: 'compliance',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/dsr',
endpoints: [
{ path: '/requests', method: 'GET', description: 'Alle DSR-Anfragen' },
{ path: '/requests', method: 'POST', description: 'Neue Anfrage erstellen' },
{ path: '/requests/{id}', method: 'GET', description: 'Anfrage-Details' },
{ path: '/requests/{id}/process', method: 'POST', description: 'Anfrage bearbeiten' },
{ path: '/requests/{id}/export', method: 'GET', description: 'Daten exportieren' },
]
},
frontend: {
adminV2Page: '/sdk/dsr',
oldAdminPage: '/admin/dsr',
status: 'connected'
},
priority: 'high',
},
{
id: 'dsms',
name: 'Datenschutz-Management-System',
description: 'Zentrales DSMS fuer Dokumentation und Compliance',
category: 'compliance',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/dsms',
endpoints: [
{ path: '/documents', method: 'GET', description: 'DSMS-Dokumente' },
{ path: '/processes', method: 'GET', description: 'Verarbeitungsverzeichnis' },
{ path: '/toms', method: 'GET', description: 'TOM-Katalog' },
{ path: '/audits', method: 'GET', description: 'Audit-Historie' },
]
},
frontend: {
adminV2Page: '/sdk/dsms',
oldAdminPage: '/admin/dsms',
status: 'connected'
},
priority: 'medium'
},
{
id: 'cookie-categories',
name: 'Cookie-Kategorien',
description: 'Verwaltung von Cookie-Kategorien fuer Consent Banner',
category: 'compliance',
backend: {
service: 'consent-service',
port: 8081,
basePath: '/api/consent/admin',
endpoints: [
{ path: '/cookies/categories', method: 'GET', description: 'Alle Cookie-Kategorien' },
{ path: '/cookies/categories', method: 'POST', description: 'Kategorie erstellen' },
{ path: '/cookies/categories/{id}', method: 'PUT', description: 'Kategorie aktualisieren' },
{ path: '/cookies/categories/{id}', method: 'DELETE', description: 'Kategorie loeschen' },
]
},
frontend: {
adminV2Page: undefined,
oldAdminPage: '/admin/consent (Cookies Tab)',
status: 'not-connected'
},
priority: 'medium',
notes: 'Cookie-Kategorien Tab im alten Admin vorhanden'
},
// Re-export data from the data file
export { MODULE_REGISTRY } from './module-registry-data'
// ===========================================
// AI MODULES
// ===========================================
{
id: 'ai-agents',
name: 'AI Agents',
description: 'Multi-Agent System Verwaltung und Monitoring',
category: 'ai',
backend: {
service: 'voice-service',
port: 8088,
basePath: '/api/v1/agents',
endpoints: [
{ path: '/sessions', method: 'GET', description: 'Agent-Sessions' },
{ path: '/statistics', method: 'GET', description: 'Agent-Statistiken' },
{ path: '/{agentId}', method: 'GET', description: 'Agent-Details' },
{ path: '/{agentId}/soul', method: 'GET', description: 'SOUL-Konfiguration' },
]
},
frontend: {
adminV2Page: '/ai/agents',
oldAdminPage: undefined,
status: 'connected'
},
priority: 'high',
notes: 'Neues Multi-Agent System'
},
{
id: 'ai-quality',
name: 'AI Quality (BQAS)',
description: 'KI-Qualitaetssicherung und Evaluierung',
category: 'ai',
backend: {
service: 'voice-service',
port: 8088,
basePath: '/api/bqas',
endpoints: [
{ path: '/evaluate', method: 'POST', description: 'Antwort evaluieren' },
{ path: '/metrics', method: 'GET', description: 'Qualitaetsmetriken' },
]
},
frontend: {
adminV2Page: '/ai/quality',
oldAdminPage: '/admin/quality',
status: 'connected'
},
priority: 'high'
},
{
id: 'magic-help',
name: 'Magic Help (TrOCR)',
description: 'Handschrifterkennung mit TrOCR und LoRA Fine-Tuning',
category: 'ai',
backend: {
service: 'klausur-service',
port: 8086,
basePath: '/api/klausur/trocr',
endpoints: [
{ path: '/status', method: 'GET', description: 'TrOCR Status' },
{ path: '/extract', method: 'POST', description: 'Text aus Bild extrahieren' },
{ path: '/training/examples', method: 'GET', description: 'Trainingsbeispiele' },
{ path: '/training/add', method: 'POST', description: 'Trainingsbeispiel hinzufuegen' },
{ path: '/training/fine-tune', method: 'POST', description: 'Fine-Tuning starten' },
]
},
frontend: {
adminV2Page: '/ai/magic-help',
oldAdminPage: '/admin/magic-help',
status: 'connected'
},
priority: 'medium',
notes: 'Lokale Handschrifterkennung mit Privacy-by-Design'
},
{
id: 'klausur-korrektur',
name: 'Klausur-Korrektur',
description: 'KI-gestuetzte Abitur-Korrektur mit EH-Vorschlaegen',
category: 'ai',
backend: {
service: 'klausur-service',
port: 8086,
basePath: '/api/v1',
endpoints: [
{ path: '/klausuren', method: 'GET', description: 'Alle Klausuren' },
{ path: '/klausuren', method: 'POST', description: 'Klausur erstellen' },
{ path: '/klausuren/{id}/students', method: 'GET', description: 'Studentenarbeiten' },
{ path: '/students/{id}/annotations', method: 'GET', description: 'Anmerkungen' },
{ path: '/students/{id}/gutachten/generate', method: 'POST', description: 'Gutachten generieren' },
]
},
frontend: {
adminV2Page: '/ai/klausur-korrektur',
oldAdminPage: '/admin/klausur-korrektur',
status: 'not-connected'
},
priority: 'high',
notes: 'Komplexes Modul mit eigenem Backend-Service'
},
{
id: 'ocr-labeling',
name: 'OCR-Labeling',
description: 'Handschrift-Training und Label-Verwaltung',
category: 'ai',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/ocr',
endpoints: [
{ path: '/samples', method: 'GET', description: 'Training-Samples' },
{ path: '/labels', method: 'GET', description: 'Label-Kategorien' },
{ path: '/train', method: 'POST', description: 'Training starten' },
]
},
frontend: {
adminV2Page: '/ai/ocr-labeling',
oldAdminPage: '/admin/ocr-labeling',
status: 'not-connected'
},
priority: 'medium'
},
{
id: 'rag-management',
name: 'RAG & Daten',
description: 'Retrieval Augmented Generation und Training Data',
category: 'ai',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/rag',
endpoints: [
{ path: '/documents', method: 'GET', description: 'RAG-Dokumente' },
{ path: '/collections', method: 'GET', description: 'Vector-Collections' },
{ path: '/query', method: 'POST', description: 'RAG-Abfrage' },
]
},
frontend: {
adminV2Page: '/ai/rag',
oldAdminPage: '/admin/rag',
status: 'connected'
},
priority: 'medium'
},
// ===========================================
// INFRASTRUCTURE MODULES
// ===========================================
{
id: 'gpu-infrastructure',
name: 'GPU Infrastruktur',
description: 'vast.ai GPU-Management und Monitoring',
category: 'infrastructure',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/gpu',
endpoints: [
{ path: '/instances', method: 'GET', description: 'GPU-Instanzen' },
{ path: '/instances', method: 'POST', description: 'Instanz erstellen' },
{ path: '/usage', method: 'GET', description: 'Nutzungsstatistiken' },
]
},
frontend: {
adminV2Page: '/infrastructure/gpu',
oldAdminPage: '/admin/gpu',
status: 'connected'
},
priority: 'medium'
},
{
id: 'security-dashboard',
name: 'Security Dashboard',
description: 'DevSecOps Dashboard und Vulnerability Scans',
category: 'infrastructure',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/security',
endpoints: [
{ path: '/scans', method: 'GET', description: 'Security-Scans' },
{ path: '/vulnerabilities', method: 'GET', description: 'Schwachstellen' },
{ path: '/compliance', method: 'GET', description: 'Compliance-Status' },
]
},
frontend: {
adminV2Page: '/infrastructure/security',
oldAdminPage: '/admin/security',
status: 'connected'
},
priority: 'high'
},
{
id: 'sbom',
name: 'SBOM',
description: 'Software Bill of Materials',
category: 'infrastructure',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/sbom',
endpoints: [
{ path: '/components', method: 'GET', description: 'Komponenten-Liste' },
{ path: '/licenses', method: 'GET', description: 'Lizenz-Uebersicht' },
{ path: '/export', method: 'GET', description: 'SBOM exportieren' },
]
},
frontend: {
adminV2Page: '/infrastructure/sbom',
oldAdminPage: '/admin/sbom',
status: 'connected'
},
priority: 'medium'
},
{
id: 'middleware',
name: 'Middleware Manager',
description: 'Verwaltung und Monitoring der Backend-Middleware',
category: 'infrastructure',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/middleware',
endpoints: [
{ path: '/status', method: 'GET', description: 'Middleware-Status' },
{ path: '/config', method: 'GET', description: 'Konfiguration' },
]
},
frontend: {
adminV2Page: '/infrastructure/middleware',
oldAdminPage: '/admin/middleware',
status: 'connected'
},
priority: 'medium'
},
{
id: 'ci-cd',
name: 'CI/CD Pipeline',
description: 'Build-Pipeline und Deployment-Management',
category: 'infrastructure',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/builds',
endpoints: [
{ path: '/pipelines', method: 'GET', description: 'Pipeline-Status' },
{ path: '/builds', method: 'GET', description: 'Build-Historie' },
]
},
frontend: {
adminV2Page: '/infrastructure/ci-cd',
oldAdminPage: '/admin/builds',
status: 'connected'
},
priority: 'medium'
},
// ===========================================
// EDUCATION MODULES
// ===========================================
{
id: 'edu-search',
name: 'Bildungssuche',
description: 'Suche nach Bildungsinhalten und Ressourcen',
category: 'education',
backend: {
service: 'edu-search-service',
port: 8089,
basePath: '/api/edu',
endpoints: [
{ path: '/search', method: 'GET', description: 'Bildungssuche' },
{ path: '/resources', method: 'GET', description: 'Ressourcen' },
]
},
frontend: {
adminV2Page: '/education/edu-search',
oldAdminPage: '/admin/edu-search',
status: 'connected'
},
priority: 'medium'
},
// ===========================================
// COMMUNICATION MODULES
// ===========================================
{
id: 'alerts',
name: 'Alerts & Benachrichtigungen',
description: 'System-Benachrichtigungen und Alerts',
category: 'communication',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/alerts',
endpoints: [
{ path: '/notifications', method: 'GET', description: 'Benachrichtigungen' },
{ path: '/alerts', method: 'GET', description: 'Aktive Alerts' },
]
},
frontend: {
adminV2Page: '/communication/alerts',
oldAdminPage: '/admin/alerts',
status: 'connected'
},
priority: 'medium'
},
{
id: 'unified-inbox',
name: 'Unified Inbox',
description: 'E-Mail-Konten und KI-Analyse',
category: 'communication',
backend: {
service: 'python-backend',
port: 8000,
basePath: '/api/mail',
endpoints: [
{ path: '/accounts', method: 'GET', description: 'E-Mail-Konten' },
{ path: '/messages', method: 'GET', description: 'Nachrichten' },
{ path: '/analyze', method: 'POST', description: 'KI-Analyse' },
]
},
frontend: {
adminV2Page: '/communication/mail',
oldAdminPage: '/admin/mail',
status: 'connected'
},
priority: 'low'
},
// ===========================================
// DEVELOPMENT MODULES
// ===========================================
{
id: 'voice-service',
name: 'Voice Service',
description: 'Voice-First Interface',
category: 'development',
backend: {
service: 'voice-service',
port: 8088,
basePath: '/api/voice',
endpoints: [
{ path: '/transcribe', method: 'POST', description: 'Sprache transkribieren' },
{ path: '/synthesize', method: 'POST', description: 'Text zu Sprache' },
]
},
frontend: {
adminV2Page: '/development/voice',
oldAdminPage: '/admin/voice',
status: 'not-connected'
},
priority: 'low'
},
]
// Import for use in helper functions
import { MODULE_REGISTRY } from './module-registry-data'
// Helper functions
export function getModulesByCategory(category: BackendModule['category']): BackendModule[] {

View File

@@ -0,0 +1,488 @@
/**
* SDK Catalog Manager - Registry Data
*
* Contains CATALOG_REGISTRY definitions and SYSTEM_ENTRIES_MAP.
* Separated from catalog-registry.ts to keep each file under 500 LOC.
*/
import type { CatalogId, CatalogMeta } from './types'
import { RISK_CATALOG } from '../dsfa/risk-catalog'
import { MITIGATION_LIBRARY } from '../dsfa/mitigation-library'
import { AI_RISK_CATALOG } from '../dsfa/ai-risk-catalog'
import { AI_MITIGATION_LIBRARY } from '../dsfa/ai-mitigation-library'
import { PROHIBITED_AI_PRACTICES } from '../dsfa/prohibited-ai-practices'
import { EU_BASE_FRAMEWORKS, NATIONAL_FRAMEWORKS } from '../dsfa/eu-legal-frameworks'
import { GDPR_ENFORCEMENT_CASES } from '../dsfa/gdpr-enforcement-cases'
import { WP248_CRITERIA, SDM_GOALS, DSFA_AUTHORITY_RESOURCES } from '../dsfa/types'
import { VVT_BASELINE_CATALOG } from '../vvt-baseline-catalog'
import { BASELINE_TEMPLATES } from '../loeschfristen-baseline-catalog'
import { VENDOR_TEMPLATES, COUNTRY_RISK_PROFILES } from '../vendor-compliance/catalog/vendor-templates'
import { LEGAL_BASIS_INFO, STANDARD_RETENTION_PERIODS } from '../vendor-compliance/catalog/legal-basis'
// SDM_GOALS as entries array (it's a Record, not an array)
const SDM_GOALS_ENTRIES = Object.entries(SDM_GOALS).map(([key, val]) => ({
id: key,
name: val.name,
description: val.description,
article: val.article,
}))
export const CATALOG_REGISTRY: Record<CatalogId, CatalogMeta> = {
'dsfa-risks': {
id: 'dsfa-risks',
name: 'DSFA Risikokatalog',
description: 'Standardrisiken fuer Datenschutz-Folgenabschaetzungen',
module: 'dsfa',
icon: 'ShieldAlert',
systemCount: RISK_CATALOG.length,
allowCustom: true,
idField: 'id',
nameField: 'title',
descriptionField: 'description',
categoryField: 'category',
fields: [
{ key: 'id', label: 'Risiko-ID', type: 'text', required: true, placeholder: 'R-XXX-01' },
{ key: 'title', label: 'Titel', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'category', label: 'Kategorie', type: 'select', required: true, options: [
{ value: 'confidentiality', label: 'Vertraulichkeit' },
{ value: 'integrity', label: 'Integritaet' },
{ value: 'availability', label: 'Verfuegbarkeit' },
{ value: 'rights_freedoms', label: 'Rechte & Freiheiten' },
]},
{ key: 'typicalLikelihood', label: 'Typische Eintrittswahrscheinlichkeit', type: 'select', required: false, options: [
{ value: 'low', label: 'Niedrig' },
{ value: 'medium', label: 'Mittel' },
{ value: 'high', label: 'Hoch' },
]},
{ key: 'typicalImpact', label: 'Typische Auswirkung', type: 'select', required: false, options: [
{ value: 'low', label: 'Niedrig' },
{ value: 'medium', label: 'Mittel' },
{ value: 'high', label: 'Hoch' },
]},
],
searchableFields: ['id', 'title', 'description', 'category'],
},
'dsfa-mitigations': {
id: 'dsfa-mitigations',
name: 'DSFA Massnahmenbibliothek',
description: 'Technische und organisatorische Massnahmen fuer DSFAs',
module: 'dsfa',
icon: 'Shield',
systemCount: MITIGATION_LIBRARY.length,
allowCustom: true,
idField: 'id',
nameField: 'title',
descriptionField: 'description',
categoryField: 'type',
fields: [
{ key: 'id', label: 'Massnahmen-ID', type: 'text', required: true, placeholder: 'M-XXX-01' },
{ key: 'title', label: 'Titel', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'type', label: 'Typ', type: 'select', required: true, options: [
{ value: 'technical', label: 'Technisch' },
{ value: 'organizational', label: 'Organisatorisch' },
{ value: 'legal', label: 'Rechtlich' },
]},
{ key: 'effectiveness', label: 'Wirksamkeit', type: 'select', required: false, options: [
{ value: 'low', label: 'Niedrig' },
{ value: 'medium', label: 'Mittel' },
{ value: 'high', label: 'Hoch' },
]},
{ key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: false },
],
searchableFields: ['id', 'title', 'description', 'type', 'legalBasis'],
},
'ai-risks': {
id: 'ai-risks',
name: 'KI-Risikokatalog',
description: 'Spezifische Risiken fuer KI-Systeme',
module: 'ai_act',
icon: 'Bot',
systemCount: AI_RISK_CATALOG.length,
allowCustom: true,
idField: 'id',
nameField: 'title',
descriptionField: 'description',
categoryField: 'category',
fields: [
{ key: 'id', label: 'Risiko-ID', type: 'text', required: true, placeholder: 'R-AI-XXX-01' },
{ key: 'title', label: 'Titel', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'category', label: 'Kategorie', type: 'select', required: true, options: [
{ value: 'confidentiality', label: 'Vertraulichkeit' },
{ value: 'integrity', label: 'Integritaet' },
{ value: 'availability', label: 'Verfuegbarkeit' },
{ value: 'rights_freedoms', label: 'Rechte & Freiheiten' },
]},
{ key: 'typicalLikelihood', label: 'Eintrittswahrscheinlichkeit', type: 'select', required: false, options: [
{ value: 'low', label: 'Niedrig' },
{ value: 'medium', label: 'Mittel' },
{ value: 'high', label: 'Hoch' },
]},
{ key: 'typicalImpact', label: 'Auswirkung', type: 'select', required: false, options: [
{ value: 'low', label: 'Niedrig' },
{ value: 'medium', label: 'Mittel' },
{ value: 'high', label: 'Hoch' },
]},
],
searchableFields: ['id', 'title', 'description', 'category'],
},
'ai-mitigations': {
id: 'ai-mitigations',
name: 'KI-Massnahmenbibliothek',
description: 'Massnahmen fuer KI-spezifische Risiken',
module: 'ai_act',
icon: 'ShieldCheck',
systemCount: AI_MITIGATION_LIBRARY.length,
allowCustom: true,
idField: 'id',
nameField: 'title',
descriptionField: 'description',
categoryField: 'type',
fields: [
{ key: 'id', label: 'Massnahmen-ID', type: 'text', required: true },
{ key: 'title', label: 'Titel', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'type', label: 'Typ', type: 'select', required: true, options: [
{ value: 'technical', label: 'Technisch' },
{ value: 'organizational', label: 'Organisatorisch' },
{ value: 'legal', label: 'Rechtlich' },
]},
{ key: 'effectiveness', label: 'Wirksamkeit', type: 'select', required: false, options: [
{ value: 'low', label: 'Niedrig' },
{ value: 'medium', label: 'Mittel' },
{ value: 'high', label: 'Hoch' },
]},
],
searchableFields: ['id', 'title', 'description', 'type'],
},
'prohibited-ai-practices': {
id: 'prohibited-ai-practices',
name: 'Verbotene KI-Praktiken',
description: 'Absolut und bedingt verbotene KI-Anwendungen nach AI Act',
module: 'ai_act',
icon: 'Ban',
systemCount: PROHIBITED_AI_PRACTICES.length,
allowCustom: false,
idField: 'id',
nameField: 'title',
descriptionField: 'description',
categoryField: 'severity',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'title', label: 'Titel', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'severity', label: 'Schwere', type: 'select', required: true, options: [
{ value: 'absolute', label: 'Absolutes Verbot' },
{ value: 'conditional', label: 'Bedingtes Verbot' },
]},
{ key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: false },
],
searchableFields: ['id', 'title', 'description', 'severity', 'legalBasis'],
},
'vvt-templates': {
id: 'vvt-templates',
name: 'VVT Baseline-Vorlagen',
description: 'Vorlagen fuer Verarbeitungstaetigkeiten',
module: 'vvt',
icon: 'FileText',
systemCount: VVT_BASELINE_CATALOG.length,
allowCustom: true,
idField: 'templateId',
nameField: 'name',
descriptionField: 'description',
categoryField: 'businessFunction',
fields: [
{ key: 'templateId', label: 'Vorlagen-ID', type: 'text', required: true },
{ key: 'name', label: 'Name', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'businessFunction', label: 'Geschaeftsbereich', type: 'select', required: true, options: [
{ value: 'hr', label: 'Personal' },
{ value: 'finance', label: 'Finanzen' },
{ value: 'sales', label: 'Vertrieb' },
{ value: 'marketing', label: 'Marketing' },
{ value: 'support', label: 'Support' },
{ value: 'it', label: 'IT' },
{ value: 'other', label: 'Sonstige' },
]},
{ key: 'protectionLevel', label: 'Schutzniveau', type: 'select', required: false, options: [
{ value: 'LOW', label: 'Niedrig' },
{ value: 'MEDIUM', label: 'Mittel' },
{ value: 'HIGH', label: 'Hoch' },
]},
],
searchableFields: ['templateId', 'name', 'description', 'businessFunction'],
},
'loeschfristen-templates': {
id: 'loeschfristen-templates',
name: 'Loeschfristen-Vorlagen',
description: 'Baseline-Vorlagen fuer Aufbewahrungsfristen',
module: 'vvt',
icon: 'Clock',
systemCount: BASELINE_TEMPLATES.length,
allowCustom: true,
idField: 'templateId',
nameField: 'dataObjectName',
descriptionField: 'description',
categoryField: 'retentionDriver',
fields: [
{ key: 'templateId', label: 'Vorlagen-ID', type: 'text', required: true },
{ key: 'dataObjectName', label: 'Datenobjekt', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'retentionDuration', label: 'Aufbewahrungsdauer', type: 'number', required: true, min: 0 },
{ key: 'retentionUnit', label: 'Einheit', type: 'select', required: true, options: [
{ value: 'days', label: 'Tage' },
{ value: 'months', label: 'Monate' },
{ value: 'years', label: 'Jahre' },
]},
{ key: 'deletionMethod', label: 'Loeschmethode', type: 'text', required: false },
{ key: 'responsibleRole', label: 'Verantwortlich', type: 'text', required: false },
],
searchableFields: ['templateId', 'dataObjectName', 'description', 'retentionDriver'],
},
'vendor-templates': {
id: 'vendor-templates',
name: 'AV-Vorlagen',
description: 'Vorlagen fuer Auftragsverarbeitungsvertraege',
module: 'vendor',
icon: 'Building2',
systemCount: VENDOR_TEMPLATES.length,
allowCustom: true,
idField: 'id',
nameField: 'name',
descriptionField: 'description',
categoryField: 'serviceCategory',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'name', label: 'Name', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'serviceCategory', label: 'Kategorie', type: 'text', required: true },
],
searchableFields: ['id', 'name', 'description', 'serviceCategory'],
},
'country-risk-profiles': {
id: 'country-risk-profiles',
name: 'Laenderrisikoprofile',
description: 'Datenschutz-Risikobewertung nach Laendern',
module: 'vendor',
icon: 'Globe',
systemCount: COUNTRY_RISK_PROFILES.length,
allowCustom: false,
idField: 'code',
nameField: 'name',
categoryField: 'riskLevel',
fields: [
{ key: 'code', label: 'Laendercode', type: 'text', required: true },
{ key: 'name', label: 'Land', type: 'text', required: true },
{ key: 'riskLevel', label: 'Risikostufe', type: 'select', required: true, options: [
{ value: 'LOW', label: 'Niedrig' },
{ value: 'MEDIUM', label: 'Mittel' },
{ value: 'HIGH', label: 'Hoch' },
{ value: 'VERY_HIGH', label: 'Sehr hoch' },
]},
{ key: 'isEU', label: 'EU-Mitglied', type: 'boolean', required: false },
{ key: 'isEEA', label: 'EWR-Mitglied', type: 'boolean', required: false },
{ key: 'hasAdequacyDecision', label: 'Angemessenheitsbeschluss', type: 'boolean', required: false },
],
searchableFields: ['code', 'name', 'riskLevel'],
},
'legal-bases': {
id: 'legal-bases',
name: 'Rechtsgrundlagen',
description: 'DSGVO Art. 6 und Art. 9 Rechtsgrundlagen',
module: 'reference',
icon: 'Scale',
systemCount: LEGAL_BASIS_INFO.length,
allowCustom: false,
idField: 'type',
nameField: 'name',
descriptionField: 'description',
categoryField: 'article',
fields: [
{ key: 'type', label: 'Typ', type: 'text', required: true },
{ key: 'article', label: 'Artikel', type: 'text', required: true },
{ key: 'name', label: 'Name', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'isSpecialCategory', label: 'Besondere Kategorie (Art. 9)', type: 'boolean', required: false },
],
searchableFields: ['type', 'article', 'name', 'description'],
},
'retention-periods': {
id: 'retention-periods',
name: 'Aufbewahrungsfristen',
description: 'Gesetzliche Standard-Aufbewahrungsfristen',
module: 'reference',
icon: 'Timer',
systemCount: STANDARD_RETENTION_PERIODS.length,
allowCustom: false,
idField: 'id',
nameField: 'name',
descriptionField: 'description',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'name', label: 'Name', type: 'text', required: true },
{ key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
],
searchableFields: ['id', 'name', 'legalBasis', 'description'],
},
'eu-legal-frameworks': {
id: 'eu-legal-frameworks',
name: 'EU-Rechtsrahmen',
description: 'EU-weite Datenschutzgesetze und -verordnungen',
module: 'reference',
icon: 'Landmark',
systemCount: EU_BASE_FRAMEWORKS.length,
allowCustom: false,
idField: 'id',
nameField: 'name',
descriptionField: 'description',
categoryField: 'type',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'name', label: 'Name', type: 'text', required: true },
{ key: 'fullName', label: 'Vollstaendiger Name', type: 'text', required: true },
{ key: 'type', label: 'Typ', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
],
searchableFields: ['id', 'name', 'fullName', 'description', 'type'],
},
'national-legal-frameworks': {
id: 'national-legal-frameworks',
name: 'Nationale Rechtsrahmen',
description: 'Nationale Datenschutzgesetze der EU/EWR-Staaten',
module: 'reference',
icon: 'Flag',
systemCount: NATIONAL_FRAMEWORKS.length,
allowCustom: false,
idField: 'id',
nameField: 'name',
descriptionField: 'description',
categoryField: 'countryCode',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'name', label: 'Name', type: 'text', required: true },
{ key: 'countryCode', label: 'Land', type: 'text', required: true },
{ key: 'type', label: 'Typ', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
],
searchableFields: ['id', 'name', 'countryCode', 'description', 'type'],
},
'gdpr-enforcement-cases': {
id: 'gdpr-enforcement-cases',
name: 'DSGVO-Bussgeldentscheidungen',
description: 'Relevante Bussgeldentscheidungen als Referenz',
module: 'reference',
icon: 'Gavel',
systemCount: GDPR_ENFORCEMENT_CASES.length,
allowCustom: false,
idField: 'id',
nameField: 'company',
descriptionField: 'description',
categoryField: 'country',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'company', label: 'Unternehmen', type: 'text', required: true },
{ key: 'country', label: 'Land', type: 'text', required: true },
{ key: 'year', label: 'Jahr', type: 'number', required: true },
{ key: 'fineOriginal', label: 'Bussgeld (EUR)', type: 'number', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
],
searchableFields: ['id', 'company', 'country', 'description'],
},
'wp248-criteria': {
id: 'wp248-criteria',
name: 'WP248 Kriterien',
description: 'Kriterien zur DSFA-Pflichtpruefung nach WP248',
module: 'dsfa',
icon: 'ClipboardCheck',
systemCount: WP248_CRITERIA.length,
allowCustom: false,
idField: 'code',
nameField: 'title',
descriptionField: 'description',
fields: [
{ key: 'code', label: 'Code', type: 'text', required: true },
{ key: 'title', label: 'Titel', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'gdprRef', label: 'DSGVO-Referenz', type: 'text', required: false },
],
searchableFields: ['code', 'title', 'description', 'gdprRef'],
},
'sdm-goals': {
id: 'sdm-goals',
name: 'SDM Gewaehrleistungsziele',
description: 'Standard-Datenschutzmodell Gewaehrleistungsziele',
module: 'dsfa',
icon: 'Target',
systemCount: SDM_GOALS_ENTRIES.length,
allowCustom: false,
idField: 'id',
nameField: 'name',
descriptionField: 'description',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'name', label: 'Name', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'article', label: 'DSGVO-Artikel', type: 'text', required: false },
],
searchableFields: ['id', 'name', 'description', 'article'],
},
'dsfa-authority-resources': {
id: 'dsfa-authority-resources',
name: 'Aufsichtsbehoerden-Ressourcen',
description: 'DSFA-Ressourcen der deutschen Aufsichtsbehoerden',
module: 'dsfa',
icon: 'Building',
systemCount: DSFA_AUTHORITY_RESOURCES.length,
allowCustom: false,
idField: 'id',
nameField: 'shortName',
descriptionField: 'name',
categoryField: 'state',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'shortName', label: 'Kurzname', type: 'text', required: true },
{ key: 'name', label: 'Voller Name', type: 'text', required: true },
{ key: 'state', label: 'Bundesland', type: 'text', required: true },
],
searchableFields: ['id', 'shortName', 'name', 'state'],
},
}
export const SYSTEM_ENTRIES_MAP: Record<CatalogId, Record<string, unknown>[]> = {
'dsfa-risks': RISK_CATALOG as unknown as Record<string, unknown>[],
'dsfa-mitigations': MITIGATION_LIBRARY as unknown as Record<string, unknown>[],
'ai-risks': AI_RISK_CATALOG as unknown as Record<string, unknown>[],
'ai-mitigations': AI_MITIGATION_LIBRARY as unknown as Record<string, unknown>[],
'prohibited-ai-practices': PROHIBITED_AI_PRACTICES as unknown as Record<string, unknown>[],
'vvt-templates': VVT_BASELINE_CATALOG as unknown as Record<string, unknown>[],
'loeschfristen-templates': BASELINE_TEMPLATES as unknown as Record<string, unknown>[],
'vendor-templates': VENDOR_TEMPLATES as unknown as Record<string, unknown>[],
'country-risk-profiles': COUNTRY_RISK_PROFILES as unknown as Record<string, unknown>[],
'legal-bases': LEGAL_BASIS_INFO as unknown as Record<string, unknown>[],
'retention-periods': STANDARD_RETENTION_PERIODS as unknown as Record<string, unknown>[],
'eu-legal-frameworks': EU_BASE_FRAMEWORKS as unknown as Record<string, unknown>[],
'national-legal-frameworks': NATIONAL_FRAMEWORKS as unknown as Record<string, unknown>[],
'gdpr-enforcement-cases': GDPR_ENFORCEMENT_CASES as unknown as Record<string, unknown>[],
'wp248-criteria': WP248_CRITERIA as unknown as Record<string, unknown>[],
'sdm-goals': SDM_GOALS_ENTRIES as unknown as Record<string, unknown>[],
'dsfa-authority-resources': DSFA_AUTHORITY_RESOURCES as unknown as Record<string, unknown>[],
}

View File

@@ -1,7 +1,8 @@
/**
* SDK Catalog Manager - Central Registry
*
* Maps all SDK catalogs to a unified interface for browsing, searching, and CRUD.
*
* Public API for browsing, searching, and converting catalog entries.
* Registry data definitions live in catalog-registry-data.ts.
*/
import type {
@@ -15,22 +16,10 @@ import type {
CustomCatalogs,
} from './types'
// =============================================================================
// CATALOG DATA IMPORTS
// =============================================================================
import { CATALOG_REGISTRY, SYSTEM_ENTRIES_MAP } from './catalog-registry-data'
import { RISK_CATALOG } from '../dsfa/risk-catalog'
import { MITIGATION_LIBRARY } from '../dsfa/mitigation-library'
import { AI_RISK_CATALOG } from '../dsfa/ai-risk-catalog'
import { AI_MITIGATION_LIBRARY } from '../dsfa/ai-mitigation-library'
import { PROHIBITED_AI_PRACTICES } from '../dsfa/prohibited-ai-practices'
import { EU_BASE_FRAMEWORKS, NATIONAL_FRAMEWORKS } from '../dsfa/eu-legal-frameworks'
import { GDPR_ENFORCEMENT_CASES } from '../dsfa/gdpr-enforcement-cases'
import { WP248_CRITERIA, SDM_GOALS, DSFA_AUTHORITY_RESOURCES } from '../dsfa/types'
import { VVT_BASELINE_CATALOG } from '../vvt-baseline-catalog'
import { BASELINE_TEMPLATES } from '../loeschfristen-baseline-catalog'
import { VENDOR_TEMPLATES, COUNTRY_RISK_PROFILES } from '../vendor-compliance/catalog/vendor-templates'
import { LEGAL_BASIS_INFO, STANDARD_RETENTION_PERIODS } from '../vendor-compliance/catalog/legal-basis'
// Re-export so existing imports keep working
export { CATALOG_REGISTRY }
// =============================================================================
// HELPER: Resolve localized text fields
@@ -46,485 +35,6 @@ function resolveField(value: unknown): string {
return String(value)
}
// =============================================================================
// SDM_GOALS as entries array (it's a Record, not an array)
// =============================================================================
const SDM_GOALS_ENTRIES = Object.entries(SDM_GOALS).map(([key, val]) => ({
id: key,
name: val.name,
description: val.description,
article: val.article,
}))
// =============================================================================
// CATALOG REGISTRY
// =============================================================================
export const CATALOG_REGISTRY: Record<CatalogId, CatalogMeta> = {
'dsfa-risks': {
id: 'dsfa-risks',
name: 'DSFA Risikokatalog',
description: 'Standardrisiken fuer Datenschutz-Folgenabschaetzungen',
module: 'dsfa',
icon: 'ShieldAlert',
systemCount: RISK_CATALOG.length,
allowCustom: true,
idField: 'id',
nameField: 'title',
descriptionField: 'description',
categoryField: 'category',
fields: [
{ key: 'id', label: 'Risiko-ID', type: 'text', required: true, placeholder: 'R-XXX-01' },
{ key: 'title', label: 'Titel', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'category', label: 'Kategorie', type: 'select', required: true, options: [
{ value: 'confidentiality', label: 'Vertraulichkeit' },
{ value: 'integrity', label: 'Integritaet' },
{ value: 'availability', label: 'Verfuegbarkeit' },
{ value: 'rights_freedoms', label: 'Rechte & Freiheiten' },
]},
{ key: 'typicalLikelihood', label: 'Typische Eintrittswahrscheinlichkeit', type: 'select', required: false, options: [
{ value: 'low', label: 'Niedrig' },
{ value: 'medium', label: 'Mittel' },
{ value: 'high', label: 'Hoch' },
]},
{ key: 'typicalImpact', label: 'Typische Auswirkung', type: 'select', required: false, options: [
{ value: 'low', label: 'Niedrig' },
{ value: 'medium', label: 'Mittel' },
{ value: 'high', label: 'Hoch' },
]},
],
searchableFields: ['id', 'title', 'description', 'category'],
},
'dsfa-mitigations': {
id: 'dsfa-mitigations',
name: 'DSFA Massnahmenbibliothek',
description: 'Technische und organisatorische Massnahmen fuer DSFAs',
module: 'dsfa',
icon: 'Shield',
systemCount: MITIGATION_LIBRARY.length,
allowCustom: true,
idField: 'id',
nameField: 'title',
descriptionField: 'description',
categoryField: 'type',
fields: [
{ key: 'id', label: 'Massnahmen-ID', type: 'text', required: true, placeholder: 'M-XXX-01' },
{ key: 'title', label: 'Titel', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'type', label: 'Typ', type: 'select', required: true, options: [
{ value: 'technical', label: 'Technisch' },
{ value: 'organizational', label: 'Organisatorisch' },
{ value: 'legal', label: 'Rechtlich' },
]},
{ key: 'effectiveness', label: 'Wirksamkeit', type: 'select', required: false, options: [
{ value: 'low', label: 'Niedrig' },
{ value: 'medium', label: 'Mittel' },
{ value: 'high', label: 'Hoch' },
]},
{ key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: false },
],
searchableFields: ['id', 'title', 'description', 'type', 'legalBasis'],
},
'ai-risks': {
id: 'ai-risks',
name: 'KI-Risikokatalog',
description: 'Spezifische Risiken fuer KI-Systeme',
module: 'ai_act',
icon: 'Bot',
systemCount: AI_RISK_CATALOG.length,
allowCustom: true,
idField: 'id',
nameField: 'title',
descriptionField: 'description',
categoryField: 'category',
fields: [
{ key: 'id', label: 'Risiko-ID', type: 'text', required: true, placeholder: 'R-AI-XXX-01' },
{ key: 'title', label: 'Titel', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'category', label: 'Kategorie', type: 'select', required: true, options: [
{ value: 'confidentiality', label: 'Vertraulichkeit' },
{ value: 'integrity', label: 'Integritaet' },
{ value: 'availability', label: 'Verfuegbarkeit' },
{ value: 'rights_freedoms', label: 'Rechte & Freiheiten' },
]},
{ key: 'typicalLikelihood', label: 'Eintrittswahrscheinlichkeit', type: 'select', required: false, options: [
{ value: 'low', label: 'Niedrig' },
{ value: 'medium', label: 'Mittel' },
{ value: 'high', label: 'Hoch' },
]},
{ key: 'typicalImpact', label: 'Auswirkung', type: 'select', required: false, options: [
{ value: 'low', label: 'Niedrig' },
{ value: 'medium', label: 'Mittel' },
{ value: 'high', label: 'Hoch' },
]},
],
searchableFields: ['id', 'title', 'description', 'category'],
},
'ai-mitigations': {
id: 'ai-mitigations',
name: 'KI-Massnahmenbibliothek',
description: 'Massnahmen fuer KI-spezifische Risiken',
module: 'ai_act',
icon: 'ShieldCheck',
systemCount: AI_MITIGATION_LIBRARY.length,
allowCustom: true,
idField: 'id',
nameField: 'title',
descriptionField: 'description',
categoryField: 'type',
fields: [
{ key: 'id', label: 'Massnahmen-ID', type: 'text', required: true },
{ key: 'title', label: 'Titel', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'type', label: 'Typ', type: 'select', required: true, options: [
{ value: 'technical', label: 'Technisch' },
{ value: 'organizational', label: 'Organisatorisch' },
{ value: 'legal', label: 'Rechtlich' },
]},
{ key: 'effectiveness', label: 'Wirksamkeit', type: 'select', required: false, options: [
{ value: 'low', label: 'Niedrig' },
{ value: 'medium', label: 'Mittel' },
{ value: 'high', label: 'Hoch' },
]},
],
searchableFields: ['id', 'title', 'description', 'type'],
},
'prohibited-ai-practices': {
id: 'prohibited-ai-practices',
name: 'Verbotene KI-Praktiken',
description: 'Absolut und bedingt verbotene KI-Anwendungen nach AI Act',
module: 'ai_act',
icon: 'Ban',
systemCount: PROHIBITED_AI_PRACTICES.length,
allowCustom: false,
idField: 'id',
nameField: 'title',
descriptionField: 'description',
categoryField: 'severity',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'title', label: 'Titel', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'severity', label: 'Schwere', type: 'select', required: true, options: [
{ value: 'absolute', label: 'Absolutes Verbot' },
{ value: 'conditional', label: 'Bedingtes Verbot' },
]},
{ key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: false },
],
searchableFields: ['id', 'title', 'description', 'severity', 'legalBasis'],
},
'vvt-templates': {
id: 'vvt-templates',
name: 'VVT Baseline-Vorlagen',
description: 'Vorlagen fuer Verarbeitungstaetigkeiten',
module: 'vvt',
icon: 'FileText',
systemCount: VVT_BASELINE_CATALOG.length,
allowCustom: true,
idField: 'templateId',
nameField: 'name',
descriptionField: 'description',
categoryField: 'businessFunction',
fields: [
{ key: 'templateId', label: 'Vorlagen-ID', type: 'text', required: true },
{ key: 'name', label: 'Name', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'businessFunction', label: 'Geschaeftsbereich', type: 'select', required: true, options: [
{ value: 'hr', label: 'Personal' },
{ value: 'finance', label: 'Finanzen' },
{ value: 'sales', label: 'Vertrieb' },
{ value: 'marketing', label: 'Marketing' },
{ value: 'support', label: 'Support' },
{ value: 'it', label: 'IT' },
{ value: 'other', label: 'Sonstige' },
]},
{ key: 'protectionLevel', label: 'Schutzniveau', type: 'select', required: false, options: [
{ value: 'LOW', label: 'Niedrig' },
{ value: 'MEDIUM', label: 'Mittel' },
{ value: 'HIGH', label: 'Hoch' },
]},
],
searchableFields: ['templateId', 'name', 'description', 'businessFunction'],
},
'loeschfristen-templates': {
id: 'loeschfristen-templates',
name: 'Loeschfristen-Vorlagen',
description: 'Baseline-Vorlagen fuer Aufbewahrungsfristen',
module: 'vvt',
icon: 'Clock',
systemCount: BASELINE_TEMPLATES.length,
allowCustom: true,
idField: 'templateId',
nameField: 'dataObjectName',
descriptionField: 'description',
categoryField: 'retentionDriver',
fields: [
{ key: 'templateId', label: 'Vorlagen-ID', type: 'text', required: true },
{ key: 'dataObjectName', label: 'Datenobjekt', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'retentionDuration', label: 'Aufbewahrungsdauer', type: 'number', required: true, min: 0 },
{ key: 'retentionUnit', label: 'Einheit', type: 'select', required: true, options: [
{ value: 'days', label: 'Tage' },
{ value: 'months', label: 'Monate' },
{ value: 'years', label: 'Jahre' },
]},
{ key: 'deletionMethod', label: 'Loeschmethode', type: 'text', required: false },
{ key: 'responsibleRole', label: 'Verantwortlich', type: 'text', required: false },
],
searchableFields: ['templateId', 'dataObjectName', 'description', 'retentionDriver'],
},
'vendor-templates': {
id: 'vendor-templates',
name: 'AV-Vorlagen',
description: 'Vorlagen fuer Auftragsverarbeitungsvertraege',
module: 'vendor',
icon: 'Building2',
systemCount: VENDOR_TEMPLATES.length,
allowCustom: true,
idField: 'id',
nameField: 'name',
descriptionField: 'description',
categoryField: 'serviceCategory',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'name', label: 'Name', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'serviceCategory', label: 'Kategorie', type: 'text', required: true },
],
searchableFields: ['id', 'name', 'description', 'serviceCategory'],
},
'country-risk-profiles': {
id: 'country-risk-profiles',
name: 'Laenderrisikoprofile',
description: 'Datenschutz-Risikobewertung nach Laendern',
module: 'vendor',
icon: 'Globe',
systemCount: COUNTRY_RISK_PROFILES.length,
allowCustom: false,
idField: 'code',
nameField: 'name',
categoryField: 'riskLevel',
fields: [
{ key: 'code', label: 'Laendercode', type: 'text', required: true },
{ key: 'name', label: 'Land', type: 'text', required: true },
{ key: 'riskLevel', label: 'Risikostufe', type: 'select', required: true, options: [
{ value: 'LOW', label: 'Niedrig' },
{ value: 'MEDIUM', label: 'Mittel' },
{ value: 'HIGH', label: 'Hoch' },
{ value: 'VERY_HIGH', label: 'Sehr hoch' },
]},
{ key: 'isEU', label: 'EU-Mitglied', type: 'boolean', required: false },
{ key: 'isEEA', label: 'EWR-Mitglied', type: 'boolean', required: false },
{ key: 'hasAdequacyDecision', label: 'Angemessenheitsbeschluss', type: 'boolean', required: false },
],
searchableFields: ['code', 'name', 'riskLevel'],
},
'legal-bases': {
id: 'legal-bases',
name: 'Rechtsgrundlagen',
description: 'DSGVO Art. 6 und Art. 9 Rechtsgrundlagen',
module: 'reference',
icon: 'Scale',
systemCount: LEGAL_BASIS_INFO.length,
allowCustom: false,
idField: 'type',
nameField: 'name',
descriptionField: 'description',
categoryField: 'article',
fields: [
{ key: 'type', label: 'Typ', type: 'text', required: true },
{ key: 'article', label: 'Artikel', type: 'text', required: true },
{ key: 'name', label: 'Name', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'isSpecialCategory', label: 'Besondere Kategorie (Art. 9)', type: 'boolean', required: false },
],
searchableFields: ['type', 'article', 'name', 'description'],
},
'retention-periods': {
id: 'retention-periods',
name: 'Aufbewahrungsfristen',
description: 'Gesetzliche Standard-Aufbewahrungsfristen',
module: 'reference',
icon: 'Timer',
systemCount: STANDARD_RETENTION_PERIODS.length,
allowCustom: false,
idField: 'id',
nameField: 'name',
descriptionField: 'description',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'name', label: 'Name', type: 'text', required: true },
{ key: 'legalBasis', label: 'Rechtsgrundlage', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
],
searchableFields: ['id', 'name', 'legalBasis', 'description'],
},
'eu-legal-frameworks': {
id: 'eu-legal-frameworks',
name: 'EU-Rechtsrahmen',
description: 'EU-weite Datenschutzgesetze und -verordnungen',
module: 'reference',
icon: 'Landmark',
systemCount: EU_BASE_FRAMEWORKS.length,
allowCustom: false,
idField: 'id',
nameField: 'name',
descriptionField: 'description',
categoryField: 'type',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'name', label: 'Name', type: 'text', required: true },
{ key: 'fullName', label: 'Vollstaendiger Name', type: 'text', required: true },
{ key: 'type', label: 'Typ', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
],
searchableFields: ['id', 'name', 'fullName', 'description', 'type'],
},
'national-legal-frameworks': {
id: 'national-legal-frameworks',
name: 'Nationale Rechtsrahmen',
description: 'Nationale Datenschutzgesetze der EU/EWR-Staaten',
module: 'reference',
icon: 'Flag',
systemCount: NATIONAL_FRAMEWORKS.length,
allowCustom: false,
idField: 'id',
nameField: 'name',
descriptionField: 'description',
categoryField: 'countryCode',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'name', label: 'Name', type: 'text', required: true },
{ key: 'countryCode', label: 'Land', type: 'text', required: true },
{ key: 'type', label: 'Typ', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
],
searchableFields: ['id', 'name', 'countryCode', 'description', 'type'],
},
'gdpr-enforcement-cases': {
id: 'gdpr-enforcement-cases',
name: 'DSGVO-Bussgeldentscheidungen',
description: 'Relevante Bussgeldentscheidungen als Referenz',
module: 'reference',
icon: 'Gavel',
systemCount: GDPR_ENFORCEMENT_CASES.length,
allowCustom: false,
idField: 'id',
nameField: 'company',
descriptionField: 'description',
categoryField: 'country',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'company', label: 'Unternehmen', type: 'text', required: true },
{ key: 'country', label: 'Land', type: 'text', required: true },
{ key: 'year', label: 'Jahr', type: 'number', required: true },
{ key: 'fineOriginal', label: 'Bussgeld (EUR)', type: 'number', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
],
searchableFields: ['id', 'company', 'country', 'description'],
},
'wp248-criteria': {
id: 'wp248-criteria',
name: 'WP248 Kriterien',
description: 'Kriterien zur DSFA-Pflichtpruefung nach WP248',
module: 'dsfa',
icon: 'ClipboardCheck',
systemCount: WP248_CRITERIA.length,
allowCustom: false,
idField: 'code',
nameField: 'title',
descriptionField: 'description',
fields: [
{ key: 'code', label: 'Code', type: 'text', required: true },
{ key: 'title', label: 'Titel', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'gdprRef', label: 'DSGVO-Referenz', type: 'text', required: false },
],
searchableFields: ['code', 'title', 'description', 'gdprRef'],
},
'sdm-goals': {
id: 'sdm-goals',
name: 'SDM Gewaehrleistungsziele',
description: 'Standard-Datenschutzmodell Gewaehrleistungsziele',
module: 'dsfa',
icon: 'Target',
systemCount: SDM_GOALS_ENTRIES.length,
allowCustom: false,
idField: 'id',
nameField: 'name',
descriptionField: 'description',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'name', label: 'Name', type: 'text', required: true },
{ key: 'description', label: 'Beschreibung', type: 'textarea', required: true },
{ key: 'article', label: 'DSGVO-Artikel', type: 'text', required: false },
],
searchableFields: ['id', 'name', 'description', 'article'],
},
'dsfa-authority-resources': {
id: 'dsfa-authority-resources',
name: 'Aufsichtsbehoerden-Ressourcen',
description: 'DSFA-Ressourcen der deutschen Aufsichtsbehoerden',
module: 'dsfa',
icon: 'Building',
systemCount: DSFA_AUTHORITY_RESOURCES.length,
allowCustom: false,
idField: 'id',
nameField: 'shortName',
descriptionField: 'name',
categoryField: 'state',
fields: [
{ key: 'id', label: 'ID', type: 'text', required: true },
{ key: 'shortName', label: 'Kurzname', type: 'text', required: true },
{ key: 'name', label: 'Voller Name', type: 'text', required: true },
{ key: 'state', label: 'Bundesland', type: 'text', required: true },
],
searchableFields: ['id', 'shortName', 'name', 'state'],
},
}
// =============================================================================
// SYSTEM ENTRIES MAP
// =============================================================================
const SYSTEM_ENTRIES_MAP: Record<CatalogId, Record<string, unknown>[]> = {
'dsfa-risks': RISK_CATALOG as unknown as Record<string, unknown>[],
'dsfa-mitigations': MITIGATION_LIBRARY as unknown as Record<string, unknown>[],
'ai-risks': AI_RISK_CATALOG as unknown as Record<string, unknown>[],
'ai-mitigations': AI_MITIGATION_LIBRARY as unknown as Record<string, unknown>[],
'prohibited-ai-practices': PROHIBITED_AI_PRACTICES as unknown as Record<string, unknown>[],
'vvt-templates': VVT_BASELINE_CATALOG as unknown as Record<string, unknown>[],
'loeschfristen-templates': BASELINE_TEMPLATES as unknown as Record<string, unknown>[],
'vendor-templates': VENDOR_TEMPLATES as unknown as Record<string, unknown>[],
'country-risk-profiles': COUNTRY_RISK_PROFILES as unknown as Record<string, unknown>[],
'legal-bases': LEGAL_BASIS_INFO as unknown as Record<string, unknown>[],
'retention-periods': STANDARD_RETENTION_PERIODS as unknown as Record<string, unknown>[],
'eu-legal-frameworks': EU_BASE_FRAMEWORKS as unknown as Record<string, unknown>[],
'national-legal-frameworks': NATIONAL_FRAMEWORKS as unknown as Record<string, unknown>[],
'gdpr-enforcement-cases': GDPR_ENFORCEMENT_CASES as unknown as Record<string, unknown>[],
'wp248-criteria': WP248_CRITERIA as unknown as Record<string, unknown>[],
'sdm-goals': SDM_GOALS_ENTRIES as unknown as Record<string, unknown>[],
'dsfa-authority-resources': DSFA_AUTHORITY_RESOURCES as unknown as Record<string, unknown>[],
}
// =============================================================================
// ENTRY CONVERTERS
// =============================================================================

View File

@@ -0,0 +1,104 @@
/**
* DSFA API Types - Request/response types and UCCA integration
*
* All types used for API communication with the DSFA backend.
*/
import type { DSFAStatus, DSFARiskLevel } from './types-core'
import type { DSFA } from './types-assessment'
// =============================================================================
// API REQUEST/RESPONSE TYPES
// =============================================================================
export interface DSFAListResponse {
dsfas: DSFA[]
}
export interface DSFAStatsResponse {
status_stats: Record<DSFAStatus | 'total', number>
risk_stats: Record<DSFARiskLevel, number>
total: number
}
export interface CreateDSFARequest {
name: string
description?: string
processing_description?: string
processing_purpose?: string
data_categories?: string[]
legal_basis?: string
}
export interface CreateDSFAFromAssessmentRequest {
name?: string
description?: string
}
export interface CreateDSFAFromAssessmentResponse {
dsfa: DSFA
prefilled: boolean
assessment: unknown // UCCA Assessment
message: string
}
export interface UpdateDSFASectionRequest {
// Section 1
processing_description?: string
processing_purpose?: string
data_categories?: string[]
data_subjects?: string[]
recipients?: string[]
legal_basis?: string
legal_basis_details?: string
// Section 2
necessity_assessment?: string
proportionality_assessment?: string
data_minimization?: string
alternatives_considered?: string
retention_justification?: string
// Section 3
overall_risk_level?: DSFARiskLevel
risk_score?: number
affected_rights?: string[]
// Section 5
dpo_consulted?: boolean
dpo_name?: string
dpo_opinion?: string
authority_consulted?: boolean
authority_reference?: string
authority_decision?: string
}
export interface SubmitForReviewResponse {
message: string
dsfa: DSFA
}
export interface ApproveDSFARequest {
dpo_opinion: string
approved: boolean
}
// =============================================================================
// UCCA INTEGRATION TYPES
// =============================================================================
export interface DSFATriggerInfo {
required: boolean
reason: string
triggered_rules: string[]
assessment_id?: string
existing_dsfa_id?: string
}
export interface UCCATriggeredRule {
code: string
title: string
description: string
severity: 'INFO' | 'WARN' | 'BLOCK'
gdpr_ref?: string
}

View File

@@ -0,0 +1,246 @@
/**
* DSFA Assessment Types - Sub-types and main DSFA interface
*
* Risk, mitigation, review, threshold analysis, stakeholder consultation,
* and the main DSFA data model.
*/
import type {
DSFAStatus,
DSFARiskLevel,
DSFARiskCategory,
DSFAMitigationType,
DSFAMitigationStatus,
} from './types-core'
// =============================================================================
// SUB-TYPES
// =============================================================================
export interface DSFARisk {
id: string
category: DSFARiskCategory
description: string
likelihood: 'low' | 'medium' | 'high'
impact: 'low' | 'medium' | 'high'
risk_level: string
affected_data: string[]
}
export interface DSFAMitigation {
id: string
risk_id: string
description: string
type: DSFAMitigationType
status: DSFAMitigationStatus
implemented_at?: string
verified_at?: string
residual_risk: 'low' | 'medium' | 'high'
tom_reference?: string
responsible_party: string
}
export interface DSFAReviewComment {
id: string
section: number
comment: string
created_by: string
created_at: string
resolved: boolean
}
export interface DSFASectionProgress {
section_0_complete: boolean // Schwellwertanalyse
section_1_complete: boolean // Systematische Beschreibung
section_2_complete: boolean // Notwendigkeit & Verhältnismäßigkeit
section_3_complete: boolean // Risikobewertung
section_4_complete: boolean // Abhilfemaßnahmen
section_5_complete: boolean // Betroffenenperspektive (optional)
section_6_complete: boolean // DSB & Behördenkonsultation
section_7_complete: boolean // Fortschreibung & Review
}
// =============================================================================
// SCHWELLWERTANALYSE / VORABPRÜFUNG (Art. 35 Abs. 1 DSGVO)
// =============================================================================
export interface DSFAThresholdAnalysis {
id: string
dsfa_id?: string
performed_at: string
performed_by: string
// WP248 Kriterien-Bewertung
criteria_assessment: Array<{
criterion_id: string // K1-K9
applies: boolean
justification: string
}>
// Art. 35 Abs. 3 Prüfung
art35_abs3_assessment: Array<{
case_id: string // a, b, c
applies: boolean
justification: string
}>
// Ergebnis
dsfa_required: boolean
decision_justification: string
// Dokumentation der Entscheidung (gem. DSK Kurzpapier Nr. 5)
documented: boolean
documentation_reference?: string
}
// =============================================================================
// BETROFFENENPERSPEKTIVE (Art. 35 Abs. 9 DSGVO)
// =============================================================================
export interface DSFAStakeholderConsultation {
id: string
stakeholder_type: 'data_subjects' | 'representatives' | 'works_council' | 'other'
stakeholder_description: string
consultation_date?: string
consultation_method: 'survey' | 'interview' | 'workshop' | 'written' | 'other'
summary: string
concerns_raised: string[]
addressed_in_dsfa: boolean
response_documentation?: string
}
// =============================================================================
// ART. 36 KONSULTATIONSPFLICHT
// =============================================================================
export interface DSFAConsultationRequirement {
high_residual_risk: boolean
consultation_required: boolean // Art. 36 Abs. 1 DSGVO
consultation_reason?: string
authority_notified: boolean
notification_date?: string
authority_response?: string
authority_recommendations?: string[]
waiting_period_observed: boolean // 8 Wochen gem. Art. 36 Abs. 2
}
// =============================================================================
// FORTSCHREIBUNG / REVIEW (Art. 35 Abs. 11 DSGVO)
// =============================================================================
export interface DSFAReviewTrigger {
id: string
trigger_type: 'scheduled' | 'risk_change' | 'new_technology' | 'new_purpose' | 'incident' | 'regulatory' | 'other'
description: string
detected_at: string
detected_by: string
review_required: boolean
review_completed: boolean
review_date?: string
changes_made: string[]
}
export interface DSFAReviewSchedule {
next_review_date: string
review_frequency_months: number
last_review_date?: string
review_responsible: string
}
// =============================================================================
// MAIN DSFA TYPE
// =============================================================================
export interface DSFA {
id: string
tenant_id: string
namespace_id?: string
processing_activity_id?: string
assessment_id?: string
name: string
description: string
// Section 0: Schwellwertanalyse / Vorabprüfung (NEU - Art. 35 Abs. 1)
threshold_analysis?: DSFAThresholdAnalysis
wp248_criteria_met?: string[] // IDs der erfüllten WP248-Kriterien (K1-K9)
art35_abs3_triggered?: string[] // IDs der ausgelösten Art. 35 Abs. 3 Fälle
// Section 1: Systematische Beschreibung (Art. 35 Abs. 7 lit. a)
processing_description: string
processing_purpose: string
data_categories: string[]
data_subjects: string[]
recipients: string[]
legal_basis: string
legal_basis_details?: string
// Section 2: Notwendigkeit & Verhältnismäßigkeit (Art. 35 Abs. 7 lit. b)
necessity_assessment: string
proportionality_assessment: string
data_minimization?: string
alternatives_considered?: string
retention_justification?: string
// Section 3: Risikobewertung (Art. 35 Abs. 7 lit. c)
risks: DSFARisk[]
overall_risk_level: DSFARiskLevel
risk_score: number
affected_rights?: string[]
triggered_rule_codes?: string[]
// KI-spezifische Trigger (NEU)
involves_ai?: boolean
ai_trigger_ids?: string[] // IDs der ausgelösten KI-Trigger
// Section 4: Abhilfemaßnahmen (Art. 35 Abs. 7 lit. d)
mitigations: DSFAMitigation[]
tom_references?: string[]
residual_risk_level?: DSFARiskLevel // Restrisiko nach Maßnahmen
// Section 5: Stellungnahme DSB (Art. 35 Abs. 2 + Art. 36)
dpo_consulted: boolean
dpo_consulted_at?: string
dpo_name?: string
dpo_opinion?: string
dpo_approved?: boolean
authority_consulted: boolean
authority_consulted_at?: string
authority_reference?: string
authority_decision?: string
// Art. 36 Konsultationspflicht (NEU)
consultation_requirement?: DSFAConsultationRequirement
// Betroffenenperspektive (NEU - Art. 35 Abs. 9)
stakeholder_consultations?: DSFAStakeholderConsultation[]
stakeholder_consultation_not_appropriate?: boolean
stakeholder_consultation_not_appropriate_reason?: string
// Workflow & Approval
status: DSFAStatus
submitted_for_review_at?: string
submitted_by?: string
conclusion: string
review_comments?: DSFAReviewComment[]
// Section Progress Tracking
section_progress: DSFASectionProgress
// Fortschreibung / Review (NEU - Art. 35 Abs. 11)
review_schedule?: DSFAReviewSchedule
review_triggers?: DSFAReviewTrigger[]
version: number // DSFA-Version für Fortschreibung
previous_version_id?: string
// Referenzen zu behördlichen Ressourcen
federal_state?: string // Bundesland für zuständige Aufsichtsbehörde
authority_resource_id?: string // ID aus DSFA_AUTHORITY_RESOURCES
// Metadata & Audit
metadata?: Record<string, unknown>
created_at: string
updated_at: string
created_by: string
approved_by?: string
approved_at?: string
}

View File

@@ -0,0 +1,177 @@
/**
* DSFA Authority Types - German data protection authority resources
*
* DSFA must-lists and resources for all 16 Bundeslaender + BfDI.
*/
// =============================================================================
// DSFA MUSS-LISTEN NACH BUNDESLÄNDERN
// Quellen: Jeweilige Landesdatenschutzbeauftragte
// =============================================================================
export interface DSFAAuthorityResource {
id: string
name: string
shortName: string
state: string // Bundesland oder 'Bund'
overviewUrl: string
publicSectorListUrl?: string
privateSectorListUrl?: string
templateUrl?: string
additionalResources?: Array<{ title: string; url: string }>
}
export const DSFA_AUTHORITY_RESOURCES: DSFAAuthorityResource[] = [
{
id: 'bund',
name: 'Bundesbeauftragter für den Datenschutz und die Informationsfreiheit',
shortName: 'BfDI',
state: 'Bund',
overviewUrl: 'https://www.bfdi.bund.de/DE/Fachthemen/Inhalte/Technik/Datenschutz-Folgenabschaetzungen.html',
publicSectorListUrl: 'https://www.bfdi.bund.de/SharedDocs/Downloads/DE/Muster/Liste_VerarbeitungsvorgaengeArt35.pdf',
templateUrl: 'https://www.bfdi.bund.de/SharedDocs/Downloads/DE/Muster/Muster_Hinweise_DSFA.html',
},
{
id: 'bw',
name: 'Landesbeauftragter für den Datenschutz und die Informationsfreiheit Baden-Württemberg',
shortName: 'LfDI BW',
state: 'Baden-Württemberg',
overviewUrl: 'https://www.baden-wuerttemberg.datenschutz.de/datenschutz-folgenabschaetzung/',
privateSectorListUrl: 'https://www.baden-wuerttemberg.datenschutz.de/wp-content/uploads/2018/05/Liste-von-Verarbeitungsvorg%C3%A4ngen-nach-Art.-35-Abs.-4-DS-GVO-LfDI-BW.pdf',
},
{
id: 'by',
name: 'Bayerischer Landesbeauftragter für den Datenschutz',
shortName: 'BayLfD',
state: 'Bayern',
overviewUrl: 'https://www.datenschutz-bayern.de/dsfa/',
additionalResources: [
{ title: 'DSFA-Module und Formulare', url: 'https://www.datenschutz-bayern.de/dsfa/' },
],
},
{
id: 'be',
name: 'Berliner Beauftragte für Datenschutz und Informationsfreiheit',
shortName: 'BlnBDI',
state: 'Berlin',
overviewUrl: 'https://www.datenschutz-berlin.de/themen/unternehmen/datenschutz-folgenabschaetzung/',
publicSectorListUrl: 'https://www.datenschutz-berlin.de/fileadmin/user_upload/pdf/dokumente/2018-BlnBDI_DSFA-oeffentlich.pdf',
privateSectorListUrl: 'https://www.datenschutz-berlin.de/fileadmin/user_upload/pdf/dokumente/2018-BlnBDI_DSFA-nicht-oeffentlich.pdf',
},
{
id: 'bb',
name: 'Landesbeauftragte für den Datenschutz und für das Recht auf Akteneinsicht Brandenburg',
shortName: 'LDA BB',
state: 'Brandenburg',
overviewUrl: 'https://www.lda.brandenburg.de/lda/de/datenschutz/datenschutz-folgenabschaetzung/',
publicSectorListUrl: 'https://www.lda.brandenburg.de/sixcms/media.php/9/DSFA-Liste_%C3%B6ffentlicher_Bereich.pdf',
privateSectorListUrl: 'https://www.lda.brandenburg.de/sixcms/media.php/9/DSFA-Liste_nicht_%C3%B6ffentlicher_Bereich.pdf',
},
{
id: 'hb',
name: 'Landesbeauftragte für Datenschutz und Informationsfreiheit Bremen',
shortName: 'LfDI HB',
state: 'Bremen',
overviewUrl: 'https://www.datenschutz.bremen.de/datenschutz/datenschutz-folgenabschaetzung-3884',
publicSectorListUrl: 'https://www.datenschutz.bremen.de/sixcms/media.php/13/Liste%20von%20Verarbeitungsvorg%C3%A4ngen%20nach%20Artikel%2035.pdf',
privateSectorListUrl: 'https://www.datenschutz.bremen.de/sixcms/media.php/13/DSFA%20Muss-Liste%20LfDI%20HB.pdf',
},
{
id: 'hh',
name: 'Hamburgischer Beauftragter für Datenschutz und Informationsfreiheit',
shortName: 'HmbBfDI',
state: 'Hamburg',
overviewUrl: 'https://datenschutz-hamburg.de/datenschutz-folgenabschaetzung',
publicSectorListUrl: 'https://datenschutz-hamburg.de/fileadmin/user_upload/HmbBfDI/Datenschutz/Informationen/Liste_Art_35-4_DSGVO_HmbBfDI-oeffentlicher_Bereich_v2.0a.pdf',
privateSectorListUrl: 'https://datenschutz-hamburg.de/fileadmin/user_upload/HmbBfDI/Datenschutz/Informationen/DSFA_Muss-Liste_fuer_den_nicht-oeffentlicher_Bereich_-_Stand_17.10.2018.pdf',
},
{
id: 'he',
name: 'Hessischer Beauftragter für Datenschutz und Informationsfreiheit',
shortName: 'HBDI',
state: 'Hessen',
overviewUrl: 'https://datenschutz.hessen.de/datenschutz/it-und-datenschutz/datenschutz-folgenabschaetzung',
},
{
id: 'mv',
name: 'Landesbeauftragter für Datenschutz und Informationsfreiheit Mecklenburg-Vorpommern',
shortName: 'LfDI MV',
state: 'Mecklenburg-Vorpommern',
overviewUrl: 'https://www.datenschutz-mv.de/datenschutz/DSGVO/Hilfsmittel-zur-Umsetzung/',
publicSectorListUrl: 'https://www.datenschutz-mv.de/static/DS/Dateien/DS-GVO/HilfsmittelzurUmsetzung/MV-DSFA-Muss-Liste-Oeffentlicher-Bereich.pdf',
},
{
id: 'ni',
name: 'Die Landesbeauftragte für den Datenschutz Niedersachsen',
shortName: 'LfD NI',
state: 'Niedersachsen',
overviewUrl: 'https://www.lfd.niedersachsen.de/dsgvo/liste_von_verarbeitungsvorgangen_nach_art_35_abs_4_ds_gvo/muss-listen-zur-datenschutz-folgenabschatzung-179663.html',
publicSectorListUrl: 'https://www.lfd.niedersachsen.de/download/134414/DSFA_Muss-Liste_fuer_den_oeffentlichen_Bereich.pdf',
privateSectorListUrl: 'https://www.lfd.niedersachsen.de/download/131098/Liste_von_Verarbeitungsvorgaengen_nach_Art._35_Abs._4_DS-GVO.pdf',
},
{
id: 'nw',
name: 'Landesbeauftragte für Datenschutz und Informationsfreiheit Nordrhein-Westfalen',
shortName: 'LDI NRW',
state: 'Nordrhein-Westfalen',
overviewUrl: 'https://www.ldi.nrw.de/datenschutz/wirtschaft/datenschutz-folgenabschaetzung',
publicSectorListUrl: 'https://www.ldi.nrw.de/liste-von-verarbeitungsvorgaengen-nach-art-35-abs-4-ds-gvo-fuer-den-oeffentlichen-bereich',
},
{
id: 'rp',
name: 'Landesbeauftragter für den Datenschutz und die Informationsfreiheit Rheinland-Pfalz',
shortName: 'LfDI RP',
state: 'Rheinland-Pfalz',
overviewUrl: 'https://www.datenschutz.rlp.de/themen/datenschutz-folgenabschaetzung',
},
{
id: 'sl',
name: 'Unabhängiges Datenschutzzentrum Saarland',
shortName: 'UDZ SL',
state: 'Saarland',
overviewUrl: 'https://www.datenschutz.saarland.de/themen/datenschutz-folgenabschaetzung',
privateSectorListUrl: 'https://www.datenschutz.saarland.de/fileadmin/user_upload/uds/alle_Dateien_und_Ordner_bis_2025/Download/dsfa_muss_liste_dsk_de.pdf',
},
{
id: 'sn',
name: 'Sächsische Datenschutz- und Transparenzbeauftragte',
shortName: 'SDTB',
state: 'Sachsen',
overviewUrl: 'https://www.datenschutz.sachsen.de/datenschutz-folgenabschaetzung.html',
additionalResources: [
{ title: 'Erforderlichkeit der DSFA', url: 'https://www.datenschutz.sachsen.de/erforderlichkeit.html' },
],
},
{
id: 'st',
name: 'Landesbeauftragter für den Datenschutz Sachsen-Anhalt',
shortName: 'LfD ST',
state: 'Sachsen-Anhalt',
overviewUrl: 'https://datenschutz.sachsen-anhalt.de/informationen/datenschutz-grundverordnung/liste-datenschutz-folgenabschaetzung',
publicSectorListUrl: 'https://datenschutz.sachsen-anhalt.de/fileadmin/Bibliothek/Landesaemter/LfD/Informationen/Internationales/Datenschutz-Grundverordnung/Liste_DSFA/Art-35-Liste-oeffentlicher_Bereich.pdf',
privateSectorListUrl: 'https://datenschutz.sachsen-anhalt.de/fileadmin/Bibliothek/Landesaemter/LfD/Informationen/Internationales/Datenschutz-Grundverordnung/Liste_DSFA/Art-35-Liste-nichtoeffentlicher_Bereich.pdf',
},
{
id: 'sh',
name: 'Unabhängiges Landeszentrum für Datenschutz Schleswig-Holstein',
shortName: 'ULD SH',
state: 'Schleswig-Holstein',
overviewUrl: 'https://www.datenschutzzentrum.de/datenschutzfolgenabschaetzung/',
privateSectorListUrl: 'https://www.datenschutzzentrum.de/uploads/datenschutzfolgenabschaetzung/20180525_LfD-SH_DSFA_Muss-Liste_V1.0.pdf',
additionalResources: [
{ title: 'Begleittext zur DSFA-Liste', url: 'https://www.datenschutzzentrum.de/uploads/dsgvo/2018_0807_LfD-SH_DSFA_Begleittext_V1.0a.pdf' },
],
},
{
id: 'th',
name: 'Thüringer Landesbeauftragter für den Datenschutz und die Informationsfreiheit',
shortName: 'TLfDI',
state: 'Thüringen',
overviewUrl: 'https://www.tlfdi.de/datenschutz/datenschutz-folgenabschaetzung/',
privateSectorListUrl: 'https://tlfdi.de/fileadmin/tlfdi/datenschutz/dsfa_muss-liste_04_07_18.pdf',
additionalResources: [
{ title: 'Handreichung DS-FA (nicht-öffentlich)', url: 'https://tlfdi.de/fileadmin/tlfdi/datenschutz/handreichung_ds-fa.pdf' },
{ title: 'Handreichung DS-FA (öffentlich)', url: 'https://tlfdi.de/fileadmin/tlfdi/Europa/Handreichung_zur_Datenschutz-Folgenabschaetzung_oeffentlicher_Bereich.pdf' },
],
},
]

View File

@@ -0,0 +1,109 @@
/**
* DSFA Core Types - Base enums, type aliases, and constants
*
* SDM goals, status labels, risk levels, legal bases, affected rights.
*/
// =============================================================================
// SDM GEWAEHRLEISTUNGSZIELE (Standard-Datenschutzmodell V2.0)
// =============================================================================
export type SDMGoal =
| 'datenminimierung'
| 'verfuegbarkeit'
| 'integritaet'
| 'vertraulichkeit'
| 'nichtverkettung'
| 'transparenz'
| 'intervenierbarkeit'
export const SDM_GOALS: Record<SDMGoal, { name: string; description: string; article: string }> = {
datenminimierung: {
name: 'Datenminimierung',
description: 'Verarbeitung personenbezogener Daten auf das dem Zweck angemessene, erhebliche und notwendige Mass beschraenken.',
article: 'Art. 5 Abs. 1 lit. c DSGVO',
},
verfuegbarkeit: {
name: 'Verfuegbarkeit',
description: 'Personenbezogene Daten muessen dem Verantwortlichen zur Verfuegung stehen und ordnungsgemaess im vorgesehenen Prozess verwendet werden koennen.',
article: 'Art. 32 Abs. 1 lit. b DSGVO',
},
integritaet: {
name: 'Integritaet',
description: 'Personenbezogene Daten bleiben waehrend der Verarbeitung unversehrt, vollstaendig und aktuell.',
article: 'Art. 5 Abs. 1 lit. d DSGVO',
},
vertraulichkeit: {
name: 'Vertraulichkeit',
description: 'Kein unbefugter Zugriff auf personenbezogene Daten. Nur befugte Personen koennen auf Daten zugreifen.',
article: 'Art. 32 Abs. 1 lit. b DSGVO',
},
nichtverkettung: {
name: 'Nichtverkettung',
description: 'Personenbezogene Daten duerfen nicht ohne Weiteres fuer einen anderen als den erhobenen Zweck zusammengefuehrt werden (Zweckbindung).',
article: 'Art. 5 Abs. 1 lit. b DSGVO',
},
transparenz: {
name: 'Transparenz',
description: 'Die Verarbeitung personenbezogener Daten muss fuer Betroffene und Aufsichtsbehoerden nachvollziehbar sein.',
article: 'Art. 5 Abs. 1 lit. a DSGVO',
},
intervenierbarkeit: {
name: 'Intervenierbarkeit',
description: 'Den Betroffenen werden wirksame Moeglichkeiten der Einflussnahme (Auskunft, Berichtigung, Loeschung, Widerspruch) auf die Verarbeitung gewaehrt.',
article: 'Art. 15-21 DSGVO',
},
}
// =============================================================================
// ENUMS & CONSTANTS
// =============================================================================
export type DSFAStatus = 'draft' | 'in_review' | 'approved' | 'rejected' | 'needs_update'
export type DSFARiskLevel = 'low' | 'medium' | 'high' | 'very_high'
export type DSFARiskCategory = 'confidentiality' | 'integrity' | 'availability' | 'rights_freedoms'
export type DSFAMitigationType = 'technical' | 'organizational' | 'legal'
export type DSFAMitigationStatus = 'planned' | 'in_progress' | 'implemented' | 'verified'
export const DSFA_STATUS_LABELS: Record<DSFAStatus, string> = {
draft: 'Entwurf',
in_review: 'In Prüfung',
approved: 'Genehmigt',
rejected: 'Abgelehnt',
needs_update: 'Überarbeitung erforderlich',
}
export const DSFA_RISK_LEVEL_LABELS: Record<DSFARiskLevel, string> = {
low: 'Niedrig',
medium: 'Mittel',
high: 'Hoch',
very_high: 'Sehr Hoch',
}
export const DSFA_LEGAL_BASES = {
consent: 'Art. 6 Abs. 1 lit. a DSGVO - Einwilligung',
contract: 'Art. 6 Abs. 1 lit. b DSGVO - Vertrag',
legal_obligation: 'Art. 6 Abs. 1 lit. c DSGVO - Rechtliche Verpflichtung',
vital_interests: 'Art. 6 Abs. 1 lit. d DSGVO - Lebenswichtige Interessen',
public_interest: 'Art. 6 Abs. 1 lit. e DSGVO - Öffentliches Interesse',
legitimate_interest: 'Art. 6 Abs. 1 lit. f DSGVO - Berechtigtes Interesse',
}
export const DSFA_AFFECTED_RIGHTS = [
{ id: 'right_to_information', label: 'Recht auf Information (Art. 13/14)' },
{ id: 'right_of_access', label: 'Auskunftsrecht (Art. 15)' },
{ id: 'right_to_rectification', label: 'Recht auf Berichtigung (Art. 16)' },
{ id: 'right_to_erasure', label: 'Recht auf Löschung (Art. 17)' },
{ id: 'right_to_restriction', label: 'Recht auf Einschränkung (Art. 18)' },
{ id: 'right_to_data_portability', label: 'Recht auf Datenübertragbarkeit (Art. 20)' },
{ id: 'right_to_object', label: 'Widerspruchsrecht (Art. 21)' },
{ id: 'right_not_to_be_profiled', label: 'Recht bzgl. Profiling (Art. 22)' },
{ id: 'freedom_of_expression', label: 'Meinungsfreiheit' },
{ id: 'freedom_of_association', label: 'Versammlungsfreiheit' },
{ id: 'non_discrimination', label: 'Nichtdiskriminierung' },
{ id: 'data_security', label: 'Datensicherheit' },
]

View File

@@ -0,0 +1,202 @@
/**
* DSFA Risk Helpers - Risk matrix, calculation functions, and utility helpers
*
* Risk matrix cells, risk level calculation, WP248 checks,
* Art. 36 consultation, authority lookups, review helpers, AI triggers.
*/
import type { DSFARiskLevel } from './types-core'
import type { DSFAConsultationRequirement, DSFAReviewTrigger, DSFAReviewSchedule } from './types-assessment'
import { DSFA_AUTHORITY_RESOURCES } from './types-authorities'
import type { DSFAAuthorityResource } from './types-authorities'
import { WP248_CRITERIA, ART35_ABS3_CASES, AI_DSFA_TRIGGERS } from './types-wp248'
// =============================================================================
// RISK MATRIX HELPERS
// =============================================================================
export interface RiskMatrixCell {
likelihood: 'low' | 'medium' | 'high'
impact: 'low' | 'medium' | 'high'
level: DSFARiskLevel
score: number
}
export const RISK_MATRIX: RiskMatrixCell[] = [
// Low likelihood
{ likelihood: 'low', impact: 'low', level: 'low', score: 10 },
{ likelihood: 'low', impact: 'medium', level: 'low', score: 20 },
{ likelihood: 'low', impact: 'high', level: 'medium', score: 40 },
// Medium likelihood
{ likelihood: 'medium', impact: 'low', level: 'low', score: 20 },
{ likelihood: 'medium', impact: 'medium', level: 'medium', score: 50 },
{ likelihood: 'medium', impact: 'high', level: 'high', score: 70 },
// High likelihood
{ likelihood: 'high', impact: 'low', level: 'medium', score: 40 },
{ likelihood: 'high', impact: 'medium', level: 'high', score: 70 },
{ likelihood: 'high', impact: 'high', level: 'very_high', score: 90 },
]
export function calculateRiskLevel(
likelihood: 'low' | 'medium' | 'high',
impact: 'low' | 'medium' | 'high'
): { level: DSFARiskLevel; score: number } {
const cell = RISK_MATRIX.find(c => c.likelihood === likelihood && c.impact === impact)
return cell ? { level: cell.level, score: cell.score } : { level: 'medium', score: 50 }
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
/**
* Prüft anhand der WP248-Kriterien, ob eine DSFA erforderlich ist.
* Regel: Bei >= 2 erfüllten Kriterien ist DSFA in den meisten Fällen erforderlich.
* @param criteriaIds Array der erfüllten Kriterien-IDs (z.B. ['K1', 'K4'])
* @returns Objekt mit Ergebnis und Begründung
*/
export function checkDSFARequiredByWP248(criteriaIds: string[]): {
required: boolean
confidence: 'definite' | 'likely' | 'possible' | 'unlikely'
reason: string
} {
const count = criteriaIds.length
if (count >= 2) {
return {
required: true,
confidence: 'definite',
reason: `${count} WP248-Kriterien erfüllt (>= 2). DSFA ist in den meisten Fällen erforderlich.`,
}
}
if (count === 1) {
return {
required: false,
confidence: 'possible',
reason: '1 WP248-Kriterium erfüllt. DSFA kann je nach Risiko dennoch erforderlich sein. Einzelfallprüfung empfohlen.',
}
}
return {
required: false,
confidence: 'unlikely',
reason: 'Keine WP248-Kriterien erfüllt. DSFA wahrscheinlich nicht erforderlich, sofern kein Art. 35 Abs. 3 Fall vorliegt.',
}
}
/**
* Prüft, ob eine Konsultation der Aufsichtsbehörde gem. Art. 36 DSGVO erforderlich ist.
* Erforderlich wenn: Hohes Restrisiko trotz geplanter Maßnahmen.
*/
export function checkArt36ConsultationRequired(
residualRiskLevel: DSFARiskLevel,
mitigationsImplemented: boolean
): DSFAConsultationRequirement {
const highResidual = residualRiskLevel === 'high' || residualRiskLevel === 'very_high'
const consultationRequired = highResidual && mitigationsImplemented
return {
high_residual_risk: highResidual,
consultation_required: consultationRequired,
consultation_reason: consultationRequired
? 'Trotz geplanter Maßnahmen verbleibt ein hohes Restrisiko. Gem. Art. 36 Abs. 1 DSGVO ist vor der Verarbeitung die Aufsichtsbehörde zu konsultieren.'
: highResidual
? 'Hohes Restrisiko festgestellt, aber Maßnahmen noch nicht vollständig umgesetzt.'
: undefined,
authority_notified: false,
waiting_period_observed: false,
}
}
/**
* Gibt die zuständige Aufsichtsbehörde für ein Bundesland zurück.
*/
export function getAuthorityResource(stateId: string): DSFAAuthorityResource | undefined {
return DSFA_AUTHORITY_RESOURCES.find(r => r.id === stateId)
}
/**
* Gibt alle Bundesländer als Auswahlliste zurück.
*/
export function getFederalStateOptions(): Array<{ value: string; label: string }> {
return DSFA_AUTHORITY_RESOURCES.map(r => ({
value: r.id,
label: r.state,
}))
}
/**
* Prüft, ob ein Review-Trigger eine Aktualisierung der DSFA erfordert.
*/
export function checkReviewRequired(triggers: DSFAReviewTrigger[]): {
required: boolean
pendingTriggers: DSFAReviewTrigger[]
} {
const pendingTriggers = triggers.filter(t => t.review_required && !t.review_completed)
return {
required: pendingTriggers.length > 0,
pendingTriggers,
}
}
/**
* Berechnet das nächste Review-Datum basierend auf dem Schedule.
*/
export function calculateNextReviewDate(schedule: DSFAReviewSchedule): Date {
const lastReview = schedule.last_review_date
? new Date(schedule.last_review_date)
: new Date()
const nextReview = new Date(lastReview)
nextReview.setMonth(nextReview.getMonth() + schedule.review_frequency_months)
return nextReview
}
/**
* Prüft, ob KI-spezifische DSFA-Trigger erfüllt sind.
*/
export function checkAIDSFATriggers(
aiTriggerIds: string[]
): { triggered: boolean; triggers: typeof AI_DSFA_TRIGGERS } {
const triggered = AI_DSFA_TRIGGERS.filter(t => aiTriggerIds.includes(t.id))
return {
triggered: triggered.length > 0,
triggers: triggered,
}
}
/**
* Generiert eine Checkliste für die Schwellwertanalyse.
*/
export function generateThresholdAnalysisChecklist(): Array<{
category: string
items: Array<{ id: string; label: string; description: string }>
}> {
return [
{
category: 'WP248 Kriterien (Art.-29-Datenschutzgruppe)',
items: WP248_CRITERIA.map(c => ({
id: c.id,
label: `${c.code}: ${c.title}`,
description: c.description,
})),
},
{
category: 'Art. 35 Abs. 3 DSGVO Regelbeispiele',
items: ART35_ABS3_CASES.map(c => ({
id: c.id,
label: `lit. ${c.lit}: ${c.title}`,
description: c.description,
})),
},
{
category: 'KI-spezifische Trigger (Deutsche DSFA-Liste)',
items: AI_DSFA_TRIGGERS.map(t => ({
id: t.id,
label: t.title,
description: t.description,
})),
},
]
}

View File

@@ -0,0 +1,95 @@
/**
* DSFA UI Types - Section configuration for the DSFA wizard UI
*
* Defines the section structure, labels, and GDPR references
* used by the frontend wizard components.
*/
// =============================================================================
// HELPER TYPES FOR UI
// =============================================================================
export interface DSFASectionConfig {
number: number
title: string
titleDE: string
description: string
gdprRef: string
fields: string[]
required: boolean
}
export const DSFA_SECTIONS: DSFASectionConfig[] = [
{
number: 0,
title: 'Threshold Analysis',
titleDE: 'Schwellwertanalyse',
description: 'Prüfen Sie anhand der WP248-Kriterien und Art. 35 Abs. 3, ob eine DSFA erforderlich ist. Die Entscheidung ist zu dokumentieren.',
gdprRef: 'Art. 35 Abs. 1 DSGVO, WP248 rev.01',
fields: ['threshold_analysis', 'wp248_criteria_met', 'art35_abs3_triggered'],
required: true,
},
{
number: 1,
title: 'Processing Description',
titleDE: 'Systematische Beschreibung',
description: 'Beschreiben Sie die geplante Verarbeitung, ihren Zweck, die Datenkategorien und Rechtsgrundlage.',
gdprRef: 'Art. 35 Abs. 7 lit. a DSGVO',
fields: ['processing_description', 'processing_purpose', 'data_categories', 'data_subjects', 'recipients', 'legal_basis'],
required: true,
},
{
number: 2,
title: 'Necessity & Proportionality',
titleDE: 'Notwendigkeit & Verhältnismäßigkeit',
description: 'Begründen Sie, warum die Verarbeitung notwendig ist und welche Alternativen geprüft wurden.',
gdprRef: 'Art. 35 Abs. 7 lit. b DSGVO',
fields: ['necessity_assessment', 'proportionality_assessment', 'data_minimization', 'alternatives_considered'],
required: true,
},
{
number: 3,
title: 'Risk Assessment',
titleDE: 'Risikobewertung',
description: 'Identifizieren und bewerten Sie die Risiken für die Rechte und Freiheiten der Betroffenen.',
gdprRef: 'Art. 35 Abs. 7 lit. c DSGVO',
fields: ['risks', 'overall_risk_level', 'risk_score', 'affected_rights', 'involves_ai', 'ai_trigger_ids'],
required: true,
},
{
number: 4,
title: 'Mitigation Measures',
titleDE: 'Abhilfemaßnahmen',
description: 'Definieren Sie technische und organisatorische Maßnahmen zur Risikominimierung und bewerten Sie das Restrisiko.',
gdprRef: 'Art. 35 Abs. 7 lit. d DSGVO',
fields: ['mitigations', 'tom_references', 'residual_risk_level'],
required: true,
},
{
number: 5,
title: 'Stakeholder Consultation',
titleDE: 'Betroffenenperspektive',
description: 'Dokumentieren Sie, ob und wie die Standpunkte der Betroffenen eingeholt wurden (z.B. Betriebsrat, Nutzerumfragen).',
gdprRef: 'Art. 35 Abs. 9 DSGVO',
fields: ['stakeholder_consultations', 'stakeholder_consultation_not_appropriate', 'stakeholder_consultation_not_appropriate_reason'],
required: false,
},
{
number: 6,
title: 'DPO Opinion & Authority Consultation',
titleDE: 'DSB-Stellungnahme & Behördenkonsultation',
description: 'Dokumentieren Sie die Konsultation des DSB und prüfen Sie, ob bei hohem Restrisiko eine Behördenkonsultation erforderlich ist.',
gdprRef: 'Art. 35 Abs. 2, Art. 36 DSGVO',
fields: ['dpo_consulted', 'dpo_opinion', 'consultation_requirement', 'authority_consulted', 'authority_reference'],
required: true,
},
{
number: 7,
title: 'Review & Maintenance',
titleDE: 'Fortschreibung & Review',
description: 'Planen Sie regelmäßige Überprüfungen und dokumentieren Sie Änderungen, die eine Aktualisierung der DSFA erfordern.',
gdprRef: 'Art. 35 Abs. 11 DSGVO',
fields: ['review_schedule', 'review_triggers', 'version'],
required: true,
},
]

View File

@@ -0,0 +1,183 @@
/**
* DSFA WP248 Types - Threshold analysis criteria, Art. 35 cases, AI triggers
*
* WP248 rev.01 criteria, DSK Kurzpapier Nr. 5, Art. 35 Abs. 3 DSGVO cases,
* and AI-specific DSFA triggers.
*/
// =============================================================================
// WP248 REV.01 KRITERIEN (Schwellwertanalyse)
// Quelle: Artikel-29-Datenschutzgruppe, bestätigt durch EDSA
// =============================================================================
export interface WP248Criterion {
id: string
code: string
title: string
description: string
examples: string[]
gdprRef?: string
}
/**
* WP248 rev.01 Kriterien zur Bestimmung der DSFA-Pflicht
* Regel: Bei >= 2 erfüllten Kriterien ist DSFA in den meisten Fällen erforderlich
*/
export const WP248_CRITERIA: WP248Criterion[] = [
{
id: 'scoring_profiling',
code: 'K1',
title: 'Bewertung oder Scoring',
description: 'Einschließlich Profiling und Prognosen, insbesondere zu Arbeitsleistung, wirtschaftlicher Lage, Gesundheit, persönlichen Vorlieben, Zuverlässigkeit, Verhalten, Aufenthaltsort oder Ortswechsel.',
examples: ['Bonitätsprüfung', 'Leistungsbeurteilung', 'Verhaltensanalyse'],
gdprRef: 'Art. 35 Abs. 3 lit. a DSGVO',
},
{
id: 'automated_decision',
code: 'K2',
title: 'Automatisierte Entscheidungsfindung mit Rechtswirkung',
description: 'Automatisierte Verarbeitung, die als Grundlage für Entscheidungen dient, die Rechtswirkung gegenüber natürlichen Personen entfalten oder diese erheblich beeinträchtigen.',
examples: ['Automatische Kreditvergabe', 'Automatische Bewerbungsablehnung', 'Algorithmenbasierte Preisgestaltung'],
gdprRef: 'Art. 22 DSGVO',
},
{
id: 'systematic_monitoring',
code: 'K3',
title: 'Systematische Überwachung',
description: 'Verarbeitung zur Beobachtung, Überwachung oder Kontrolle von betroffenen Personen, einschließlich Datenerhebung über Netzwerke oder systematische Überwachung öffentlicher Bereiche.',
examples: ['Videoüberwachung', 'WLAN-Tracking', 'GPS-Ortung', 'Mitarbeiterüberwachung'],
gdprRef: 'Art. 35 Abs. 3 lit. c DSGVO',
},
{
id: 'sensitive_data',
code: 'K4',
title: 'Sensible Daten oder höchst persönliche Daten',
description: 'Verarbeitung besonderer Kategorien personenbezogener Daten (Art. 9), strafrechtlicher Daten (Art. 10) oder anderer höchst persönlicher Daten wie Kommunikationsinhalte, Standortdaten, Finanzinformationen.',
examples: ['Gesundheitsdaten', 'Biometrische Daten', 'Genetische Daten', 'Politische Meinungen', 'Gewerkschaftszugehörigkeit'],
gdprRef: 'Art. 9, Art. 10 DSGVO',
},
{
id: 'large_scale',
code: 'K5',
title: 'Datenverarbeitung in großem Umfang',
description: 'Berücksichtigt werden: Zahl der Betroffenen, Datenmenge, Dauer der Verarbeitung, geografische Reichweite.',
examples: ['Landesweite Datenbanken', 'Millionen von Nutzern', 'Mehrjährige Speicherung'],
gdprRef: 'Erwägungsgrund 91 DSGVO',
},
{
id: 'matching_combining',
code: 'K6',
title: 'Abgleichen oder Zusammenführen von Datensätzen',
description: 'Datensätze aus verschiedenen Quellen, die für unterschiedliche Zwecke und/oder von verschiedenen Verantwortlichen erhoben wurden, werden abgeglichen oder zusammengeführt.',
examples: ['Data Warehousing', 'Big Data Analytics', 'Zusammenführung von Online-/Offline-Daten'],
},
{
id: 'vulnerable_subjects',
code: 'K7',
title: 'Daten zu schutzbedürftigen Betroffenen',
description: 'Verarbeitung von Daten schutzbedürftiger Personen, bei denen ein Ungleichgewicht zwischen Betroffenem und Verantwortlichem besteht.',
examples: ['Kinder/Minderjährige', 'Arbeitnehmer', 'Patienten', 'Ältere Menschen', 'Asylbewerber'],
gdprRef: 'Erwägungsgrund 75 DSGVO',
},
{
id: 'innovative_technology',
code: 'K8',
title: 'Innovative Nutzung oder Anwendung neuer technologischer oder organisatorischer Lösungen',
description: 'Einsatz neuer Technologien kann neue Formen der Datenerhebung und -nutzung mit sich bringen, möglicherweise mit hohem Risiko für Rechte und Freiheiten.',
examples: ['Künstliche Intelligenz', 'Machine Learning', 'IoT-Geräte', 'Biometrische Erkennung', 'Blockchain'],
gdprRef: 'Erwägungsgrund 89, 91 DSGVO',
},
{
id: 'preventing_rights',
code: 'K9',
title: 'Verarbeitung, die Betroffene an der Ausübung eines Rechts oder der Nutzung einer Dienstleistung hindert',
description: 'Verarbeitungsvorgänge, die darauf abzielen, einer Person den Zugang zu einer Dienstleistung oder den Abschluss eines Vertrags zu ermöglichen oder zu verweigern.',
examples: ['Zugang zu Sozialleistungen', 'Kreditvergabe', 'Versicherungsabschluss'],
gdprRef: 'Art. 22 DSGVO',
},
]
// =============================================================================
// DSK KURZPAPIER NR. 5 REFERENZEN
// =============================================================================
export const DSK_KURZPAPIER_5 = {
title: 'Kurzpapier Nr. 5: Datenschutz-Folgenabschätzung nach Art. 35 DS-GVO',
source: 'Datenschutzkonferenz (DSK)',
url: 'https://www.datenschutzkonferenz-online.de/media/kp/dsk_kpnr_5.pdf',
license: 'Datenlizenz Deutschland Namensnennung Version 2.0 (DL-DE BY 2.0)',
licenseUrl: 'https://www.govdata.de/dl-de/by-2-0',
processSteps: [
{ step: 1, title: 'Projektteam bilden', description: 'Interdisziplinäres Team aus Datenschutz, Fachprozess, IT/Sicherheit' },
{ step: 2, title: 'Verarbeitung abgrenzen', description: 'Scope definieren, Datenflüsse und Zwecke beschreiben' },
{ step: 3, title: 'Prüfung der Notwendigkeit', description: 'Alternativen prüfen, Datenminimierung bewerten' },
{ step: 4, title: 'Risiken identifizieren', description: 'Risikoquellen ermitteln, Schäden bewerten' },
{ step: 5, title: 'Maßnahmen festlegen', description: 'TOM definieren, Restrisiko bewerten' },
{ step: 6, title: 'Bericht erstellen', description: 'DSFA-Bericht dokumentieren, ggf. veröffentlichen' },
{ step: 7, title: 'Fortschreibung', description: 'DSFA bei Änderungen aktualisieren' },
],
}
// =============================================================================
// ART. 35 ABS. 3 DSGVO - REGELBEISPIELE
// =============================================================================
export const ART35_ABS3_CASES = [
{
id: 'profiling_legal_effects',
lit: 'a',
title: 'Profiling mit Rechtswirkung',
description: 'Systematische und umfassende Bewertung persönlicher Aspekte natürlicher Personen, die sich auf automatisierte Verarbeitung einschließlich Profiling gründet und die ihrerseits als Grundlage für Entscheidungen dient, die Rechtswirkung gegenüber natürlichen Personen entfalten oder diese in ähnlich erheblicher Weise beeinträchtigen.',
gdprRef: 'Art. 35 Abs. 3 lit. a DSGVO',
},
{
id: 'special_categories',
lit: 'b',
title: 'Besondere Datenkategorien in großem Umfang',
description: 'Umfangreiche Verarbeitung besonderer Kategorien von personenbezogenen Daten gemäß Artikel 9 Absatz 1 oder von personenbezogenen Daten über strafrechtliche Verurteilungen und Straftaten gemäß Artikel 10.',
gdprRef: 'Art. 35 Abs. 3 lit. b DSGVO',
},
{
id: 'public_monitoring',
lit: 'c',
title: 'Systematische Überwachung öffentlicher Bereiche',
description: 'Systematische umfangreiche Überwachung öffentlich zugänglicher Bereiche.',
gdprRef: 'Art. 35 Abs. 3 lit. c DSGVO',
},
]
// =============================================================================
// KI-SPEZIFISCHE DSFA-TRIGGER
// Quelle: Deutsche DSFA-Liste (nicht-öffentlicher Bereich)
// =============================================================================
export const AI_DSFA_TRIGGERS = [
{
id: 'ai_interaction',
title: 'KI zur Steuerung der Interaktion mit Betroffenen',
description: 'Einsatz von künstlicher Intelligenz zur Steuerung der Interaktion mit betroffenen Personen.',
examples: ['KI-gestützter Kundensupport', 'Chatbots mit personenbezogener Verarbeitung', 'Automatisierte Kommunikation'],
requiresDSFA: true,
},
{
id: 'ai_personal_aspects',
title: 'KI zur Bewertung persönlicher Aspekte',
description: 'Einsatz von künstlicher Intelligenz zur Bewertung persönlicher Aspekte natürlicher Personen.',
examples: ['Automatisierte Stimmungsanalyse', 'Verhaltensvorhersagen', 'Persönlichkeitsprofile'],
requiresDSFA: true,
},
{
id: 'ai_decision_making',
title: 'KI-basierte automatisierte Entscheidungen',
description: 'Automatisierte Entscheidungsfindung auf Basis von KI mit erheblicher Auswirkung auf Betroffene.',
examples: ['Automatische Kreditvergabe', 'KI-basiertes Recruiting', 'Algorithmenbasierte Preisgestaltung'],
requiresDSFA: true,
},
{
id: 'ai_training_personal_data',
title: 'KI-Training mit personenbezogenen Daten',
description: 'Training von KI-Modellen mit personenbezogenen Daten, insbesondere sensiblen Daten.',
examples: ['Training mit Gesundheitsdaten', 'Fine-Tuning mit Kundendaten', 'ML mit biometrischen Daten'],
requiresDSFA: true,
},
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,99 @@
/**
* PDF Export Helper Functions
* Shared formatting utilities for jsPDF document generation
*/
import jsPDF from 'jspdf'
import { SDKState } from './types'
import { LABELS_DE } from './export-types'
// =============================================================================
// FORMATTING HELPERS
// =============================================================================
export function formatDate(date: Date | string | undefined): string {
if (!date) return '-'
const d = typeof date === 'string' ? new Date(date) : date
return d.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
})
}
// =============================================================================
// PAGE LAYOUT HELPERS
// =============================================================================
export function addHeader(doc: jsPDF, title: string, pageNum: number, totalPages: number): void {
const pageWidth = doc.internal.pageSize.getWidth()
// Header line
doc.setDrawColor(147, 51, 234) // Purple
doc.setLineWidth(0.5)
doc.line(20, 15, pageWidth - 20, 15)
// Title
doc.setFontSize(10)
doc.setTextColor(100)
doc.text(title, 20, 12)
// Page number
doc.text(`${LABELS_DE.page} ${pageNum}/${totalPages}`, pageWidth - 40, 12)
}
export function addFooter(doc: jsPDF, state: SDKState): void {
const pageWidth = doc.internal.pageSize.getWidth()
const pageHeight = doc.internal.pageSize.getHeight()
// Footer line
doc.setDrawColor(200)
doc.setLineWidth(0.3)
doc.line(20, pageHeight - 15, pageWidth - 20, pageHeight - 15)
// Footer text
doc.setFontSize(8)
doc.setTextColor(150)
doc.text(`Tenant: ${state.tenantId} | ${LABELS_DE.generatedAt}: ${formatDate(new Date())}`, 20, pageHeight - 10)
}
// =============================================================================
// CONTENT HELPERS
// =============================================================================
export function addSectionTitle(doc: jsPDF, title: string, y: number): number {
doc.setFontSize(14)
doc.setTextColor(147, 51, 234) // Purple
doc.setFont('helvetica', 'bold')
doc.text(title, 20, y)
doc.setFont('helvetica', 'normal')
return y + 10
}
export function addSubsectionTitle(doc: jsPDF, title: string, y: number): number {
doc.setFontSize(11)
doc.setTextColor(60)
doc.setFont('helvetica', 'bold')
doc.text(title, 25, y)
doc.setFont('helvetica', 'normal')
return y + 7
}
export function addText(doc: jsPDF, text: string, x: number, y: number, maxWidth: number = 170): number {
doc.setFontSize(10)
doc.setTextColor(60)
const lines = doc.splitTextToSize(text, maxWidth)
doc.text(lines, x, y)
return y + lines.length * 5
}
export function checkPageBreak(doc: jsPDF, y: number, requiredSpace: number = 40): number {
const pageHeight = doc.internal.pageSize.getHeight()
if (y + requiredSpace > pageHeight - 25) {
doc.addPage()
return 30
}
return y
}

View File

@@ -0,0 +1,312 @@
/**
* PDF Export
* Generates a multi-page compliance report as PDF
*/
import jsPDF from 'jspdf'
import { SDKState, SDK_STEPS } from './types'
import { ExportOptions, DEFAULT_OPTIONS, LABELS_DE } from './export-types'
import {
formatDate,
addHeader,
addFooter,
addSectionTitle,
addSubsectionTitle,
addText,
checkPageBreak,
} from './export-pdf-helpers'
export async function exportToPDF(state: SDKState, options: ExportOptions = {}): Promise<Blob> {
const opts = { ...DEFAULT_OPTIONS, ...options }
const doc = new jsPDF()
let y = 30
const pageWidth = doc.internal.pageSize.getWidth()
// ==========================================================================
// Title Page
// ==========================================================================
// Logo/Title area
doc.setFillColor(147, 51, 234)
doc.rect(0, 0, pageWidth, 60, 'F')
doc.setFontSize(24)
doc.setTextColor(255)
doc.setFont('helvetica', 'bold')
doc.text(LABELS_DE.title, 20, 35)
doc.setFontSize(14)
doc.setFont('helvetica', 'normal')
doc.text(LABELS_DE.subtitle, 20, 48)
// Reset for content
y = 80
// Summary box
doc.setDrawColor(200)
doc.setFillColor(249, 250, 251)
doc.roundedRect(20, y, pageWidth - 40, 50, 3, 3, 'FD')
y += 15
doc.setFontSize(12)
doc.setTextColor(60)
doc.text(`${LABELS_DE.generatedAt}: ${formatDate(new Date())}`, 30, y)
y += 10
doc.text(`Tenant ID: ${state.tenantId}`, 30, y)
y += 10
doc.text(`Version: ${state.version}`, 30, y)
y += 10
const completedSteps = state.completedSteps.length
const totalSteps = SDK_STEPS.length
doc.text(`${LABELS_DE.progress}: ${completedSteps}/${totalSteps} Schritte (${Math.round(completedSteps / totalSteps * 100)}%)`, 30, y)
y += 30
// Table of Contents
y = addSectionTitle(doc, 'Inhaltsverzeichnis', y)
const tocItems = [
{ title: 'Zusammenfassung', page: 2 },
{ title: 'Phase 1: Compliance Assessment', page: 3 },
{ title: 'Phase 2: Dokumentengenerierung', page: 4 },
{ title: 'Risiken & Controls', page: 5 },
{ title: 'Checkpoints', page: 6 },
]
doc.setFontSize(10)
doc.setTextColor(80)
tocItems.forEach((item, idx) => {
doc.text(`${idx + 1}. ${item.title}`, 25, y)
doc.text(`${item.page}`, pageWidth - 30, y, { align: 'right' })
y += 7
})
// ==========================================================================
// Summary Page
// ==========================================================================
doc.addPage()
y = 30
y = addSectionTitle(doc, LABELS_DE.summary, y)
// Progress overview
doc.setFillColor(249, 250, 251)
doc.roundedRect(20, y, pageWidth - 40, 40, 3, 3, 'F')
y += 15
const phase1Steps = SDK_STEPS.filter(s => s.phase === 1)
const phase2Steps = SDK_STEPS.filter(s => s.phase === 2)
const phase1Completed = phase1Steps.filter(s => state.completedSteps.includes(s.id)).length
const phase2Completed = phase2Steps.filter(s => state.completedSteps.includes(s.id)).length
doc.setFontSize(10)
doc.setTextColor(60)
doc.text(`${LABELS_DE.phase1}: ${phase1Completed}/${phase1Steps.length} ${LABELS_DE.completed}`, 30, y)
y += 8
doc.text(`${LABELS_DE.phase2}: ${phase2Completed}/${phase2Steps.length} ${LABELS_DE.completed}`, 30, y)
y += 25
// Key metrics
y = addSubsectionTitle(doc, 'Kennzahlen', y)
const metrics = [
{ label: 'Use Cases', value: state.useCases.length },
{ label: 'Risiken identifiziert', value: state.risks.length },
{ label: 'Controls definiert', value: state.controls.length },
{ label: 'Anforderungen', value: state.requirements.length },
{ label: 'Nachweise', value: state.evidence.length },
]
metrics.forEach(metric => {
doc.text(`${metric.label}: ${metric.value}`, 30, y)
y += 7
})
// ==========================================================================
// Use Cases
// ==========================================================================
y += 10
y = checkPageBreak(doc, y)
y = addSectionTitle(doc, LABELS_DE.useCases, y)
if (state.useCases.length === 0) {
y = addText(doc, LABELS_DE.noData, 25, y)
} else {
state.useCases.forEach((uc, idx) => {
y = checkPageBreak(doc, y, 50)
doc.setFillColor(249, 250, 251)
doc.roundedRect(20, y - 5, pageWidth - 40, 35, 2, 2, 'F')
doc.setFontSize(11)
doc.setTextColor(40)
doc.setFont('helvetica', 'bold')
doc.text(`${idx + 1}. ${uc.name}`, 25, y + 5)
doc.setFont('helvetica', 'normal')
doc.setFontSize(9)
doc.setTextColor(100)
const ucStatus = uc.stepsCompleted === uc.steps.length ? LABELS_DE.completed : `${uc.stepsCompleted}/${uc.steps.length} Schritte`
doc.text(`ID: ${uc.id} | ${LABELS_DE.status}: ${ucStatus}`, 25, y + 13)
if (uc.description) {
y = addText(doc, uc.description, 25, y + 21, 160)
}
y += 40
})
}
// ==========================================================================
// Risks
// ==========================================================================
doc.addPage()
y = 30
y = addSectionTitle(doc, LABELS_DE.risks, y)
if (state.risks.length === 0) {
y = addText(doc, LABELS_DE.noData, 25, y)
} else {
// Sort by severity
const sortedRisks = [...state.risks].sort((a, b) => {
const order = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 }
return (order[a.severity] || 4) - (order[b.severity] || 4)
})
sortedRisks.forEach((risk, idx) => {
y = checkPageBreak(doc, y, 45)
// Severity color
const severityColors: Record<string, [number, number, number]> = {
CRITICAL: [220, 38, 38],
HIGH: [234, 88, 12],
MEDIUM: [234, 179, 8],
LOW: [34, 197, 94],
}
const color = severityColors[risk.severity] || [100, 100, 100]
doc.setFillColor(color[0], color[1], color[2])
doc.rect(20, y - 3, 3, 30, 'F')
doc.setFontSize(11)
doc.setTextColor(40)
doc.setFont('helvetica', 'bold')
doc.text(`${idx + 1}. ${risk.title}`, 28, y + 5)
doc.setFont('helvetica', 'normal')
doc.setFontSize(9)
doc.setTextColor(100)
doc.text(`${LABELS_DE.severity}: ${risk.severity} | ${LABELS_DE.category}: ${risk.category}`, 28, y + 13)
if (risk.description) {
y = addText(doc, risk.description, 28, y + 21, 155)
}
if (risk.mitigation && risk.mitigation.length > 0) {
y += 5
doc.setFontSize(9)
doc.setTextColor(34, 197, 94)
doc.text(`${LABELS_DE.mitigation}: ${risk.mitigation.join(', ')}`, 28, y)
}
y += 15
})
}
// ==========================================================================
// Controls
// ==========================================================================
doc.addPage()
y = 30
y = addSectionTitle(doc, LABELS_DE.controls, y)
if (state.controls.length === 0) {
y = addText(doc, LABELS_DE.noData, 25, y)
} else {
state.controls.forEach((ctrl, idx) => {
y = checkPageBreak(doc, y, 35)
doc.setFillColor(249, 250, 251)
doc.roundedRect(20, y - 5, pageWidth - 40, 28, 2, 2, 'F')
doc.setFontSize(10)
doc.setTextColor(40)
doc.setFont('helvetica', 'bold')
doc.text(`${idx + 1}. ${ctrl.name}`, 25, y + 5)
doc.setFont('helvetica', 'normal')
doc.setFontSize(9)
doc.setTextColor(100)
doc.text(`${LABELS_DE.category}: ${ctrl.category} | ${LABELS_DE.implementation}: ${ctrl.implementationStatus || 'Nicht definiert'}`, 25, y + 13)
if (ctrl.description) {
y = addText(doc, ctrl.description.substring(0, 150) + (ctrl.description.length > 150 ? '...' : ''), 25, y + 20, 160)
}
y += 35
})
}
// ==========================================================================
// Checkpoints
// ==========================================================================
doc.addPage()
y = 30
y = addSectionTitle(doc, LABELS_DE.checkpoints, y)
const checkpointIds = Object.keys(state.checkpoints)
if (checkpointIds.length === 0) {
y = addText(doc, LABELS_DE.noData, 25, y)
} else {
checkpointIds.forEach((cpId) => {
const cp = state.checkpoints[cpId]
y = checkPageBreak(doc, y, 25)
const statusColor = cp.passed ? [34, 197, 94] : [220, 38, 38]
doc.setFillColor(statusColor[0], statusColor[1], statusColor[2])
doc.circle(25, y + 2, 3, 'F')
doc.setFontSize(10)
doc.setTextColor(40)
doc.text(cpId, 35, y + 5)
doc.setFontSize(9)
doc.setTextColor(100)
doc.text(`${LABELS_DE.status}: ${cp.passed ? LABELS_DE.completed : LABELS_DE.pending}`, 35, y + 12)
if (cp.errors && cp.errors.length > 0) {
doc.setTextColor(220, 38, 38)
doc.text(`Fehler: ${cp.errors.map(e => e.message).join(', ')}`, 35, y + 19)
y += 7
}
y += 20
})
}
// ==========================================================================
// Add page numbers
// ==========================================================================
const pageCount = doc.getNumberOfPages()
for (let i = 1; i <= pageCount; i++) {
doc.setPage(i)
if (i > 1) {
addHeader(doc, LABELS_DE.title, i, pageCount)
}
addFooter(doc, state)
}
return doc.output('blob')
}

View File

@@ -0,0 +1,53 @@
/**
* SDK Export Types, Labels, and Constants
*/
// =============================================================================
// TYPES
// =============================================================================
export interface ExportOptions {
includeEvidence?: boolean
includeDocuments?: boolean
includeRawData?: boolean
language?: 'de' | 'en'
}
export const DEFAULT_OPTIONS: ExportOptions = {
includeEvidence: true,
includeDocuments: true,
includeRawData: true,
language: 'de',
}
// =============================================================================
// LABELS (German)
// =============================================================================
export const LABELS_DE = {
title: 'AI Compliance SDK - Export',
subtitle: 'Compliance-Dokumentation',
generatedAt: 'Generiert am',
page: 'Seite',
summary: 'Zusammenfassung',
progress: 'Fortschritt',
phase1: 'Phase 1: Automatisches Compliance Assessment',
phase2: 'Phase 2: Dokumentengenerierung',
useCases: 'Use Cases',
risks: 'Risiken',
controls: 'Controls',
requirements: 'Anforderungen',
modules: 'Compliance-Module',
evidence: 'Nachweise',
checkpoints: 'Checkpoints',
noData: 'Keine Daten vorhanden',
status: 'Status',
completed: 'Abgeschlossen',
pending: 'Ausstehend',
inProgress: 'In Bearbeitung',
severity: 'Schweregrad',
mitigation: 'Mitigation',
description: 'Beschreibung',
category: 'Kategorie',
implementation: 'Implementierung',
}

View File

@@ -0,0 +1,278 @@
/**
* ZIP Export
* Generates a structured ZIP archive with all compliance data
*/
import JSZip from 'jszip'
import { SDKState, SDK_STEPS } from './types'
import { ExportOptions, DEFAULT_OPTIONS } from './export-types'
import { formatDate } from './export-pdf-helpers'
import { exportToPDF } from './export-pdf'
export async function exportToZIP(state: SDKState, options: ExportOptions = {}): Promise<Blob> {
const opts = { ...DEFAULT_OPTIONS, ...options }
const zip = new JSZip()
// Create folder structure
const rootFolder = zip.folder('ai-compliance-sdk-export')
if (!rootFolder) throw new Error('Failed to create ZIP folder')
const phase1Folder = rootFolder.folder('phase1-assessment')
const phase2Folder = rootFolder.folder('phase2-documents')
const dataFolder = rootFolder.folder('data')
// ==========================================================================
// Main State JSON
// ==========================================================================
if (opts.includeRawData && dataFolder) {
dataFolder.file('state.json', JSON.stringify(state, null, 2))
}
// ==========================================================================
// README
// ==========================================================================
const readmeContent = `# AI Compliance SDK Export
Generated: ${formatDate(new Date())}
Tenant: ${state.tenantId}
Version: ${state.version}
## Folder Structure
- **phase1-assessment/**: Compliance Assessment Ergebnisse
- use-cases.json: Alle Use Cases
- risks.json: Identifizierte Risiken
- controls.json: Definierte Controls
- requirements.json: Compliance-Anforderungen
- **phase2-documents/**: Generierte Dokumente
- dsfa.json: Datenschutz-Folgenabschaetzung
- toms.json: Technische und organisatorische Massnahmen
- vvt.json: Verarbeitungsverzeichnis
- documents.json: Rechtliche Dokumente
- **data/**: Rohdaten
- state.json: Kompletter SDK State
## Progress
Phase 1: ${SDK_STEPS.filter(s => s.phase === 1 && state.completedSteps.includes(s.id)).length}/${SDK_STEPS.filter(s => s.phase === 1).length} completed
Phase 2: ${SDK_STEPS.filter(s => s.phase === 2 && state.completedSteps.includes(s.id)).length}/${SDK_STEPS.filter(s => s.phase === 2).length} completed
## Key Metrics
- Use Cases: ${state.useCases.length}
- Risks: ${state.risks.length}
- Controls: ${state.controls.length}
- Requirements: ${state.requirements.length}
- Evidence: ${state.evidence.length}
`
rootFolder.file('README.md', readmeContent)
// ==========================================================================
// Phase 1 Files
// ==========================================================================
if (phase1Folder) {
// Use Cases
phase1Folder.file('use-cases.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.useCases.length,
useCases: state.useCases,
}, null, 2))
// Risks
phase1Folder.file('risks.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.risks.length,
risks: state.risks,
summary: {
critical: state.risks.filter(r => r.severity === 'CRITICAL').length,
high: state.risks.filter(r => r.severity === 'HIGH').length,
medium: state.risks.filter(r => r.severity === 'MEDIUM').length,
low: state.risks.filter(r => r.severity === 'LOW').length,
},
}, null, 2))
// Controls
phase1Folder.file('controls.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.controls.length,
controls: state.controls,
}, null, 2))
// Requirements
phase1Folder.file('requirements.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.requirements.length,
requirements: state.requirements,
}, null, 2))
// Modules
phase1Folder.file('modules.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.modules.length,
modules: state.modules,
}, null, 2))
// Evidence
if (opts.includeEvidence) {
phase1Folder.file('evidence.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.evidence.length,
evidence: state.evidence,
}, null, 2))
}
// Checkpoints
phase1Folder.file('checkpoints.json', JSON.stringify({
exportedAt: new Date().toISOString(),
checkpoints: state.checkpoints,
}, null, 2))
// Screening
if (state.screening) {
phase1Folder.file('screening.json', JSON.stringify({
exportedAt: new Date().toISOString(),
screening: state.screening,
}, null, 2))
}
}
// ==========================================================================
// Phase 2 Files
// ==========================================================================
if (phase2Folder) {
// DSFA
if (state.dsfa) {
phase2Folder.file('dsfa.json', JSON.stringify({
exportedAt: new Date().toISOString(),
dsfa: state.dsfa,
}, null, 2))
}
// TOMs
phase2Folder.file('toms.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.toms.length,
toms: state.toms,
}, null, 2))
// VVT (Processing Activities)
phase2Folder.file('vvt.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.vvt.length,
processingActivities: state.vvt,
}, null, 2))
// Legal Documents
if (opts.includeDocuments) {
phase2Folder.file('documents.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.documents.length,
documents: state.documents,
}, null, 2))
}
// Cookie Banner Config
if (state.cookieBanner) {
phase2Folder.file('cookie-banner.json', JSON.stringify({
exportedAt: new Date().toISOString(),
config: state.cookieBanner,
}, null, 2))
}
// Retention Policies
phase2Folder.file('retention-policies.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.retentionPolicies.length,
policies: state.retentionPolicies,
}, null, 2))
// AI Act Classification
if (state.aiActClassification) {
phase2Folder.file('ai-act-classification.json', JSON.stringify({
exportedAt: new Date().toISOString(),
classification: state.aiActClassification,
}, null, 2))
}
// Obligations
phase2Folder.file('obligations.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.obligations.length,
obligations: state.obligations,
}, null, 2))
// Consent Records
phase2Folder.file('consents.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.consents.length,
consents: state.consents,
}, null, 2))
// DSR Config
if (state.dsrConfig) {
phase2Folder.file('dsr-config.json', JSON.stringify({
exportedAt: new Date().toISOString(),
config: state.dsrConfig,
}, null, 2))
}
// Escalation Workflows
phase2Folder.file('escalation-workflows.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.escalationWorkflows.length,
workflows: state.escalationWorkflows,
}, null, 2))
}
// ==========================================================================
// Security Data
// ==========================================================================
if (dataFolder) {
if (state.sbom) {
dataFolder.file('sbom.json', JSON.stringify({
exportedAt: new Date().toISOString(),
sbom: state.sbom,
}, null, 2))
}
if (state.securityIssues.length > 0) {
dataFolder.file('security-issues.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.securityIssues.length,
issues: state.securityIssues,
}, null, 2))
}
if (state.securityBacklog.length > 0) {
dataFolder.file('security-backlog.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.securityBacklog.length,
backlog: state.securityBacklog,
}, null, 2))
}
}
// ==========================================================================
// Generate PDF and include in ZIP
// ==========================================================================
try {
const pdfBlob = await exportToPDF(state, options)
const pdfArrayBuffer = await pdfBlob.arrayBuffer()
rootFolder.file('compliance-report.pdf', pdfArrayBuffer)
} catch (error) {
console.error('Failed to generate PDF for ZIP:', error)
// Continue without PDF
}
// Generate ZIP
return zip.generateAsync({ type: 'blob', compression: 'DEFLATE' })
}

View File

@@ -1,714 +1,20 @@
/**
* SDK Export Utilities
* Handles PDF and ZIP export of SDK state and documents
* Barrel module — re-exports PDF, ZIP, and helpers
*/
import jsPDF from 'jspdf'
import JSZip from 'jszip'
import { SDKState, SDK_STEPS, getStepById } from './types'
import { SDKState } from './types'
import { ExportOptions } from './export-types'
import { exportToPDF } from './export-pdf'
import { exportToZIP } from './export-zip'
// Re-export all public API
export type { ExportOptions } from './export-types'
export { exportToPDF } from './export-pdf'
export { exportToZIP } from './export-zip'
// =============================================================================
// TYPES
// =============================================================================
export interface ExportOptions {
includeEvidence?: boolean
includeDocuments?: boolean
includeRawData?: boolean
language?: 'de' | 'en'
}
const DEFAULT_OPTIONS: ExportOptions = {
includeEvidence: true,
includeDocuments: true,
includeRawData: true,
language: 'de',
}
// =============================================================================
// LABELS (German)
// =============================================================================
const LABELS_DE = {
title: 'AI Compliance SDK - Export',
subtitle: 'Compliance-Dokumentation',
generatedAt: 'Generiert am',
page: 'Seite',
summary: 'Zusammenfassung',
progress: 'Fortschritt',
phase1: 'Phase 1: Automatisches Compliance Assessment',
phase2: 'Phase 2: Dokumentengenerierung',
useCases: 'Use Cases',
risks: 'Risiken',
controls: 'Controls',
requirements: 'Anforderungen',
modules: 'Compliance-Module',
evidence: 'Nachweise',
checkpoints: 'Checkpoints',
noData: 'Keine Daten vorhanden',
status: 'Status',
completed: 'Abgeschlossen',
pending: 'Ausstehend',
inProgress: 'In Bearbeitung',
severity: 'Schweregrad',
mitigation: 'Mitigation',
description: 'Beschreibung',
category: 'Kategorie',
implementation: 'Implementierung',
}
// =============================================================================
// PDF EXPORT
// =============================================================================
function formatDate(date: Date | string | undefined): string {
if (!date) return '-'
const d = typeof date === 'string' ? new Date(date) : date
return d.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
})
}
function addHeader(doc: jsPDF, title: string, pageNum: number, totalPages: number): void {
const pageWidth = doc.internal.pageSize.getWidth()
// Header line
doc.setDrawColor(147, 51, 234) // Purple
doc.setLineWidth(0.5)
doc.line(20, 15, pageWidth - 20, 15)
// Title
doc.setFontSize(10)
doc.setTextColor(100)
doc.text(title, 20, 12)
// Page number
doc.text(`${LABELS_DE.page} ${pageNum}/${totalPages}`, pageWidth - 40, 12)
}
function addFooter(doc: jsPDF, state: SDKState): void {
const pageWidth = doc.internal.pageSize.getWidth()
const pageHeight = doc.internal.pageSize.getHeight()
// Footer line
doc.setDrawColor(200)
doc.setLineWidth(0.3)
doc.line(20, pageHeight - 15, pageWidth - 20, pageHeight - 15)
// Footer text
doc.setFontSize(8)
doc.setTextColor(150)
doc.text(`Tenant: ${state.tenantId} | ${LABELS_DE.generatedAt}: ${formatDate(new Date())}`, 20, pageHeight - 10)
}
function addSectionTitle(doc: jsPDF, title: string, y: number): number {
doc.setFontSize(14)
doc.setTextColor(147, 51, 234) // Purple
doc.setFont('helvetica', 'bold')
doc.text(title, 20, y)
doc.setFont('helvetica', 'normal')
return y + 10
}
function addSubsectionTitle(doc: jsPDF, title: string, y: number): number {
doc.setFontSize(11)
doc.setTextColor(60)
doc.setFont('helvetica', 'bold')
doc.text(title, 25, y)
doc.setFont('helvetica', 'normal')
return y + 7
}
function addText(doc: jsPDF, text: string, x: number, y: number, maxWidth: number = 170): number {
doc.setFontSize(10)
doc.setTextColor(60)
const lines = doc.splitTextToSize(text, maxWidth)
doc.text(lines, x, y)
return y + lines.length * 5
}
function checkPageBreak(doc: jsPDF, y: number, requiredSpace: number = 40): number {
const pageHeight = doc.internal.pageSize.getHeight()
if (y + requiredSpace > pageHeight - 25) {
doc.addPage()
return 30
}
return y
}
export async function exportToPDF(state: SDKState, options: ExportOptions = {}): Promise<Blob> {
const opts = { ...DEFAULT_OPTIONS, ...options }
const doc = new jsPDF()
let y = 30
const pageWidth = doc.internal.pageSize.getWidth()
// ==========================================================================
// Title Page
// ==========================================================================
// Logo/Title area
doc.setFillColor(147, 51, 234)
doc.rect(0, 0, pageWidth, 60, 'F')
doc.setFontSize(24)
doc.setTextColor(255)
doc.setFont('helvetica', 'bold')
doc.text(LABELS_DE.title, 20, 35)
doc.setFontSize(14)
doc.setFont('helvetica', 'normal')
doc.text(LABELS_DE.subtitle, 20, 48)
// Reset for content
y = 80
// Summary box
doc.setDrawColor(200)
doc.setFillColor(249, 250, 251)
doc.roundedRect(20, y, pageWidth - 40, 50, 3, 3, 'FD')
y += 15
doc.setFontSize(12)
doc.setTextColor(60)
doc.text(`${LABELS_DE.generatedAt}: ${formatDate(new Date())}`, 30, y)
y += 10
doc.text(`Tenant ID: ${state.tenantId}`, 30, y)
y += 10
doc.text(`Version: ${state.version}`, 30, y)
y += 10
const completedSteps = state.completedSteps.length
const totalSteps = SDK_STEPS.length
doc.text(`${LABELS_DE.progress}: ${completedSteps}/${totalSteps} Schritte (${Math.round(completedSteps / totalSteps * 100)}%)`, 30, y)
y += 30
// Table of Contents
y = addSectionTitle(doc, 'Inhaltsverzeichnis', y)
const tocItems = [
{ title: 'Zusammenfassung', page: 2 },
{ title: 'Phase 1: Compliance Assessment', page: 3 },
{ title: 'Phase 2: Dokumentengenerierung', page: 4 },
{ title: 'Risiken & Controls', page: 5 },
{ title: 'Checkpoints', page: 6 },
]
doc.setFontSize(10)
doc.setTextColor(80)
tocItems.forEach((item, idx) => {
doc.text(`${idx + 1}. ${item.title}`, 25, y)
doc.text(`${item.page}`, pageWidth - 30, y, { align: 'right' })
y += 7
})
// ==========================================================================
// Summary Page
// ==========================================================================
doc.addPage()
y = 30
y = addSectionTitle(doc, LABELS_DE.summary, y)
// Progress overview
doc.setFillColor(249, 250, 251)
doc.roundedRect(20, y, pageWidth - 40, 40, 3, 3, 'F')
y += 15
const phase1Steps = SDK_STEPS.filter(s => s.phase === 1)
const phase2Steps = SDK_STEPS.filter(s => s.phase === 2)
const phase1Completed = phase1Steps.filter(s => state.completedSteps.includes(s.id)).length
const phase2Completed = phase2Steps.filter(s => state.completedSteps.includes(s.id)).length
doc.setFontSize(10)
doc.setTextColor(60)
doc.text(`${LABELS_DE.phase1}: ${phase1Completed}/${phase1Steps.length} ${LABELS_DE.completed}`, 30, y)
y += 8
doc.text(`${LABELS_DE.phase2}: ${phase2Completed}/${phase2Steps.length} ${LABELS_DE.completed}`, 30, y)
y += 25
// Key metrics
y = addSubsectionTitle(doc, 'Kennzahlen', y)
const metrics = [
{ label: 'Use Cases', value: state.useCases.length },
{ label: 'Risiken identifiziert', value: state.risks.length },
{ label: 'Controls definiert', value: state.controls.length },
{ label: 'Anforderungen', value: state.requirements.length },
{ label: 'Nachweise', value: state.evidence.length },
]
metrics.forEach(metric => {
doc.text(`${metric.label}: ${metric.value}`, 30, y)
y += 7
})
// ==========================================================================
// Use Cases
// ==========================================================================
y += 10
y = checkPageBreak(doc, y)
y = addSectionTitle(doc, LABELS_DE.useCases, y)
if (state.useCases.length === 0) {
y = addText(doc, LABELS_DE.noData, 25, y)
} else {
state.useCases.forEach((uc, idx) => {
y = checkPageBreak(doc, y, 50)
doc.setFillColor(249, 250, 251)
doc.roundedRect(20, y - 5, pageWidth - 40, 35, 2, 2, 'F')
doc.setFontSize(11)
doc.setTextColor(40)
doc.setFont('helvetica', 'bold')
doc.text(`${idx + 1}. ${uc.name}`, 25, y + 5)
doc.setFont('helvetica', 'normal')
doc.setFontSize(9)
doc.setTextColor(100)
const ucStatus = uc.stepsCompleted === uc.steps.length ? LABELS_DE.completed : `${uc.stepsCompleted}/${uc.steps.length} Schritte`
doc.text(`ID: ${uc.id} | ${LABELS_DE.status}: ${ucStatus}`, 25, y + 13)
if (uc.description) {
y = addText(doc, uc.description, 25, y + 21, 160)
}
y += 40
})
}
// ==========================================================================
// Risks
// ==========================================================================
doc.addPage()
y = 30
y = addSectionTitle(doc, LABELS_DE.risks, y)
if (state.risks.length === 0) {
y = addText(doc, LABELS_DE.noData, 25, y)
} else {
// Sort by severity
const sortedRisks = [...state.risks].sort((a, b) => {
const order = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 }
return (order[a.severity] || 4) - (order[b.severity] || 4)
})
sortedRisks.forEach((risk, idx) => {
y = checkPageBreak(doc, y, 45)
// Severity color
const severityColors: Record<string, [number, number, number]> = {
CRITICAL: [220, 38, 38],
HIGH: [234, 88, 12],
MEDIUM: [234, 179, 8],
LOW: [34, 197, 94],
}
const color = severityColors[risk.severity] || [100, 100, 100]
doc.setFillColor(color[0], color[1], color[2])
doc.rect(20, y - 3, 3, 30, 'F')
doc.setFontSize(11)
doc.setTextColor(40)
doc.setFont('helvetica', 'bold')
doc.text(`${idx + 1}. ${risk.title}`, 28, y + 5)
doc.setFont('helvetica', 'normal')
doc.setFontSize(9)
doc.setTextColor(100)
doc.text(`${LABELS_DE.severity}: ${risk.severity} | ${LABELS_DE.category}: ${risk.category}`, 28, y + 13)
if (risk.description) {
y = addText(doc, risk.description, 28, y + 21, 155)
}
if (risk.mitigation && risk.mitigation.length > 0) {
y += 5
doc.setFontSize(9)
doc.setTextColor(34, 197, 94)
doc.text(`${LABELS_DE.mitigation}: ${risk.mitigation.join(', ')}`, 28, y)
}
y += 15
})
}
// ==========================================================================
// Controls
// ==========================================================================
doc.addPage()
y = 30
y = addSectionTitle(doc, LABELS_DE.controls, y)
if (state.controls.length === 0) {
y = addText(doc, LABELS_DE.noData, 25, y)
} else {
state.controls.forEach((ctrl, idx) => {
y = checkPageBreak(doc, y, 35)
doc.setFillColor(249, 250, 251)
doc.roundedRect(20, y - 5, pageWidth - 40, 28, 2, 2, 'F')
doc.setFontSize(10)
doc.setTextColor(40)
doc.setFont('helvetica', 'bold')
doc.text(`${idx + 1}. ${ctrl.name}`, 25, y + 5)
doc.setFont('helvetica', 'normal')
doc.setFontSize(9)
doc.setTextColor(100)
doc.text(`${LABELS_DE.category}: ${ctrl.category} | ${LABELS_DE.implementation}: ${ctrl.implementationStatus || 'Nicht definiert'}`, 25, y + 13)
if (ctrl.description) {
y = addText(doc, ctrl.description.substring(0, 150) + (ctrl.description.length > 150 ? '...' : ''), 25, y + 20, 160)
}
y += 35
})
}
// ==========================================================================
// Checkpoints
// ==========================================================================
doc.addPage()
y = 30
y = addSectionTitle(doc, LABELS_DE.checkpoints, y)
const checkpointIds = Object.keys(state.checkpoints)
if (checkpointIds.length === 0) {
y = addText(doc, LABELS_DE.noData, 25, y)
} else {
checkpointIds.forEach((cpId) => {
const cp = state.checkpoints[cpId]
y = checkPageBreak(doc, y, 25)
const statusColor = cp.passed ? [34, 197, 94] : [220, 38, 38]
doc.setFillColor(statusColor[0], statusColor[1], statusColor[2])
doc.circle(25, y + 2, 3, 'F')
doc.setFontSize(10)
doc.setTextColor(40)
doc.text(cpId, 35, y + 5)
doc.setFontSize(9)
doc.setTextColor(100)
doc.text(`${LABELS_DE.status}: ${cp.passed ? LABELS_DE.completed : LABELS_DE.pending}`, 35, y + 12)
if (cp.errors && cp.errors.length > 0) {
doc.setTextColor(220, 38, 38)
doc.text(`Fehler: ${cp.errors.map(e => e.message).join(', ')}`, 35, y + 19)
y += 7
}
y += 20
})
}
// ==========================================================================
// Add page numbers
// ==========================================================================
const pageCount = doc.getNumberOfPages()
for (let i = 1; i <= pageCount; i++) {
doc.setPage(i)
if (i > 1) {
addHeader(doc, LABELS_DE.title, i, pageCount)
}
addFooter(doc, state)
}
return doc.output('blob')
}
// =============================================================================
// ZIP EXPORT
// =============================================================================
export async function exportToZIP(state: SDKState, options: ExportOptions = {}): Promise<Blob> {
const opts = { ...DEFAULT_OPTIONS, ...options }
const zip = new JSZip()
// Create folder structure
const rootFolder = zip.folder('ai-compliance-sdk-export')
if (!rootFolder) throw new Error('Failed to create ZIP folder')
const phase1Folder = rootFolder.folder('phase1-assessment')
const phase2Folder = rootFolder.folder('phase2-documents')
const dataFolder = rootFolder.folder('data')
// ==========================================================================
// Main State JSON
// ==========================================================================
if (opts.includeRawData && dataFolder) {
dataFolder.file('state.json', JSON.stringify(state, null, 2))
}
// ==========================================================================
// README
// ==========================================================================
const readmeContent = `# AI Compliance SDK Export
Generated: ${formatDate(new Date())}
Tenant: ${state.tenantId}
Version: ${state.version}
## Folder Structure
- **phase1-assessment/**: Compliance Assessment Ergebnisse
- use-cases.json: Alle Use Cases
- risks.json: Identifizierte Risiken
- controls.json: Definierte Controls
- requirements.json: Compliance-Anforderungen
- **phase2-documents/**: Generierte Dokumente
- dsfa.json: Datenschutz-Folgenabschaetzung
- toms.json: Technische und organisatorische Massnahmen
- vvt.json: Verarbeitungsverzeichnis
- documents.json: Rechtliche Dokumente
- **data/**: Rohdaten
- state.json: Kompletter SDK State
## Progress
Phase 1: ${SDK_STEPS.filter(s => s.phase === 1 && state.completedSteps.includes(s.id)).length}/${SDK_STEPS.filter(s => s.phase === 1).length} completed
Phase 2: ${SDK_STEPS.filter(s => s.phase === 2 && state.completedSteps.includes(s.id)).length}/${SDK_STEPS.filter(s => s.phase === 2).length} completed
## Key Metrics
- Use Cases: ${state.useCases.length}
- Risks: ${state.risks.length}
- Controls: ${state.controls.length}
- Requirements: ${state.requirements.length}
- Evidence: ${state.evidence.length}
`
rootFolder.file('README.md', readmeContent)
// ==========================================================================
// Phase 1 Files
// ==========================================================================
if (phase1Folder) {
// Use Cases
phase1Folder.file('use-cases.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.useCases.length,
useCases: state.useCases,
}, null, 2))
// Risks
phase1Folder.file('risks.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.risks.length,
risks: state.risks,
summary: {
critical: state.risks.filter(r => r.severity === 'CRITICAL').length,
high: state.risks.filter(r => r.severity === 'HIGH').length,
medium: state.risks.filter(r => r.severity === 'MEDIUM').length,
low: state.risks.filter(r => r.severity === 'LOW').length,
},
}, null, 2))
// Controls
phase1Folder.file('controls.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.controls.length,
controls: state.controls,
}, null, 2))
// Requirements
phase1Folder.file('requirements.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.requirements.length,
requirements: state.requirements,
}, null, 2))
// Modules
phase1Folder.file('modules.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.modules.length,
modules: state.modules,
}, null, 2))
// Evidence
if (opts.includeEvidence) {
phase1Folder.file('evidence.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.evidence.length,
evidence: state.evidence,
}, null, 2))
}
// Checkpoints
phase1Folder.file('checkpoints.json', JSON.stringify({
exportedAt: new Date().toISOString(),
checkpoints: state.checkpoints,
}, null, 2))
// Screening
if (state.screening) {
phase1Folder.file('screening.json', JSON.stringify({
exportedAt: new Date().toISOString(),
screening: state.screening,
}, null, 2))
}
}
// ==========================================================================
// Phase 2 Files
// ==========================================================================
if (phase2Folder) {
// DSFA
if (state.dsfa) {
phase2Folder.file('dsfa.json', JSON.stringify({
exportedAt: new Date().toISOString(),
dsfa: state.dsfa,
}, null, 2))
}
// TOMs
phase2Folder.file('toms.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.toms.length,
toms: state.toms,
}, null, 2))
// VVT (Processing Activities)
phase2Folder.file('vvt.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.vvt.length,
processingActivities: state.vvt,
}, null, 2))
// Legal Documents
if (opts.includeDocuments) {
phase2Folder.file('documents.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.documents.length,
documents: state.documents,
}, null, 2))
}
// Cookie Banner Config
if (state.cookieBanner) {
phase2Folder.file('cookie-banner.json', JSON.stringify({
exportedAt: new Date().toISOString(),
config: state.cookieBanner,
}, null, 2))
}
// Retention Policies
phase2Folder.file('retention-policies.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.retentionPolicies.length,
policies: state.retentionPolicies,
}, null, 2))
// AI Act Classification
if (state.aiActClassification) {
phase2Folder.file('ai-act-classification.json', JSON.stringify({
exportedAt: new Date().toISOString(),
classification: state.aiActClassification,
}, null, 2))
}
// Obligations
phase2Folder.file('obligations.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.obligations.length,
obligations: state.obligations,
}, null, 2))
// Consent Records
phase2Folder.file('consents.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.consents.length,
consents: state.consents,
}, null, 2))
// DSR Config
if (state.dsrConfig) {
phase2Folder.file('dsr-config.json', JSON.stringify({
exportedAt: new Date().toISOString(),
config: state.dsrConfig,
}, null, 2))
}
// Escalation Workflows
phase2Folder.file('escalation-workflows.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.escalationWorkflows.length,
workflows: state.escalationWorkflows,
}, null, 2))
}
// ==========================================================================
// Security Data
// ==========================================================================
if (dataFolder) {
if (state.sbom) {
dataFolder.file('sbom.json', JSON.stringify({
exportedAt: new Date().toISOString(),
sbom: state.sbom,
}, null, 2))
}
if (state.securityIssues.length > 0) {
dataFolder.file('security-issues.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.securityIssues.length,
issues: state.securityIssues,
}, null, 2))
}
if (state.securityBacklog.length > 0) {
dataFolder.file('security-backlog.json', JSON.stringify({
exportedAt: new Date().toISOString(),
count: state.securityBacklog.length,
backlog: state.securityBacklog,
}, null, 2))
}
}
// ==========================================================================
// Generate PDF and include in ZIP
// ==========================================================================
try {
const pdfBlob = await exportToPDF(state, options)
const pdfArrayBuffer = await pdfBlob.arrayBuffer()
rootFolder.file('compliance-report.pdf', pdfArrayBuffer)
} catch (error) {
console.error('Failed to generate PDF for ZIP:', error)
// Continue without PDF
}
// Generate ZIP
return zip.generateAsync({ type: 'blob', compression: 'DEFLATE' })
}
// =============================================================================
// EXPORT HELPER
// DOWNLOAD HELPER
// =============================================================================
export async function downloadExport(

View File

@@ -0,0 +1,491 @@
import type React from 'react'
import type {
VendorComplianceAction,
VendorComplianceState,
ProcessingActivity,
Vendor,
ContractDocument,
Finding,
ControlInstance,
ExportFormat,
} from './types'
type Dispatch = React.Dispatch<VendorComplianceAction>
const API_BASE = '/api/sdk/v1/vendor-compliance'
// ==========================================
// DATA LOADING
// ==========================================
export async function loadAllData(dispatch: Dispatch): Promise<void> {
dispatch({ type: 'SET_LOADING', payload: true })
dispatch({ type: 'SET_ERROR', payload: null })
try {
const [
activitiesRes,
vendorsRes,
contractsRes,
findingsRes,
controlsRes,
controlInstancesRes,
] = await Promise.all([
fetch(`${API_BASE}/processing-activities`),
fetch(`${API_BASE}/vendors`),
fetch(`${API_BASE}/contracts`),
fetch(`${API_BASE}/findings`),
fetch(`${API_BASE}/controls`),
fetch(`${API_BASE}/control-instances`),
])
if (activitiesRes.ok) {
const data = await activitiesRes.json()
dispatch({ type: 'SET_PROCESSING_ACTIVITIES', payload: data.data || [] })
}
if (vendorsRes.ok) {
const data = await vendorsRes.json()
dispatch({ type: 'SET_VENDORS', payload: data.data || [] })
}
if (contractsRes.ok) {
const data = await contractsRes.json()
dispatch({ type: 'SET_CONTRACTS', payload: data.data || [] })
}
if (findingsRes.ok) {
const data = await findingsRes.json()
dispatch({ type: 'SET_FINDINGS', payload: data.data || [] })
}
if (controlsRes.ok) {
const data = await controlsRes.json()
dispatch({ type: 'SET_CONTROLS', payload: data.data || [] })
}
if (controlInstancesRes.ok) {
const data = await controlInstancesRes.json()
dispatch({ type: 'SET_CONTROL_INSTANCES', payload: data.data || [] })
}
} catch (error) {
console.error('Failed to load vendor compliance data:', error)
dispatch({
type: 'SET_ERROR',
payload: 'Fehler beim Laden der Daten',
})
} finally {
dispatch({ type: 'SET_LOADING', payload: false })
}
}
// ==========================================
// PROCESSING ACTIVITIES ACTIONS
// ==========================================
export async function apiCreateProcessingActivity(
dispatch: Dispatch,
data: Omit<ProcessingActivity, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>
): Promise<ProcessingActivity> {
const response = await fetch(`${API_BASE}/processing-activities`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Erstellen der Verarbeitungstaetigkeit')
}
const result = await response.json()
const activity = result.data
dispatch({ type: 'ADD_PROCESSING_ACTIVITY', payload: activity })
return activity
}
export async function apiUpdateProcessingActivity(
dispatch: Dispatch,
id: string,
data: Partial<ProcessingActivity>
): Promise<void> {
const response = await fetch(`${API_BASE}/processing-activities/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren der Verarbeitungstaetigkeit')
}
dispatch({ type: 'UPDATE_PROCESSING_ACTIVITY', payload: { id, data } })
}
export async function apiDeleteProcessingActivity(
dispatch: Dispatch,
id: string
): Promise<void> {
const response = await fetch(`${API_BASE}/processing-activities/${id}`, {
method: 'DELETE',
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Loeschen der Verarbeitungstaetigkeit')
}
dispatch({ type: 'DELETE_PROCESSING_ACTIVITY', payload: id })
}
export async function apiDuplicateProcessingActivity(
dispatch: Dispatch,
state: VendorComplianceState,
id: string
): Promise<ProcessingActivity> {
const original = state.processingActivities.find((a) => a.id === id)
if (!original) {
throw new Error('Verarbeitungstaetigkeit nicht gefunden')
}
const { id: _id, vvtId: _vvtId, createdAt: _createdAt, updatedAt: _updatedAt, tenantId: _tenantId, ...rest } = original
const newActivity = await apiCreateProcessingActivity(dispatch, {
...rest,
vvtId: '', // Will be generated by backend
name: {
de: `${original.name.de} (Kopie)`,
en: `${original.name.en} (Copy)`,
},
status: 'DRAFT',
})
return newActivity
}
// ==========================================
// VENDOR ACTIONS
// ==========================================
export async function apiCreateVendor(
dispatch: Dispatch,
data: Omit<Vendor, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>
): Promise<Vendor> {
const response = await fetch(`${API_BASE}/vendors`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Erstellen des Vendors')
}
const result = await response.json()
const vendor = result.data
dispatch({ type: 'ADD_VENDOR', payload: vendor })
return vendor
}
export async function apiUpdateVendor(
dispatch: Dispatch,
id: string,
data: Partial<Vendor>
): Promise<void> {
const response = await fetch(`${API_BASE}/vendors/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Vendors')
}
dispatch({ type: 'UPDATE_VENDOR', payload: { id, data } })
}
export async function apiDeleteVendor(
dispatch: Dispatch,
id: string
): Promise<void> {
const response = await fetch(`${API_BASE}/vendors/${id}`, {
method: 'DELETE',
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Loeschen des Vendors')
}
dispatch({ type: 'DELETE_VENDOR', payload: id })
}
// ==========================================
// CONTRACT ACTIONS
// ==========================================
export async function apiUploadContract(
dispatch: Dispatch,
state: VendorComplianceState,
vendorId: string,
file: File,
metadata: Partial<ContractDocument>
): Promise<ContractDocument> {
const formData = new FormData()
formData.append('file', file)
formData.append('vendorId', vendorId)
formData.append('metadata', JSON.stringify(metadata))
const response = await fetch(`${API_BASE}/contracts`, {
method: 'POST',
body: formData,
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Hochladen des Vertrags')
}
const result = await response.json()
const contract = result.data
dispatch({ type: 'ADD_CONTRACT', payload: contract })
// Update vendor's contracts list
const vendor = state.vendors.find((v) => v.id === vendorId)
if (vendor) {
dispatch({
type: 'UPDATE_VENDOR',
payload: {
id: vendorId,
data: { contracts: [...vendor.contracts, contract.id] },
},
})
}
return contract
}
export async function apiUpdateContract(
dispatch: Dispatch,
id: string,
data: Partial<ContractDocument>
): Promise<void> {
const response = await fetch(`${API_BASE}/contracts/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Vertrags')
}
dispatch({ type: 'UPDATE_CONTRACT', payload: { id, data } })
}
export async function apiDeleteContract(
dispatch: Dispatch,
state: VendorComplianceState,
id: string
): Promise<void> {
const contract = state.contracts.find((c) => c.id === id)
const response = await fetch(`${API_BASE}/contracts/${id}`, {
method: 'DELETE',
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Loeschen des Vertrags')
}
dispatch({ type: 'DELETE_CONTRACT', payload: id })
// Update vendor's contracts list
if (contract) {
const vendor = state.vendors.find((v) => v.id === contract.vendorId)
if (vendor) {
dispatch({
type: 'UPDATE_VENDOR',
payload: {
id: vendor.id,
data: { contracts: vendor.contracts.filter((cId) => cId !== id) },
},
})
}
}
}
export async function apiStartContractReview(
dispatch: Dispatch,
contractId: string
): Promise<void> {
dispatch({
type: 'UPDATE_CONTRACT',
payload: { id: contractId, data: { reviewStatus: 'IN_PROGRESS' } },
})
const response = await fetch(`${API_BASE}/contracts/${contractId}/review`, {
method: 'POST',
})
if (!response.ok) {
dispatch({
type: 'UPDATE_CONTRACT',
payload: { id: contractId, data: { reviewStatus: 'FAILED' } },
})
const error = await response.json()
throw new Error(error.error || 'Fehler beim Starten der Vertragspruefung')
}
const result = await response.json()
// Update contract with review results
dispatch({
type: 'UPDATE_CONTRACT',
payload: {
id: contractId,
data: {
reviewStatus: 'COMPLETED',
reviewCompletedAt: new Date(),
complianceScore: result.data.complianceScore,
},
},
})
// Add findings
if (result.data.findings && result.data.findings.length > 0) {
dispatch({ type: 'ADD_FINDINGS', payload: result.data.findings })
}
}
// ==========================================
// FINDINGS ACTIONS
// ==========================================
export async function apiUpdateFinding(
dispatch: Dispatch,
id: string,
data: Partial<Finding>
): Promise<void> {
const response = await fetch(`${API_BASE}/findings/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Findings')
}
dispatch({ type: 'UPDATE_FINDING', payload: { id, data } })
}
export async function apiResolveFinding(
dispatch: Dispatch,
id: string,
resolution: string
): Promise<void> {
await apiUpdateFinding(dispatch, id, {
status: 'RESOLVED',
resolution,
resolvedAt: new Date(),
})
}
// ==========================================
// CONTROL ACTIONS
// ==========================================
export async function apiUpdateControlInstance(
dispatch: Dispatch,
id: string,
data: Partial<ControlInstance>
): Promise<void> {
const response = await fetch(`${API_BASE}/control-instances/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Control-Status')
}
dispatch({ type: 'UPDATE_CONTROL_INSTANCE', payload: { id, data } })
}
// ==========================================
// EXPORT ACTIONS
// ==========================================
export async function apiExportVVT(
format: ExportFormat,
activityIds?: string[]
): Promise<string> {
const params = new URLSearchParams({ format })
if (activityIds && activityIds.length > 0) {
params.append('activityIds', activityIds.join(','))
}
const response = await fetch(`${API_BASE}/export/vvt?${params}`)
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Exportieren des VVT')
}
const blob = await response.blob()
const url = URL.createObjectURL(blob)
return url
}
export async function apiExportVendorAuditPack(
vendorId: string,
format: ExportFormat
): Promise<string> {
const params = new URLSearchParams({ format, vendorId })
const response = await fetch(`${API_BASE}/export/vendor-audit?${params}`)
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Exportieren des Vendor Audit Packs')
}
const blob = await response.blob()
const url = URL.createObjectURL(blob)
return url
}
export async function apiExportRoPA(
format: ExportFormat
): Promise<string> {
const params = new URLSearchParams({ format })
const response = await fetch(`${API_BASE}/export/ropa?${params}`)
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Exportieren des RoPA')
}
const blob = await response.blob()
const url = URL.createObjectURL(blob)
return url
}

View File

@@ -0,0 +1,334 @@
import type {
VendorComplianceState,
VendorComplianceAction,
VendorStatistics,
ComplianceStatistics,
RiskOverview,
VendorStatus,
VendorRole,
RiskLevel,
FindingType,
FindingSeverity,
} from './types'
import { getRiskLevelFromScore } from './types'
// ==========================================
// INITIAL STATE
// ==========================================
export const initialState: VendorComplianceState = {
processingActivities: [],
vendors: [],
contracts: [],
findings: [],
controls: [],
controlInstances: [],
riskAssessments: [],
isLoading: false,
error: null,
selectedVendorId: null,
selectedActivityId: null,
activeTab: 'overview',
lastModified: null,
}
// ==========================================
// REDUCER
// ==========================================
export function vendorComplianceReducer(
state: VendorComplianceState,
action: VendorComplianceAction
): VendorComplianceState {
const updateState = (updates: Partial<VendorComplianceState>): VendorComplianceState => ({
...state,
...updates,
lastModified: new Date(),
})
switch (action.type) {
// Processing Activities
case 'SET_PROCESSING_ACTIVITIES':
return updateState({ processingActivities: action.payload })
case 'ADD_PROCESSING_ACTIVITY':
return updateState({
processingActivities: [...state.processingActivities, action.payload],
})
case 'UPDATE_PROCESSING_ACTIVITY':
return updateState({
processingActivities: state.processingActivities.map((activity) =>
activity.id === action.payload.id
? { ...activity, ...action.payload.data, updatedAt: new Date() }
: activity
),
})
case 'DELETE_PROCESSING_ACTIVITY':
return updateState({
processingActivities: state.processingActivities.filter(
(activity) => activity.id !== action.payload
),
})
// Vendors
case 'SET_VENDORS':
return updateState({ vendors: action.payload })
case 'ADD_VENDOR':
return updateState({
vendors: [...state.vendors, action.payload],
})
case 'UPDATE_VENDOR':
return updateState({
vendors: state.vendors.map((vendor) =>
vendor.id === action.payload.id
? { ...vendor, ...action.payload.data, updatedAt: new Date() }
: vendor
),
})
case 'DELETE_VENDOR':
return updateState({
vendors: state.vendors.filter((vendor) => vendor.id !== action.payload),
})
// Contracts
case 'SET_CONTRACTS':
return updateState({ contracts: action.payload })
case 'ADD_CONTRACT':
return updateState({
contracts: [...state.contracts, action.payload],
})
case 'UPDATE_CONTRACT':
return updateState({
contracts: state.contracts.map((contract) =>
contract.id === action.payload.id
? { ...contract, ...action.payload.data, updatedAt: new Date() }
: contract
),
})
case 'DELETE_CONTRACT':
return updateState({
contracts: state.contracts.filter((contract) => contract.id !== action.payload),
})
// Findings
case 'SET_FINDINGS':
return updateState({ findings: action.payload })
case 'ADD_FINDINGS':
return updateState({
findings: [...state.findings, ...action.payload],
})
case 'UPDATE_FINDING':
return updateState({
findings: state.findings.map((finding) =>
finding.id === action.payload.id
? { ...finding, ...action.payload.data, updatedAt: new Date() }
: finding
),
})
// Controls
case 'SET_CONTROLS':
return updateState({ controls: action.payload })
case 'SET_CONTROL_INSTANCES':
return updateState({ controlInstances: action.payload })
case 'UPDATE_CONTROL_INSTANCE':
return updateState({
controlInstances: state.controlInstances.map((instance) =>
instance.id === action.payload.id
? { ...instance, ...action.payload.data }
: instance
),
})
// Risk Assessments
case 'SET_RISK_ASSESSMENTS':
return updateState({ riskAssessments: action.payload })
case 'UPDATE_RISK_ASSESSMENT':
return updateState({
riskAssessments: state.riskAssessments.map((assessment) =>
assessment.id === action.payload.id
? { ...assessment, ...action.payload.data }
: assessment
),
})
// UI State
case 'SET_LOADING':
return { ...state, isLoading: action.payload }
case 'SET_ERROR':
return { ...state, error: action.payload }
case 'SET_SELECTED_VENDOR':
return { ...state, selectedVendorId: action.payload }
case 'SET_SELECTED_ACTIVITY':
return { ...state, selectedActivityId: action.payload }
case 'SET_ACTIVE_TAB':
return { ...state, activeTab: action.payload }
default:
return state
}
}
// ==========================================
// COMPUTED VALUES (pure functions of state)
// ==========================================
export function computeVendorStats(state: VendorComplianceState): VendorStatistics {
const vendors = state.vendors
const byStatus = vendors.reduce(
(acc, v) => {
acc[v.status] = (acc[v.status] || 0) + 1
return acc
},
{} as Record<VendorStatus, number>
)
const byRole = vendors.reduce(
(acc, v) => {
acc[v.role] = (acc[v.role] || 0) + 1
return acc
},
{} as Record<VendorRole, number>
)
const byRiskLevel = vendors.reduce(
(acc, v) => {
const level = getRiskLevelFromScore(v.residualRiskScore / 4) // Normalize to 1-25
acc[level] = (acc[level] || 0) + 1
return acc
},
{} as Record<RiskLevel, number>
)
const now = new Date()
const pendingReviews = vendors.filter(
(v) => v.nextReviewDate && new Date(v.nextReviewDate) <= now
).length
const withExpiredContracts = vendors.filter((v) =>
state.contracts.some(
(c) =>
c.vendorId === v.id &&
c.expirationDate &&
new Date(c.expirationDate) <= now &&
c.status === 'ACTIVE'
)
).length
return {
total: vendors.length,
byStatus,
byRole,
byRiskLevel,
pendingReviews,
withExpiredContracts,
}
}
export function computeComplianceStats(state: VendorComplianceState): ComplianceStatistics {
const findings = state.findings
const contracts = state.contracts
const controlInstances = state.controlInstances
const averageComplianceScore =
contracts.length > 0
? contracts.reduce((sum, c) => sum + (c.complianceScore || 0), 0) /
contracts.filter((c) => c.complianceScore !== undefined).length || 0
: 0
const findingsByType = findings.reduce(
(acc, f) => {
acc[f.type] = (acc[f.type] || 0) + 1
return acc
},
{} as Record<FindingType, number>
)
const findingsBySeverity = findings.reduce(
(acc, f) => {
acc[f.severity] = (acc[f.severity] || 0) + 1
return acc
},
{} as Record<FindingSeverity, number>
)
const openFindings = findings.filter(
(f) => f.status === 'OPEN' || f.status === 'IN_PROGRESS'
).length
const resolvedFindings = findings.filter(
(f) => f.status === 'RESOLVED' || f.status === 'FALSE_POSITIVE'
).length
const passedControls = controlInstances.filter(
(ci) => ci.status === 'PASS'
).length
const applicableControls = controlInstances.filter(
(ci) => ci.status !== 'NOT_APPLICABLE'
).length
const controlPassRate =
applicableControls > 0 ? (passedControls / applicableControls) * 100 : 0
return {
averageComplianceScore,
findingsByType,
findingsBySeverity,
openFindings,
resolvedFindings,
controlPassRate,
}
}
export function computeRiskOverview(state: VendorComplianceState): RiskOverview {
const vendors = state.vendors
const findings = state.findings
const averageInherentRisk =
vendors.length > 0
? vendors.reduce((sum, v) => sum + v.inherentRiskScore, 0) / vendors.length
: 0
const averageResidualRisk =
vendors.length > 0
? vendors.reduce((sum, v) => sum + v.residualRiskScore, 0) / vendors.length
: 0
const highRiskVendors = vendors.filter(
(v) => v.residualRiskScore >= 60
).length
const criticalFindings = findings.filter(
(f) => f.severity === 'CRITICAL' && f.status === 'OPEN'
).length
const transfersToThirdCountries = vendors.filter((v) =>
v.processingLocations.some((pl) => !pl.isEU && !pl.isAdequate)
).length
return {
averageInherentRisk,
averageResidualRisk,
highRiskVendors,
criticalFindings,
transfersToThirdCountries,
}
}

View File

@@ -10,202 +10,44 @@ import React, {
useState,
} from 'react'
import {
VendorComplianceState,
VendorComplianceAction,
import type {
VendorComplianceContextValue,
ProcessingActivity,
Vendor,
ContractDocument,
Finding,
Control,
ControlInstance,
RiskAssessment,
VendorStatistics,
ComplianceStatistics,
RiskOverview,
ExportFormat,
VendorStatus,
VendorRole,
RiskLevel,
FindingType,
FindingSeverity,
getRiskLevelFromScore,
} from './types'
// ==========================================
// INITIAL STATE
// ==========================================
import {
vendorComplianceReducer,
initialState,
computeVendorStats,
computeComplianceStats,
computeRiskOverview,
} from './context-reducer'
const initialState: VendorComplianceState = {
processingActivities: [],
vendors: [],
contracts: [],
findings: [],
controls: [],
controlInstances: [],
riskAssessments: [],
isLoading: false,
error: null,
selectedVendorId: null,
selectedActivityId: null,
activeTab: 'overview',
lastModified: null,
}
// ==========================================
// REDUCER
// ==========================================
function vendorComplianceReducer(
state: VendorComplianceState,
action: VendorComplianceAction
): VendorComplianceState {
const updateState = (updates: Partial<VendorComplianceState>): VendorComplianceState => ({
...state,
...updates,
lastModified: new Date(),
})
switch (action.type) {
// Processing Activities
case 'SET_PROCESSING_ACTIVITIES':
return updateState({ processingActivities: action.payload })
case 'ADD_PROCESSING_ACTIVITY':
return updateState({
processingActivities: [...state.processingActivities, action.payload],
})
case 'UPDATE_PROCESSING_ACTIVITY':
return updateState({
processingActivities: state.processingActivities.map((activity) =>
activity.id === action.payload.id
? { ...activity, ...action.payload.data, updatedAt: new Date() }
: activity
),
})
case 'DELETE_PROCESSING_ACTIVITY':
return updateState({
processingActivities: state.processingActivities.filter(
(activity) => activity.id !== action.payload
),
})
// Vendors
case 'SET_VENDORS':
return updateState({ vendors: action.payload })
case 'ADD_VENDOR':
return updateState({
vendors: [...state.vendors, action.payload],
})
case 'UPDATE_VENDOR':
return updateState({
vendors: state.vendors.map((vendor) =>
vendor.id === action.payload.id
? { ...vendor, ...action.payload.data, updatedAt: new Date() }
: vendor
),
})
case 'DELETE_VENDOR':
return updateState({
vendors: state.vendors.filter((vendor) => vendor.id !== action.payload),
})
// Contracts
case 'SET_CONTRACTS':
return updateState({ contracts: action.payload })
case 'ADD_CONTRACT':
return updateState({
contracts: [...state.contracts, action.payload],
})
case 'UPDATE_CONTRACT':
return updateState({
contracts: state.contracts.map((contract) =>
contract.id === action.payload.id
? { ...contract, ...action.payload.data, updatedAt: new Date() }
: contract
),
})
case 'DELETE_CONTRACT':
return updateState({
contracts: state.contracts.filter((contract) => contract.id !== action.payload),
})
// Findings
case 'SET_FINDINGS':
return updateState({ findings: action.payload })
case 'ADD_FINDINGS':
return updateState({
findings: [...state.findings, ...action.payload],
})
case 'UPDATE_FINDING':
return updateState({
findings: state.findings.map((finding) =>
finding.id === action.payload.id
? { ...finding, ...action.payload.data, updatedAt: new Date() }
: finding
),
})
// Controls
case 'SET_CONTROLS':
return updateState({ controls: action.payload })
case 'SET_CONTROL_INSTANCES':
return updateState({ controlInstances: action.payload })
case 'UPDATE_CONTROL_INSTANCE':
return updateState({
controlInstances: state.controlInstances.map((instance) =>
instance.id === action.payload.id
? { ...instance, ...action.payload.data }
: instance
),
})
// Risk Assessments
case 'SET_RISK_ASSESSMENTS':
return updateState({ riskAssessments: action.payload })
case 'UPDATE_RISK_ASSESSMENT':
return updateState({
riskAssessments: state.riskAssessments.map((assessment) =>
assessment.id === action.payload.id
? { ...assessment, ...action.payload.data }
: assessment
),
})
// UI State
case 'SET_LOADING':
return { ...state, isLoading: action.payload }
case 'SET_ERROR':
return { ...state, error: action.payload }
case 'SET_SELECTED_VENDOR':
return { ...state, selectedVendorId: action.payload }
case 'SET_SELECTED_ACTIVITY':
return { ...state, selectedActivityId: action.payload }
case 'SET_ACTIVE_TAB':
return { ...state, activeTab: action.payload }
default:
return state
}
}
import {
loadAllData,
apiCreateProcessingActivity,
apiUpdateProcessingActivity,
apiDeleteProcessingActivity,
apiDuplicateProcessingActivity,
apiCreateVendor,
apiUpdateVendor,
apiDeleteVendor,
apiUploadContract,
apiUpdateContract,
apiDeleteContract,
apiStartContractReview,
apiUpdateFinding,
apiResolveFinding,
apiUpdateControlInstance,
apiExportVVT,
apiExportVendorAuditPack,
apiExportRoPA,
} from './context-actions'
// ==========================================
// CONTEXT
@@ -233,626 +75,150 @@ export function VendorComplianceProvider({
// COMPUTED VALUES
// ==========================================
const vendorStats = useMemo<VendorStatistics>(() => {
const vendors = state.vendors
const vendorStats = useMemo(
() => computeVendorStats(state),
[state.vendors, state.contracts]
)
const byStatus = vendors.reduce(
(acc, v) => {
acc[v.status] = (acc[v.status] || 0) + 1
return acc
},
{} as Record<VendorStatus, number>
)
const complianceStats = useMemo(
() => computeComplianceStats(state),
[state.findings, state.contracts, state.controlInstances]
)
const byRole = vendors.reduce(
(acc, v) => {
acc[v.role] = (acc[v.role] || 0) + 1
return acc
},
{} as Record<VendorRole, number>
)
const byRiskLevel = vendors.reduce(
(acc, v) => {
const level = getRiskLevelFromScore(v.residualRiskScore / 4) // Normalize to 1-25
acc[level] = (acc[level] || 0) + 1
return acc
},
{} as Record<RiskLevel, number>
)
const now = new Date()
const pendingReviews = vendors.filter(
(v) => v.nextReviewDate && new Date(v.nextReviewDate) <= now
).length
const withExpiredContracts = vendors.filter((v) =>
state.contracts.some(
(c) =>
c.vendorId === v.id &&
c.expirationDate &&
new Date(c.expirationDate) <= now &&
c.status === 'ACTIVE'
)
).length
return {
total: vendors.length,
byStatus,
byRole,
byRiskLevel,
pendingReviews,
withExpiredContracts,
}
}, [state.vendors, state.contracts])
const complianceStats = useMemo<ComplianceStatistics>(() => {
const findings = state.findings
const contracts = state.contracts
const controlInstances = state.controlInstances
const averageComplianceScore =
contracts.length > 0
? contracts.reduce((sum, c) => sum + (c.complianceScore || 0), 0) /
contracts.filter((c) => c.complianceScore !== undefined).length || 0
: 0
const findingsByType = findings.reduce(
(acc, f) => {
acc[f.type] = (acc[f.type] || 0) + 1
return acc
},
{} as Record<FindingType, number>
)
const findingsBySeverity = findings.reduce(
(acc, f) => {
acc[f.severity] = (acc[f.severity] || 0) + 1
return acc
},
{} as Record<FindingSeverity, number>
)
const openFindings = findings.filter(
(f) => f.status === 'OPEN' || f.status === 'IN_PROGRESS'
).length
const resolvedFindings = findings.filter(
(f) => f.status === 'RESOLVED' || f.status === 'FALSE_POSITIVE'
).length
const passedControls = controlInstances.filter(
(ci) => ci.status === 'PASS'
).length
const applicableControls = controlInstances.filter(
(ci) => ci.status !== 'NOT_APPLICABLE'
).length
const controlPassRate =
applicableControls > 0 ? (passedControls / applicableControls) * 100 : 0
return {
averageComplianceScore,
findingsByType,
findingsBySeverity,
openFindings,
resolvedFindings,
controlPassRate,
}
}, [state.findings, state.contracts, state.controlInstances])
const riskOverview = useMemo<RiskOverview>(() => {
const vendors = state.vendors
const findings = state.findings
const averageInherentRisk =
vendors.length > 0
? vendors.reduce((sum, v) => sum + v.inherentRiskScore, 0) / vendors.length
: 0
const averageResidualRisk =
vendors.length > 0
? vendors.reduce((sum, v) => sum + v.residualRiskScore, 0) / vendors.length
: 0
const highRiskVendors = vendors.filter(
(v) => v.residualRiskScore >= 60
).length
const criticalFindings = findings.filter(
(f) => f.severity === 'CRITICAL' && f.status === 'OPEN'
).length
const transfersToThirdCountries = vendors.filter((v) =>
v.processingLocations.some((pl) => !pl.isEU && !pl.isAdequate)
).length
return {
averageInherentRisk,
averageResidualRisk,
highRiskVendors,
criticalFindings,
transfersToThirdCountries,
}
}, [state.vendors, state.findings])
const riskOverview = useMemo(
() => computeRiskOverview(state),
[state.vendors, state.findings]
)
// ==========================================
// API CALLS
// ACTION WRAPPERS
// ==========================================
const apiBase = '/api/sdk/v1/vendor-compliance'
const loadData = useCallback(async () => {
dispatch({ type: 'SET_LOADING', payload: true })
dispatch({ type: 'SET_ERROR', payload: null })
try {
const [
activitiesRes,
vendorsRes,
contractsRes,
findingsRes,
controlsRes,
controlInstancesRes,
] = await Promise.all([
fetch(`${apiBase}/processing-activities`),
fetch(`${apiBase}/vendors`),
fetch(`${apiBase}/contracts`),
fetch(`${apiBase}/findings`),
fetch(`${apiBase}/controls`),
fetch(`${apiBase}/control-instances`),
])
if (activitiesRes.ok) {
const data = await activitiesRes.json()
dispatch({ type: 'SET_PROCESSING_ACTIVITIES', payload: data.data || [] })
}
if (vendorsRes.ok) {
const data = await vendorsRes.json()
dispatch({ type: 'SET_VENDORS', payload: data.data || [] })
}
if (contractsRes.ok) {
const data = await contractsRes.json()
dispatch({ type: 'SET_CONTRACTS', payload: data.data || [] })
}
if (findingsRes.ok) {
const data = await findingsRes.json()
dispatch({ type: 'SET_FINDINGS', payload: data.data || [] })
}
if (controlsRes.ok) {
const data = await controlsRes.json()
dispatch({ type: 'SET_CONTROLS', payload: data.data || [] })
}
if (controlInstancesRes.ok) {
const data = await controlInstancesRes.json()
dispatch({ type: 'SET_CONTROL_INSTANCES', payload: data.data || [] })
}
} catch (error) {
console.error('Failed to load vendor compliance data:', error)
dispatch({
type: 'SET_ERROR',
payload: 'Fehler beim Laden der Daten',
})
} finally {
dispatch({ type: 'SET_LOADING', payload: false })
}
}, [apiBase])
await loadAllData(dispatch)
}, [])
const refresh = useCallback(async () => {
await loadData()
}, [loadData])
// ==========================================
// PROCESSING ACTIVITIES ACTIONS
// ==========================================
const createProcessingActivity = useCallback(
async (
data: Omit<ProcessingActivity, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>
): Promise<ProcessingActivity> => {
const response = await fetch(`${apiBase}/processing-activities`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Erstellen der Verarbeitungstätigkeit')
}
const result = await response.json()
const activity = result.data
dispatch({ type: 'ADD_PROCESSING_ACTIVITY', payload: activity })
return activity
async (data: Omit<ProcessingActivity, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>) => {
return apiCreateProcessingActivity(dispatch, data)
},
[apiBase]
[]
)
const updateProcessingActivity = useCallback(
async (id: string, data: Partial<ProcessingActivity>): Promise<void> => {
const response = await fetch(`${apiBase}/processing-activities/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren der Verarbeitungstätigkeit')
}
dispatch({ type: 'UPDATE_PROCESSING_ACTIVITY', payload: { id, data } })
async (id: string, data: Partial<ProcessingActivity>) => {
await apiUpdateProcessingActivity(dispatch, id, data)
},
[apiBase]
[]
)
const deleteProcessingActivity = useCallback(
async (id: string): Promise<void> => {
const response = await fetch(`${apiBase}/processing-activities/${id}`, {
method: 'DELETE',
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Löschen der Verarbeitungstätigkeit')
}
dispatch({ type: 'DELETE_PROCESSING_ACTIVITY', payload: id })
async (id: string) => {
await apiDeleteProcessingActivity(dispatch, id)
},
[apiBase]
[]
)
const duplicateProcessingActivity = useCallback(
async (id: string): Promise<ProcessingActivity> => {
const original = state.processingActivities.find((a) => a.id === id)
if (!original) {
throw new Error('Verarbeitungstätigkeit nicht gefunden')
}
const { id: _id, vvtId: _vvtId, createdAt: _createdAt, updatedAt: _updatedAt, tenantId: _tenantId, ...rest } = original
const newActivity = await createProcessingActivity({
...rest,
vvtId: '', // Will be generated by backend
name: {
de: `${original.name.de} (Kopie)`,
en: `${original.name.en} (Copy)`,
},
status: 'DRAFT',
})
return newActivity
async (id: string) => {
return apiDuplicateProcessingActivity(dispatch, state, id)
},
[state.processingActivities, createProcessingActivity]
[state]
)
// ==========================================
// VENDOR ACTIONS
// ==========================================
const createVendor = useCallback(
async (
data: Omit<Vendor, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>
): Promise<Vendor> => {
const response = await fetch(`${apiBase}/vendors`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Erstellen des Vendors')
}
const result = await response.json()
const vendor = result.data
dispatch({ type: 'ADD_VENDOR', payload: vendor })
return vendor
async (data: Omit<Vendor, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>) => {
return apiCreateVendor(dispatch, data)
},
[apiBase]
[]
)
const updateVendor = useCallback(
async (id: string, data: Partial<Vendor>): Promise<void> => {
const response = await fetch(`${apiBase}/vendors/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Vendors')
}
dispatch({ type: 'UPDATE_VENDOR', payload: { id, data } })
async (id: string, data: Partial<Vendor>) => {
await apiUpdateVendor(dispatch, id, data)
},
[apiBase]
[]
)
const deleteVendor = useCallback(
async (id: string): Promise<void> => {
const response = await fetch(`${apiBase}/vendors/${id}`, {
method: 'DELETE',
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Löschen des Vendors')
}
dispatch({ type: 'DELETE_VENDOR', payload: id })
async (id: string) => {
await apiDeleteVendor(dispatch, id)
},
[apiBase]
[]
)
// ==========================================
// CONTRACT ACTIONS
// ==========================================
const uploadContract = useCallback(
async (
vendorId: string,
file: File,
metadata: Partial<ContractDocument>
): Promise<ContractDocument> => {
const formData = new FormData()
formData.append('file', file)
formData.append('vendorId', vendorId)
formData.append('metadata', JSON.stringify(metadata))
const response = await fetch(`${apiBase}/contracts`, {
method: 'POST',
body: formData,
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Hochladen des Vertrags')
}
const result = await response.json()
const contract = result.data
dispatch({ type: 'ADD_CONTRACT', payload: contract })
// Update vendor's contracts list
const vendor = state.vendors.find((v) => v.id === vendorId)
if (vendor) {
dispatch({
type: 'UPDATE_VENDOR',
payload: {
id: vendorId,
data: { contracts: [...vendor.contracts, contract.id] },
},
})
}
return contract
async (vendorId: string, file: File, metadata: Partial<ContractDocument>) => {
return apiUploadContract(dispatch, state, vendorId, file, metadata)
},
[apiBase, state.vendors]
[state]
)
const updateContract = useCallback(
async (id: string, data: Partial<ContractDocument>): Promise<void> => {
const response = await fetch(`${apiBase}/contracts/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Vertrags')
}
dispatch({ type: 'UPDATE_CONTRACT', payload: { id, data } })
async (id: string, data: Partial<ContractDocument>) => {
await apiUpdateContract(dispatch, id, data)
},
[apiBase]
[]
)
const deleteContract = useCallback(
async (id: string): Promise<void> => {
const contract = state.contracts.find((c) => c.id === id)
const response = await fetch(`${apiBase}/contracts/${id}`, {
method: 'DELETE',
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Löschen des Vertrags')
}
dispatch({ type: 'DELETE_CONTRACT', payload: id })
// Update vendor's contracts list
if (contract) {
const vendor = state.vendors.find((v) => v.id === contract.vendorId)
if (vendor) {
dispatch({
type: 'UPDATE_VENDOR',
payload: {
id: vendor.id,
data: { contracts: vendor.contracts.filter((cId) => cId !== id) },
},
})
}
}
async (id: string) => {
await apiDeleteContract(dispatch, state, id)
},
[apiBase, state.contracts, state.vendors]
[state]
)
const startContractReview = useCallback(
async (contractId: string): Promise<void> => {
dispatch({
type: 'UPDATE_CONTRACT',
payload: { id: contractId, data: { reviewStatus: 'IN_PROGRESS' } },
})
const response = await fetch(`${apiBase}/contracts/${contractId}/review`, {
method: 'POST',
})
if (!response.ok) {
dispatch({
type: 'UPDATE_CONTRACT',
payload: { id: contractId, data: { reviewStatus: 'FAILED' } },
})
const error = await response.json()
throw new Error(error.error || 'Fehler beim Starten der Vertragsprüfung')
}
const result = await response.json()
// Update contract with review results
dispatch({
type: 'UPDATE_CONTRACT',
payload: {
id: contractId,
data: {
reviewStatus: 'COMPLETED',
reviewCompletedAt: new Date(),
complianceScore: result.data.complianceScore,
},
},
})
// Add findings
if (result.data.findings && result.data.findings.length > 0) {
dispatch({ type: 'ADD_FINDINGS', payload: result.data.findings })
}
async (contractId: string) => {
await apiStartContractReview(dispatch, contractId)
},
[apiBase]
[]
)
// ==========================================
// FINDINGS ACTIONS
// ==========================================
const updateFinding = useCallback(
async (id: string, data: Partial<Finding>): Promise<void> => {
const response = await fetch(`${apiBase}/findings/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Findings')
}
dispatch({ type: 'UPDATE_FINDING', payload: { id, data } })
async (id: string, data: Partial<Finding>) => {
await apiUpdateFinding(dispatch, id, data)
},
[apiBase]
[]
)
const resolveFinding = useCallback(
async (id: string, resolution: string): Promise<void> => {
await updateFinding(id, {
status: 'RESOLVED',
resolution,
resolvedAt: new Date(),
})
async (id: string, resolution: string) => {
await apiResolveFinding(dispatch, id, resolution)
},
[updateFinding]
[]
)
// ==========================================
// CONTROL ACTIONS
// ==========================================
const updateControlInstance = useCallback(
async (id: string, data: Partial<ControlInstance>): Promise<void> => {
const response = await fetch(`${apiBase}/control-instances/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Control-Status')
}
dispatch({ type: 'UPDATE_CONTROL_INSTANCE', payload: { id, data } })
async (id: string, data: Partial<ControlInstance>) => {
await apiUpdateControlInstance(dispatch, id, data)
},
[apiBase]
[]
)
// ==========================================
// EXPORT ACTIONS
// ==========================================
const exportVVT = useCallback(
async (format: ExportFormat, activityIds?: string[]): Promise<string> => {
const params = new URLSearchParams({ format })
if (activityIds && activityIds.length > 0) {
params.append('activityIds', activityIds.join(','))
}
const response = await fetch(`${apiBase}/export/vvt?${params}`)
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Exportieren des VVT')
}
const blob = await response.blob()
const url = URL.createObjectURL(blob)
return url
async (format: ExportFormat, activityIds?: string[]) => {
return apiExportVVT(format, activityIds)
},
[apiBase]
[]
)
const exportVendorAuditPack = useCallback(
async (vendorId: string, format: ExportFormat): Promise<string> => {
const params = new URLSearchParams({ format, vendorId })
const response = await fetch(`${apiBase}/export/vendor-audit?${params}`)
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Exportieren des Vendor Audit Packs')
}
const blob = await response.blob()
const url = URL.createObjectURL(blob)
return url
async (vendorId: string, format: ExportFormat) => {
return apiExportVendorAuditPack(vendorId, format)
},
[apiBase]
[]
)
const exportRoPA = useCallback(
async (format: ExportFormat): Promise<string> => {
const params = new URLSearchParams({ format })
const response = await fetch(`${apiBase}/export/ropa?${params}`)
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Exportieren des RoPA')
}
const blob = await response.blob()
const url = URL.createObjectURL(blob)
return url
async (format: ExportFormat) => {
return apiExportRoPA(format)
},
[apiBase]
[]
)
// ==========================================

View File

@@ -0,0 +1,37 @@
/**
* Controls Library - Merged Array
*
* Assembles all domain-specific control arrays into the single CONTROLS_LIBRARY.
* This file exists to avoid circular imports between the barrel and helpers.
*/
import { Control } from '../types'
import {
TRANSFER_CONTROLS,
AUDIT_CONTROLS,
DELETION_CONTROLS,
INCIDENT_CONTROLS,
} from './controls-data-compliance'
import {
SUBPROCESSOR_CONTROLS,
TOM_CONTROLS,
CONTRACT_CONTROLS,
DATA_SUBJECT_CONTROLS,
SECURITY_CONTROLS,
GOVERNANCE_CONTROLS,
} from './controls-data-operations'
export const CONTROLS_LIBRARY: Control[] = [
...TRANSFER_CONTROLS,
...AUDIT_CONTROLS,
...DELETION_CONTROLS,
...INCIDENT_CONTROLS,
...SUBPROCESSOR_CONTROLS,
...TOM_CONTROLS,
...CONTRACT_CONTROLS,
...DATA_SUBJECT_CONTROLS,
...SECURITY_CONTROLS,
...GOVERNANCE_CONTROLS,
]

View File

@@ -0,0 +1,377 @@
/**
* Control Definitions - Compliance Domains
*
* Controls for: TRANSFER, AUDIT, DELETION, INCIDENT
*/
import { Control } from '../types'
// ==========================================
// TRANSFER - Drittlandtransfer Controls
// ==========================================
export const TRANSFER_CONTROLS: Control[] = [
{
id: 'VND-TRF-01',
domain: 'TRANSFER',
title: {
de: 'Drittlandtransfer nur mit Rechtsgrundlage',
en: 'Third country transfer with legal basis',
},
description: {
de: 'Drittlandtransfers erfolgen nur auf Basis von SCC, BCR oder Angemessenheitsbeschluss',
en: 'Third country transfers only based on SCC, BCR or adequacy decision',
},
passCriteria: {
de: 'SCC oder BCR vertraglich vereinbart ODER Angemessenheitsbeschluss vorhanden',
en: 'SCC or BCR contractually agreed OR adequacy decision exists',
},
requirements: ['Art. 44-49 DSGVO', 'ISO 27001 A.15.1.2'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-02',
domain: 'TRANSFER',
title: {
de: 'Aktuelle Standardvertragsklauseln',
en: 'Current Standard Contractual Clauses',
},
description: {
de: 'Bei SCC-Nutzung: Verwendung der aktuellen EU-Kommission-Klauseln (2021)',
en: 'When using SCC: Current EU Commission clauses (2021) are used',
},
passCriteria: {
de: 'SCC 2021 (Durchführungsbeschluss (EU) 2021/914) verwendet',
en: 'SCC 2021 (Implementing Decision (EU) 2021/914) used',
},
requirements: ['Art. 46 Abs. 2 lit. c DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-03',
domain: 'TRANSFER',
title: {
de: 'Transfer Impact Assessment (TIA)',
en: 'Transfer Impact Assessment (TIA)',
},
description: {
de: 'Bei Transfers in Drittländer ohne Angemessenheitsbeschluss ist TIA durchzuführen',
en: 'TIA required for transfers to third countries without adequacy decision',
},
passCriteria: {
de: 'TIA dokumentiert und bewertet Risiken als akzeptabel',
en: 'TIA documented and risks assessed as acceptable',
},
requirements: ['Schrems II Urteil', 'EDSA Empfehlungen 01/2020'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-04',
domain: 'TRANSFER',
title: {
de: 'Zusätzliche Schutzmaßnahmen',
en: 'Supplementary Measures',
},
description: {
de: 'Bei Bedarf sind zusätzliche technische/organisatorische Maßnahmen implementiert',
en: 'Supplementary technical/organizational measures implemented where needed',
},
passCriteria: {
de: 'Ergänzende Maßnahmen dokumentiert (Verschlüsselung, Pseudonymisierung, etc.)',
en: 'Supplementary measures documented (encryption, pseudonymization, etc.)',
},
requirements: ['EDSA Empfehlungen 01/2020'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-05',
domain: 'TRANSFER',
title: {
de: 'Überwachung Angemessenheitsbeschlüsse',
en: 'Monitoring Adequacy Decisions',
},
description: {
de: 'Änderungen bei Angemessenheitsbeschlüssen werden überwacht',
en: 'Changes to adequacy decisions are monitored',
},
passCriteria: {
de: 'Prozess zur Überwachung und Reaktion auf Änderungen etabliert',
en: 'Process for monitoring and responding to changes established',
},
requirements: ['Art. 45 DSGVO'],
isRequired: false,
defaultFrequency: 'QUARTERLY',
},
]
// ==========================================
// AUDIT - Auditrechte Controls
// ==========================================
export const AUDIT_CONTROLS: Control[] = [
{
id: 'VND-AUD-01',
domain: 'AUDIT',
title: {
de: 'Auditrecht vertraglich vereinbart',
en: 'Audit right contractually agreed',
},
description: {
de: 'Vertrag enthält wirksames Auditrecht ohne unangemessene Einschränkungen',
en: 'Contract contains effective audit right without unreasonable restrictions',
},
passCriteria: {
de: 'Auditrecht im AVV enthalten, max. 30 Tage Vorlaufzeit, keine Ausschlussklausel',
en: 'Audit right in DPA, max 30 days notice, no exclusion clause',
},
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-02',
domain: 'AUDIT',
title: {
de: 'Vor-Ort-Inspektionen möglich',
en: 'On-site inspections possible',
},
description: {
de: 'Vertrag erlaubt Vor-Ort-Inspektionen bei dem Auftragsverarbeiter',
en: 'Contract allows on-site inspections at the processor',
},
passCriteria: {
de: 'Vor-Ort-Audit explizit erlaubt, Zugang zu relevanten Bereichen',
en: 'On-site audit explicitly allowed, access to relevant areas',
},
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-03',
domain: 'AUDIT',
title: {
de: 'Aktuelle Zertifizierungen',
en: 'Current Certifications',
},
description: {
de: 'Relevante Sicherheitszertifizierungen sind aktuell und gültig',
en: 'Relevant security certifications are current and valid',
},
passCriteria: {
de: 'ISO 27001, SOC 2 oder vergleichbar, nicht abgelaufen',
en: 'ISO 27001, SOC 2 or equivalent, not expired',
},
requirements: ['Art. 32 DSGVO', 'ISO 27001 A.15.1.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-04',
domain: 'AUDIT',
title: {
de: 'Letzte Prüfung durchgeführt',
en: 'Last review conducted',
},
description: {
de: 'Vendor wurde innerhalb des Review-Zyklus geprüft',
en: 'Vendor was reviewed within the review cycle',
},
passCriteria: {
de: 'Dokumentierte Prüfung innerhalb des festgelegten Intervalls',
en: 'Documented review within the defined interval',
},
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-05',
domain: 'AUDIT',
title: {
de: 'Prüfberichte verfügbar',
en: 'Audit reports available',
},
description: {
de: 'Aktuelle Prüfberichte (SOC 2, Penetrationstest, etc.) liegen vor',
en: 'Current audit reports (SOC 2, penetration test, etc.) are available',
},
passCriteria: {
de: 'Prüfberichte nicht älter als 12 Monate',
en: 'Audit reports not older than 12 months',
},
requirements: ['ISO 27001 A.18.2.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// DELETION - Löschung Controls
// ==========================================
export const DELETION_CONTROLS: Control[] = [
{
id: 'VND-DEL-01',
domain: 'DELETION',
title: {
de: 'Löschung/Rückgabe nach Vertragsende',
en: 'Deletion/return after contract end',
},
description: {
de: 'Klare Regelung zur Löschung oder Rückgabe aller Daten nach Vertragsende',
en: 'Clear provision for deletion or return of all data after contract end',
},
passCriteria: {
de: 'Löschfrist max. 30 Tage, Löschbestätigung vorgesehen',
en: 'Deletion within max 30 days, deletion confirmation provided',
},
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DEL-02',
domain: 'DELETION',
title: {
de: 'Löschbestätigung',
en: 'Deletion confirmation',
},
description: {
de: 'Schriftliche Bestätigung der vollständigen Datenlöschung',
en: 'Written confirmation of complete data deletion',
},
passCriteria: {
de: 'Löschbestätigung vertraglich vereinbart und einforderbar',
en: 'Deletion confirmation contractually agreed and enforceable',
},
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DEL-03',
domain: 'DELETION',
title: {
de: 'Löschung bei Unterauftragnehmern',
en: 'Deletion at sub-processors',
},
description: {
de: 'Löschpflicht erstreckt sich auf alle Unterauftragnehmer',
en: 'Deletion obligation extends to all sub-processors',
},
passCriteria: {
de: 'Weitergabe der Löschpflicht an Unterauftragnehmer vertraglich vereinbart',
en: 'Transfer of deletion obligation to sub-processors contractually agreed',
},
requirements: ['Art. 28 Abs. 3 lit. g, d DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DEL-04',
domain: 'DELETION',
title: {
de: 'Backup-Löschung',
en: 'Backup deletion',
},
description: {
de: 'Daten werden auch aus Backups gelöscht',
en: 'Data is also deleted from backups',
},
passCriteria: {
de: 'Backup-Löschung geregelt, max. Aufbewahrungsfrist für Backups definiert',
en: 'Backup deletion regulated, max retention period for backups defined',
},
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// INCIDENT - Incident Response Controls
// ==========================================
export const INCIDENT_CONTROLS: Control[] = [
{
id: 'VND-INC-01',
domain: 'INCIDENT',
title: {
de: 'Meldepflicht bei Datenpannen',
en: 'Data breach notification obligation',
},
description: {
de: 'Unverzügliche Meldung von Datenschutzverletzungen',
en: 'Immediate notification of data protection violations',
},
passCriteria: {
de: 'Meldepflicht vereinbart, Frist max. 24-48h, Mindestinhalte definiert',
en: 'Notification obligation agreed, deadline max 24-48h, minimum content defined',
},
requirements: ['Art. 33 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-INC-02',
domain: 'INCIDENT',
title: {
de: 'Incident Response Plan',
en: 'Incident Response Plan',
},
description: {
de: 'Vendor hat dokumentierten Incident Response Plan',
en: 'Vendor has documented incident response plan',
},
passCriteria: {
de: 'Incident Response Plan liegt vor und wurde getestet',
en: 'Incident response plan exists and has been tested',
},
requirements: ['ISO 27001 A.16.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-INC-03',
domain: 'INCIDENT',
title: {
de: 'Kontaktstelle für Incidents',
en: 'Contact point for incidents',
},
description: {
de: 'Definierte Kontaktstelle für Datenschutzvorfälle',
en: 'Defined contact point for data protection incidents',
},
passCriteria: {
de: 'Kontaktdaten für Incident-Meldungen bekannt und aktuell',
en: 'Contact details for incident reporting known and current',
},
requirements: ['Art. 33 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'QUARTERLY',
},
{
id: 'VND-INC-04',
domain: 'INCIDENT',
title: {
de: 'Unterstützung bei Incident-Dokumentation',
en: 'Support with incident documentation',
},
description: {
de: 'Vendor unterstützt bei der Dokumentation von Vorfällen',
en: 'Vendor supports documentation of incidents',
},
passCriteria: {
de: 'Unterstützungspflicht bei Dokumentation vertraglich vereinbart',
en: 'Support obligation for documentation contractually agreed',
},
requirements: ['Art. 33 Abs. 5 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
]

View File

@@ -0,0 +1,486 @@
/**
* Control Definitions - Operations Domains
*
* Controls for: SUBPROCESSOR, TOM, CONTRACT, DATA_SUBJECT, SECURITY, GOVERNANCE
*/
import { Control } from '../types'
// ==========================================
// SUBPROCESSOR - Unterauftragnehmer Controls
// ==========================================
export const SUBPROCESSOR_CONTROLS: Control[] = [
{
id: 'VND-SUB-01',
domain: 'SUBPROCESSOR',
title: {
de: 'Genehmigungspflicht für Unterauftragnehmer',
en: 'Approval requirement for sub-processors',
},
description: {
de: 'Einsatz von Unterauftragnehmern nur mit Genehmigung',
en: 'Use of sub-processors only with approval',
},
passCriteria: {
de: 'Genehmigungserfordernis (spezifisch oder allgemein mit Widerspruchsrecht) vereinbart',
en: 'Approval requirement (specific or general with objection right) agreed',
},
requirements: ['Art. 28 Abs. 2, 4 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SUB-02',
domain: 'SUBPROCESSOR',
title: {
de: 'Aktuelle Unterauftragnehmer-Liste',
en: 'Current sub-processor list',
},
description: {
de: 'Vollständige und aktuelle Liste aller Unterauftragnehmer',
en: 'Complete and current list of all sub-processors',
},
passCriteria: {
de: 'Liste liegt vor mit Name, Sitz, Verarbeitungszweck',
en: 'List available with name, location, processing purpose',
},
requirements: ['Art. 28 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'QUARTERLY',
},
{
id: 'VND-SUB-03',
domain: 'SUBPROCESSOR',
title: {
de: 'Informationspflicht bei Änderungen',
en: 'Notification obligation for changes',
},
description: {
de: 'Information über neue oder geänderte Unterauftragnehmer',
en: 'Information about new or changed sub-processors',
},
passCriteria: {
de: 'Vorabinformation vereinbart, ausreichende Frist für Widerspruch',
en: 'Advance notification agreed, sufficient time for objection',
},
requirements: ['Art. 28 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SUB-04',
domain: 'SUBPROCESSOR',
title: {
de: 'Weitergabe der Datenschutzpflichten',
en: 'Transfer of data protection obligations',
},
description: {
de: 'Datenschutzpflichten werden an Unterauftragnehmer weitergegeben',
en: 'Data protection obligations are transferred to sub-processors',
},
passCriteria: {
de: 'Vertraglich vereinbart, dass Unterauftragnehmer gleichen Pflichten unterliegen',
en: 'Contractually agreed that sub-processors are subject to same obligations',
},
requirements: ['Art. 28 Abs. 4 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SUB-05',
domain: 'SUBPROCESSOR',
title: {
de: 'Haftung für Unterauftragnehmer',
en: 'Liability for sub-processors',
},
description: {
de: 'Klare Haftungsregelung für Unterauftragnehmer',
en: 'Clear liability provision for sub-processors',
},
passCriteria: {
de: 'Auftragsverarbeiter haftet für Unterauftragnehmer wie für eigenes Handeln',
en: 'Processor is liable for sub-processors as for own actions',
},
requirements: ['Art. 28 Abs. 4 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// TOM - Technische/Organisatorische Maßnahmen
// ==========================================
export const TOM_CONTROLS: Control[] = [
{
id: 'VND-TOM-01',
domain: 'TOM',
title: {
de: 'TOM-Dokumentation vorhanden',
en: 'TOM documentation available',
},
description: {
de: 'Vollständige Dokumentation der technischen und organisatorischen Maßnahmen',
en: 'Complete documentation of technical and organizational measures',
},
passCriteria: {
de: 'TOM-Anlage vorhanden, aktuell, spezifisch für die Verarbeitung',
en: 'TOM annex available, current, specific to the processing',
},
requirements: ['Art. 28 Abs. 3 lit. c DSGVO', 'Art. 32 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-02',
domain: 'TOM',
title: {
de: 'Verschlüsselung',
en: 'Encryption',
},
description: {
de: 'Angemessene Verschlüsselung für Daten in Transit und at Rest',
en: 'Appropriate encryption for data in transit and at rest',
},
passCriteria: {
de: 'TLS 1.2+ für Transit, AES-256 für at Rest',
en: 'TLS 1.2+ for transit, AES-256 for at rest',
},
requirements: ['Art. 32 Abs. 1 lit. a DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-03',
domain: 'TOM',
title: {
de: 'Zugriffskontrolle',
en: 'Access control',
},
description: {
de: 'Angemessene Zugriffskontrollmechanismen',
en: 'Appropriate access control mechanisms',
},
passCriteria: {
de: 'Rollenbasierte Zugriffskontrolle, Least Privilege, Logging',
en: 'Role-based access control, least privilege, logging',
},
requirements: ['Art. 32 Abs. 1 lit. b DSGVO', 'ISO 27001 A.9'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-04',
domain: 'TOM',
title: {
de: 'Verfügbarkeit und Wiederherstellung',
en: 'Availability and recovery',
},
description: {
de: 'Maßnahmen zur Sicherstellung der Verfügbarkeit und Wiederherstellung',
en: 'Measures to ensure availability and recovery',
},
passCriteria: {
de: 'Backup-Konzept, DR-Plan, RTO/RPO definiert',
en: 'Backup concept, DR plan, RTO/RPO defined',
},
requirements: ['Art. 32 Abs. 1 lit. b, c DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-05',
domain: 'TOM',
title: {
de: 'Regelmäßige TOM-Überprüfung',
en: 'Regular TOM review',
},
description: {
de: 'Regelmäßige Überprüfung und Aktualisierung der TOM',
en: 'Regular review and update of TOM',
},
passCriteria: {
de: 'TOM werden mindestens jährlich überprüft und bei Bedarf aktualisiert',
en: 'TOM are reviewed at least annually and updated as needed',
},
requirements: ['Art. 32 Abs. 1 lit. d DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-06',
domain: 'TOM',
title: {
de: 'Penetrationstest',
en: 'Penetration testing',
},
description: {
de: 'Regelmäßige Penetrationstests der relevanten Systeme',
en: 'Regular penetration testing of relevant systems',
},
passCriteria: {
de: 'Jährlicher Pentest, kritische Findings behoben',
en: 'Annual pentest, critical findings resolved',
},
requirements: ['ISO 27001 A.12.6.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// CONTRACT - Vertragliche Grundlagen
// ==========================================
export const CONTRACT_CONTROLS: Control[] = [
{
id: 'VND-CON-01',
domain: 'CONTRACT',
title: {
de: 'Weisungsgebundenheit',
en: 'Instruction binding',
},
description: {
de: 'Auftragsverarbeiter ist an Weisungen gebunden',
en: 'Processor is bound by instructions',
},
passCriteria: {
de: 'Weisungsgebundenheit explizit vereinbart, Hinweispflicht bei rechtswidrigen Weisungen',
en: 'Instruction binding explicitly agreed, notification obligation for unlawful instructions',
},
requirements: ['Art. 28 Abs. 3 lit. a DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-CON-02',
domain: 'CONTRACT',
title: {
de: 'Vertraulichkeitsverpflichtung',
en: 'Confidentiality obligation',
},
description: {
de: 'Mitarbeiter sind zur Vertraulichkeit verpflichtet',
en: 'Employees are obligated to confidentiality',
},
passCriteria: {
de: 'Vertraulichkeitsverpflichtung für alle Mitarbeiter mit Datenzugriff',
en: 'Confidentiality obligation for all employees with data access',
},
requirements: ['Art. 28 Abs. 3 lit. b DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-CON-03',
domain: 'CONTRACT',
title: {
de: 'Gegenstand und Dauer der Verarbeitung',
en: 'Subject and duration of processing',
},
description: {
de: 'Klare Definition von Gegenstand und Dauer der Verarbeitung',
en: 'Clear definition of subject and duration of processing',
},
passCriteria: {
de: 'Verarbeitungsgegenstand, Dauer, Art der Daten, Betroffene definiert',
en: 'Processing subject, duration, type of data, data subjects defined',
},
requirements: ['Art. 28 Abs. 3 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-CON-04',
domain: 'CONTRACT',
title: {
de: 'Schriftform/Textform',
en: 'Written/text form',
},
description: {
de: 'AVV in Schriftform oder elektronischem Format',
en: 'DPA in written or electronic format',
},
passCriteria: {
de: 'AVV in Schriftform oder elektronisch mit qualifizierter Signatur',
en: 'DPA in written form or electronically with qualified signature',
},
requirements: ['Art. 28 Abs. 9 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// DATA_SUBJECT - Betroffenenrechte
// ==========================================
export const DATA_SUBJECT_CONTROLS: Control[] = [
{
id: 'VND-DSR-01',
domain: 'DATA_SUBJECT',
title: {
de: 'Unterstützung bei Betroffenenrechten',
en: 'Support for data subject rights',
},
description: {
de: 'Vendor unterstützt bei der Erfüllung von Betroffenenrechten',
en: 'Vendor supports fulfillment of data subject rights',
},
passCriteria: {
de: 'Unterstützungspflicht vereinbart, Prozess zur Weiterleitung definiert',
en: 'Support obligation agreed, process for forwarding defined',
},
requirements: ['Art. 28 Abs. 3 lit. e DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DSR-02',
domain: 'DATA_SUBJECT',
title: {
de: 'Reaktionszeit für Anfragen',
en: 'Response time for requests',
},
description: {
de: 'Definierte Reaktionszeit für Betroffenenanfragen',
en: 'Defined response time for data subject requests',
},
passCriteria: {
de: 'Reaktionszeit max. 5 Werktage, um Frist von 1 Monat einhalten zu können',
en: 'Response time max. 5 business days to meet 1 month deadline',
},
requirements: ['Art. 12 Abs. 3 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// SECURITY - Sicherheit
// ==========================================
export const SECURITY_CONTROLS: Control[] = [
{
id: 'VND-SEC-01',
domain: 'SECURITY',
title: {
de: 'Sicherheitsbewertung',
en: 'Security assessment',
},
description: {
de: 'Regelmäßige Sicherheitsbewertung des Vendors',
en: 'Regular security assessment of the vendor',
},
passCriteria: {
de: 'Sicherheitsfragebogen ausgefüllt, keine kritischen Lücken',
en: 'Security questionnaire completed, no critical gaps',
},
requirements: ['Art. 32 DSGVO', 'ISO 27001 A.15.2.1'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SEC-02',
domain: 'SECURITY',
title: {
de: 'Vulnerability Management',
en: 'Vulnerability management',
},
description: {
de: 'Etabliertes Vulnerability Management beim Vendor',
en: 'Established vulnerability management at the vendor',
},
passCriteria: {
de: 'Regelmäßige Schwachstellen-Scans, Patch-Management dokumentiert',
en: 'Regular vulnerability scans, patch management documented',
},
requirements: ['ISO 27001 A.12.6'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SEC-03',
domain: 'SECURITY',
title: {
de: 'Mitarbeiter-Schulung',
en: 'Employee training',
},
description: {
de: 'Datenschutz-Schulung für Mitarbeiter des Vendors',
en: 'Data protection training for vendor employees',
},
passCriteria: {
de: 'Regelmäßige Schulungen (mind. jährlich), Nachweis verfügbar',
en: 'Regular training (at least annually), proof available',
},
requirements: ['Art. 39 Abs. 1 lit. b DSGVO'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// GOVERNANCE - Governance
// ==========================================
export const GOVERNANCE_CONTROLS: Control[] = [
{
id: 'VND-GOV-01',
domain: 'GOVERNANCE',
title: {
de: 'Datenschutzbeauftragter benannt',
en: 'Data protection officer appointed',
},
description: {
de: 'Vendor hat DSB benannt (wenn erforderlich)',
en: 'Vendor has appointed DPO (if required)',
},
passCriteria: {
de: 'DSB benannt und Kontaktdaten verfügbar',
en: 'DPO appointed and contact details available',
},
requirements: ['Art. 37 DSGVO'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-GOV-02',
domain: 'GOVERNANCE',
title: {
de: 'Verzeichnis der Verarbeitungstätigkeiten',
en: 'Records of processing activities',
},
description: {
de: 'Vendor führt eigenes Verarbeitungsverzeichnis',
en: 'Vendor maintains own processing records',
},
passCriteria: {
de: 'Verzeichnis nach Art. 30 Abs. 2 DSGVO vorhanden',
en: 'Records according to Art. 30(2) GDPR available',
},
requirements: ['Art. 30 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-GOV-03',
domain: 'GOVERNANCE',
title: {
de: 'Unterstützung bei DSFA',
en: 'Support for DPIA',
},
description: {
de: 'Vendor unterstützt bei Datenschutz-Folgenabschätzung',
en: 'Vendor supports data protection impact assessment',
},
passCriteria: {
de: 'Unterstützungspflicht bei DSFA vertraglich vereinbart',
en: 'Support obligation for DPIA contractually agreed',
},
requirements: ['Art. 28 Abs. 3 lit. f DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
]

View File

@@ -0,0 +1,115 @@
/**
* Controls Library - Helper Functions
*
* Query, filter, and analysis utilities for the controls library.
*/
import { Control, ControlDomain, ReviewFrequency, LocalizedText } from '../types'
import { CONTROLS_LIBRARY } from './controls-all'
/**
* Get all controls
*/
export function getAllControls(): Control[] {
return CONTROLS_LIBRARY
}
/**
* Get controls by domain
*/
export function getControlsByDomain(domain: ControlDomain): Control[] {
return CONTROLS_LIBRARY.filter((c) => c.domain === domain)
}
/**
* Get control by ID
*/
export function getControlById(id: string): Control | undefined {
return CONTROLS_LIBRARY.find((c) => c.id === id)
}
/**
* Get required controls
*/
export function getRequiredControls(): Control[] {
return CONTROLS_LIBRARY.filter((c) => c.isRequired)
}
/**
* Get controls by frequency
*/
export function getControlsByFrequency(frequency: ReviewFrequency): Control[] {
return CONTROLS_LIBRARY.filter((c) => c.defaultFrequency === frequency)
}
/**
* Get controls applicable to vendors
*/
export function getVendorControls(): Control[] {
return CONTROLS_LIBRARY.filter((c) =>
['TRANSFER', 'AUDIT', 'DELETION', 'INCIDENT', 'SUBPROCESSOR', 'TOM', 'CONTRACT', 'DATA_SUBJECT', 'SECURITY', 'GOVERNANCE'].includes(c.domain)
)
}
/**
* Get controls applicable to processing activities
*/
export function getProcessingActivityControls(): Control[] {
return CONTROLS_LIBRARY.filter((c) =>
['TOM', 'DATA_SUBJECT', 'GOVERNANCE', 'SECURITY'].includes(c.domain)
)
}
/**
* Group controls by domain
*/
export function getControlsGroupedByDomain(): Map<ControlDomain, Control[]> {
const grouped = new Map<ControlDomain, Control[]>()
for (const control of CONTROLS_LIBRARY) {
const existing = grouped.get(control.domain) || []
grouped.set(control.domain, [...existing, control])
}
return grouped
}
/**
* Get domain metadata
*/
export function getControlDomainMeta(domain: ControlDomain): LocalizedText {
const meta: Record<ControlDomain, LocalizedText> = {
TRANSFER: { de: 'Drittlandtransfer', en: 'Third Country Transfer' },
AUDIT: { de: 'Audit & Prüfung', en: 'Audit & Review' },
DELETION: { de: 'Löschung', en: 'Deletion' },
INCIDENT: { de: 'Incident Response', en: 'Incident Response' },
SUBPROCESSOR: { de: 'Unterauftragnehmer', en: 'Sub-Processors' },
TOM: { de: 'Technische/Org. Maßnahmen', en: 'Technical/Org. Measures' },
CONTRACT: { de: 'Vertragliche Grundlagen', en: 'Contractual Basics' },
DATA_SUBJECT: { de: 'Betroffenenrechte', en: 'Data Subject Rights' },
SECURITY: { de: 'Sicherheit', en: 'Security' },
GOVERNANCE: { de: 'Governance', en: 'Governance' },
}
return meta[domain]
}
/**
* Calculate control coverage
*/
export function calculateControlCoverage(
controlIds: string[],
domain?: ControlDomain
): { covered: number; total: number; percentage: number } {
const targetControls = domain
? getControlsByDomain(domain)
: getRequiredControls()
const covered = targetControls.filter((c) => controlIds.includes(c.id)).length
return {
covered,
total: targetControls.length,
percentage: targetControls.length > 0 ? Math.round((covered / targetControls.length) * 100) : 0,
}
}

View File

@@ -1,943 +1,40 @@
/**
* Controls Library
*
* Standard controls for vendor and processing activity compliance
* Standard controls for vendor and processing activity compliance.
* Barrel file that re-exports the merged array, domain arrays, and helpers.
*/
import { Control, ControlDomain, ReviewFrequency, LocalizedText } from '../types'
// Merged controls array
export { CONTROLS_LIBRARY } from './controls-all'
// ==========================================
// CONTROL DEFINITIONS
// ==========================================
// Domain-specific arrays
export {
TRANSFER_CONTROLS,
AUDIT_CONTROLS,
DELETION_CONTROLS,
INCIDENT_CONTROLS,
} from './controls-data-compliance'
export const CONTROLS_LIBRARY: Control[] = [
// ==========================================
// TRANSFER - Drittlandtransfer Controls
// ==========================================
{
id: 'VND-TRF-01',
domain: 'TRANSFER',
title: {
de: 'Drittlandtransfer nur mit Rechtsgrundlage',
en: 'Third country transfer with legal basis',
},
description: {
de: 'Drittlandtransfers erfolgen nur auf Basis von SCC, BCR oder Angemessenheitsbeschluss',
en: 'Third country transfers only based on SCC, BCR or adequacy decision',
},
passCriteria: {
de: 'SCC oder BCR vertraglich vereinbart ODER Angemessenheitsbeschluss vorhanden',
en: 'SCC or BCR contractually agreed OR adequacy decision exists',
},
requirements: ['Art. 44-49 DSGVO', 'ISO 27001 A.15.1.2'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-02',
domain: 'TRANSFER',
title: {
de: 'Aktuelle Standardvertragsklauseln',
en: 'Current Standard Contractual Clauses',
},
description: {
de: 'Bei SCC-Nutzung: Verwendung der aktuellen EU-Kommission-Klauseln (2021)',
en: 'When using SCC: Current EU Commission clauses (2021) are used',
},
passCriteria: {
de: 'SCC 2021 (Durchführungsbeschluss (EU) 2021/914) verwendet',
en: 'SCC 2021 (Implementing Decision (EU) 2021/914) used',
},
requirements: ['Art. 46 Abs. 2 lit. c DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-03',
domain: 'TRANSFER',
title: {
de: 'Transfer Impact Assessment (TIA)',
en: 'Transfer Impact Assessment (TIA)',
},
description: {
de: 'Bei Transfers in Drittländer ohne Angemessenheitsbeschluss ist TIA durchzuführen',
en: 'TIA required for transfers to third countries without adequacy decision',
},
passCriteria: {
de: 'TIA dokumentiert und bewertet Risiken als akzeptabel',
en: 'TIA documented and risks assessed as acceptable',
},
requirements: ['Schrems II Urteil', 'EDSA Empfehlungen 01/2020'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-04',
domain: 'TRANSFER',
title: {
de: 'Zusätzliche Schutzmaßnahmen',
en: 'Supplementary Measures',
},
description: {
de: 'Bei Bedarf sind zusätzliche technische/organisatorische Maßnahmen implementiert',
en: 'Supplementary technical/organizational measures implemented where needed',
},
passCriteria: {
de: 'Ergänzende Maßnahmen dokumentiert (Verschlüsselung, Pseudonymisierung, etc.)',
en: 'Supplementary measures documented (encryption, pseudonymization, etc.)',
},
requirements: ['EDSA Empfehlungen 01/2020'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-05',
domain: 'TRANSFER',
title: {
de: 'Überwachung Angemessenheitsbeschlüsse',
en: 'Monitoring Adequacy Decisions',
},
description: {
de: 'Änderungen bei Angemessenheitsbeschlüssen werden überwacht',
en: 'Changes to adequacy decisions are monitored',
},
passCriteria: {
de: 'Prozess zur Überwachung und Reaktion auf Änderungen etabliert',
en: 'Process for monitoring and responding to changes established',
},
requirements: ['Art. 45 DSGVO'],
isRequired: false,
defaultFrequency: 'QUARTERLY',
},
export {
SUBPROCESSOR_CONTROLS,
TOM_CONTROLS,
CONTRACT_CONTROLS,
DATA_SUBJECT_CONTROLS,
SECURITY_CONTROLS,
GOVERNANCE_CONTROLS,
} from './controls-data-operations'
// ==========================================
// AUDIT - Auditrechte Controls
// ==========================================
{
id: 'VND-AUD-01',
domain: 'AUDIT',
title: {
de: 'Auditrecht vertraglich vereinbart',
en: 'Audit right contractually agreed',
},
description: {
de: 'Vertrag enthält wirksames Auditrecht ohne unangemessene Einschränkungen',
en: 'Contract contains effective audit right without unreasonable restrictions',
},
passCriteria: {
de: 'Auditrecht im AVV enthalten, max. 30 Tage Vorlaufzeit, keine Ausschlussklausel',
en: 'Audit right in DPA, max 30 days notice, no exclusion clause',
},
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-02',
domain: 'AUDIT',
title: {
de: 'Vor-Ort-Inspektionen möglich',
en: 'On-site inspections possible',
},
description: {
de: 'Vertrag erlaubt Vor-Ort-Inspektionen bei dem Auftragsverarbeiter',
en: 'Contract allows on-site inspections at the processor',
},
passCriteria: {
de: 'Vor-Ort-Audit explizit erlaubt, Zugang zu relevanten Bereichen',
en: 'On-site audit explicitly allowed, access to relevant areas',
},
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-03',
domain: 'AUDIT',
title: {
de: 'Aktuelle Zertifizierungen',
en: 'Current Certifications',
},
description: {
de: 'Relevante Sicherheitszertifizierungen sind aktuell und gültig',
en: 'Relevant security certifications are current and valid',
},
passCriteria: {
de: 'ISO 27001, SOC 2 oder vergleichbar, nicht abgelaufen',
en: 'ISO 27001, SOC 2 or equivalent, not expired',
},
requirements: ['Art. 32 DSGVO', 'ISO 27001 A.15.1.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-04',
domain: 'AUDIT',
title: {
de: 'Letzte Prüfung durchgeführt',
en: 'Last review conducted',
},
description: {
de: 'Vendor wurde innerhalb des Review-Zyklus geprüft',
en: 'Vendor was reviewed within the review cycle',
},
passCriteria: {
de: 'Dokumentierte Prüfung innerhalb des festgelegten Intervalls',
en: 'Documented review within the defined interval',
},
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-05',
domain: 'AUDIT',
title: {
de: 'Prüfberichte verfügbar',
en: 'Audit reports available',
},
description: {
de: 'Aktuelle Prüfberichte (SOC 2, Penetrationstest, etc.) liegen vor',
en: 'Current audit reports (SOC 2, penetration test, etc.) are available',
},
passCriteria: {
de: 'Prüfberichte nicht älter als 12 Monate',
en: 'Audit reports not older than 12 months',
},
requirements: ['ISO 27001 A.18.2.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// DELETION - Löschung Controls
// ==========================================
{
id: 'VND-DEL-01',
domain: 'DELETION',
title: {
de: 'Löschung/Rückgabe nach Vertragsende',
en: 'Deletion/return after contract end',
},
description: {
de: 'Klare Regelung zur Löschung oder Rückgabe aller Daten nach Vertragsende',
en: 'Clear provision for deletion or return of all data after contract end',
},
passCriteria: {
de: 'Löschfrist max. 30 Tage, Löschbestätigung vorgesehen',
en: 'Deletion within max 30 days, deletion confirmation provided',
},
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DEL-02',
domain: 'DELETION',
title: {
de: 'Löschbestätigung',
en: 'Deletion confirmation',
},
description: {
de: 'Schriftliche Bestätigung der vollständigen Datenlöschung',
en: 'Written confirmation of complete data deletion',
},
passCriteria: {
de: 'Löschbestätigung vertraglich vereinbart und einforderbar',
en: 'Deletion confirmation contractually agreed and enforceable',
},
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DEL-03',
domain: 'DELETION',
title: {
de: 'Löschung bei Unterauftragnehmern',
en: 'Deletion at sub-processors',
},
description: {
de: 'Löschpflicht erstreckt sich auf alle Unterauftragnehmer',
en: 'Deletion obligation extends to all sub-processors',
},
passCriteria: {
de: 'Weitergabe der Löschpflicht an Unterauftragnehmer vertraglich vereinbart',
en: 'Transfer of deletion obligation to sub-processors contractually agreed',
},
requirements: ['Art. 28 Abs. 3 lit. g, d DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DEL-04',
domain: 'DELETION',
title: {
de: 'Backup-Löschung',
en: 'Backup deletion',
},
description: {
de: 'Daten werden auch aus Backups gelöscht',
en: 'Data is also deleted from backups',
},
passCriteria: {
de: 'Backup-Löschung geregelt, max. Aufbewahrungsfrist für Backups definiert',
en: 'Backup deletion regulated, max retention period for backups defined',
},
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// INCIDENT - Incident Response Controls
// ==========================================
{
id: 'VND-INC-01',
domain: 'INCIDENT',
title: {
de: 'Meldepflicht bei Datenpannen',
en: 'Data breach notification obligation',
},
description: {
de: 'Unverzügliche Meldung von Datenschutzverletzungen',
en: 'Immediate notification of data protection violations',
},
passCriteria: {
de: 'Meldepflicht vereinbart, Frist max. 24-48h, Mindestinhalte definiert',
en: 'Notification obligation agreed, deadline max 24-48h, minimum content defined',
},
requirements: ['Art. 33 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-INC-02',
domain: 'INCIDENT',
title: {
de: 'Incident Response Plan',
en: 'Incident Response Plan',
},
description: {
de: 'Vendor hat dokumentierten Incident Response Plan',
en: 'Vendor has documented incident response plan',
},
passCriteria: {
de: 'Incident Response Plan liegt vor und wurde getestet',
en: 'Incident response plan exists and has been tested',
},
requirements: ['ISO 27001 A.16.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-INC-03',
domain: 'INCIDENT',
title: {
de: 'Kontaktstelle für Incidents',
en: 'Contact point for incidents',
},
description: {
de: 'Definierte Kontaktstelle für Datenschutzvorfälle',
en: 'Defined contact point for data protection incidents',
},
passCriteria: {
de: 'Kontaktdaten für Incident-Meldungen bekannt und aktuell',
en: 'Contact details for incident reporting known and current',
},
requirements: ['Art. 33 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'QUARTERLY',
},
{
id: 'VND-INC-04',
domain: 'INCIDENT',
title: {
de: 'Unterstützung bei Incident-Dokumentation',
en: 'Support with incident documentation',
},
description: {
de: 'Vendor unterstützt bei der Dokumentation von Vorfällen',
en: 'Vendor supports documentation of incidents',
},
passCriteria: {
de: 'Unterstützungspflicht bei Dokumentation vertraglich vereinbart',
en: 'Support obligation for documentation contractually agreed',
},
requirements: ['Art. 33 Abs. 5 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// SUBPROCESSOR - Unterauftragnehmer Controls
// ==========================================
{
id: 'VND-SUB-01',
domain: 'SUBPROCESSOR',
title: {
de: 'Genehmigungspflicht für Unterauftragnehmer',
en: 'Approval requirement for sub-processors',
},
description: {
de: 'Einsatz von Unterauftragnehmern nur mit Genehmigung',
en: 'Use of sub-processors only with approval',
},
passCriteria: {
de: 'Genehmigungserfordernis (spezifisch oder allgemein mit Widerspruchsrecht) vereinbart',
en: 'Approval requirement (specific or general with objection right) agreed',
},
requirements: ['Art. 28 Abs. 2, 4 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SUB-02',
domain: 'SUBPROCESSOR',
title: {
de: 'Aktuelle Unterauftragnehmer-Liste',
en: 'Current sub-processor list',
},
description: {
de: 'Vollständige und aktuelle Liste aller Unterauftragnehmer',
en: 'Complete and current list of all sub-processors',
},
passCriteria: {
de: 'Liste liegt vor mit Name, Sitz, Verarbeitungszweck',
en: 'List available with name, location, processing purpose',
},
requirements: ['Art. 28 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'QUARTERLY',
},
{
id: 'VND-SUB-03',
domain: 'SUBPROCESSOR',
title: {
de: 'Informationspflicht bei Änderungen',
en: 'Notification obligation for changes',
},
description: {
de: 'Information über neue oder geänderte Unterauftragnehmer',
en: 'Information about new or changed sub-processors',
},
passCriteria: {
de: 'Vorabinformation vereinbart, ausreichende Frist für Widerspruch',
en: 'Advance notification agreed, sufficient time for objection',
},
requirements: ['Art. 28 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SUB-04',
domain: 'SUBPROCESSOR',
title: {
de: 'Weitergabe der Datenschutzpflichten',
en: 'Transfer of data protection obligations',
},
description: {
de: 'Datenschutzpflichten werden an Unterauftragnehmer weitergegeben',
en: 'Data protection obligations are transferred to sub-processors',
},
passCriteria: {
de: 'Vertraglich vereinbart, dass Unterauftragnehmer gleichen Pflichten unterliegen',
en: 'Contractually agreed that sub-processors are subject to same obligations',
},
requirements: ['Art. 28 Abs. 4 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SUB-05',
domain: 'SUBPROCESSOR',
title: {
de: 'Haftung für Unterauftragnehmer',
en: 'Liability for sub-processors',
},
description: {
de: 'Klare Haftungsregelung für Unterauftragnehmer',
en: 'Clear liability provision for sub-processors',
},
passCriteria: {
de: 'Auftragsverarbeiter haftet für Unterauftragnehmer wie für eigenes Handeln',
en: 'Processor is liable for sub-processors as for own actions',
},
requirements: ['Art. 28 Abs. 4 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// TOM - Technische/Organisatorische Maßnahmen
// ==========================================
{
id: 'VND-TOM-01',
domain: 'TOM',
title: {
de: 'TOM-Dokumentation vorhanden',
en: 'TOM documentation available',
},
description: {
de: 'Vollständige Dokumentation der technischen und organisatorischen Maßnahmen',
en: 'Complete documentation of technical and organizational measures',
},
passCriteria: {
de: 'TOM-Anlage vorhanden, aktuell, spezifisch für die Verarbeitung',
en: 'TOM annex available, current, specific to the processing',
},
requirements: ['Art. 28 Abs. 3 lit. c DSGVO', 'Art. 32 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-02',
domain: 'TOM',
title: {
de: 'Verschlüsselung',
en: 'Encryption',
},
description: {
de: 'Angemessene Verschlüsselung für Daten in Transit und at Rest',
en: 'Appropriate encryption for data in transit and at rest',
},
passCriteria: {
de: 'TLS 1.2+ für Transit, AES-256 für at Rest',
en: 'TLS 1.2+ for transit, AES-256 for at rest',
},
requirements: ['Art. 32 Abs. 1 lit. a DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-03',
domain: 'TOM',
title: {
de: 'Zugriffskontrolle',
en: 'Access control',
},
description: {
de: 'Angemessene Zugriffskontrollmechanismen',
en: 'Appropriate access control mechanisms',
},
passCriteria: {
de: 'Rollenbasierte Zugriffskontrolle, Least Privilege, Logging',
en: 'Role-based access control, least privilege, logging',
},
requirements: ['Art. 32 Abs. 1 lit. b DSGVO', 'ISO 27001 A.9'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-04',
domain: 'TOM',
title: {
de: 'Verfügbarkeit und Wiederherstellung',
en: 'Availability and recovery',
},
description: {
de: 'Maßnahmen zur Sicherstellung der Verfügbarkeit und Wiederherstellung',
en: 'Measures to ensure availability and recovery',
},
passCriteria: {
de: 'Backup-Konzept, DR-Plan, RTO/RPO definiert',
en: 'Backup concept, DR plan, RTO/RPO defined',
},
requirements: ['Art. 32 Abs. 1 lit. b, c DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-05',
domain: 'TOM',
title: {
de: 'Regelmäßige TOM-Überprüfung',
en: 'Regular TOM review',
},
description: {
de: 'Regelmäßige Überprüfung und Aktualisierung der TOM',
en: 'Regular review and update of TOM',
},
passCriteria: {
de: 'TOM werden mindestens jährlich überprüft und bei Bedarf aktualisiert',
en: 'TOM are reviewed at least annually and updated as needed',
},
requirements: ['Art. 32 Abs. 1 lit. d DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-06',
domain: 'TOM',
title: {
de: 'Penetrationstest',
en: 'Penetration testing',
},
description: {
de: 'Regelmäßige Penetrationstests der relevanten Systeme',
en: 'Regular penetration testing of relevant systems',
},
passCriteria: {
de: 'Jährlicher Pentest, kritische Findings behoben',
en: 'Annual pentest, critical findings resolved',
},
requirements: ['ISO 27001 A.12.6.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// CONTRACT - Vertragliche Grundlagen
// ==========================================
{
id: 'VND-CON-01',
domain: 'CONTRACT',
title: {
de: 'Weisungsgebundenheit',
en: 'Instruction binding',
},
description: {
de: 'Auftragsverarbeiter ist an Weisungen gebunden',
en: 'Processor is bound by instructions',
},
passCriteria: {
de: 'Weisungsgebundenheit explizit vereinbart, Hinweispflicht bei rechtswidrigen Weisungen',
en: 'Instruction binding explicitly agreed, notification obligation for unlawful instructions',
},
requirements: ['Art. 28 Abs. 3 lit. a DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-CON-02',
domain: 'CONTRACT',
title: {
de: 'Vertraulichkeitsverpflichtung',
en: 'Confidentiality obligation',
},
description: {
de: 'Mitarbeiter sind zur Vertraulichkeit verpflichtet',
en: 'Employees are obligated to confidentiality',
},
passCriteria: {
de: 'Vertraulichkeitsverpflichtung für alle Mitarbeiter mit Datenzugriff',
en: 'Confidentiality obligation for all employees with data access',
},
requirements: ['Art. 28 Abs. 3 lit. b DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-CON-03',
domain: 'CONTRACT',
title: {
de: 'Gegenstand und Dauer der Verarbeitung',
en: 'Subject and duration of processing',
},
description: {
de: 'Klare Definition von Gegenstand und Dauer der Verarbeitung',
en: 'Clear definition of subject and duration of processing',
},
passCriteria: {
de: 'Verarbeitungsgegenstand, Dauer, Art der Daten, Betroffene definiert',
en: 'Processing subject, duration, type of data, data subjects defined',
},
requirements: ['Art. 28 Abs. 3 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-CON-04',
domain: 'CONTRACT',
title: {
de: 'Schriftform/Textform',
en: 'Written/text form',
},
description: {
de: 'AVV in Schriftform oder elektronischem Format',
en: 'DPA in written or electronic format',
},
passCriteria: {
de: 'AVV in Schriftform oder elektronisch mit qualifizierter Signatur',
en: 'DPA in written form or electronically with qualified signature',
},
requirements: ['Art. 28 Abs. 9 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// DATA_SUBJECT - Betroffenenrechte
// ==========================================
{
id: 'VND-DSR-01',
domain: 'DATA_SUBJECT',
title: {
de: 'Unterstützung bei Betroffenenrechten',
en: 'Support for data subject rights',
},
description: {
de: 'Vendor unterstützt bei der Erfüllung von Betroffenenrechten',
en: 'Vendor supports fulfillment of data subject rights',
},
passCriteria: {
de: 'Unterstützungspflicht vereinbart, Prozess zur Weiterleitung definiert',
en: 'Support obligation agreed, process for forwarding defined',
},
requirements: ['Art. 28 Abs. 3 lit. e DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DSR-02',
domain: 'DATA_SUBJECT',
title: {
de: 'Reaktionszeit für Anfragen',
en: 'Response time for requests',
},
description: {
de: 'Definierte Reaktionszeit für Betroffenenanfragen',
en: 'Defined response time for data subject requests',
},
passCriteria: {
de: 'Reaktionszeit max. 5 Werktage, um Frist von 1 Monat einhalten zu können',
en: 'Response time max. 5 business days to meet 1 month deadline',
},
requirements: ['Art. 12 Abs. 3 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// SECURITY - Sicherheit
// ==========================================
{
id: 'VND-SEC-01',
domain: 'SECURITY',
title: {
de: 'Sicherheitsbewertung',
en: 'Security assessment',
},
description: {
de: 'Regelmäßige Sicherheitsbewertung des Vendors',
en: 'Regular security assessment of the vendor',
},
passCriteria: {
de: 'Sicherheitsfragebogen ausgefüllt, keine kritischen Lücken',
en: 'Security questionnaire completed, no critical gaps',
},
requirements: ['Art. 32 DSGVO', 'ISO 27001 A.15.2.1'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SEC-02',
domain: 'SECURITY',
title: {
de: 'Vulnerability Management',
en: 'Vulnerability management',
},
description: {
de: 'Etabliertes Vulnerability Management beim Vendor',
en: 'Established vulnerability management at the vendor',
},
passCriteria: {
de: 'Regelmäßige Schwachstellen-Scans, Patch-Management dokumentiert',
en: 'Regular vulnerability scans, patch management documented',
},
requirements: ['ISO 27001 A.12.6'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SEC-03',
domain: 'SECURITY',
title: {
de: 'Mitarbeiter-Schulung',
en: 'Employee training',
},
description: {
de: 'Datenschutz-Schulung für Mitarbeiter des Vendors',
en: 'Data protection training for vendor employees',
},
passCriteria: {
de: 'Regelmäßige Schulungen (mind. jährlich), Nachweis verfügbar',
en: 'Regular training (at least annually), proof available',
},
requirements: ['Art. 39 Abs. 1 lit. b DSGVO'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// GOVERNANCE - Governance
// ==========================================
{
id: 'VND-GOV-01',
domain: 'GOVERNANCE',
title: {
de: 'Datenschutzbeauftragter benannt',
en: 'Data protection officer appointed',
},
description: {
de: 'Vendor hat DSB benannt (wenn erforderlich)',
en: 'Vendor has appointed DPO (if required)',
},
passCriteria: {
de: 'DSB benannt und Kontaktdaten verfügbar',
en: 'DPO appointed and contact details available',
},
requirements: ['Art. 37 DSGVO'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-GOV-02',
domain: 'GOVERNANCE',
title: {
de: 'Verzeichnis der Verarbeitungstätigkeiten',
en: 'Records of processing activities',
},
description: {
de: 'Vendor führt eigenes Verarbeitungsverzeichnis',
en: 'Vendor maintains own processing records',
},
passCriteria: {
de: 'Verzeichnis nach Art. 30 Abs. 2 DSGVO vorhanden',
en: 'Records according to Art. 30(2) GDPR available',
},
requirements: ['Art. 30 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-GOV-03',
domain: 'GOVERNANCE',
title: {
de: 'Unterstützung bei DSFA',
en: 'Support for DPIA',
},
description: {
de: 'Vendor unterstützt bei Datenschutz-Folgenabschätzung',
en: 'Vendor supports data protection impact assessment',
},
passCriteria: {
de: 'Unterstützungspflicht bei DSFA vertraglich vereinbart',
en: 'Support obligation for DPIA contractually agreed',
},
requirements: ['Art. 28 Abs. 3 lit. f DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// HELPER FUNCTIONS
// ==========================================
/**
* Get all controls
*/
export function getAllControls(): Control[] {
return CONTROLS_LIBRARY
}
/**
* Get controls by domain
*/
export function getControlsByDomain(domain: ControlDomain): Control[] {
return CONTROLS_LIBRARY.filter((c) => c.domain === domain)
}
/**
* Get control by ID
*/
export function getControlById(id: string): Control | undefined {
return CONTROLS_LIBRARY.find((c) => c.id === id)
}
/**
* Get required controls
*/
export function getRequiredControls(): Control[] {
return CONTROLS_LIBRARY.filter((c) => c.isRequired)
}
/**
* Get controls by frequency
*/
export function getControlsByFrequency(frequency: ReviewFrequency): Control[] {
return CONTROLS_LIBRARY.filter((c) => c.defaultFrequency === frequency)
}
/**
* Get controls applicable to vendors
*/
export function getVendorControls(): Control[] {
return CONTROLS_LIBRARY.filter((c) =>
['TRANSFER', 'AUDIT', 'DELETION', 'INCIDENT', 'SUBPROCESSOR', 'TOM', 'CONTRACT', 'DATA_SUBJECT', 'SECURITY', 'GOVERNANCE'].includes(c.domain)
)
}
/**
* Get controls applicable to processing activities
*/
export function getProcessingActivityControls(): Control[] {
return CONTROLS_LIBRARY.filter((c) =>
['TOM', 'DATA_SUBJECT', 'GOVERNANCE', 'SECURITY'].includes(c.domain)
)
}
/**
* Group controls by domain
*/
export function getControlsGroupedByDomain(): Map<ControlDomain, Control[]> {
const grouped = new Map<ControlDomain, Control[]>()
for (const control of CONTROLS_LIBRARY) {
const existing = grouped.get(control.domain) || []
grouped.set(control.domain, [...existing, control])
}
return grouped
}
/**
* Get domain metadata
*/
export function getControlDomainMeta(domain: ControlDomain): LocalizedText {
const meta: Record<ControlDomain, LocalizedText> = {
TRANSFER: { de: 'Drittlandtransfer', en: 'Third Country Transfer' },
AUDIT: { de: 'Audit & Prüfung', en: 'Audit & Review' },
DELETION: { de: 'Löschung', en: 'Deletion' },
INCIDENT: { de: 'Incident Response', en: 'Incident Response' },
SUBPROCESSOR: { de: 'Unterauftragnehmer', en: 'Sub-Processors' },
TOM: { de: 'Technische/Org. Maßnahmen', en: 'Technical/Org. Measures' },
CONTRACT: { de: 'Vertragliche Grundlagen', en: 'Contractual Basics' },
DATA_SUBJECT: { de: 'Betroffenenrechte', en: 'Data Subject Rights' },
SECURITY: { de: 'Sicherheit', en: 'Security' },
GOVERNANCE: { de: 'Governance', en: 'Governance' },
}
return meta[domain]
}
/**
* Calculate control coverage
*/
export function calculateControlCoverage(
controlIds: string[],
domain?: ControlDomain
): { covered: number; total: number; percentage: number } {
const targetControls = domain
? getControlsByDomain(domain)
: getRequiredControls()
const covered = targetControls.filter((c) => controlIds.includes(c.id)).length
return {
covered,
total: targetControls.length,
percentage: targetControls.length > 0 ? Math.round((covered / targetControls.length) * 100) : 0,
}
}
// Helper functions
export {
getAllControls,
getControlsByDomain,
getControlById,
getRequiredControls,
getControlsByFrequency,
getVendorControls,
getProcessingActivityControls,
getControlsGroupedByDomain,
getControlDomainMeta,
calculateControlCoverage,
} from './controls-helpers'

View File

@@ -0,0 +1,50 @@
/**
* Audit & Export Types
*
* Types for report generation and data export:
* - Report types (VVT, RoPA, vendor audit, etc.)
* - Export formats (PDF, DOCX, XLSX, JSON)
* - Audit report snapshots
*/
// ==========================================
// ENUMS - EXPORT
// ==========================================
export type ReportType =
| 'VVT_EXPORT'
| 'VENDOR_AUDIT'
| 'ROPA'
| 'MANAGEMENT_SUMMARY'
| 'DPIA_INPUT'
export type ExportFormat = 'PDF' | 'DOCX' | 'XLSX' | 'JSON'
// ==========================================
// INTERFACES - AUDIT REPORTS
// ==========================================
export interface ReportScope {
vendorIds?: string[]
processingActivityIds?: string[]
dateRange?: { from: Date; to: Date }
}
export interface AuditReport {
id: string
tenantId: string
type: ReportType
// Scope
scope: ReportScope
// Generierung
format: ExportFormat
storagePath: string
generatedAt: Date
generatedBy: string
// Snapshot-Daten (fuer Revisionssicherheit)
snapshotHash: string // SHA-256 des Inhalts
}

View File

@@ -0,0 +1,58 @@
/**
* Common / Shared Types
*
* Foundational types used across all vendor-compliance domains:
* LocalizedText, Address, Contact, ResponsibleParty, Organization
*/
// ==========================================
// LOCALIZED TEXT
// ==========================================
export interface LocalizedText {
de: string
en: string
}
// ==========================================
// COMMON TYPES
// ==========================================
export interface Address {
street: string
city: string
postalCode: string
country: string // ISO 3166-1 alpha-2
state?: string
}
export interface Contact {
name: string
email: string
phone?: string
department?: string
role?: string
}
export interface ResponsibleParty {
organizationName: string
legalForm?: string
address: Address
contact: Contact
}
// ==========================================
// ORGANISATION / TENANT
// ==========================================
export interface Organization {
id: string
name: string
legalForm: string // GmbH, AG, e.V., etc.
address: Address
country: string // ISO 3166-1 alpha-2
vatId?: string
dpoContact: Contact // Datenschutzbeauftragter
createdAt: Date
updatedAt: Date
}

View File

@@ -0,0 +1,117 @@
/**
* Contract Types
*
* Types for contract document management:
* - Document types (AVV, MSA, SLA, SCC, etc.)
* - Contract review and lifecycle statuses
* - Contract document and version interfaces
*/
import type { Address } from './types-common'
// ==========================================
// ENUMS - CONTRACT
// ==========================================
export type DocumentType =
| 'AVV' // Auftragsverarbeitungsvertrag
| 'MSA' // Master Service Agreement
| 'SLA' // Service Level Agreement
| 'SCC' // Standard Contractual Clauses
| 'NDA' // Non-Disclosure Agreement
| 'TOM_ANNEX' // TOM-Anlage
| 'CERTIFICATION' // Zertifikat
| 'SUB_PROCESSOR_LIST' // Unterauftragsverarbeiter-Liste
| 'OTHER'
export type ContractReviewStatus =
| 'PENDING'
| 'IN_PROGRESS'
| 'COMPLETED'
| 'FAILED'
export type ContractStatus =
| 'DRAFT'
| 'SIGNED'
| 'ACTIVE'
| 'EXPIRED'
| 'TERMINATED'
// ==========================================
// INTERFACES - CONTRACT
// ==========================================
export interface ContractParty {
role: 'CONTROLLER' | 'PROCESSOR' | 'PARTY'
name: string
address?: Address
signatoryName?: string
signatoryRole?: string
}
export interface ContractDocument {
id: string
tenantId: string
vendorId: string
// Dokument
fileName: string
originalName: string
mimeType: string
fileSize: number
storagePath: string // MinIO path
// Klassifikation
documentType: DocumentType
// Versioning
version: string
previousVersionId?: string
// Metadaten (extrahiert)
parties?: ContractParty[]
effectiveDate?: Date
expirationDate?: Date
autoRenewal?: boolean
renewalNoticePeriod?: number // Tage
terminationNoticePeriod?: number // Tage
// Review Status
reviewStatus: ContractReviewStatus
reviewCompletedAt?: Date
complianceScore?: number // 0-100
// Workflow
status: ContractStatus
signedAt?: Date
// Extracted text for search
extractedText?: string
pageCount?: number
createdAt: Date
updatedAt: Date
}
export interface DocumentVersion {
id: string
documentId: string
version: string
storagePath: string
extractedText?: string
pageCount: number
createdAt: Date
}
// ==========================================
// FORM TYPES - CONTRACT
// ==========================================
export interface ContractUploadData {
vendorId: string
documentType: DocumentType
version: string
effectiveDate?: Date
expirationDate?: Date
autoRenewal?: boolean
}

View File

@@ -0,0 +1,93 @@
/**
* Finding Types
*
* Types for compliance findings from contract reviews:
* - Finding classification (type, category, severity)
* - Citation references to source documents
* - Finding workflow (status, resolution)
*/
import type { LocalizedText } from './types-common'
// ==========================================
// ENUMS - FINDINGS
// ==========================================
export type FindingType =
| 'OK' // Anforderung erfuellt
| 'GAP' // Luecke/fehlend
| 'RISK' // Risiko identifiziert
| 'UNKNOWN' // Nicht eindeutig
export type FindingCategory =
| 'AVV_CONTENT' // Art. 28 Abs. 3 Mindestinhalte
| 'SUBPROCESSOR' // Unterauftragnehmer-Regelung
| 'INCIDENT' // Incident-Meldepflichten
| 'AUDIT_RIGHTS' // Audit-/Inspektionsrechte
| 'DELETION' // Loeschung/Rueckgabe
| 'TOM' // Technische/Org. Massnahmen
| 'TRANSFER' // Drittlandtransfer
| 'LIABILITY' // Haftung/Indemnity
| 'SLA' // Verfuegbarkeit
| 'DATA_SUBJECT_RIGHTS' // Betroffenenrechte
| 'CONFIDENTIALITY' // Vertraulichkeit
| 'INSTRUCTION' // Weisungsgebundenheit
| 'TERMINATION' // Vertragsbeendigung
| 'GENERAL' // Allgemein
export type FindingSeverity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
export type FindingStatus =
| 'OPEN'
| 'IN_PROGRESS'
| 'RESOLVED'
| 'ACCEPTED'
| 'FALSE_POSITIVE'
// ==========================================
// INTERFACES - FINDINGS
// ==========================================
export interface Citation {
documentId: string
versionId?: string
page: number
startChar: number
endChar: number
quotedText: string
quoteHash: string // SHA-256 zur Verifizierung
}
export interface Finding {
id: string
tenantId: string
contractId: string
vendorId: string
// Klassifikation
type: FindingType
category: FindingCategory
severity: FindingSeverity
// Inhalt
title: LocalizedText
description: LocalizedText
recommendation?: LocalizedText
// Citations (Textstellen-Belege)
citations: Citation[]
// Verknuepfung
affectedRequirement?: string // z.B. "Art. 28 Abs. 3 lit. a DSGVO"
triggeredControls: string[] // Control-IDs
// Workflow
status: FindingStatus
assignee?: string
dueDate?: Date
resolution?: string
resolvedAt?: Date
createdAt: Date
updatedAt: Date
}

View File

@@ -0,0 +1,280 @@
/**
* Helper Functions & Constants (Metadata)
*
* Utility functions for risk calculation, formatting, validation.
* Localized metadata constants for enums (labels, articles, etc.).
*/
import type { LocalizedText } from './types-common'
import type {
PersonalDataCategory,
DataSubjectCategory,
LegalBasisType,
TransferMechanismType,
} from './types-processing'
import type {
VendorRole,
VendorStatus,
ServiceCategory,
} from './types-vendor'
import type {
DocumentType,
ContractStatus,
} from './types-contract'
import type {
FindingSeverity,
} from './types-finding'
import type {
RiskLevel,
} from './types-risk'
import type {
ProcessingActivityStatus,
} from './types-processing'
// ==========================================
// HELPER FUNCTIONS
// ==========================================
/**
* Calculate risk level from score
*/
export function getRiskLevelFromScore(score: number): RiskLevel {
if (score <= 4) return 'LOW'
if (score <= 9) return 'MEDIUM'
if (score <= 16) return 'HIGH'
return 'CRITICAL'
}
/**
* Calculate risk score from likelihood and impact
*/
export function calculateRiskScore(likelihood: number, impact: number): number {
return likelihood * impact
}
/**
* Check if data category is special (Art. 9 DSGVO)
*/
export function isSpecialCategory(category: PersonalDataCategory): boolean {
const specialCategories: PersonalDataCategory[] = [
'HEALTH_DATA',
'GENETIC_DATA',
'BIOMETRIC_DATA',
'RACIAL_ETHNIC',
'POLITICAL_OPINIONS',
'RELIGIOUS_BELIEFS',
'TRADE_UNION',
'SEX_LIFE',
'CRIMINAL_DATA',
]
return specialCategories.includes(category)
}
/**
* Check if country has adequacy decision
*/
export function hasAdequacyDecision(countryCode: string): boolean {
const adequateCountries = [
'AD', 'AR', 'CA', 'FO', 'GG', 'IL', 'IM', 'JP', 'JE', 'NZ', 'KR', 'CH', 'GB', 'UY',
// EU/EEA countries
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU',
'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE',
'IS', 'LI', 'NO',
]
return adequateCountries.includes(countryCode.toUpperCase())
}
/**
* Generate VVT ID
*/
export function generateVVTId(existingIds: string[]): string {
const year = new Date().getFullYear()
const prefix = `VVT-${year}-`
const existingNumbers = existingIds
.filter(id => id.startsWith(prefix))
.map(id => parseInt(id.replace(prefix, ''), 10))
.filter(n => !isNaN(n))
const nextNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1
return `${prefix}${nextNumber.toString().padStart(3, '0')}`
}
/**
* Format date for display
*/
export function formatDate(date: Date | string | undefined): string {
if (!date) return '-'
const d = typeof date === 'string' ? new Date(date) : date
return d.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
})
}
/**
* Get severity color class
*/
export function getSeverityColor(severity: FindingSeverity): string {
switch (severity) {
case 'LOW': return 'text-blue-600 bg-blue-100'
case 'MEDIUM': return 'text-yellow-600 bg-yellow-100'
case 'HIGH': return 'text-orange-600 bg-orange-100'
case 'CRITICAL': return 'text-red-600 bg-red-100'
}
}
/**
* Get status color class
*/
export function getStatusColor(status: VendorStatus | ProcessingActivityStatus | ContractStatus): string {
switch (status) {
case 'ACTIVE':
case 'APPROVED':
case 'SIGNED':
return 'text-green-600 bg-green-100'
case 'DRAFT':
case 'PENDING_REVIEW':
return 'text-yellow-600 bg-yellow-100'
case 'REVIEW':
case 'INACTIVE':
return 'text-blue-600 bg-blue-100'
case 'ARCHIVED':
case 'EXPIRED':
case 'TERMINATED':
return 'text-gray-600 bg-gray-100'
default:
return 'text-gray-600 bg-gray-100'
}
}
// ==========================================
// CONSTANTS - METADATA
// ==========================================
export const DATA_SUBJECT_CATEGORY_META: Record<DataSubjectCategory, LocalizedText> = {
EMPLOYEES: { de: 'Beschäftigte', en: 'Employees' },
APPLICANTS: { de: 'Bewerber', en: 'Applicants' },
CUSTOMERS: { de: 'Kunden', en: 'Customers' },
PROSPECTIVE_CUSTOMERS: { de: 'Interessenten', en: 'Prospective Customers' },
SUPPLIERS: { de: 'Lieferanten', en: 'Suppliers' },
BUSINESS_PARTNERS: { de: 'Geschäftspartner', en: 'Business Partners' },
VISITORS: { de: 'Besucher', en: 'Visitors' },
WEBSITE_USERS: { de: 'Website-Nutzer', en: 'Website Users' },
APP_USERS: { de: 'App-Nutzer', en: 'App Users' },
NEWSLETTER_SUBSCRIBERS: { de: 'Newsletter-Abonnenten', en: 'Newsletter Subscribers' },
MEMBERS: { de: 'Mitglieder', en: 'Members' },
PATIENTS: { de: 'Patienten', en: 'Patients' },
STUDENTS: { de: 'Schüler/Studenten', en: 'Students' },
MINORS: { de: 'Minderjährige', en: 'Minors' },
OTHER: { de: 'Sonstige', en: 'Other' },
}
export const PERSONAL_DATA_CATEGORY_META: Record<PersonalDataCategory, { label: LocalizedText; isSpecial: boolean }> = {
NAME: { label: { de: 'Name', en: 'Name' }, isSpecial: false },
CONTACT: { label: { de: 'Kontaktdaten', en: 'Contact Data' }, isSpecial: false },
ADDRESS: { label: { de: 'Adressdaten', en: 'Address Data' }, isSpecial: false },
DOB: { label: { de: 'Geburtsdatum', en: 'Date of Birth' }, isSpecial: false },
ID_NUMBER: { label: { de: 'Ausweisnummern', en: 'ID Numbers' }, isSpecial: false },
SOCIAL_SECURITY: { label: { de: 'Sozialversicherungsnummer', en: 'Social Security Number' }, isSpecial: false },
TAX_ID: { label: { de: 'Steuer-ID', en: 'Tax ID' }, isSpecial: false },
BANK_ACCOUNT: { label: { de: 'Bankverbindung', en: 'Bank Account' }, isSpecial: false },
PAYMENT_DATA: { label: { de: 'Zahlungsdaten', en: 'Payment Data' }, isSpecial: false },
EMPLOYMENT_DATA: { label: { de: 'Beschäftigungsdaten', en: 'Employment Data' }, isSpecial: false },
SALARY_DATA: { label: { de: 'Gehaltsdaten', en: 'Salary Data' }, isSpecial: false },
EDUCATION_DATA: { label: { de: 'Bildungsdaten', en: 'Education Data' }, isSpecial: false },
PHOTO_VIDEO: { label: { de: 'Fotos/Videos', en: 'Photos/Videos' }, isSpecial: false },
IP_ADDRESS: { label: { de: 'IP-Adressen', en: 'IP Addresses' }, isSpecial: false },
DEVICE_ID: { label: { de: 'Gerätekennungen', en: 'Device IDs' }, isSpecial: false },
LOCATION_DATA: { label: { de: 'Standortdaten', en: 'Location Data' }, isSpecial: false },
USAGE_DATA: { label: { de: 'Nutzungsdaten', en: 'Usage Data' }, isSpecial: false },
COMMUNICATION_DATA: { label: { de: 'Kommunikationsdaten', en: 'Communication Data' }, isSpecial: false },
CONTRACT_DATA: { label: { de: 'Vertragsdaten', en: 'Contract Data' }, isSpecial: false },
LOGIN_DATA: { label: { de: 'Login-Daten', en: 'Login Data' }, isSpecial: false },
HEALTH_DATA: { label: { de: 'Gesundheitsdaten', en: 'Health Data' }, isSpecial: true },
GENETIC_DATA: { label: { de: 'Genetische Daten', en: 'Genetic Data' }, isSpecial: true },
BIOMETRIC_DATA: { label: { de: 'Biometrische Daten', en: 'Biometric Data' }, isSpecial: true },
RACIAL_ETHNIC: { label: { de: 'Rassische/Ethnische Herkunft', en: 'Racial/Ethnic Origin' }, isSpecial: true },
POLITICAL_OPINIONS: { label: { de: 'Politische Meinungen', en: 'Political Opinions' }, isSpecial: true },
RELIGIOUS_BELIEFS: { label: { de: 'Religiöse Überzeugungen', en: 'Religious Beliefs' }, isSpecial: true },
TRADE_UNION: { label: { de: 'Gewerkschaftszugehörigkeit', en: 'Trade Union Membership' }, isSpecial: true },
SEX_LIFE: { label: { de: 'Sexualleben/Orientierung', en: 'Sex Life/Orientation' }, isSpecial: true },
CRIMINAL_DATA: { label: { de: 'Strafrechtliche Daten', en: 'Criminal Data' }, isSpecial: true },
OTHER: { label: { de: 'Sonstige', en: 'Other' }, isSpecial: false },
}
export const LEGAL_BASIS_META: Record<LegalBasisType, { label: LocalizedText; article: string }> = {
CONSENT: { label: { de: 'Einwilligung', en: 'Consent' }, article: 'Art. 6 Abs. 1 lit. a DSGVO' },
CONTRACT: { label: { de: 'Vertragserfüllung', en: 'Contract Performance' }, article: 'Art. 6 Abs. 1 lit. b DSGVO' },
LEGAL_OBLIGATION: { label: { de: 'Rechtliche Verpflichtung', en: 'Legal Obligation' }, article: 'Art. 6 Abs. 1 lit. c DSGVO' },
VITAL_INTEREST: { label: { de: 'Lebenswichtige Interessen', en: 'Vital Interests' }, article: 'Art. 6 Abs. 1 lit. d DSGVO' },
PUBLIC_TASK: { label: { de: 'Öffentliche Aufgabe', en: 'Public Task' }, article: 'Art. 6 Abs. 1 lit. e DSGVO' },
LEGITIMATE_INTEREST: { label: { de: 'Berechtigtes Interesse', en: 'Legitimate Interest' }, article: 'Art. 6 Abs. 1 lit. f DSGVO' },
ART9_CONSENT: { label: { de: 'Ausdrückliche Einwilligung', en: 'Explicit Consent' }, article: 'Art. 9 Abs. 2 lit. a DSGVO' },
ART9_EMPLOYMENT: { label: { de: 'Arbeitsrecht', en: 'Employment Law' }, article: 'Art. 9 Abs. 2 lit. b DSGVO' },
ART9_VITAL_INTEREST: { label: { de: 'Lebenswichtige Interessen', en: 'Vital Interests' }, article: 'Art. 9 Abs. 2 lit. c DSGVO' },
ART9_FOUNDATION: { label: { de: 'Stiftung/Verein', en: 'Foundation/Association' }, article: 'Art. 9 Abs. 2 lit. d DSGVO' },
ART9_PUBLIC: { label: { de: 'Offenkundig öffentlich', en: 'Manifestly Public' }, article: 'Art. 9 Abs. 2 lit. e DSGVO' },
ART9_LEGAL_CLAIMS: { label: { de: 'Rechtsansprüche', en: 'Legal Claims' }, article: 'Art. 9 Abs. 2 lit. f DSGVO' },
ART9_PUBLIC_INTEREST: { label: { de: 'Öffentliches Interesse', en: 'Public Interest' }, article: 'Art. 9 Abs. 2 lit. g DSGVO' },
ART9_HEALTH: { label: { de: 'Gesundheitsversorgung', en: 'Health Care' }, article: 'Art. 9 Abs. 2 lit. h DSGVO' },
ART9_PUBLIC_HEALTH: { label: { de: 'Öffentliche Gesundheit', en: 'Public Health' }, article: 'Art. 9 Abs. 2 lit. i DSGVO' },
ART9_ARCHIVING: { label: { de: 'Archivzwecke', en: 'Archiving Purposes' }, article: 'Art. 9 Abs. 2 lit. j DSGVO' },
}
export const VENDOR_ROLE_META: Record<VendorRole, LocalizedText> = {
PROCESSOR: { de: 'Auftragsverarbeiter', en: 'Processor' },
JOINT_CONTROLLER: { de: 'Gemeinsam Verantwortlicher', en: 'Joint Controller' },
CONTROLLER: { de: 'Eigenständiger Verantwortlicher', en: 'Independent Controller' },
SUB_PROCESSOR: { de: 'Unterauftragnehmer', en: 'Sub-Processor' },
THIRD_PARTY: { de: 'Dritter', en: 'Third Party' },
}
export const SERVICE_CATEGORY_META: Record<ServiceCategory, LocalizedText> = {
HOSTING: { de: 'Hosting', en: 'Hosting' },
CLOUD_INFRASTRUCTURE: { de: 'Cloud-Infrastruktur', en: 'Cloud Infrastructure' },
ANALYTICS: { de: 'Analytics', en: 'Analytics' },
CRM: { de: 'CRM', en: 'CRM' },
ERP: { de: 'ERP', en: 'ERP' },
HR_SOFTWARE: { de: 'HR-Software', en: 'HR Software' },
PAYMENT: { de: 'Zahlungsabwicklung', en: 'Payment Processing' },
EMAIL: { de: 'E-Mail', en: 'Email' },
MARKETING: { de: 'Marketing', en: 'Marketing' },
SUPPORT: { de: 'Support', en: 'Support' },
SECURITY: { de: 'Sicherheit', en: 'Security' },
INTEGRATION: { de: 'Integration', en: 'Integration' },
CONSULTING: { de: 'Beratung', en: 'Consulting' },
LEGAL: { de: 'Rechtliches', en: 'Legal' },
ACCOUNTING: { de: 'Buchhaltung', en: 'Accounting' },
COMMUNICATION: { de: 'Kommunikation', en: 'Communication' },
STORAGE: { de: 'Speicher', en: 'Storage' },
BACKUP: { de: 'Backup', en: 'Backup' },
CDN: { de: 'CDN', en: 'CDN' },
OTHER: { de: 'Sonstige', en: 'Other' },
}
export const DOCUMENT_TYPE_META: Record<DocumentType, LocalizedText> = {
AVV: { de: 'Auftragsverarbeitungsvertrag', en: 'Data Processing Agreement' },
MSA: { de: 'Rahmenvertrag', en: 'Master Service Agreement' },
SLA: { de: 'Service Level Agreement', en: 'Service Level Agreement' },
SCC: { de: 'Standardvertragsklauseln', en: 'Standard Contractual Clauses' },
NDA: { de: 'Geheimhaltungsvereinbarung', en: 'Non-Disclosure Agreement' },
TOM_ANNEX: { de: 'TOM-Anlage', en: 'TOM Annex' },
CERTIFICATION: { de: 'Zertifikat', en: 'Certification' },
SUB_PROCESSOR_LIST: { de: 'Unterauftragnehmer-Liste', en: 'Sub-Processor List' },
OTHER: { de: 'Sonstige', en: 'Other' },
}
export const TRANSFER_MECHANISM_META: Record<TransferMechanismType, LocalizedText> = {
ADEQUACY_DECISION: { de: 'Angemessenheitsbeschluss', en: 'Adequacy Decision' },
SCC_CONTROLLER: { de: 'SCC (Controller-to-Controller)', en: 'SCC (Controller-to-Controller)' },
SCC_PROCESSOR: { de: 'SCC (Controller-to-Processor)', en: 'SCC (Controller-to-Processor)' },
BCR: { de: 'Binding Corporate Rules', en: 'Binding Corporate Rules' },
DEROGATION_CONSENT: { de: 'Ausdrückliche Einwilligung', en: 'Explicit Consent' },
DEROGATION_CONTRACT: { de: 'Vertragserfüllung', en: 'Contract Performance' },
DEROGATION_LEGAL: { de: 'Rechtsansprüche', en: 'Legal Claims' },
DEROGATION_PUBLIC: { de: 'Öffentliches Interesse', en: 'Public Interest' },
CERTIFICATION: { de: 'Zertifizierung', en: 'Certification' },
CODE_OF_CONDUCT: { de: 'Verhaltensregeln', en: 'Code of Conduct' },
}

View File

@@ -0,0 +1,213 @@
/**
* Processing Activity Types (VVT / RoPA)
*
* Types for Art. 30 DSGVO processing activities:
* - Enums for data categories, legal bases, transfer mechanisms
* - Interfaces for processing activities and related structures
*/
import type {
LocalizedText,
Contact,
ResponsibleParty,
} from './types-common'
// ==========================================
// ENUMS - VVT / PROCESSING ACTIVITIES
// ==========================================
export type ProcessingActivityStatus = 'DRAFT' | 'REVIEW' | 'APPROVED' | 'ARCHIVED'
export type ProtectionLevel = 'LOW' | 'MEDIUM' | 'HIGH'
export type DataSubjectCategory =
| 'EMPLOYEES' // Beschaeftigte
| 'APPLICANTS' // Bewerber
| 'CUSTOMERS' // Kunden
| 'PROSPECTIVE_CUSTOMERS' // Interessenten
| 'SUPPLIERS' // Lieferanten
| 'BUSINESS_PARTNERS' // Geschaeftspartner
| 'VISITORS' // Besucher
| 'WEBSITE_USERS' // Website-Nutzer
| 'APP_USERS' // App-Nutzer
| 'NEWSLETTER_SUBSCRIBERS' // Newsletter-Abonnenten
| 'MEMBERS' // Mitglieder
| 'PATIENTS' // Patienten
| 'STUDENTS' // Schueler/Studenten
| 'MINORS' // Minderjaehrige
| 'OTHER'
export type PersonalDataCategory =
| 'NAME' // Name
| 'CONTACT' // Kontaktdaten
| 'ADDRESS' // Adressdaten
| 'DOB' // Geburtsdatum
| 'ID_NUMBER' // Ausweisnummern
| 'SOCIAL_SECURITY' // Sozialversicherungsnummer
| 'TAX_ID' // Steuer-ID
| 'BANK_ACCOUNT' // Bankverbindung
| 'PAYMENT_DATA' // Zahlungsdaten
| 'EMPLOYMENT_DATA' // Beschaeftigungsdaten
| 'SALARY_DATA' // Gehaltsdaten
| 'EDUCATION_DATA' // Bildungsdaten
| 'PHOTO_VIDEO' // Fotos/Videos
| 'IP_ADDRESS' // IP-Adressen
| 'DEVICE_ID' // Geraete-Kennungen
| 'LOCATION_DATA' // Standortdaten
| 'USAGE_DATA' // Nutzungsdaten
| 'COMMUNICATION_DATA' // Kommunikationsdaten
| 'CONTRACT_DATA' // Vertragsdaten
| 'LOGIN_DATA' // Login-Daten
// Besondere Kategorien Art. 9 DSGVO
| 'HEALTH_DATA' // Gesundheitsdaten
| 'GENETIC_DATA' // Genetische Daten
| 'BIOMETRIC_DATA' // Biometrische Daten
| 'RACIAL_ETHNIC' // Rassische/Ethnische Herkunft
| 'POLITICAL_OPINIONS' // Politische Meinungen
| 'RELIGIOUS_BELIEFS' // Religiose Ueberzeugungen
| 'TRADE_UNION' // Gewerkschaftszugehoerigkeit
| 'SEX_LIFE' // Sexualleben/Orientierung
// Art. 10 DSGVO
| 'CRIMINAL_DATA' // Strafrechtliche Daten
| 'OTHER'
export type RecipientCategoryType =
| 'INTERNAL' // Interne Stellen
| 'GROUP_COMPANY' // Konzernunternehmen
| 'PROCESSOR' // Auftragsverarbeiter
| 'CONTROLLER' // Verantwortlicher
| 'AUTHORITY' // Behoerden
| 'OTHER'
export type LegalBasisType =
// Art. 6 Abs. 1 DSGVO
| 'CONSENT' // lit. a - Einwilligung
| 'CONTRACT' // lit. b - Vertragsdurchfuehrung
| 'LEGAL_OBLIGATION' // lit. c - Rechtliche Verpflichtung
| 'VITAL_INTEREST' // lit. d - Lebenswichtige Interessen
| 'PUBLIC_TASK' // lit. e - Oeffentliche Aufgabe
| 'LEGITIMATE_INTEREST' // lit. f - Berechtigtes Interesse
// Art. 9 Abs. 2 DSGVO (besondere Kategorien)
| 'ART9_CONSENT' // lit. a - Ausdrueckliche Einwilligung
| 'ART9_EMPLOYMENT' // lit. b - Arbeitsrecht
| 'ART9_VITAL_INTEREST' // lit. c - Lebenswichtige Interessen
| 'ART9_FOUNDATION' // lit. d - Stiftung/Verein
| 'ART9_PUBLIC' // lit. e - Offenkundig oeffentlich
| 'ART9_LEGAL_CLAIMS' // lit. f - Rechtsansprueche
| 'ART9_PUBLIC_INTEREST'// lit. g - Oeffentliches Interesse
| 'ART9_HEALTH' // lit. h - Gesundheitsversorgung
| 'ART9_PUBLIC_HEALTH' // lit. i - Oeffentliche Gesundheit
| 'ART9_ARCHIVING' // lit. j - Archivzwecke
export type TransferMechanismType =
| 'ADEQUACY_DECISION' // Angemessenheitsbeschluss
| 'SCC_CONTROLLER' // SCC Controller-to-Controller
| 'SCC_PROCESSOR' // SCC Controller-to-Processor
| 'BCR' // Binding Corporate Rules
| 'DEROGATION_CONSENT' // Ausdrueckliche Einwilligung
| 'DEROGATION_CONTRACT' // Vertragserfuellung
| 'DEROGATION_LEGAL' // Rechtsansprueche
| 'DEROGATION_PUBLIC' // Oeffentliches Interesse
| 'CERTIFICATION' // Zertifizierung
| 'CODE_OF_CONDUCT' // Verhaltensregeln
export type DataSourceType =
| 'DATA_SUBJECT' // Betroffene Person selbst
| 'THIRD_PARTY' // Dritte
| 'PUBLIC_SOURCE' // Oeffentliche Quellen
| 'AUTOMATED' // Automatisiert generiert
| 'EMPLOYEE' // Mitarbeiter
| 'OTHER'
// ==========================================
// INTERFACES - VVT / PROCESSING ACTIVITIES
// ==========================================
export interface LegalBasis {
type: LegalBasisType
description?: string
reference?: string // z.B. "§ 26 BDSG"
}
export interface RecipientCategory {
type: RecipientCategoryType
name: string
description?: string
isThirdCountry?: boolean
country?: string
}
export interface ThirdCountryTransfer {
country: string // ISO 3166-1 alpha-2
recipient: string
transferMechanism: TransferMechanismType
sccVersion?: string
tiaCompleted?: boolean
tiaDate?: Date
additionalMeasures?: string[]
}
export interface RetentionPeriod {
duration?: number // in Monaten
durationUnit?: 'DAYS' | 'MONTHS' | 'YEARS'
description: LocalizedText
legalBasis?: string // z.B. "HGB § 257", "AO § 147"
deletionProcedure?: string
}
export interface DataSource {
type: DataSourceType
description?: string
}
export interface SystemReference {
systemId: string
name: string
description?: string
type?: string // CRM, ERP, etc.
}
export interface DataFlow {
sourceSystem?: string
targetSystem?: string
description: string
dataCategories: PersonalDataCategory[]
}
export interface ProcessingActivity {
id: string
tenantId: string
// Pflichtfelder Art. 30(1) DSGVO
vvtId: string // Eindeutige VVT-Nummer (z.B. VVT-2024-001)
name: LocalizedText
responsible: ResponsibleParty
dpoContact?: Contact
purposes: LocalizedText[]
dataSubjectCategories: DataSubjectCategory[]
personalDataCategories: PersonalDataCategory[]
recipientCategories: RecipientCategory[]
thirdCountryTransfers: ThirdCountryTransfer[]
retentionPeriod: RetentionPeriod
technicalMeasures: string[] // TOM-Referenzen
// Empfohlene Zusatzfelder
legalBasis: LegalBasis[]
dataSources: DataSource[]
systems: SystemReference[]
dataFlows: DataFlow[]
protectionLevel: ProtectionLevel
dpiaRequired: boolean
dpiaJustification?: string
subProcessors: string[] // Vendor-IDs
legalRetentionBasis?: string
// Workflow
status: ProcessingActivityStatus
owner: string
lastReviewDate?: Date
nextReviewDate?: Date
createdAt: Date
updatedAt: Date
}

View File

@@ -0,0 +1,147 @@
/**
* Risk & Controls Types
*
* Types for risk assessment and control management:
* - Risk scoring (likelihood x impact matrix)
* - Control definitions and instances
* - Evidence tracking
*/
import type { LocalizedText } from './types-common'
import type { ReviewFrequency } from './types-vendor'
// ==========================================
// ENUMS - RISK & CONTROLS
// ==========================================
export type ControlDomain =
| 'TRANSFER' // Drittlandtransfer
| 'AUDIT' // Auditrechte
| 'DELETION' // Loeschung
| 'INCIDENT' // Incident Response
| 'SUBPROCESSOR' // Unterauftragnehmer
| 'TOM' // Tech/Org Massnahmen
| 'CONTRACT' // Vertragliche Grundlagen
| 'DATA_SUBJECT' // Betroffenenrechte
| 'SECURITY' // Sicherheit
| 'GOVERNANCE' // Governance
export type ControlStatus =
| 'PASS'
| 'PARTIAL'
| 'FAIL'
| 'NOT_APPLICABLE'
| 'PLANNED'
export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
export type EntityType = 'VENDOR' | 'PROCESSING_ACTIVITY' | 'CONTRACT'
export type EvidenceType = 'DOCUMENT' | 'SCREENSHOT' | 'LINK' | 'ATTESTATION'
// ==========================================
// INTERFACES - RISK & CONTROLS
// ==========================================
export interface RiskFactor {
id: string
name: LocalizedText
category: string
weight: number
value: number // 1-5
rationale?: string
}
export interface RiskScore {
likelihood: 1 | 2 | 3 | 4 | 5
impact: 1 | 2 | 3 | 4 | 5
score: number // likelihood * impact (1-25)
level: RiskLevel
rationale: string
}
export interface RiskAssessment {
id: string
tenantId: string
entityType: EntityType
entityId: string
// Bewertung
inherentRisk: RiskScore
residualRisk: RiskScore
// Faktoren
riskFactors: RiskFactor[]
mitigatingControls: string[] // Control-IDs
// Workflow
assessedBy: string
assessedAt: Date
approvedBy?: string
approvedAt?: Date
nextAssessmentDate: Date
}
export interface Control {
id: string // z.B. VND-TRF-01
domain: ControlDomain
title: LocalizedText
description: LocalizedText
passCriteria: LocalizedText
// Mapping
requirements: string[] // Art. 28 Abs. 3 lit. a, ISO 27001 A.15.1.2
// Standard
isRequired: boolean
defaultFrequency: ReviewFrequency
}
export interface ControlInstance {
id: string
tenantId: string
controlId: string
entityType: EntityType
entityId: string
// Status
status: ControlStatus
// Evidenz
evidenceIds: string[]
// Workflow
lastAssessedAt: Date
lastAssessedBy: string
nextAssessmentDate: Date
notes?: string
}
export interface Evidence {
id: string
tenantId: string
controlInstanceId: string
type: EvidenceType
title: string
description?: string
// Fuer Dokumente
storagePath?: string
fileName?: string
// Fuer Links
url?: string
// Fuer Attestation
attestedBy?: string
attestedAt?: Date
validFrom: Date
validUntil?: Date
createdAt: Date
}

View File

@@ -0,0 +1,263 @@
/**
* State Management Types
*
* Types for React context state management:
* - Reducer actions (VendorComplianceAction)
* - State shape (VendorComplianceState)
* - Context value with computed properties and action methods
* - Statistics and API response types
* - Form data types
*/
import type React from 'react'
import type { LocalizedText, Contact, ResponsibleParty, Address } from './types-common'
import type {
ProcessingActivity,
ProcessingActivityStatus,
DataSubjectCategory,
PersonalDataCategory,
RecipientCategory,
ThirdCountryTransfer,
RetentionPeriod,
LegalBasis,
DataSource,
SystemReference,
DataFlow,
ProtectionLevel,
TransferMechanismType,
} from './types-processing'
import type {
Vendor,
VendorStatus,
VendorRole,
ServiceCategory,
DataAccessLevel,
ProcessingLocation,
Certification,
ReviewFrequency,
} from './types-vendor'
import type {
ContractDocument,
ContractStatus,
DocumentType,
} from './types-contract'
import type {
Finding,
FindingType,
FindingSeverity,
} from './types-finding'
import type {
Control,
ControlInstance,
RiskAssessment,
RiskLevel,
} from './types-risk'
import type { ExportFormat } from './types-audit'
// ==========================================
// STATE MANAGEMENT - ACTIONS
// ==========================================
export type VendorComplianceAction =
// Processing Activities
| { type: 'SET_PROCESSING_ACTIVITIES'; payload: ProcessingActivity[] }
| { type: 'ADD_PROCESSING_ACTIVITY'; payload: ProcessingActivity }
| { type: 'UPDATE_PROCESSING_ACTIVITY'; payload: { id: string; data: Partial<ProcessingActivity> } }
| { type: 'DELETE_PROCESSING_ACTIVITY'; payload: string }
// Vendors
| { type: 'SET_VENDORS'; payload: Vendor[] }
| { type: 'ADD_VENDOR'; payload: Vendor }
| { type: 'UPDATE_VENDOR'; payload: { id: string; data: Partial<Vendor> } }
| { type: 'DELETE_VENDOR'; payload: string }
// Contracts
| { type: 'SET_CONTRACTS'; payload: ContractDocument[] }
| { type: 'ADD_CONTRACT'; payload: ContractDocument }
| { type: 'UPDATE_CONTRACT'; payload: { id: string; data: Partial<ContractDocument> } }
| { type: 'DELETE_CONTRACT'; payload: string }
// Findings
| { type: 'SET_FINDINGS'; payload: Finding[] }
| { type: 'ADD_FINDINGS'; payload: Finding[] }
| { type: 'UPDATE_FINDING'; payload: { id: string; data: Partial<Finding> } }
// Controls
| { type: 'SET_CONTROLS'; payload: Control[] }
| { type: 'SET_CONTROL_INSTANCES'; payload: ControlInstance[] }
| { type: 'UPDATE_CONTROL_INSTANCE'; payload: { id: string; data: Partial<ControlInstance> } }
// Risk Assessments
| { type: 'SET_RISK_ASSESSMENTS'; payload: RiskAssessment[] }
| { type: 'UPDATE_RISK_ASSESSMENT'; payload: { id: string; data: Partial<RiskAssessment> } }
// UI State
| { type: 'SET_LOADING'; payload: boolean }
| { type: 'SET_ERROR'; payload: string | null }
| { type: 'SET_SELECTED_VENDOR'; payload: string | null }
| { type: 'SET_SELECTED_ACTIVITY'; payload: string | null }
| { type: 'SET_ACTIVE_TAB'; payload: string }
// ==========================================
// STATE MANAGEMENT - STATE
// ==========================================
export interface VendorComplianceState {
// Data
processingActivities: ProcessingActivity[]
vendors: Vendor[]
contracts: ContractDocument[]
findings: Finding[]
controls: Control[]
controlInstances: ControlInstance[]
riskAssessments: RiskAssessment[]
// UI State
isLoading: boolean
error: string | null
selectedVendorId: string | null
selectedActivityId: string | null
activeTab: string
// Metadata
lastModified: Date | null
}
// ==========================================
// STATISTICS INTERFACES
// ==========================================
export interface VendorStatistics {
total: number
byStatus: Record<VendorStatus, number>
byRole: Record<VendorRole, number>
byRiskLevel: Record<RiskLevel, number>
pendingReviews: number
withExpiredContracts: number
}
export interface ComplianceStatistics {
averageComplianceScore: number
findingsByType: Record<FindingType, number>
findingsBySeverity: Record<FindingSeverity, number>
openFindings: number
resolvedFindings: number
controlPassRate: number
}
export interface RiskOverview {
averageInherentRisk: number
averageResidualRisk: number
highRiskVendors: number
criticalFindings: number
transfersToThirdCountries: number
}
// ==========================================
// CONTEXT VALUE
// ==========================================
export interface VendorComplianceContextValue extends VendorComplianceState {
// Dispatch
dispatch: React.Dispatch<VendorComplianceAction>
// Computed
vendorStats: VendorStatistics
complianceStats: ComplianceStatistics
riskOverview: RiskOverview
// Actions - Processing Activities
createProcessingActivity: (data: Omit<ProcessingActivity, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>) => Promise<ProcessingActivity>
updateProcessingActivity: (id: string, data: Partial<ProcessingActivity>) => Promise<void>
deleteProcessingActivity: (id: string) => Promise<void>
duplicateProcessingActivity: (id: string) => Promise<ProcessingActivity>
// Actions - Vendors
createVendor: (data: Omit<Vendor, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>) => Promise<Vendor>
updateVendor: (id: string, data: Partial<Vendor>) => Promise<void>
deleteVendor: (id: string) => Promise<void>
// Actions - Contracts
uploadContract: (vendorId: string, file: File, metadata: Partial<ContractDocument>) => Promise<ContractDocument>
updateContract: (id: string, data: Partial<ContractDocument>) => Promise<void>
deleteContract: (id: string) => Promise<void>
startContractReview: (contractId: string) => Promise<void>
// Actions - Findings
updateFinding: (id: string, data: Partial<Finding>) => Promise<void>
resolveFinding: (id: string, resolution: string) => Promise<void>
// Actions - Controls
updateControlInstance: (id: string, data: Partial<ControlInstance>) => Promise<void>
// Actions - Export
exportVVT: (format: ExportFormat, activityIds?: string[]) => Promise<string>
exportVendorAuditPack: (vendorId: string, format: ExportFormat) => Promise<string>
exportRoPA: (format: ExportFormat) => Promise<string>
// Data Loading
loadData: () => Promise<void>
refresh: () => Promise<void>
}
// ==========================================
// API RESPONSE TYPES
// ==========================================
export interface ApiResponse<T> {
success: boolean
data?: T
error?: string
timestamp: string
}
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
pagination: {
page: number
pageSize: number
total: number
totalPages: number
}
}
// ==========================================
// FORM TYPES
// ==========================================
export interface ProcessingActivityFormData {
vvtId: string
name: LocalizedText
responsible: ResponsibleParty
dpoContact?: Contact
purposes: LocalizedText[]
dataSubjectCategories: DataSubjectCategory[]
personalDataCategories: PersonalDataCategory[]
recipientCategories: RecipientCategory[]
thirdCountryTransfers: ThirdCountryTransfer[]
retentionPeriod: RetentionPeriod
technicalMeasures: string[]
legalBasis: LegalBasis[]
dataSources: DataSource[]
systems: SystemReference[]
dataFlows: DataFlow[]
protectionLevel: ProtectionLevel
dpiaRequired: boolean
dpiaJustification?: string
subProcessors: string[]
owner: string
}
export interface VendorFormData {
name: string
legalForm?: string
country: string
address: Address
website?: string
role: VendorRole
serviceDescription: string
serviceCategory: ServiceCategory
dataAccessLevel: DataAccessLevel
processingLocations: ProcessingLocation[]
transferMechanisms: TransferMechanismType[]
certifications: Certification[]
primaryContact: Contact
dpoContact?: Contact
securityContact?: Contact
contractTypes: DocumentType[]
reviewFrequency: ReviewFrequency
notes?: string
}

View File

@@ -0,0 +1,146 @@
/**
* Vendor Types
*
* Types for vendor/supplier management:
* - Vendor roles, statuses, service categories
* - Processing locations, certifications
* - Vendor interface with risk scoring
*/
import type { Address, Contact } from './types-common'
import type { DocumentType } from './types-contract'
import type { TransferMechanismType } from './types-processing'
// ==========================================
// ENUMS - VENDOR
// ==========================================
export type VendorRole =
| 'PROCESSOR' // Auftragsverarbeiter
| 'JOINT_CONTROLLER' // Gemeinsam Verantwortlicher
| 'CONTROLLER' // Eigenstaendiger Verantwortlicher
| 'SUB_PROCESSOR' // Unterauftragnehmer
| 'THIRD_PARTY' // Dritter (kein Datenzugriff)
export type ServiceCategory =
| 'HOSTING'
| 'CLOUD_INFRASTRUCTURE'
| 'ANALYTICS'
| 'CRM'
| 'ERP'
| 'HR_SOFTWARE'
| 'PAYMENT'
| 'EMAIL'
| 'MARKETING'
| 'SUPPORT'
| 'SECURITY'
| 'INTEGRATION'
| 'CONSULTING'
| 'LEGAL'
| 'ACCOUNTING'
| 'COMMUNICATION'
| 'STORAGE'
| 'BACKUP'
| 'CDN'
| 'OTHER'
export type DataAccessLevel =
| 'NONE' // Kein Datenzugriff
| 'POTENTIAL' // Potenzieller Zugriff (z.B. Admin)
| 'ADMINISTRATIVE' // Administrativer Zugriff
| 'CONTENT' // Inhaltlicher Zugriff
export type VendorStatus =
| 'ACTIVE'
| 'INACTIVE'
| 'PENDING_REVIEW'
| 'TERMINATED'
export type ReviewFrequency =
| 'QUARTERLY'
| 'SEMI_ANNUAL'
| 'ANNUAL'
| 'BIENNIAL'
// ==========================================
// INTERFACES - VENDOR
// ==========================================
export interface ProcessingLocation {
country: string // ISO 3166-1 alpha-2
region?: string
city?: string
dataCenter?: string
isEU: boolean
isAdequate: boolean // Angemessenheitsbeschluss
type?: string // e.g., 'primary', 'backup', 'disaster-recovery'
description?: string
isPrimary?: boolean
}
export interface Certification {
type: string // ISO 27001, SOC2, TISAX, C5, etc.
issuer?: string
issuedDate?: Date
expirationDate?: Date
scope?: string
certificateNumber?: string
documentId?: string // Referenz zum hochgeladenen Zertifikat
}
export interface Vendor {
id: string
tenantId: string
// Stammdaten
name: string
legalForm?: string
country: string
address: Address
website?: string
// Rolle
role: VendorRole
serviceDescription: string
serviceCategory: ServiceCategory
// Datenzugriff
dataAccessLevel: DataAccessLevel
processingLocations: ProcessingLocation[]
transferMechanisms: TransferMechanismType[]
// Zertifizierungen
certifications: Certification[]
// Kontakte
primaryContact: Contact
dpoContact?: Contact
securityContact?: Contact
// Vertraege
contractTypes: DocumentType[]
contracts: string[] // Contract-IDs
// Risiko
inherentRiskScore: number // 0-100 (auto-berechnet)
residualRiskScore: number // 0-100 (nach Controls)
manualRiskAdjustment?: number
riskJustification?: string
// Review
reviewFrequency: ReviewFrequency
lastReviewDate?: Date
nextReviewDate?: Date
// Workflow
status: VendorStatus
// Linked Processing Activities
processingActivityIds: string[]
// Notes
notes?: string
createdAt: Date
updatedAt: Date
}

File diff suppressed because it is too large Load Diff