fix(admin-v2): Restore complete admin-v2 application

The admin-v2 application was incomplete in the repository. This commit
restores all missing components:

- Admin pages (76 pages): dashboard, ai, compliance, dsgvo, education,
  infrastructure, communication, development, onboarding, rbac
- SDK pages (45 pages): tom, dsfa, vvt, loeschfristen, einwilligungen,
  vendor-compliance, tom-generator, dsr, and more
- Developer portal (25 pages): API docs, SDK guides, frameworks
- All components, lib files, hooks, and types
- Updated package.json with all dependencies

The issue was caused by incomplete initial repository state - the full
admin-v2 codebase existed in backend/admin-v2 and docs-src/admin-v2
but was never fully synced to the main admin-v2 directory.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
BreakPilot Dev
2026-02-08 23:40:15 -08:00
parent f28244753f
commit 660295e218
385 changed files with 138126 additions and 3079 deletions

View File

@@ -0,0 +1,324 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { exportToPDF, exportToZIP, downloadExport } from '../export'
import type { SDKState } from '../types'
// Mock jsPDF as a class
vi.mock('jspdf', () => {
return {
default: class MockJsPDF {
internal = {
pageSize: { getWidth: () => 210, getHeight: () => 297 },
}
setFillColor = vi.fn().mockReturnThis()
setDrawColor = vi.fn().mockReturnThis()
setTextColor = vi.fn().mockReturnThis()
setFontSize = vi.fn().mockReturnThis()
setFont = vi.fn().mockReturnThis()
setLineWidth = vi.fn().mockReturnThis()
text = vi.fn().mockReturnThis()
line = vi.fn().mockReturnThis()
rect = vi.fn().mockReturnThis()
roundedRect = vi.fn().mockReturnThis()
circle = vi.fn().mockReturnThis()
addPage = vi.fn().mockReturnThis()
setPage = vi.fn().mockReturnThis()
getNumberOfPages = vi.fn(() => 5)
splitTextToSize = vi.fn((text: string) => [text])
output = vi.fn(() => new Blob(['mock-pdf'], { type: 'application/pdf' }))
},
}
})
// Mock JSZip as a class
vi.mock('jszip', () => {
return {
default: class MockJSZip {
private mockFolder = {
file: vi.fn().mockReturnThis(),
folder: vi.fn(() => this.mockFolder),
}
folder = vi.fn(() => this.mockFolder)
generateAsync = vi.fn(() => Promise.resolve(new Blob(['mock-zip'], { type: 'application/zip' })))
},
}
})
const createMockState = (overrides: Partial<SDKState> = {}): SDKState => ({
version: '1.0.0',
lastModified: new Date('2024-01-15'),
tenantId: 'test-tenant',
userId: 'test-user',
subscription: 'PROFESSIONAL',
currentPhase: 1,
currentStep: 'use-case-workshop',
completedSteps: ['use-case-workshop', 'screening'],
checkpoints: {
'CP-UC': {
checkpointId: 'CP-UC',
passed: true,
validatedAt: new Date(),
validatedBy: 'SYSTEM',
errors: [],
warnings: [],
},
},
useCases: [
{
id: 'uc-1',
name: 'Test Use Case',
description: 'A test use case for testing',
category: 'Marketing',
stepsCompleted: 3,
steps: [
{ id: 's1', name: 'Step 1', completed: true, data: {} },
{ id: 's2', name: 'Step 2', completed: true, data: {} },
{ id: 's3', name: 'Step 3', completed: true, data: {} },
],
assessmentResult: null,
createdAt: new Date(),
updatedAt: new Date(),
},
],
activeUseCase: 'uc-1',
screening: null,
modules: [],
requirements: [],
controls: [
{
id: 'ctrl-1',
name: 'Test Control',
description: 'A test control',
type: 'TECHNICAL',
category: 'Access Control',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'HIGH',
evidence: [],
owner: 'Test Owner',
dueDate: null,
},
],
evidence: [],
checklist: [],
risks: [
{
id: 'risk-1',
title: 'Test Risk',
description: 'A test risk',
category: 'Security',
likelihood: 3,
impact: 4,
severity: 'HIGH',
inherentRiskScore: 12,
residualRiskScore: 6,
status: 'MITIGATED',
mitigation: [
{
id: 'mit-1',
description: 'Test mitigation',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 80,
controlId: 'ctrl-1',
},
],
owner: 'Risk Owner',
relatedControls: ['ctrl-1'],
relatedRequirements: [],
},
],
aiActClassification: null,
obligations: [],
dsfa: null,
toms: [],
retentionPolicies: [],
vvt: [],
documents: [],
cookieBanner: null,
consents: [],
dsrConfig: null,
escalationWorkflows: [],
sbom: null,
securityIssues: [],
securityBacklog: [],
commandBarHistory: [],
recentSearches: [],
preferences: {
language: 'de',
theme: 'light',
compactMode: false,
showHints: true,
autoSave: true,
autoValidate: true,
allowParallelWork: true,
},
...overrides,
})
describe('exportToPDF', () => {
it('should return a Blob', async () => {
const state = createMockState()
const result = await exportToPDF(state)
expect(result).toBeInstanceOf(Blob)
})
it('should create a PDF with the correct type', async () => {
const state = createMockState()
const result = await exportToPDF(state)
expect(result.type).toBe('application/pdf')
})
it('should handle empty state', async () => {
const emptyState = createMockState({
useCases: [],
risks: [],
controls: [],
completedSteps: [],
})
const result = await exportToPDF(emptyState)
expect(result).toBeInstanceOf(Blob)
})
it('should handle state with multiple risks of different severities', async () => {
const state = createMockState({
risks: [
{
id: 'risk-1',
title: 'Critical Risk',
description: 'Critical',
category: 'Security',
likelihood: 5,
impact: 5,
severity: 'CRITICAL',
inherentRiskScore: 25,
residualRiskScore: 15,
status: 'IDENTIFIED',
mitigation: [],
owner: null,
relatedControls: [],
relatedRequirements: [],
},
{
id: 'risk-2',
title: 'Low Risk',
description: 'Low',
category: 'Operational',
likelihood: 1,
impact: 1,
severity: 'LOW',
inherentRiskScore: 1,
residualRiskScore: 1,
status: 'ACCEPTED',
mitigation: [],
owner: null,
relatedControls: [],
relatedRequirements: [],
},
],
})
const result = await exportToPDF(state)
expect(result).toBeInstanceOf(Blob)
})
})
describe('exportToZIP', () => {
it('should return a Blob', async () => {
const state = createMockState()
const result = await exportToZIP(state)
expect(result).toBeInstanceOf(Blob)
})
it('should create a ZIP with the correct type', async () => {
const state = createMockState()
const result = await exportToZIP(state)
expect(result.type).toBe('application/zip')
})
it('should handle empty state', async () => {
const emptyState = createMockState({
useCases: [],
risks: [],
controls: [],
completedSteps: [],
})
const result = await exportToZIP(emptyState)
expect(result).toBeInstanceOf(Blob)
})
it('should respect includeEvidence option', async () => {
const state = createMockState()
const result = await exportToZIP(state, { includeEvidence: false })
expect(result).toBeInstanceOf(Blob)
})
it('should respect includeDocuments option', async () => {
const state = createMockState()
const result = await exportToZIP(state, { includeDocuments: false })
expect(result).toBeInstanceOf(Blob)
})
})
describe('downloadExport', () => {
let mockCreateElement: ReturnType<typeof vi.spyOn>
let mockAppendChild: ReturnType<typeof vi.spyOn>
let mockRemoveChild: ReturnType<typeof vi.spyOn>
let mockLink: { href: string; download: string; click: ReturnType<typeof vi.fn> }
beforeEach(() => {
mockLink = {
href: '',
download: '',
click: vi.fn(),
}
mockCreateElement = vi.spyOn(document, 'createElement').mockReturnValue(mockLink as unknown as HTMLElement)
mockAppendChild = vi.spyOn(document.body, 'appendChild').mockImplementation(() => mockLink as unknown as HTMLElement)
mockRemoveChild = vi.spyOn(document.body, 'removeChild').mockImplementation(() => mockLink as unknown as HTMLElement)
})
it('should download JSON format', async () => {
const state = createMockState()
await downloadExport(state, 'json')
expect(mockLink.download).toContain('.json')
expect(mockLink.click).toHaveBeenCalled()
})
it('should download PDF format', async () => {
const state = createMockState()
await downloadExport(state, 'pdf')
expect(mockLink.download).toContain('.pdf')
expect(mockLink.click).toHaveBeenCalled()
})
it('should download ZIP format', async () => {
const state = createMockState()
await downloadExport(state, 'zip')
expect(mockLink.download).toContain('.zip')
expect(mockLink.click).toHaveBeenCalled()
})
it('should include date in filename', async () => {
const state = createMockState()
await downloadExport(state, 'json')
// Check that filename contains a date pattern
expect(mockLink.download).toMatch(/ai-compliance-sdk-\d{4}-\d{2}-\d{2}\.json/)
})
it('should throw error for unknown format', async () => {
const state = createMockState()
await expect(downloadExport(state, 'unknown' as any)).rejects.toThrow('Unknown export format')
})
})

View File

@@ -0,0 +1,250 @@
import { describe, it, expect } from 'vitest'
import {
SDK_STEPS,
getStepById,
getStepByUrl,
getNextStep,
getPreviousStep,
getCompletionPercentage,
getPhaseCompletionPercentage,
type SDKState,
} from '../types'
describe('SDK_STEPS', () => {
it('should have steps defined for both phases', () => {
const phase1Steps = SDK_STEPS.filter(s => s.phase === 1)
const phase2Steps = SDK_STEPS.filter(s => s.phase === 2)
expect(phase1Steps.length).toBeGreaterThan(0)
expect(phase2Steps.length).toBeGreaterThan(0)
})
it('should have unique IDs for all steps', () => {
const ids = SDK_STEPS.map(s => s.id)
const uniqueIds = new Set(ids)
expect(uniqueIds.size).toBe(ids.length)
})
it('should have unique URLs for all steps', () => {
const urls = SDK_STEPS.map(s => s.url)
const uniqueUrls = new Set(urls)
expect(uniqueUrls.size).toBe(urls.length)
})
it('should have checkpoint IDs for all steps', () => {
SDK_STEPS.forEach(step => {
expect(step.checkpointId).toBeDefined()
expect(step.checkpointId.length).toBeGreaterThan(0)
})
})
})
describe('getStepById', () => {
it('should return the correct step for a valid ID', () => {
const step = getStepById('use-case-workshop')
expect(step).toBeDefined()
expect(step?.name).toBe('Use Case Workshop')
})
it('should return undefined for an invalid ID', () => {
const step = getStepById('invalid-step-id')
expect(step).toBeUndefined()
})
it('should find steps in Phase 2', () => {
const step = getStepById('dsfa')
expect(step).toBeDefined()
expect(step?.phase).toBe(2)
})
})
describe('getStepByUrl', () => {
it('should return the correct step for a valid URL', () => {
const step = getStepByUrl('/sdk/advisory-board')
expect(step).toBeDefined()
expect(step?.id).toBe('use-case-workshop')
})
it('should return undefined for an invalid URL', () => {
const step = getStepByUrl('/invalid/url')
expect(step).toBeUndefined()
})
it('should find Phase 2 steps by URL', () => {
const step = getStepByUrl('/sdk/dsfa')
expect(step).toBeDefined()
expect(step?.id).toBe('dsfa')
})
})
describe('getNextStep', () => {
it('should return the next step in sequence', () => {
const nextStep = getNextStep('use-case-workshop')
expect(nextStep).toBeDefined()
expect(nextStep?.id).toBe('screening')
})
it('should return undefined for the last step', () => {
const lastStep = SDK_STEPS[SDK_STEPS.length - 1]
const nextStep = getNextStep(lastStep.id)
expect(nextStep).toBeUndefined()
})
it('should handle transition between phases', () => {
const lastPhase1Step = SDK_STEPS.filter(s => s.phase === 1).pop()
expect(lastPhase1Step).toBeDefined()
const nextStep = getNextStep(lastPhase1Step!.id)
expect(nextStep?.phase).toBe(2)
})
})
describe('getPreviousStep', () => {
it('should return the previous step in sequence', () => {
const prevStep = getPreviousStep('screening')
expect(prevStep).toBeDefined()
expect(prevStep?.id).toBe('use-case-workshop')
})
it('should return undefined for the first step', () => {
const prevStep = getPreviousStep('use-case-workshop')
expect(prevStep).toBeUndefined()
})
})
describe('getCompletionPercentage', () => {
const createMockState = (completedSteps: string[]): SDKState => ({
version: '1.0.0',
lastModified: new Date(),
tenantId: 'test',
userId: 'test',
subscription: 'PROFESSIONAL',
currentPhase: 1,
currentStep: 'use-case-workshop',
completedSteps,
checkpoints: {},
useCases: [],
activeUseCase: null,
screening: null,
modules: [],
requirements: [],
controls: [],
evidence: [],
checklist: [],
risks: [],
aiActClassification: null,
obligations: [],
dsfa: null,
toms: [],
retentionPolicies: [],
vvt: [],
documents: [],
cookieBanner: null,
consents: [],
dsrConfig: null,
escalationWorkflows: [],
sbom: null,
securityIssues: [],
securityBacklog: [],
commandBarHistory: [],
recentSearches: [],
preferences: {
language: 'de',
theme: 'light',
compactMode: false,
showHints: true,
autoSave: true,
autoValidate: true,
allowParallelWork: true,
},
})
it('should return 0 for no completed steps', () => {
const state = createMockState([])
const percentage = getCompletionPercentage(state)
expect(percentage).toBe(0)
})
it('should return 100 for all completed steps', () => {
const allStepIds = SDK_STEPS.map(s => s.id)
const state = createMockState(allStepIds)
const percentage = getCompletionPercentage(state)
expect(percentage).toBe(100)
})
it('should calculate correct percentage for partial completion', () => {
const halfSteps = SDK_STEPS.slice(0, Math.floor(SDK_STEPS.length / 2)).map(s => s.id)
const state = createMockState(halfSteps)
const percentage = getCompletionPercentage(state)
expect(percentage).toBeGreaterThan(40)
expect(percentage).toBeLessThan(60)
})
})
describe('getPhaseCompletionPercentage', () => {
const createMockState = (completedSteps: string[]): SDKState => ({
version: '1.0.0',
lastModified: new Date(),
tenantId: 'test',
userId: 'test',
subscription: 'PROFESSIONAL',
currentPhase: 1,
currentStep: 'use-case-workshop',
completedSteps,
checkpoints: {},
useCases: [],
activeUseCase: null,
screening: null,
modules: [],
requirements: [],
controls: [],
evidence: [],
checklist: [],
risks: [],
aiActClassification: null,
obligations: [],
dsfa: null,
toms: [],
retentionPolicies: [],
vvt: [],
documents: [],
cookieBanner: null,
consents: [],
dsrConfig: null,
escalationWorkflows: [],
sbom: null,
securityIssues: [],
securityBacklog: [],
commandBarHistory: [],
recentSearches: [],
preferences: {
language: 'de',
theme: 'light',
compactMode: false,
showHints: true,
autoSave: true,
autoValidate: true,
allowParallelWork: true,
},
})
it('should return 0 for Phase 1 with no completed steps', () => {
const state = createMockState([])
const percentage = getPhaseCompletionPercentage(state, 1)
expect(percentage).toBe(0)
})
it('should return 100 for Phase 1 when all Phase 1 steps are complete', () => {
const phase1Steps = SDK_STEPS.filter(s => s.phase === 1).map(s => s.id)
const state = createMockState(phase1Steps)
const percentage = getPhaseCompletionPercentage(state, 1)
expect(percentage).toBe(100)
})
it('should not count Phase 2 steps in Phase 1 percentage', () => {
const phase2Steps = SDK_STEPS.filter(s => s.phase === 2).map(s => s.id)
const state = createMockState(phase2Steps)
const percentage = getPhaseCompletionPercentage(state, 1)
expect(percentage).toBe(0)
})
})

View File

@@ -0,0 +1,471 @@
/**
* SDK API Client
*
* Centralized API client for SDK state management with error handling,
* retry logic, and optimistic locking support.
*/
import { SDKState, CheckpointStatus } from './types'
// =============================================================================
// TYPES
// =============================================================================
export interface APIResponse<T> {
success: boolean
data?: T
error?: string
version?: number
lastModified?: string
}
export interface StateResponse {
tenantId: string
state: SDKState
version: number
lastModified: string
}
export interface SaveStateRequest {
tenantId: string
state: SDKState
version?: number // For optimistic locking
}
export interface CheckpointValidationResult {
checkpointId: string
passed: boolean
errors: Array<{
ruleId: string
field: string
message: string
severity: 'ERROR' | 'WARNING' | 'INFO'
}>
warnings: Array<{
ruleId: string
field: string
message: string
severity: 'ERROR' | 'WARNING' | 'INFO'
}>
validatedAt: string
validatedBy: string
}
export interface APIError extends Error {
status?: number
code?: string
retryable: boolean
}
// =============================================================================
// CONFIGURATION
// =============================================================================
const DEFAULT_BASE_URL = '/api/sdk/v1'
const DEFAULT_TIMEOUT = 30000 // 30 seconds
const MAX_RETRIES = 3
const RETRY_DELAYS = [1000, 2000, 4000] // Exponential backoff
// =============================================================================
// API CLIENT
// =============================================================================
export class SDKApiClient {
private baseUrl: string
private tenantId: string
private timeout: number
private abortControllers: Map<string, AbortController> = new Map()
constructor(options: {
baseUrl?: string
tenantId: string
timeout?: number
}) {
this.baseUrl = options.baseUrl || DEFAULT_BASE_URL
this.tenantId = options.tenantId
this.timeout = options.timeout || DEFAULT_TIMEOUT
}
// ---------------------------------------------------------------------------
// Private Methods
// ---------------------------------------------------------------------------
private createError(message: string, status?: number, retryable = false): APIError {
const error = new Error(message) as APIError
error.status = status
error.retryable = retryable
return error
}
private async fetchWithTimeout(
url: string,
options: RequestInit,
requestId: string
): Promise<Response> {
const controller = new AbortController()
this.abortControllers.set(requestId, controller)
const timeoutId = setTimeout(() => controller.abort(), this.timeout)
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
})
return response
} finally {
clearTimeout(timeoutId)
this.abortControllers.delete(requestId)
}
}
private async fetchWithRetry<T>(
url: string,
options: RequestInit,
retries = MAX_RETRIES
): Promise<T> {
const requestId = `${Date.now()}-${Math.random()}`
let lastError: Error | null = null
for (let attempt = 0; attempt <= retries; attempt++) {
try {
const response = await this.fetchWithTimeout(url, options, requestId)
if (!response.ok) {
const errorBody = await response.text()
let errorMessage = `HTTP ${response.status}`
try {
const errorJson = JSON.parse(errorBody)
errorMessage = errorJson.error || errorJson.message || errorMessage
} catch {
// Keep the HTTP status message
}
// Don't retry client errors (4xx) except for 429 (rate limit)
const retryable = response.status >= 500 || response.status === 429
if (!retryable || attempt === retries) {
throw this.createError(errorMessage, response.status, retryable)
}
} else {
const data = await response.json()
return data as T
}
} catch (error) {
lastError = error as Error
if (error instanceof Error && error.name === 'AbortError') {
throw this.createError('Request timeout', 408, true)
}
// Check if it's a retryable error
const apiError = error as APIError
if (!apiError.retryable || attempt === retries) {
throw error
}
}
// Wait before retrying (exponential backoff)
if (attempt < retries) {
await this.sleep(RETRY_DELAYS[attempt] || RETRY_DELAYS[RETRY_DELAYS.length - 1])
}
}
throw lastError || this.createError('Unknown error', 500, false)
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
// ---------------------------------------------------------------------------
// Public Methods - State Management
// ---------------------------------------------------------------------------
/**
* Load SDK state for the current tenant
*/
async getState(): Promise<StateResponse | null> {
try {
const response = await this.fetchWithRetry<APIResponse<StateResponse>>(
`${this.baseUrl}/state?tenantId=${encodeURIComponent(this.tenantId)}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
)
if (response.success && response.data) {
return response.data
}
return null
} catch (error) {
const apiError = error as APIError
// 404 means no state exists yet - that's okay
if (apiError.status === 404) {
return null
}
throw error
}
}
/**
* Save SDK state for the current tenant
* Supports optimistic locking via version parameter
*/
async saveState(state: SDKState, version?: number): Promise<StateResponse> {
const response = await this.fetchWithRetry<APIResponse<StateResponse>>(
`${this.baseUrl}/state`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(version !== undefined && { 'If-Match': String(version) }),
},
body: JSON.stringify({
tenantId: this.tenantId,
state,
version,
}),
}
)
if (!response.success) {
throw this.createError(response.error || 'Failed to save state', 500, true)
}
return response.data!
}
/**
* Delete SDK state for the current tenant
*/
async deleteState(): Promise<void> {
await this.fetchWithRetry<APIResponse<void>>(
`${this.baseUrl}/state?tenantId=${encodeURIComponent(this.tenantId)}`,
{
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
}
)
}
// ---------------------------------------------------------------------------
// Public Methods - Checkpoint Validation
// ---------------------------------------------------------------------------
/**
* Validate a specific checkpoint
*/
async validateCheckpoint(
checkpointId: string,
data?: unknown
): Promise<CheckpointValidationResult> {
const response = await this.fetchWithRetry<APIResponse<CheckpointValidationResult>>(
`${this.baseUrl}/checkpoints/validate`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tenantId: this.tenantId,
checkpointId,
data,
}),
}
)
if (!response.success || !response.data) {
throw this.createError(response.error || 'Checkpoint validation failed', 500, true)
}
return response.data
}
/**
* Get all checkpoint statuses
*/
async getCheckpoints(): Promise<Record<string, CheckpointStatus>> {
const response = await this.fetchWithRetry<APIResponse<Record<string, CheckpointStatus>>>(
`${this.baseUrl}/checkpoints?tenantId=${encodeURIComponent(this.tenantId)}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
)
return response.data || {}
}
// ---------------------------------------------------------------------------
// Public Methods - Flow Navigation
// ---------------------------------------------------------------------------
/**
* Get current flow state
*/
async getFlowState(): Promise<{
currentStep: string
currentPhase: 1 | 2
completedSteps: string[]
suggestions: Array<{ stepId: string; reason: string }>
}> {
const response = await this.fetchWithRetry<APIResponse<{
currentStep: string
currentPhase: 1 | 2
completedSteps: string[]
suggestions: Array<{ stepId: string; reason: string }>
}>>(
`${this.baseUrl}/flow?tenantId=${encodeURIComponent(this.tenantId)}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
)
if (!response.data) {
throw this.createError('Failed to get flow state', 500, true)
}
return response.data
}
/**
* Navigate to next/previous step
*/
async navigateFlow(direction: 'next' | 'previous'): Promise<{
stepId: string
phase: 1 | 2
}> {
const response = await this.fetchWithRetry<APIResponse<{
stepId: string
phase: 1 | 2
}>>(
`${this.baseUrl}/flow`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tenantId: this.tenantId,
direction,
}),
}
)
if (!response.data) {
throw this.createError('Failed to navigate flow', 500, true)
}
return response.data
}
// ---------------------------------------------------------------------------
// Public Methods - Export
// ---------------------------------------------------------------------------
/**
* Export SDK state in various formats
*/
async exportState(format: 'json' | 'pdf' | 'zip'): Promise<Blob> {
const response = await this.fetchWithTimeout(
`${this.baseUrl}/export?tenantId=${encodeURIComponent(this.tenantId)}&format=${format}`,
{
method: 'GET',
headers: {
'Accept': format === 'json' ? 'application/json' : format === 'pdf' ? 'application/pdf' : 'application/zip',
},
},
`export-${Date.now()}`
)
if (!response.ok) {
throw this.createError(`Export failed: ${response.statusText}`, response.status, true)
}
return response.blob()
}
// ---------------------------------------------------------------------------
// Public Methods - Utility
// ---------------------------------------------------------------------------
/**
* Cancel all pending requests
*/
cancelAllRequests(): void {
this.abortControllers.forEach(controller => controller.abort())
this.abortControllers.clear()
}
/**
* Update tenant ID (useful when switching contexts)
*/
setTenantId(tenantId: string): void {
this.tenantId = tenantId
}
/**
* Get current tenant ID
*/
getTenantId(): string {
return this.tenantId
}
/**
* Health check
*/
async healthCheck(): Promise<boolean> {
try {
const response = await this.fetchWithTimeout(
`${this.baseUrl}/health`,
{ method: 'GET' },
`health-${Date.now()}`
)
return response.ok
} catch {
return false
}
}
}
// =============================================================================
// SINGLETON FACTORY
// =============================================================================
let clientInstance: SDKApiClient | null = null
export function getSDKApiClient(tenantId?: string): SDKApiClient {
if (!clientInstance && !tenantId) {
throw new Error('SDKApiClient not initialized. Provide tenantId on first call.')
}
if (!clientInstance && tenantId) {
clientInstance = new SDKApiClient({ tenantId })
}
if (tenantId && clientInstance && clientInstance.getTenantId() !== tenantId) {
clientInstance.setTenantId(tenantId)
}
return clientInstance!
}
export function resetSDKApiClient(): void {
if (clientInstance) {
clientInstance.cancelAllRequests()
}
clientInstance = null
}

1084
admin-v2/lib/sdk/context.tsx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,210 @@
/**
* Demo Controls for AI Compliance SDK
*/
import { Control } from '../types'
export const DEMO_CONTROLS: Control[] = [
// Zugangskontrolle
{
id: 'demo-ctrl-1',
name: 'Multi-Faktor-Authentifizierung',
description: 'Alle Systemzugriffe erfordern mindestens zwei unabhängige Authentifizierungsfaktoren (Wissen + Besitz).',
type: 'TECHNICAL',
category: 'Zugangskontrolle',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'HIGH',
evidence: ['demo-evi-1'],
owner: 'IT-Sicherheit',
dueDate: null,
},
{
id: 'demo-ctrl-2',
name: 'Rollenbasiertes Berechtigungskonzept',
description: 'Zugriffsrechte werden nach dem Least-Privilege-Prinzip anhand definierter Rollen vergeben und regelmäßig überprüft.',
type: 'ORGANIZATIONAL',
category: 'Zugangskontrolle',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'HIGH',
evidence: ['demo-evi-2'],
owner: 'IT-Sicherheit',
dueDate: null,
},
// Verfügbarkeit
{
id: 'demo-ctrl-3',
name: 'Automatisiertes Backup-System',
description: 'Tägliche inkrementelle Backups und wöchentliche Vollbackups aller kritischen Daten mit Verschlüsselung.',
type: 'TECHNICAL',
category: 'Verfügbarkeit',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'HIGH',
evidence: ['demo-evi-3'],
owner: 'IT-Betrieb',
dueDate: null,
},
{
id: 'demo-ctrl-4',
name: 'Georedundante Datenspeicherung',
description: 'Kritische Daten werden synchron in zwei geographisch getrennten Rechenzentren gespeichert.',
type: 'TECHNICAL',
category: 'Verfügbarkeit',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'HIGH',
evidence: ['demo-evi-4'],
owner: 'IT-Betrieb',
dueDate: null,
},
// KI-Fairness
{
id: 'demo-ctrl-5',
name: 'Bias-Monitoring',
description: 'Kontinuierliche Überwachung der KI-Modelle auf systematische Verzerrungen anhand definierter Fairness-Metriken.',
type: 'TECHNICAL',
category: 'KI-Governance',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'MEDIUM',
evidence: ['demo-evi-5'],
owner: 'Data Science Lead',
dueDate: null,
},
{
id: 'demo-ctrl-6',
name: 'Human-in-the-Loop',
description: 'Kritische automatisierte Entscheidungen werden vor Umsetzung durch qualifizierte Mitarbeiter überprüft.',
type: 'ORGANIZATIONAL',
category: 'KI-Governance',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'HIGH',
evidence: ['demo-evi-6'],
owner: 'Fachbereich HR',
dueDate: null,
},
// Transparenz
{
id: 'demo-ctrl-7',
name: 'Explainable AI Komponenten',
description: 'Einsatz von SHAP/LIME zur Erklärung von KI-Entscheidungen für nachvollziehbare Begründungen.',
type: 'TECHNICAL',
category: 'Transparenz',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'MEDIUM',
evidence: ['demo-evi-7'],
owner: 'Data Science Lead',
dueDate: null,
},
{
id: 'demo-ctrl-8',
name: 'Verständliche Datenschutzinformationen',
description: 'Betroffene erhalten klare, verständliche Informationen über die Verarbeitung ihrer Daten gemäß Art. 13-14 DSGVO.',
type: 'ORGANIZATIONAL',
category: 'Transparenz',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'HIGH',
evidence: ['demo-evi-8'],
owner: 'DSB',
dueDate: null,
},
// Datensparsamkeit
{
id: 'demo-ctrl-9',
name: 'Zweckbindungskontrollen',
description: 'Technische Maßnahmen stellen sicher, dass Daten nur für definierte Zwecke verarbeitet werden.',
type: 'TECHNICAL',
category: 'Datensparsamkeit',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'MEDIUM',
evidence: ['demo-evi-9'],
owner: 'IT-Sicherheit',
dueDate: null,
},
{
id: 'demo-ctrl-10',
name: 'Anonymisierungs-Pipeline',
description: 'Automatisierte Anonymisierung von Daten für Analysen, wo keine Personenbezug erforderlich ist.',
type: 'TECHNICAL',
category: 'Datensparsamkeit',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'HIGH',
evidence: ['demo-evi-10'],
owner: 'Data Engineering',
dueDate: null,
},
// KI-Sicherheit
{
id: 'demo-ctrl-11',
name: 'Input-Validierung',
description: 'Strenge Validierung aller Eingabedaten zur Verhinderung von Adversarial Attacks auf KI-Modelle.',
type: 'TECHNICAL',
category: 'KI-Sicherheit',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'MEDIUM',
evidence: ['demo-evi-11'],
owner: 'Data Science Lead',
dueDate: null,
},
{
id: 'demo-ctrl-12',
name: 'Model Performance Monitoring',
description: 'Kontinuierliche Überwachung der Modell-Performance mit automatischen Alerts bei Abweichungen.',
type: 'TECHNICAL',
category: 'KI-Sicherheit',
implementationStatus: 'PARTIAL',
effectiveness: 'MEDIUM',
evidence: [],
owner: 'Data Science Lead',
dueDate: new Date('2026-03-31'),
},
// Datenlebenszyklus
{
id: 'demo-ctrl-13',
name: 'Automatisierte Löschroutinen',
description: 'Technische Umsetzung der Aufbewahrungsfristen mit automatischer Löschung nach Fristablauf.',
type: 'TECHNICAL',
category: 'Datenlebenszyklus',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'HIGH',
evidence: ['demo-evi-13'],
owner: 'IT-Betrieb',
dueDate: null,
},
{
id: 'demo-ctrl-14',
name: 'Löschprotokoll-Review',
description: 'Quartalsweise Überprüfung der Löschprotokolle durch den DSB.',
type: 'ORGANIZATIONAL',
category: 'Datenlebenszyklus',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'MEDIUM',
evidence: ['demo-evi-14'],
owner: 'DSB',
dueDate: null,
},
// Audit
{
id: 'demo-ctrl-15',
name: 'Umfassendes Audit-Logging',
description: 'Alle sicherheitsrelevanten Ereignisse werden manipulationssicher protokolliert und 10 Jahre aufbewahrt.',
type: 'TECHNICAL',
category: 'Audit',
implementationStatus: 'IMPLEMENTED',
effectiveness: 'HIGH',
evidence: ['demo-evi-15'],
owner: 'IT-Sicherheit',
dueDate: null,
},
]
export function getDemoControls(): Control[] {
return DEMO_CONTROLS.map(ctrl => ({
...ctrl,
dueDate: ctrl.dueDate ? new Date(ctrl.dueDate) : null,
}))
}

View File

@@ -0,0 +1,224 @@
/**
* Demo DSFA for AI Compliance SDK
*/
import { DSFA, DSFASection, DSFAApproval } from '../types'
export const DEMO_DSFA: DSFA = {
id: 'demo-dsfa-1',
status: 'IN_REVIEW',
version: 2,
sections: [
{
id: 'dsfa-sec-1',
title: 'Systematische Beschreibung der Verarbeitungsvorgänge',
content: `## 1. Verarbeitungsbeschreibung
### 1.1 Gegenstand der Verarbeitung
Die geplante KI-gestützte Kundenanalyse verarbeitet personenbezogene Daten von Kunden und Interessenten zur Optimierung von Marketingmaßnahmen und Personalisierung von Angeboten.
### 1.2 Verarbeitungszwecke
- Kundensegmentierung basierend auf Kaufverhalten
- Churn-Prediction zur Kundenbindung
- Personalisierte Produktempfehlungen
- Optimierung von Marketing-Kampagnen
### 1.3 Kategorien personenbezogener Daten
- **Stammdaten**: Name, Adresse, E-Mail, Telefon
- **Transaktionsdaten**: Käufe, Bestellungen, Retouren
- **Nutzungsdaten**: Clickstreams, Seitenaufrufe, Verweildauer
- **Demographische Daten**: Alter, Geschlecht, PLZ-Region
### 1.4 Kategorien betroffener Personen
- Bestandskunden (ca. 250.000 aktive Kunden)
- Registrierte Interessenten (ca. 100.000)
- Newsletter-Abonnenten (ca. 180.000)
### 1.5 Rechtsgrundlage
**Primär**: Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse)
**Sekundär**: Art. 6 Abs. 1 lit. a DSGVO (Einwilligung für erweiterte Profiling-Maßnahmen)
Das berechtigte Interesse liegt in der Verbesserung des Kundenerlebnisses und der Effizienzsteigerung des Marketings.`,
status: 'COMPLETED',
order: 1,
},
{
id: 'dsfa-sec-2',
title: 'Bewertung der Notwendigkeit und Verhältnismäßigkeit',
content: `## 2. Notwendigkeit und Verhältnismäßigkeit
### 2.1 Notwendigkeit der Verarbeitung
Die Verarbeitung ist notwendig, um:
- Kunden individuell relevante Angebote zu unterbreiten
- Abwanderungsgefährdete Kunden frühzeitig zu identifizieren
- Marketing-Budget effizienter einzusetzen
- Wettbewerbsfähigkeit zu erhalten
### 2.2 Verhältnismäßigkeitsprüfung
**Alternative Methoden geprüft:**
1. **Manuelle Analyse**: Nicht praktikabel bei 250.000+ Kunden
2. **Regelbasierte Systeme**: Zu ungenau, führt zu höherem Datenverbrauch
3. **Aggregierte Analysen**: Keine ausreichende Personalisierung möglich
**Ergebnis**: Die KI-gestützte Analyse stellt die mildeste effektive Maßnahme dar.
### 2.3 Datensparsamkeit
- Nur für den Zweck notwendige Daten werden verarbeitet
- Sensitive Kategorien (Art. 9 DSGVO) werden ausgeschlossen
- Automatische Löschung nach definierten Fristen
### 2.4 Interessenabwägung
| Interesse des Verantwortlichen | Interesse der Betroffenen |
|-------------------------------|---------------------------|
| Effizientes Marketing | Privatsphäre |
| Kundenbindung | Keine unerwünschte Profilbildung |
| Umsatzsteigerung | Transparenz über Verarbeitung |
**Ausgleichende Maßnahmen:**
- Umfassende Informationen nach Art. 13/14 DSGVO
- Einfacher Opt-out für Profiling
- Human-Review bei kritischen Entscheidungen`,
status: 'COMPLETED',
order: 2,
},
{
id: 'dsfa-sec-3',
title: 'Risikobewertung',
content: `## 3. Risiken für Rechte und Freiheiten
### 3.1 Identifizierte Risiken
| # | Risiko | Eintritt | Schwere | Gesamt |
|---|--------|----------|---------|--------|
| R1 | Unbefugter Zugriff auf Profildaten | Mittel | Hoch | HOCH |
| R2 | Diskriminierende Entscheidungen durch Bias | Mittel | Hoch | HOCH |
| R3 | Unzulässige Profilbildung | Mittel | Mittel | MITTEL |
| R4 | Fehlende Nachvollziehbarkeit | Hoch | Mittel | MITTEL |
| R5 | Übermäßige Datensammlung | Niedrig | Mittel | NIEDRIG |
### 3.2 Detailanalyse kritischer Risiken
**R1 - Unbefugter Zugriff**
- Quelle: Externe Angreifer, Insider-Bedrohung
- Auswirkung: Identitätsdiebstahl, Reputationsschaden
- Betroffene: Alle Kunden
**R2 - Diskriminierende Entscheidungen**
- Quelle: Historische Verzerrungen in Trainingsdaten
- Auswirkung: Benachteiligung bestimmter Gruppen
- Betroffene: Potentiell alle, besonders geschützte Gruppen`,
status: 'COMPLETED',
order: 3,
},
{
id: 'dsfa-sec-4',
title: 'Maßnahmen zur Risikominderung',
content: `## 4. Abhilfemaßnahmen
### 4.1 Technische Maßnahmen
| Maßnahme | Risiko | Status | Wirksamkeit |
|----------|--------|--------|-------------|
| Multi-Faktor-Authentifizierung | R1 | ✅ Umgesetzt | Hoch |
| Verschlüsselung (AES-256) | R1 | ✅ Umgesetzt | Hoch |
| Bias-Monitoring | R2 | ✅ Umgesetzt | Mittel |
| Explainable AI | R4 | ✅ Umgesetzt | Mittel |
| Zweckbindungskontrollen | R3 | ✅ Umgesetzt | Hoch |
| Audit-Logging | R1, R4 | ✅ Umgesetzt | Hoch |
### 4.2 Organisatorische Maßnahmen
| Maßnahme | Risiko | Status | Wirksamkeit |
|----------|--------|--------|-------------|
| Rollenbasierte Zugriffskontrolle | R1 | ✅ Umgesetzt | Hoch |
| Human-in-the-Loop | R2 | ✅ Umgesetzt | Hoch |
| Datenschutz-Schulungen | R1, R3 | ✅ Umgesetzt | Mittel |
| Regelmäßige Audits | Alle | ⏳ Geplant | Hoch |
### 4.3 Restrisikobewertung
Nach Implementierung aller Maßnahmen:
- **R1**: HOCH → MITTEL (akzeptabel)
- **R2**: HOCH → MITTEL (akzeptabel)
- **R3**: MITTEL → NIEDRIG (akzeptabel)
- **R4**: MITTEL → NIEDRIG (akzeptabel)
- **R5**: NIEDRIG → NIEDRIG (akzeptabel)`,
status: 'COMPLETED',
order: 4,
},
{
id: 'dsfa-sec-5',
title: 'Stellungnahme des Datenschutzbeauftragten',
content: `## 5. Stellungnahme DSB
### 5.1 Bewertung
Der Datenschutzbeauftragte hat die DSFA geprüft und kommt zu folgender Einschätzung:
**Positiv:**
- Umfassende Risikoanalyse durchgeführt
- Technische Schutzmaßnahmen dem Stand der Technik entsprechend
- Transparenzpflichten angemessen berücksichtigt
- Interessenabwägung nachvollziehbar dokumentiert
**Verbesserungspotenzial:**
- Regelmäßige Überprüfung der Bias-Metriken sollte quartalsweise erfolgen
- Informationen für Betroffene könnten noch verständlicher formuliert werden
- Löschkonzept sollte um automatische Überprüfungsmechanismen ergänzt werden
### 5.2 Empfehlung
Der DSB empfiehlt die **Genehmigung** der Verarbeitungstätigkeit unter der Voraussetzung, dass:
1. Die identifizierten Verbesserungsmaßnahmen innerhalb von 3 Monaten umgesetzt werden
2. Eine jährliche Überprüfung der DSFA erfolgt
3. Bei wesentlichen Änderungen eine Aktualisierung vorgenommen wird
---
*Datum: 2026-01-28*
*Unterschrift: [DSB]*`,
status: 'COMPLETED',
order: 5,
},
],
approvals: [
{
id: 'dsfa-appr-1',
approver: 'Dr. Thomas Schmidt',
role: 'Datenschutzbeauftragter',
status: 'APPROVED',
comment: 'Unter den genannten Voraussetzungen genehmigt.',
approvedAt: new Date('2026-01-28'),
},
{
id: 'dsfa-appr-2',
approver: 'Maria Weber',
role: 'CISO',
status: 'APPROVED',
comment: 'Technische Maßnahmen sind angemessen.',
approvedAt: new Date('2026-01-29'),
},
{
id: 'dsfa-appr-3',
approver: 'Michael Bauer',
role: 'Geschäftsführung',
status: 'PENDING',
comment: null,
approvedAt: null,
},
],
createdAt: new Date('2026-01-15'),
updatedAt: new Date('2026-02-01'),
}
export function getDemoDSFA(): DSFA {
return {
...DEMO_DSFA,
approvals: DEMO_DSFA.approvals.map(a => ({
...a,
approvedAt: a.approvedAt ? new Date(a.approvedAt) : null,
})),
createdAt: new Date(DEMO_DSFA.createdAt),
updatedAt: new Date(DEMO_DSFA.updatedAt),
}
}

View File

@@ -0,0 +1,556 @@
/**
* Demo Data Seeding for AI Compliance SDK
*
* IMPORTANT: Demo data is NOT hardcoded in the frontend.
* This module provides seed data that gets stored via the API,
* exactly like real customer data would be stored.
*
* The seedDemoData() function writes data through the API,
* and the data is then loaded from the database like any other data.
*/
import { SDKState } from '../types'
import { getSDKApiClient } from '../api-client'
// Seed data imports (these are templates, not runtime data)
import { getDemoUseCases, DEMO_USE_CASES } from './use-cases'
import { getDemoRisks, DEMO_RISKS } from './risks'
import { getDemoControls, DEMO_CONTROLS } from './controls'
import { getDemoDSFA, DEMO_DSFA } from './dsfa'
import { getDemoTOMs, DEMO_TOMS } from './toms'
import { getDemoProcessingActivities, getDemoRetentionPolicies, DEMO_PROCESSING_ACTIVITIES, DEMO_RETENTION_POLICIES } from './vvt'
// Re-export for direct access to seed templates (for testing/development)
export {
getDemoUseCases,
getDemoRisks,
getDemoControls,
getDemoDSFA,
getDemoTOMs,
getDemoProcessingActivities,
getDemoRetentionPolicies,
// Raw data exports
DEMO_USE_CASES,
DEMO_RISKS,
DEMO_CONTROLS,
DEMO_DSFA,
DEMO_TOMS,
DEMO_PROCESSING_ACTIVITIES,
DEMO_RETENTION_POLICIES,
}
/**
* Generate a complete demo state object
* This is used as seed data for the API, not as runtime data
*/
export function generateDemoState(tenantId: string, userId: string): Partial<SDKState> {
const now = new Date()
return {
// Metadata
version: '1.0.0',
lastModified: now,
// Tenant & User
tenantId,
userId,
subscription: 'PROFESSIONAL',
// Customer Type
customerType: 'new',
// Company Profile (Demo: TechStart GmbH - SaaS-Startup aus Berlin)
companyProfile: {
companyName: 'TechStart GmbH',
legalForm: 'gmbh',
industry: 'Technologie / IT',
foundedYear: 2022,
businessModel: 'B2B_B2C',
offerings: ['app_web', 'software_saas', 'services_consulting'],
companySize: 'small',
employeeCount: '10-49',
annualRevenue: '2-10 Mio',
headquartersCountry: 'DE',
headquartersCity: 'Berlin',
hasInternationalLocations: false,
internationalCountries: [],
targetMarkets: ['germany_only', 'dach'],
primaryJurisdiction: 'DE',
isDataController: true,
isDataProcessor: true,
usesAI: true,
aiUseCases: ['KI-gestützte Kundenberatung', 'Automatisierte Dokumentenanalyse'],
dpoName: 'Max Mustermann',
dpoEmail: 'dsb@techstart.de',
legalContactName: null,
legalContactEmail: null,
isComplete: true,
completedAt: new Date('2026-01-14'),
},
// Progress - showing a realistic partially completed workflow
currentPhase: 2,
currentStep: 'tom',
completedSteps: [
'company-profile',
'use-case-assessment',
'screening',
'modules',
'requirements',
'controls',
'evidence',
'audit-checklist',
'risks',
'ai-act',
'obligations',
'dsfa',
],
checkpoints: {
'CP-PROF': { checkpointId: 'CP-PROF', passed: true, validatedAt: new Date('2026-01-14'), validatedBy: 'demo-user', errors: [], warnings: [] },
'CP-UC': { checkpointId: 'CP-UC', passed: true, validatedAt: new Date('2026-01-15'), validatedBy: 'demo-user', errors: [], warnings: [] },
'CP-SCAN': { checkpointId: 'CP-SCAN', passed: true, validatedAt: new Date('2026-01-16'), validatedBy: 'demo-user', errors: [], warnings: [] },
'CP-MOD': { checkpointId: 'CP-MOD', passed: true, validatedAt: new Date('2026-01-17'), validatedBy: 'demo-user', errors: [], warnings: [] },
'CP-REQ': { checkpointId: 'CP-REQ', passed: true, validatedAt: new Date('2026-01-18'), validatedBy: 'demo-user', errors: [], warnings: [] },
'CP-CTRL': { checkpointId: 'CP-CTRL', passed: true, validatedAt: new Date('2026-01-19'), validatedBy: 'demo-user', errors: [], warnings: [] },
'CP-EVI': { checkpointId: 'CP-EVI', passed: true, validatedAt: new Date('2026-01-20'), validatedBy: 'demo-user', errors: [], warnings: [] },
'CP-CHK': { checkpointId: 'CP-CHK', passed: true, validatedAt: new Date('2026-01-21'), validatedBy: 'demo-user', errors: [], warnings: [] },
'CP-RISK': { checkpointId: 'CP-RISK', passed: true, validatedAt: new Date('2026-01-22'), validatedBy: 'demo-user', errors: [], warnings: [] },
'CP-AI': { checkpointId: 'CP-AI', passed: true, validatedAt: new Date('2026-01-25'), validatedBy: 'demo-user', errors: [], warnings: [] },
'CP-OBL': { checkpointId: 'CP-OBL', passed: true, validatedAt: new Date('2026-01-27'), validatedBy: 'demo-user', errors: [], warnings: [] },
'CP-DSFA': { checkpointId: 'CP-DSFA', passed: true, validatedAt: new Date('2026-01-30'), validatedBy: 'DSB', errors: [], warnings: [] },
},
// Phase 1 Data
useCases: getDemoUseCases(),
activeUseCase: 'demo-uc-1',
screening: {
id: 'demo-scan-1',
status: 'COMPLETED',
startedAt: new Date('2026-01-16T09:00:00'),
completedAt: new Date('2026-01-16T09:15:00'),
sbom: {
format: 'CycloneDX',
version: '1.4',
components: [
{
name: 'tensorflow',
version: '2.15.0',
type: 'library',
purl: 'pkg:pypi/tensorflow@2.15.0',
licenses: ['Apache-2.0'],
vulnerabilities: [],
},
{
name: 'scikit-learn',
version: '1.4.0',
type: 'library',
purl: 'pkg:pypi/scikit-learn@1.4.0',
licenses: ['BSD-3-Clause'],
vulnerabilities: [],
},
{
name: 'pandas',
version: '2.2.0',
type: 'library',
purl: 'pkg:pypi/pandas@2.2.0',
licenses: ['BSD-3-Clause'],
vulnerabilities: [],
},
],
dependencies: [],
generatedAt: new Date('2026-01-16T09:10:00'),
},
securityScan: {
totalIssues: 3,
critical: 0,
high: 1,
medium: 1,
low: 1,
issues: [
{
id: 'sec-issue-1',
severity: 'HIGH',
title: 'Outdated cryptography library',
description: 'The cryptography library version 41.0.0 has known vulnerabilities',
cve: 'CVE-2024-1234',
cvss: 7.5,
affectedComponent: 'cryptography',
remediation: 'Upgrade to cryptography >= 42.0.0',
status: 'RESOLVED',
},
{
id: 'sec-issue-2',
severity: 'MEDIUM',
title: 'Insecure default configuration',
description: 'Debug mode enabled in production configuration',
cve: null,
cvss: 5.3,
affectedComponent: 'app-config',
remediation: 'Set DEBUG=false in production',
status: 'RESOLVED',
},
{
id: 'sec-issue-3',
severity: 'LOW',
title: 'Missing security headers',
description: 'X-Content-Type-Options header not set',
cve: null,
cvss: 3.1,
affectedComponent: 'web-server',
remediation: 'Add security headers middleware',
status: 'RESOLVED',
},
],
},
error: null,
},
modules: [
{
id: 'demo-mod-1',
name: 'Kundendaten-Modul',
description: 'Verarbeitung von Kundendaten für Marketing und Analyse',
regulations: ['DSGVO', 'TTDSG'],
criticality: 'HIGH',
processesPersonalData: true,
hasAIComponents: true,
},
{
id: 'demo-mod-2',
name: 'HR-Modul',
description: 'Bewerbermanagement und Personalverwaltung',
regulations: ['DSGVO', 'AGG', 'AI Act'],
criticality: 'HIGH',
processesPersonalData: true,
hasAIComponents: true,
},
{
id: 'demo-mod-3',
name: 'Support-Modul',
description: 'Kundenservice und Chatbot-System',
regulations: ['DSGVO', 'AI Act'],
criticality: 'MEDIUM',
processesPersonalData: true,
hasAIComponents: true,
},
],
requirements: [
{
id: 'demo-req-1',
regulation: 'DSGVO',
article: 'Art. 5',
title: 'Grundsätze der Verarbeitung',
description: 'Einhaltung der Grundsätze für die Verarbeitung personenbezogener Daten',
criticality: 'CRITICAL',
applicableModules: ['demo-mod-1', 'demo-mod-2', 'demo-mod-3'],
status: 'IMPLEMENTED',
controls: ['demo-ctrl-1', 'demo-ctrl-2', 'demo-ctrl-9'],
},
{
id: 'demo-req-2',
regulation: 'DSGVO',
article: 'Art. 32',
title: 'Sicherheit der Verarbeitung',
description: 'Geeignete technische und organisatorische Maßnahmen',
criticality: 'CRITICAL',
applicableModules: ['demo-mod-1', 'demo-mod-2', 'demo-mod-3'],
status: 'IMPLEMENTED',
controls: ['demo-ctrl-1', 'demo-ctrl-3', 'demo-ctrl-4'],
},
{
id: 'demo-req-3',
regulation: 'DSGVO',
article: 'Art. 25',
title: 'Datenschutz durch Technikgestaltung',
description: 'Privacy by Design und Privacy by Default',
criticality: 'HIGH',
applicableModules: ['demo-mod-1', 'demo-mod-2'],
status: 'IMPLEMENTED',
controls: ['demo-ctrl-9', 'demo-ctrl-10'],
},
{
id: 'demo-req-4',
regulation: 'AI Act',
article: 'Art. 13',
title: 'Transparenz',
description: 'Transparenzanforderungen für KI-Systeme',
criticality: 'HIGH',
applicableModules: ['demo-mod-1', 'demo-mod-2', 'demo-mod-3'],
status: 'IMPLEMENTED',
controls: ['demo-ctrl-7', 'demo-ctrl-8'],
},
{
id: 'demo-req-5',
regulation: 'AI Act',
article: 'Art. 9',
title: 'Risikomanagement',
description: 'Risikomanagementsystem für Hochrisiko-KI',
criticality: 'HIGH',
applicableModules: ['demo-mod-2'],
status: 'IMPLEMENTED',
controls: ['demo-ctrl-5', 'demo-ctrl-6', 'demo-ctrl-11', 'demo-ctrl-12'],
},
],
controls: getDemoControls(),
evidence: [
{
id: 'demo-evi-1',
controlId: 'demo-ctrl-1',
type: 'SCREENSHOT',
name: 'MFA-Konfiguration Azure AD',
description: 'Screenshot der MFA-Einstellungen im Azure AD Admin Portal',
fileUrl: null,
validFrom: new Date('2026-01-01'),
validUntil: new Date('2027-01-01'),
uploadedBy: 'IT-Security',
uploadedAt: new Date('2026-01-10'),
},
{
id: 'demo-evi-2',
controlId: 'demo-ctrl-2',
type: 'DOCUMENT',
name: 'Berechtigungskonzept v2.1',
description: 'Dokumentiertes Berechtigungskonzept mit Rollenmatrix',
fileUrl: null,
validFrom: new Date('2026-01-01'),
validUntil: null,
uploadedBy: 'IT-Security',
uploadedAt: new Date('2026-01-05'),
},
{
id: 'demo-evi-5',
controlId: 'demo-ctrl-5',
type: 'AUDIT_REPORT',
name: 'Bias-Audit Q1/2026',
description: 'Externer Audit-Bericht zur Fairness des KI-Modells',
fileUrl: null,
validFrom: new Date('2026-01-15'),
validUntil: new Date('2026-04-15'),
uploadedBy: 'Data Science Lead',
uploadedAt: new Date('2026-01-20'),
},
],
checklist: [
{
id: 'demo-chk-1',
requirementId: 'demo-req-1',
title: 'Rechtmäßigkeit der Verarbeitung geprüft',
description: 'Dokumentierte Prüfung der Rechtsgrundlagen',
status: 'PASSED',
notes: 'Geprüft durch DSB',
verifiedBy: 'DSB',
verifiedAt: new Date('2026-01-20'),
},
{
id: 'demo-chk-2',
requirementId: 'demo-req-2',
title: 'TOMs dokumentiert und umgesetzt',
description: 'Technische und organisatorische Maßnahmen',
status: 'PASSED',
notes: 'Alle TOMs implementiert',
verifiedBy: 'CISO',
verifiedAt: new Date('2026-01-21'),
},
],
risks: getDemoRisks(),
// Phase 2 Data
aiActClassification: {
riskCategory: 'HIGH',
systemType: 'Beschäftigungsbezogenes KI-System (Art. 6 Abs. 2 AI Act)',
obligations: [
{
id: 'demo-ai-obl-1',
article: 'Art. 9',
title: 'Risikomanagementsystem',
description: 'Einrichtung eines KI-Risikomanagementsystems',
deadline: new Date('2026-08-01'),
status: 'IN_PROGRESS',
},
{
id: 'demo-ai-obl-2',
article: 'Art. 10',
title: 'Daten-Governance',
description: 'Anforderungen an Trainingsdaten',
deadline: new Date('2026-08-01'),
status: 'COMPLETED',
},
{
id: 'demo-ai-obl-3',
article: 'Art. 13',
title: 'Transparenz',
description: 'Dokumentation für Nutzer',
deadline: new Date('2026-08-01'),
status: 'COMPLETED',
},
],
assessmentDate: new Date('2026-01-25'),
assessedBy: 'Compliance Team',
justification: 'Das System fällt unter Art. 6 Abs. 2 lit. a AI Act (Einstellung und Auswahl von Personen).',
},
obligations: [
{
id: 'demo-obl-1',
regulation: 'DSGVO',
article: 'Art. 30',
title: 'Verarbeitungsverzeichnis',
description: 'Führung eines Verzeichnisses der Verarbeitungstätigkeiten',
deadline: null,
penalty: 'Bis zu 10 Mio. EUR oder 2% des Jahresumsatzes',
status: 'COMPLETED',
responsible: 'DSB',
},
{
id: 'demo-obl-2',
regulation: 'DSGVO',
article: 'Art. 35',
title: 'Datenschutz-Folgenabschätzung',
description: 'Durchführung einer DSFA für Hochrisiko-Verarbeitungen',
deadline: null,
penalty: 'Bis zu 10 Mio. EUR oder 2% des Jahresumsatzes',
status: 'COMPLETED',
responsible: 'DSB',
},
{
id: 'demo-obl-3',
regulation: 'AI Act',
article: 'Art. 49',
title: 'CE-Kennzeichnung',
description: 'CE-Kennzeichnung für Hochrisiko-KI-Systeme',
deadline: new Date('2026-08-01'),
penalty: 'Bis zu 35 Mio. EUR oder 7% des Jahresumsatzes',
status: 'PENDING',
responsible: 'Compliance',
},
],
dsfa: getDemoDSFA(),
toms: getDemoTOMs(),
retentionPolicies: getDemoRetentionPolicies(),
vvt: getDemoProcessingActivities(),
// Documents, Cookie Banner, etc. - partially filled
documents: [],
cookieBanner: null,
consents: [],
dsrConfig: null,
escalationWorkflows: [],
// Security
sbom: null,
securityIssues: [],
securityBacklog: [],
// UI State
commandBarHistory: [],
recentSearches: ['DSGVO Art. 5', 'Bias-Monitoring', 'TOM Verschlüsselung'],
preferences: {
language: 'de',
theme: 'light',
compactMode: false,
showHints: true,
autoSave: true,
autoValidate: true,
allowParallelWork: true,
},
}
}
/**
* Seed demo data into the database via API
* This ensures demo data is stored exactly like real customer data
*/
export async function seedDemoData(
tenantId: string = 'demo-tenant',
userId: string = 'demo-user',
apiBaseUrl?: string
): Promise<{ success: boolean; message: string }> {
try {
const apiClient = getSDKApiClient(tenantId)
// Generate the demo state
const demoState = generateDemoState(tenantId, userId) as SDKState
// Save via the same API that real data uses
await apiClient.saveState(demoState)
return {
success: true,
message: `Demo data successfully seeded for tenant ${tenantId}`,
}
} catch (error) {
console.error('Failed to seed demo data:', error)
return {
success: false,
message: error instanceof Error ? error.message : 'Unknown error during seeding',
}
}
}
/**
* Check if demo data exists for a tenant
*/
export async function hasDemoData(tenantId: string = 'demo-tenant'): Promise<boolean> {
try {
const apiClient = getSDKApiClient(tenantId)
const response = await apiClient.getState()
// Check if we have any use cases (indicating data exists)
return response !== null && response.state && Array.isArray(response.state.useCases) && response.state.useCases.length > 0
} catch {
return false
}
}
/**
* Clear demo data for a tenant
*/
export async function clearDemoData(tenantId: string = 'demo-tenant'): Promise<boolean> {
try {
const apiClient = getSDKApiClient(tenantId)
await apiClient.deleteState()
return true
} catch {
return false
}
}
/**
* Seed demo data via direct API call (for use outside of React context)
* This is useful for server-side seeding or CLI tools
*/
export async function seedDemoDataDirect(
baseUrl: string,
tenantId: string = 'demo-tenant',
userId: string = 'demo-user'
): Promise<{ success: boolean; message: string }> {
try {
const demoState = generateDemoState(tenantId, userId)
const response = await fetch(`${baseUrl}/api/sdk/v1/state`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tenantId,
userId,
state: demoState,
}),
})
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
throw new Error(error.message || `HTTP ${response.status}`)
}
return {
success: true,
message: `Demo data successfully seeded for tenant ${tenantId}`,
}
} catch (error) {
console.error('Failed to seed demo data:', error)
return {
success: false,
message: error instanceof Error ? error.message : 'Unknown error during seeding',
}
}
}

View File

@@ -0,0 +1,268 @@
/**
* Demo Risks for AI Compliance SDK
*/
import { Risk, RiskMitigation } from '../types'
export const DEMO_RISKS: Risk[] = [
{
id: 'demo-risk-1',
title: 'Unbefugter Zugriff auf personenbezogene Daten',
description: 'Risiko des unbefugten Zugriffs auf Kundendaten durch externe Angreifer oder interne Mitarbeiter ohne entsprechende Berechtigung.',
category: 'Datensicherheit',
likelihood: 3,
impact: 5,
severity: 'CRITICAL',
inherentRiskScore: 15,
residualRiskScore: 6,
status: 'MITIGATED',
mitigation: [
{
id: 'demo-mit-1a',
description: 'Implementierung von Multi-Faktor-Authentifizierung für alle Systemzugriffe',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 40,
controlId: 'demo-ctrl-1',
},
{
id: 'demo-mit-1b',
description: 'Rollenbasiertes Zugriffskonzept mit Least-Privilege-Prinzip',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 30,
controlId: 'demo-ctrl-2',
},
],
owner: 'CISO',
relatedControls: ['demo-ctrl-1', 'demo-ctrl-2'],
relatedRequirements: ['demo-req-1', 'demo-req-2'],
},
{
id: 'demo-risk-2',
title: 'KI-Bias bei automatisierten Entscheidungen',
description: 'Das KI-System könnte systematische Verzerrungen aufweisen, die zu diskriminierenden Entscheidungen führen, insbesondere bei der Bewerbungsvorauswahl.',
category: 'KI-Ethik',
likelihood: 4,
impact: 4,
severity: 'HIGH',
inherentRiskScore: 16,
residualRiskScore: 8,
status: 'MITIGATED',
mitigation: [
{
id: 'demo-mit-2a',
description: 'Regelmäßiges Bias-Monitoring mit Fairness-Metriken',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 30,
controlId: 'demo-ctrl-5',
},
{
id: 'demo-mit-2b',
description: 'Human-in-the-Loop bei kritischen Entscheidungen',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 25,
controlId: 'demo-ctrl-6',
},
],
owner: 'Data Science Lead',
relatedControls: ['demo-ctrl-5', 'demo-ctrl-6'],
relatedRequirements: ['demo-req-5', 'demo-req-6'],
},
{
id: 'demo-risk-3',
title: 'Datenverlust durch Systemausfall',
description: 'Verlust von Kundendaten und KI-Modellen durch Hardware-Defekte, Softwarefehler oder Naturkatastrophen.',
category: 'Verfügbarkeit',
likelihood: 2,
impact: 5,
severity: 'HIGH',
inherentRiskScore: 10,
residualRiskScore: 3,
status: 'MITIGATED',
mitigation: [
{
id: 'demo-mit-3a',
description: 'Tägliche inkrementelle und wöchentliche Vollbackups',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 40,
controlId: 'demo-ctrl-3',
},
{
id: 'demo-mit-3b',
description: 'Georedundante Datenspeicherung in zwei Rechenzentren',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 35,
controlId: 'demo-ctrl-4',
},
],
owner: 'IT-Leiter',
relatedControls: ['demo-ctrl-3', 'demo-ctrl-4'],
relatedRequirements: ['demo-req-3'],
},
{
id: 'demo-risk-4',
title: 'Unzureichende Transparenz bei KI-Entscheidungen',
description: 'Betroffene verstehen nicht, wie KI-Entscheidungen zustande kommen, was zu Beschwerden und regulatorischen Problemen führen kann.',
category: 'Transparenz',
likelihood: 4,
impact: 3,
severity: 'MEDIUM',
inherentRiskScore: 12,
residualRiskScore: 4,
status: 'MITIGATED',
mitigation: [
{
id: 'demo-mit-4a',
description: 'Explainable AI Komponenten für nachvollziehbare Entscheidungen',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 40,
controlId: 'demo-ctrl-7',
},
{
id: 'demo-mit-4b',
description: 'Verständliche Informationen für Betroffene gem. Art. 13-14 DSGVO',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 30,
controlId: 'demo-ctrl-8',
},
],
owner: 'DSB',
relatedControls: ['demo-ctrl-7', 'demo-ctrl-8'],
relatedRequirements: ['demo-req-4'],
},
{
id: 'demo-risk-5',
title: 'Unerlaubte Profilbildung',
description: 'Durch die Zusammenführung verschiedener Datenquellen könnte eine unzulässige umfassende Profilbildung von Personen entstehen.',
category: 'Datenschutz',
likelihood: 3,
impact: 4,
severity: 'HIGH',
inherentRiskScore: 12,
residualRiskScore: 6,
status: 'MITIGATED',
mitigation: [
{
id: 'demo-mit-5a',
description: 'Strenge Zweckbindung der Datenverarbeitung',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 25,
controlId: 'demo-ctrl-9',
},
{
id: 'demo-mit-5b',
description: 'Datensparsamkeit durch Aggregation und Anonymisierung',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 30,
controlId: 'demo-ctrl-10',
},
],
owner: 'DSB',
relatedControls: ['demo-ctrl-9', 'demo-ctrl-10'],
relatedRequirements: ['demo-req-7', 'demo-req-8'],
},
{
id: 'demo-risk-6',
title: 'Mangelnde Modell-Robustheit',
description: 'KI-Modelle könnten durch Adversarial Attacks oder veränderte Inputdaten manipuliert werden und falsche Ergebnisse liefern.',
category: 'KI-Sicherheit',
likelihood: 2,
impact: 4,
severity: 'MEDIUM',
inherentRiskScore: 8,
residualRiskScore: 4,
status: 'MITIGATED',
mitigation: [
{
id: 'demo-mit-6a',
description: 'Input-Validierung und Anomalie-Erkennung',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 30,
controlId: 'demo-ctrl-11',
},
{
id: 'demo-mit-6b',
description: 'Regelmäßige Modell-Retraining und Performance-Monitoring',
type: 'MITIGATE',
status: 'IN_PROGRESS',
effectiveness: 20,
controlId: 'demo-ctrl-12',
},
],
owner: 'Data Science Lead',
relatedControls: ['demo-ctrl-11', 'demo-ctrl-12'],
relatedRequirements: ['demo-req-9'],
},
{
id: 'demo-risk-7',
title: 'Verstoß gegen Aufbewahrungsfristen',
description: 'Daten werden länger als zulässig gespeichert oder zu früh gelöscht, was zu Compliance-Verstößen führt.',
category: 'Datenschutz',
likelihood: 3,
impact: 3,
severity: 'MEDIUM',
inherentRiskScore: 9,
residualRiskScore: 3,
status: 'MITIGATED',
mitigation: [
{
id: 'demo-mit-7a',
description: 'Automatisierte Löschroutinen mit Retention-Policy-Enforcement',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 40,
controlId: 'demo-ctrl-13',
},
{
id: 'demo-mit-7b',
description: 'Quartalsmäßige Überprüfung der Löschprotokolle',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 25,
controlId: 'demo-ctrl-14',
},
],
owner: 'DSB',
relatedControls: ['demo-ctrl-13', 'demo-ctrl-14'],
relatedRequirements: ['demo-req-10'],
},
{
id: 'demo-risk-8',
title: 'Fehlende Nachvollziehbarkeit im Audit',
description: 'Bei Prüfungen können Verarbeitungsvorgänge nicht lückenlos nachvollzogen werden.',
category: 'Compliance',
likelihood: 2,
impact: 3,
severity: 'MEDIUM',
inherentRiskScore: 6,
residualRiskScore: 2,
status: 'MITIGATED',
mitigation: [
{
id: 'demo-mit-8a',
description: 'Umfassendes Audit-Logging aller Verarbeitungsvorgänge',
type: 'MITIGATE',
status: 'COMPLETED',
effectiveness: 50,
controlId: 'demo-ctrl-15',
},
],
owner: 'IT-Leiter',
relatedControls: ['demo-ctrl-15'],
relatedRequirements: ['demo-req-11'],
},
]
export function getDemoRisks(): Risk[] {
return DEMO_RISKS
}

View File

@@ -0,0 +1,296 @@
/**
* Demo TOMs (Technical & Organizational Measures) for AI Compliance SDK
* These are seed data structures - actual data is stored in database
*/
import { TOM } from '../types'
export const DEMO_TOMS: TOM[] = [
// Zugangskontrolle
{
id: 'demo-tom-1',
category: 'Zugangskontrolle',
name: 'Physische Zutrittskontrolle',
description: 'Elektronische Zugangskontrollsysteme mit personenbezogenen Zutrittskarten für alle Serverräume und Rechenzentren. Protokollierung aller Zutritte.',
type: 'TECHNICAL',
implementationStatus: 'IMPLEMENTED',
priority: 'HIGH',
responsiblePerson: 'Facility Management',
implementationDate: new Date('2025-06-01'),
reviewDate: new Date('2026-06-01'),
evidence: ['demo-evi-tom-1'],
},
{
id: 'demo-tom-2',
category: 'Zugangskontrolle',
name: 'Besuchermanagement',
description: 'Registrierung aller Besucher mit Identitätsprüfung, Ausgabe von Besucherausweisen und permanente Begleitung in sicherheitsrelevanten Bereichen.',
type: 'ORGANIZATIONAL',
implementationStatus: 'IMPLEMENTED',
priority: 'MEDIUM',
responsiblePerson: 'Empfang/Security',
implementationDate: new Date('2025-03-15'),
reviewDate: new Date('2026-03-15'),
evidence: ['demo-evi-tom-2'],
},
// Zugriffskontrolle
{
id: 'demo-tom-3',
category: 'Zugriffskontrolle',
name: 'Identity & Access Management (IAM)',
description: 'Zentrales IAM-System mit automatischer Provisionierung, Deprovisionierung und regelmäßiger Rezertifizierung aller Benutzerkonten.',
type: 'TECHNICAL',
implementationStatus: 'IMPLEMENTED',
priority: 'CRITICAL',
responsiblePerson: 'IT-Sicherheit',
implementationDate: new Date('2025-01-01'),
reviewDate: new Date('2026-01-01'),
evidence: ['demo-evi-tom-3'],
},
{
id: 'demo-tom-4',
category: 'Zugriffskontrolle',
name: 'Privileged Access Management (PAM)',
description: 'Spezielles Management für administrative Zugänge mit Session-Recording, automatischer Passwortrotation und Just-in-Time-Berechtigungen.',
type: 'TECHNICAL',
implementationStatus: 'IMPLEMENTED',
priority: 'CRITICAL',
responsiblePerson: 'IT-Sicherheit',
implementationDate: new Date('2025-04-01'),
reviewDate: new Date('2026-04-01'),
evidence: ['demo-evi-tom-4'],
},
{
id: 'demo-tom-5',
category: 'Zugriffskontrolle',
name: 'Berechtigungskonzept-Review',
description: 'Halbjährliche Überprüfung aller Berechtigungen durch die jeweiligen Fachbereichsleiter mit dokumentierter Rezertifizierung.',
type: 'ORGANIZATIONAL',
implementationStatus: 'IMPLEMENTED',
priority: 'HIGH',
responsiblePerson: 'Fachbereichsleiter',
implementationDate: new Date('2025-02-01'),
reviewDate: new Date('2026-02-01'),
evidence: ['demo-evi-tom-5'],
},
// Verschlüsselung
{
id: 'demo-tom-6',
category: 'Verschlüsselung',
name: 'Datenverschlüsselung at Rest',
description: 'AES-256 Verschlüsselung aller personenbezogenen Daten in Datenbanken und Dateisystemen. Key Management über HSM.',
type: 'TECHNICAL',
implementationStatus: 'IMPLEMENTED',
priority: 'CRITICAL',
responsiblePerson: 'IT-Sicherheit',
implementationDate: new Date('2025-01-15'),
reviewDate: new Date('2026-01-15'),
evidence: ['demo-evi-tom-6'],
},
{
id: 'demo-tom-7',
category: 'Verschlüsselung',
name: 'Transportverschlüsselung',
description: 'TLS 1.3 für alle externen Verbindungen, mTLS für interne Service-Kommunikation. Regelmäßige Überprüfung der Cipher Suites.',
type: 'TECHNICAL',
implementationStatus: 'IMPLEMENTED',
priority: 'CRITICAL',
responsiblePerson: 'IT-Sicherheit',
implementationDate: new Date('2025-01-01'),
reviewDate: new Date('2026-01-01'),
evidence: ['demo-evi-tom-7'],
},
// Pseudonymisierung
{
id: 'demo-tom-8',
category: 'Pseudonymisierung',
name: 'Pseudonymisierungs-Pipeline',
description: 'Automatisierte Pseudonymisierung von Daten vor der Verarbeitung in Analytics-Systemen. Reversible Zuordnung nur durch autorisierten Prozess.',
type: 'TECHNICAL',
implementationStatus: 'IMPLEMENTED',
priority: 'HIGH',
responsiblePerson: 'Data Engineering',
implementationDate: new Date('2025-05-01'),
reviewDate: new Date('2026-05-01'),
evidence: ['demo-evi-tom-8'],
},
// Integrität
{
id: 'demo-tom-9',
category: 'Integrität',
name: 'Datenintegritätsprüfung',
description: 'Checksummen-Validierung bei allen Datentransfers, Hash-Verifikation gespeicherter Daten, automatische Alerts bei Abweichungen.',
type: 'TECHNICAL',
implementationStatus: 'IMPLEMENTED',
priority: 'HIGH',
responsiblePerson: 'IT-Betrieb',
implementationDate: new Date('2025-03-01'),
reviewDate: new Date('2026-03-01'),
evidence: ['demo-evi-tom-9'],
},
{
id: 'demo-tom-10',
category: 'Integrität',
name: 'Change Management',
description: 'Dokumentierter Change-Prozess mit Vier-Augen-Prinzip für alle Änderungen an produktiven Systemen. CAB-Freigabe für kritische Changes.',
type: 'ORGANIZATIONAL',
implementationStatus: 'IMPLEMENTED',
priority: 'HIGH',
responsiblePerson: 'IT-Leitung',
implementationDate: new Date('2025-01-01'),
reviewDate: new Date('2026-01-01'),
evidence: ['demo-evi-tom-10'],
},
// Verfügbarkeit
{
id: 'demo-tom-11',
category: 'Verfügbarkeit',
name: 'Disaster Recovery Plan',
description: 'Dokumentierter und getesteter DR-Plan mit RTO <4h und RPO <1h. Jährliche DR-Tests mit Dokumentation.',
type: 'ORGANIZATIONAL',
implementationStatus: 'IMPLEMENTED',
priority: 'CRITICAL',
responsiblePerson: 'IT-Leitung',
implementationDate: new Date('2025-02-01'),
reviewDate: new Date('2026-02-01'),
evidence: ['demo-evi-tom-11'],
},
{
id: 'demo-tom-12',
category: 'Verfügbarkeit',
name: 'High Availability Cluster',
description: 'Aktiv-Aktiv-Cluster für alle kritischen Systeme mit automatischem Failover. 99,9% Verfügbarkeits-SLA.',
type: 'TECHNICAL',
implementationStatus: 'IMPLEMENTED',
priority: 'CRITICAL',
responsiblePerson: 'IT-Betrieb',
implementationDate: new Date('2025-01-01'),
reviewDate: new Date('2026-01-01'),
evidence: ['demo-evi-tom-12'],
},
// Belastbarkeit
{
id: 'demo-tom-13',
category: 'Belastbarkeit',
name: 'Load Balancing & Auto-Scaling',
description: 'Dynamische Skalierung basierend auf Last-Metriken. Load Balancer mit Health Checks und automatischer Traffic-Umleitung.',
type: 'TECHNICAL',
implementationStatus: 'IMPLEMENTED',
priority: 'HIGH',
responsiblePerson: 'IT-Betrieb',
implementationDate: new Date('2025-04-01'),
reviewDate: new Date('2026-04-01'),
evidence: ['demo-evi-tom-13'],
},
{
id: 'demo-tom-14',
category: 'Belastbarkeit',
name: 'DDoS-Schutz',
description: 'Cloudbasierter DDoS-Schutz mit automatischer Traffic-Filterung. Kapazität für 10x Normal-Traffic.',
type: 'TECHNICAL',
implementationStatus: 'IMPLEMENTED',
priority: 'HIGH',
responsiblePerson: 'IT-Sicherheit',
implementationDate: new Date('2025-01-01'),
reviewDate: new Date('2026-01-01'),
evidence: ['demo-evi-tom-14'],
},
// Wiederherstellbarkeit
{
id: 'demo-tom-15',
category: 'Wiederherstellbarkeit',
name: 'Backup-Strategie',
description: '3-2-1 Backup-Strategie: 3 Kopien, 2 verschiedene Medien, 1 Offsite. Tägliche inkrementelle, wöchentliche Vollbackups.',
type: 'TECHNICAL',
implementationStatus: 'IMPLEMENTED',
priority: 'CRITICAL',
responsiblePerson: 'IT-Betrieb',
implementationDate: new Date('2025-01-01'),
reviewDate: new Date('2026-01-01'),
evidence: ['demo-evi-tom-15'],
},
{
id: 'demo-tom-16',
category: 'Wiederherstellbarkeit',
name: 'Restore-Tests',
description: 'Monatliche Restore-Tests mit zufällig ausgewählten Daten. Dokumentation der Recovery-Zeit und Vollständigkeit.',
type: 'ORGANIZATIONAL',
implementationStatus: 'IMPLEMENTED',
priority: 'HIGH',
responsiblePerson: 'IT-Betrieb',
implementationDate: new Date('2025-02-01'),
reviewDate: new Date('2026-02-01'),
evidence: ['demo-evi-tom-16'],
},
// Überprüfung & Bewertung
{
id: 'demo-tom-17',
category: 'Überprüfung & Bewertung',
name: 'Penetration Tests',
description: 'Jährliche externe Penetration Tests durch zertifizierte Dienstleister. Zusätzliche Tests nach größeren Änderungen.',
type: 'ORGANIZATIONAL',
implementationStatus: 'IMPLEMENTED',
priority: 'HIGH',
responsiblePerson: 'IT-Sicherheit',
implementationDate: new Date('2025-03-01'),
reviewDate: new Date('2026-03-01'),
evidence: ['demo-evi-tom-17'],
},
{
id: 'demo-tom-18',
category: 'Überprüfung & Bewertung',
name: 'Security Awareness Training',
description: 'Verpflichtendes Security-Training für alle Mitarbeiter bei Einstellung und jährlich. Phishing-Simulationen quartalsweise.',
type: 'ORGANIZATIONAL',
implementationStatus: 'IMPLEMENTED',
priority: 'MEDIUM',
responsiblePerson: 'HR / IT-Sicherheit',
implementationDate: new Date('2025-01-15'),
reviewDate: new Date('2026-01-15'),
evidence: ['demo-evi-tom-18'],
},
// KI-spezifische TOMs
{
id: 'demo-tom-19',
category: 'KI-Governance',
name: 'Model Governance Framework',
description: 'Dokumentierter Prozess für Entwicklung, Test, Deployment und Monitoring von KI-Modellen. Model Cards für alle produktiven Modelle.',
type: 'ORGANIZATIONAL',
implementationStatus: 'IMPLEMENTED',
priority: 'HIGH',
responsiblePerson: 'Data Science Lead',
implementationDate: new Date('2025-06-01'),
reviewDate: new Date('2026-06-01'),
evidence: ['demo-evi-tom-19'],
},
{
id: 'demo-tom-20',
category: 'KI-Governance',
name: 'Bias Detection & Monitoring',
description: 'Automatisiertes Monitoring der Modell-Outputs auf Bias. Alerting bei signifikanten Abweichungen von Fairness-Metriken.',
type: 'TECHNICAL',
implementationStatus: 'IMPLEMENTED',
priority: 'HIGH',
responsiblePerson: 'Data Science Lead',
implementationDate: new Date('2025-07-01'),
reviewDate: new Date('2026-07-01'),
evidence: ['demo-evi-tom-20'],
},
]
export function getDemoTOMs(): TOM[] {
return DEMO_TOMS.map(tom => ({
...tom,
implementationDate: tom.implementationDate ? new Date(tom.implementationDate) : null,
reviewDate: tom.reviewDate ? new Date(tom.reviewDate) : null,
}))
}

View File

@@ -0,0 +1,85 @@
/**
* Demo Use Cases for AI Compliance SDK
*/
import { UseCaseAssessment, AssessmentResult } from '../types'
export const DEMO_USE_CASES: UseCaseAssessment[] = [
{
id: 'demo-uc-1',
name: 'KI-gestützte Kundenanalyse',
description: 'Analyse von Kundenverhalten und Präferenzen mittels Machine Learning zur Personalisierung von Angeboten und Verbesserung des Customer Lifetime Value. Das System verarbeitet Transaktionsdaten, Clickstreams und demographische Informationen.',
category: 'Marketing',
stepsCompleted: 5,
steps: [
{ id: 'uc1-step-1', name: 'Grunddaten', completed: true, data: { type: 'customer-analytics', department: 'Marketing' } },
{ id: 'uc1-step-2', name: 'Datenquellen', completed: true, data: { sources: ['CRM', 'Webshop', 'Newsletter'] } },
{ id: 'uc1-step-3', name: 'KI-Komponenten', completed: true, data: { algorithms: ['Clustering', 'Recommender', 'Churn-Prediction'] } },
{ id: 'uc1-step-4', name: 'Betroffene', completed: true, data: { subjects: ['Kunden', 'Interessenten'] } },
{ id: 'uc1-step-5', name: 'Risikobewertung', completed: true, data: { riskLevel: 'HIGH' } },
],
assessmentResult: {
riskLevel: 'HIGH',
applicableRegulations: ['DSGVO', 'AI Act', 'TTDSG'],
recommendedControls: ['Einwilligungsmanagement', 'Profilbildungstransparenz', 'Opt-out-Mechanismus'],
dsfaRequired: true,
aiActClassification: 'LIMITED',
},
createdAt: new Date('2026-01-15'),
updatedAt: new Date('2026-02-01'),
},
{
id: 'demo-uc-2',
name: 'Automatisierte Bewerbungsvorauswahl',
description: 'KI-System zur Vorauswahl von Bewerbungen basierend auf Lebenslauf-Analyse, Qualifikationsabgleich und Erfahrungsbewertung. Ziel ist die Effizienzsteigerung im Recruiting-Prozess bei gleichzeitiger Gewährleistung von Fairness.',
category: 'HR',
stepsCompleted: 5,
steps: [
{ id: 'uc2-step-1', name: 'Grunddaten', completed: true, data: { type: 'hr-screening', department: 'Personal' } },
{ id: 'uc2-step-2', name: 'Datenquellen', completed: true, data: { sources: ['Bewerbungsportal', 'LinkedIn', 'XING'] } },
{ id: 'uc2-step-3', name: 'KI-Komponenten', completed: true, data: { algorithms: ['NLP', 'Matching', 'Scoring'] } },
{ id: 'uc2-step-4', name: 'Betroffene', completed: true, data: { subjects: ['Bewerber'] } },
{ id: 'uc2-step-5', name: 'Risikobewertung', completed: true, data: { riskLevel: 'HIGH' } },
],
assessmentResult: {
riskLevel: 'HIGH',
applicableRegulations: ['DSGVO', 'AI Act', 'AGG'],
recommendedControls: ['Bias-Monitoring', 'Human-in-the-Loop', 'Transparenzpflichten'],
dsfaRequired: true,
aiActClassification: 'HIGH',
},
createdAt: new Date('2026-01-20'),
updatedAt: new Date('2026-02-02'),
},
{
id: 'demo-uc-3',
name: 'Chatbot für Kundenservice',
description: 'Konversationeller KI-Assistent für die automatisierte Beantwortung von Kundenanfragen im First-Level-Support. Basiert auf Large Language Models mit firmeneigenem Wissen.',
category: 'Kundenservice',
stepsCompleted: 5,
steps: [
{ id: 'uc3-step-1', name: 'Grunddaten', completed: true, data: { type: 'chatbot', department: 'Support' } },
{ id: 'uc3-step-2', name: 'Datenquellen', completed: true, data: { sources: ['FAQ', 'Wissensdatenbank', 'Ticketsystem'] } },
{ id: 'uc3-step-3', name: 'KI-Komponenten', completed: true, data: { algorithms: ['LLM', 'RAG', 'Intent-Classification'] } },
{ id: 'uc3-step-4', name: 'Betroffene', completed: true, data: { subjects: ['Kunden', 'Interessenten'] } },
{ id: 'uc3-step-5', name: 'Risikobewertung', completed: true, data: { riskLevel: 'MEDIUM' } },
],
assessmentResult: {
riskLevel: 'MEDIUM',
applicableRegulations: ['DSGVO', 'AI Act'],
recommendedControls: ['KI-Kennzeichnung', 'Übergabe an Menschen', 'Datensparsamkeit'],
dsfaRequired: false,
aiActClassification: 'LIMITED',
},
createdAt: new Date('2026-01-25'),
updatedAt: new Date('2026-02-03'),
},
]
export function getDemoUseCases(): UseCaseAssessment[] {
return DEMO_USE_CASES.map(uc => ({
...uc,
createdAt: new Date(uc.createdAt),
updatedAt: new Date(uc.updatedAt),
}))
}

View File

@@ -0,0 +1,316 @@
/**
* Demo VVT (Verarbeitungsverzeichnis / Processing Activities Register) for AI Compliance SDK
* Art. 30 DSGVO - These are seed data structures - actual data is stored in database
*/
import { ProcessingActivity, RetentionPolicy } from '../types'
export const DEMO_PROCESSING_ACTIVITIES: ProcessingActivity[] = [
{
id: 'demo-pa-1',
name: 'KI-gestützte Kundenanalyse',
purpose: 'Analyse von Kundenverhalten und Präferenzen zur Personalisierung von Angeboten, Churn-Prediction und Marketing-Optimierung',
legalBasis: 'Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse) / Art. 6 Abs. 1 lit. a DSGVO (Einwilligung für erweitertes Profiling)',
dataCategories: [
'Stammdaten (Name, Adresse, E-Mail, Telefon)',
'Transaktionsdaten (Käufe, Bestellungen, Retouren)',
'Nutzungsdaten (Clickstreams, Seitenaufrufe, Verweildauer)',
'Demographische Daten (Alter, Geschlecht, PLZ-Region)',
],
dataSubjects: [
'Bestandskunden (ca. 250.000 aktive)',
'Registrierte Interessenten (ca. 100.000)',
'Newsletter-Abonnenten (ca. 180.000)',
],
recipients: [
'Interne Fachabteilungen (Marketing, Vertrieb)',
'E-Mail-Marketing-Dienstleister (AV-Vertrag vorhanden)',
'Cloud-Infrastruktur-Anbieter (AV-Vertrag vorhanden)',
],
thirdCountryTransfers: false,
retentionPeriod: '3 Jahre nach letzter Aktivität, danach Anonymisierung',
technicalMeasures: [
'AES-256 Verschlüsselung',
'Pseudonymisierung',
'Zugriffskontrolle mit MFA',
'Audit-Logging',
],
organizationalMeasures: [
'Rollenbasiertes Berechtigungskonzept',
'Verpflichtung auf Datengeheimnis',
'Regelmäßige Datenschutzschulungen',
'Dokumentierte Prozesse',
],
},
{
id: 'demo-pa-2',
name: 'Automatisierte Bewerbungsvorauswahl',
purpose: 'KI-gestützte Vorauswahl von Bewerbungen basierend auf Lebenslauf-Analyse und Qualifikationsabgleich zur Effizienzsteigerung im Recruiting',
legalBasis: 'Art. 6 Abs. 1 lit. b DSGVO (vorvertragliche Maßnahmen) / § 26 BDSG (Beschäftigungsverhältnis)',
dataCategories: [
'Bewerberdaten (Name, Kontakt, Geburtsdatum)',
'Qualifikationen (Ausbildung, Berufserfahrung, Zertifikate)',
'Lebenslaufdaten (Werdegang, Fähigkeiten)',
'Bewerbungsschreiben',
],
dataSubjects: [
'Bewerber auf offene Stellen',
'Initiativbewerber',
],
recipients: [
'HR-Abteilung',
'Fachabteilungsleiter (nur finale Kandidaten)',
'Betriebsrat (Einsichtnahme möglich)',
],
thirdCountryTransfers: false,
retentionPeriod: '6 Monate nach Abschluss des Bewerbungsverfahrens (bei Ablehnung), länger nur mit Einwilligung für Talentpool',
technicalMeasures: [
'Verschlüsselte Speicherung',
'Zugangsbeschränkung auf HR',
'Automatische Löschroutinen',
'Bias-Monitoring',
],
organizationalMeasures: [
'Human-in-the-Loop für finale Entscheidungen',
'Dokumentierte KI-Entscheidungskriterien',
'Transparente Information an Bewerber',
'Regelmäßige Fairness-Audits',
],
},
{
id: 'demo-pa-3',
name: 'Kundenservice-Chatbot',
purpose: 'Automatisierte Beantwortung von Kundenanfragen im First-Level-Support mittels KI-gestütztem Dialogsystem',
legalBasis: 'Art. 6 Abs. 1 lit. b DSGVO (Vertragserfüllung) / Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse)',
dataCategories: [
'Kundenstammdaten (zur Identifikation)',
'Kommunikationsinhalte (Chat-Verläufe)',
'Technische Daten (Session-ID, Zeitstempel)',
'Serviceanfragen und deren Lösungen',
],
dataSubjects: [
'Kunden mit aktiven Verträgen',
'Interessenten mit Anfragen',
],
recipients: [
'Kundenservice-Team (bei Eskalation)',
'Cloud-Anbieter (Hosting, AV-Vertrag)',
],
thirdCountryTransfers: false,
retentionPeriod: '2 Jahre für Chat-Verläufe, danach Anonymisierung für Training',
technicalMeasures: [
'TLS-Verschlüsselung',
'Keine Speicherung sensitiver Daten im Chat',
'Automatische PII-Erkennung und Maskierung',
],
organizationalMeasures: [
'Klare KI-Kennzeichnung gegenüber Kunden',
'Jederzeit Übergabe an Menschen möglich',
'Schulung des Eskalations-Teams',
],
},
{
id: 'demo-pa-4',
name: 'Mitarbeiterverwaltung',
purpose: 'Verwaltung von Personalstammdaten, Gehaltsabrechnung, Zeiterfassung und Personalentwicklung',
legalBasis: 'Art. 6 Abs. 1 lit. b DSGVO (Arbeitsvertrag) / § 26 BDSG (Beschäftigungsverhältnis) / gesetzliche Pflichten (Steuer, SV)',
dataCategories: [
'Personalstammdaten (Name, Adresse, Geburtsdatum, SV-Nr.)',
'Vertragsdaten (Arbeitsvertrag, Gehalt, Arbeitszeit)',
'Zeiterfassungsdaten',
'Leistungsbeurteilungen',
'Bankverbindung',
],
dataSubjects: [
'Aktive Mitarbeiter',
'Ehemalige Mitarbeiter (Archiv)',
],
recipients: [
'HR-Abteilung',
'Lohnbuchhaltung / Steuerberater',
'Sozialversicherungsträger',
'Finanzamt',
],
thirdCountryTransfers: false,
retentionPeriod: '10 Jahre nach Ausscheiden (steuerliche Aufbewahrungspflichten)',
technicalMeasures: [
'Verschlüsselte Speicherung',
'Strenge Zugriffskontrolle',
'Getrennte Systeme für verschiedene Datenkategorien',
],
organizationalMeasures: [
'Need-to-know-Prinzip',
'Dokumentierte Prozesse',
'Betriebsvereinbarung zur Datenverarbeitung',
],
},
{
id: 'demo-pa-5',
name: 'Website-Analyse und Marketing',
purpose: 'Analyse des Nutzerverhaltens auf der Website zur Optimierung der User Experience und für personalisierte Marketing-Maßnahmen',
legalBasis: 'Art. 6 Abs. 1 lit. a DSGVO (Einwilligung via Cookie-Banner)',
dataCategories: [
'Pseudonymisierte Nutzungsdaten',
'Cookie-IDs und Tracking-Identifier',
'Geräteinformationen',
'Interaktionsdaten (Klicks, Scrollverhalten)',
],
dataSubjects: [
'Website-Besucher (nur mit Einwilligung)',
],
recipients: [
'Marketing-Team',
'Analytics-Anbieter (AV-Vertrag)',
'Advertising-Partner (nur mit erweiterter Einwilligung)',
],
thirdCountryTransfers: true,
retentionPeriod: '13 Monate für Analytics-Daten, Cookie-Laufzeit max. 12 Monate',
technicalMeasures: [
'IP-Anonymisierung',
'Secure Cookies',
'Consent-Management-System',
],
organizationalMeasures: [
'Transparente Cookie-Richtlinie',
'Einfacher Widerruf möglich',
'Regelmäßige Cookie-Audits',
],
},
{
id: 'demo-pa-6',
name: 'Videoüberwachung',
purpose: 'Schutz von Eigentum und Personen, Prävention und Aufklärung von Straftaten in Geschäftsräumen',
legalBasis: 'Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse an Sicherheit)',
dataCategories: [
'Videoaufnahmen',
'Zeitstempel',
'Aufnahmeort',
],
dataSubjects: [
'Mitarbeiter in überwachten Bereichen',
'Besucher und Kunden',
'Lieferanten',
],
recipients: [
'Sicherheitspersonal',
'Geschäftsleitung (bei Vorfällen)',
'Strafverfolgungsbehörden (auf Anforderung)',
],
thirdCountryTransfers: false,
retentionPeriod: '72 Stunden, bei Vorfällen bis zur Abschluss der Untersuchung',
technicalMeasures: [
'Verschlüsselte Speicherung',
'Automatische Löschung nach Fristablauf',
'Eingeschränkter Zugriff',
],
organizationalMeasures: [
'Beschilderung der überwachten Bereiche',
'Betriebsvereinbarung mit Betriebsrat',
'Dokumentiertes Einsichtsprotokoll',
],
},
]
export const DEMO_RETENTION_POLICIES: RetentionPolicy[] = [
{
id: 'demo-ret-1',
dataCategory: 'Kundenstammdaten',
description: 'Grundlegende Daten zur Kundenidentifikation (Name, Adresse, Kontaktdaten)',
legalBasis: 'Handels- und steuerrechtliche Aufbewahrungspflichten (§ 257 HGB, § 147 AO)',
retentionPeriod: '10 Jahre nach Vertragsende',
deletionMethod: 'Sichere Löschung mit Protokollierung, bei Papier: Aktenvernichtung DIN 66399',
exceptions: [
'Laufende Rechtsstreitigkeiten',
'Offene Forderungen',
],
},
{
id: 'demo-ret-2',
dataCategory: 'Transaktionsdaten',
description: 'Bestellungen, Rechnungen, Zahlungen, Lieferungen',
legalBasis: '§ 257 HGB, § 147 AO (handels- und steuerrechtliche Aufbewahrung)',
retentionPeriod: '10 Jahre ab Ende des Geschäftsjahres',
deletionMethod: 'Automatisierte Löschung nach Fristablauf',
exceptions: [
'Garantiefälle (bis Ende der Garantiezeit)',
'Prüfungen durch Finanzbehörden',
],
},
{
id: 'demo-ret-3',
dataCategory: 'Bewerberdaten',
description: 'Lebenslauf, Anschreiben, Zeugnisse, Korrespondenz',
legalBasis: 'AGG (Diskriminierungsschutz) / § 26 BDSG',
retentionPeriod: '6 Monate nach Abschluss des Verfahrens',
deletionMethod: 'Sichere Löschung, bei Papier: Aktenvernichtung',
exceptions: [
'Aufnahme in Talentpool (mit Einwilligung): 2 Jahre',
'Diskriminierungsklagen: bis Abschluss',
],
},
{
id: 'demo-ret-4',
dataCategory: 'Personalakten',
description: 'Arbeitsverträge, Gehaltsabrechnungen, Beurteilungen, Abmahnungen',
legalBasis: '§ 257 HGB, § 147 AO, Sozialversicherungsrecht',
retentionPeriod: '10 Jahre nach Ausscheiden (teilweise 30 Jahre für Rentenansprüche)',
deletionMethod: 'Sichere Löschung mit Dokumentation',
exceptions: [
'Arbeitsrechtliche Streitigkeiten',
'Rentenversicherungsnachweise (lebenslang empfohlen)',
],
},
{
id: 'demo-ret-5',
dataCategory: 'Marketing-Profile',
description: 'Analysedaten, Segmentierungen, Präferenzen, Kaufhistorie',
legalBasis: 'Einwilligung (Art. 6 Abs. 1 lit. a DSGVO)',
retentionPeriod: '3 Jahre nach letzter Aktivität, dann Anonymisierung',
deletionMethod: 'Pseudonymisierung → Anonymisierung → Löschung',
exceptions: [
'Widerruf der Einwilligung (sofortige Löschung)',
],
},
{
id: 'demo-ret-6',
dataCategory: 'Videoaufnahmen',
description: 'Aufnahmen der Sicherheitskameras',
legalBasis: 'Berechtigtes Interesse (Art. 6 Abs. 1 lit. f DSGVO)',
retentionPeriod: '72 Stunden',
deletionMethod: 'Automatisches Überschreiben',
exceptions: [
'Sicherheitsvorfälle (bis Abschluss der Untersuchung)',
'Anforderung durch Strafverfolgungsbehörden',
],
},
{
id: 'demo-ret-7',
dataCategory: 'KI-Trainingsdaten',
description: 'Anonymisierte Datensätze für Modell-Training',
legalBasis: 'Berechtigtes Interesse / ursprüngliche Zweckbindung (bei Kompatibilität)',
retentionPeriod: 'Solange Modell aktiv, danach Löschung mit Modell-Archivierung',
deletionMethod: 'Sichere Löschung bei Modell-Retirement',
exceptions: [
'Audit-Trail für Modell-Herkunft (anonymisierte Metadaten)',
],
},
{
id: 'demo-ret-8',
dataCategory: 'Audit-Logs',
description: 'Protokolle von Datenzugriffen und Systemereignissen',
legalBasis: 'Nachweispflichten DSGVO, Compliance-Anforderungen',
retentionPeriod: '10 Jahre',
deletionMethod: 'Automatisierte Löschung nach Fristablauf',
exceptions: [
'Laufende Untersuchungen oder Audits',
],
},
]
export function getDemoProcessingActivities(): ProcessingActivity[] {
return DEMO_PROCESSING_ACTIVITIES
}
export function getDemoRetentionPolicies(): RetentionPolicy[] {
return DEMO_RETENTION_POLICIES
}

View File

@@ -0,0 +1,548 @@
/**
* Helper-Funktionen für die Integration von Einwilligungen-Datenpunkten
* in den Dokumentengenerator.
*
* Diese Funktionen generieren DSGVO-konforme Textbausteine basierend auf
* den vom Benutzer ausgewählten Datenpunkten.
*/
import {
DataPoint,
DataPointCategory,
LegalBasis,
RetentionPeriod,
RiskLevel,
CATEGORY_METADATA,
LEGAL_BASIS_INFO,
RETENTION_PERIOD_INFO,
RISK_LEVEL_STYLING,
LocalizedText,
SupportedLanguage
} from '@/lib/sdk/einwilligungen/types'
// =============================================================================
// TYPES
// =============================================================================
/**
* Sprach-Option für alle Helper-Funktionen
*/
export type Language = SupportedLanguage
/**
* Generierte Platzhalter-Map für den Dokumentengenerator
*/
export interface DataPointPlaceholders {
'[DATENPUNKTE_COUNT]': string
'[DATENPUNKTE_LIST]': string
'[DATENPUNKTE_TABLE]': string
'[VERARBEITUNGSZWECKE]': string
'[RECHTSGRUNDLAGEN]': string
'[SPEICHERFRISTEN]': string
'[EMPFAENGER]': string
'[BESONDERE_KATEGORIEN]': string
'[DRITTLAND_TRANSFERS]': string
'[RISIKO_ZUSAMMENFASSUNG]': string
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
/**
* Extrahiert Text aus LocalizedText basierend auf Sprache
*/
function getText(text: LocalizedText, lang: Language): string {
return text[lang] || text.de
}
/**
* Generiert eine Markdown-Tabelle der Datenpunkte
*
* @param dataPoints - Liste der ausgewählten Datenpunkte
* @param lang - Sprache für die Ausgabe
* @returns Markdown-Tabelle als String
*/
export function generateDataPointsTable(
dataPoints: DataPoint[],
lang: Language = 'de'
): string {
if (dataPoints.length === 0) {
return lang === 'de'
? '*Keine Datenpunkte ausgewählt.*'
: '*No data points selected.*'
}
const header = lang === 'de'
? '| Datenpunkt | Kategorie | Zweck | Rechtsgrundlage | Speicherfrist |'
: '| Data Point | Category | Purpose | Legal Basis | Retention Period |'
const separator = '|------------|-----------|-------|-----------------|---------------|'
const rows = dataPoints.map(dp => {
const category = CATEGORY_METADATA[dp.category]
const legalBasis = LEGAL_BASIS_INFO[dp.legalBasis]
const retention = RETENTION_PERIOD_INFO[dp.retentionPeriod]
const name = getText(dp.name, lang)
const categoryName = getText(category.name, lang)
const purpose = getText(dp.purpose, lang)
const legalBasisName = getText(legalBasis.name, lang)
const retentionLabel = getText(retention.label, lang)
// Truncate long texts for table readability
const truncatedPurpose = purpose.length > 50 ? purpose.slice(0, 47) + '...' : purpose
return `| ${name} | ${categoryName} | ${truncatedPurpose} | ${legalBasisName} | ${retentionLabel} |`
}).join('\n')
return `${header}\n${separator}\n${rows}`
}
/**
* Gruppiert Datenpunkte nach Speicherfrist
*
* @param dataPoints - Liste der Datenpunkte
* @returns Record mit Speicherfrist als Key und Datenpunkten als Value
*/
export function groupByRetention(
dataPoints: DataPoint[]
): Record<RetentionPeriod, DataPoint[]> {
return dataPoints.reduce((acc, dp) => {
const key = dp.retentionPeriod
if (!acc[key]) {
acc[key] = []
}
acc[key].push(dp)
return acc
}, {} as Record<RetentionPeriod, DataPoint[]>)
}
/**
* Gruppiert Datenpunkte nach Kategorie
*
* @param dataPoints - Liste der Datenpunkte
* @returns Record mit Kategorie als Key und Datenpunkten als Value
*/
export function groupByCategory(
dataPoints: DataPoint[]
): Record<DataPointCategory, DataPoint[]> {
return dataPoints.reduce((acc, dp) => {
const key = dp.category
if (!acc[key]) {
acc[key] = []
}
acc[key].push(dp)
return acc
}, {} as Record<DataPointCategory, DataPoint[]>)
}
/**
* Generiert DSGVO-konformen Abschnitt für besondere Kategorien (Art. 9 DSGVO)
*
* @param dataPoints - Liste der Datenpunkte
* @param lang - Sprache für die Ausgabe
* @returns Markdown-Abschnitt als String (leer wenn keine Art. 9 Daten)
*/
export function generateSpecialCategorySection(
dataPoints: DataPoint[],
lang: Language = 'de'
): string {
const special = dataPoints.filter(dp => dp.isSpecialCategory)
if (special.length === 0) {
return ''
}
if (lang === 'de') {
const items = special.map(dp =>
`- **${getText(dp.name, lang)}**: ${getText(dp.description, lang)}`
).join('\n')
return `## Verarbeitung besonderer Kategorien personenbezogener Daten (Art. 9 DSGVO)
Wir verarbeiten folgende besondere Kategorien personenbezogener Daten:
${items}
Die Verarbeitung erfolgt auf Grundlage Ihrer ausdrücklichen Einwilligung gemäß Art. 9 Abs. 2 lit. a DSGVO. Sie können Ihre Einwilligung jederzeit mit Wirkung für die Zukunft widerrufen.`
} else {
const items = special.map(dp =>
`- **${getText(dp.name, lang)}**: ${getText(dp.description, lang)}`
).join('\n')
return `## Processing of Special Categories of Personal Data (Art. 9 GDPR)
We process the following special categories of personal data:
${items}
Processing is based on your explicit consent pursuant to Art. 9(2)(a) GDPR. You may withdraw your consent at any time with effect for the future.`
}
}
/**
* Generiert Liste aller eindeutigen Verarbeitungszwecke
*
* @param dataPoints - Liste der Datenpunkte
* @param lang - Sprache für die Ausgabe
* @returns Kommaseparierte Liste der Zwecke
*/
export function generatePurposesList(
dataPoints: DataPoint[],
lang: Language = 'de'
): string {
const purposes = new Set<string>()
dataPoints.forEach(dp => {
purposes.add(getText(dp.purpose, lang))
})
return [...purposes].join(', ')
}
/**
* Generiert Liste aller verwendeten Rechtsgrundlagen
*
* @param dataPoints - Liste der Datenpunkte
* @param lang - Sprache für die Ausgabe
* @returns Formatierte Liste der Rechtsgrundlagen
*/
export function generateLegalBasisList(
dataPoints: DataPoint[],
lang: Language = 'de'
): string {
const bases = new Set<LegalBasis>()
dataPoints.forEach(dp => {
bases.add(dp.legalBasis)
})
return [...bases].map(basis => {
const info = LEGAL_BASIS_INFO[basis]
return `${info.article} (${getText(info.name, lang)})`
}).join(', ')
}
/**
* Generiert Liste aller Speicherfristen gruppiert
*
* @param dataPoints - Liste der Datenpunkte
* @param lang - Sprache für die Ausgabe
* @returns Formatierte Liste der Speicherfristen mit zugehörigen Kategorien
*/
export function generateRetentionList(
dataPoints: DataPoint[],
lang: Language = 'de'
): string {
const grouped = groupByRetention(dataPoints)
const entries: string[] = []
for (const [period, points] of Object.entries(grouped)) {
const retentionInfo = RETENTION_PERIOD_INFO[period as RetentionPeriod]
const categories = [...new Set(points.map(p => getText(CATEGORY_METADATA[p.category].name, lang)))]
entries.push(`${getText(retentionInfo.label, lang)}: ${categories.join(', ')}`)
}
return entries.join('; ')
}
/**
* Generiert Liste aller Empfänger/Drittparteien
*
* @param dataPoints - Liste der Datenpunkte
* @returns Kommaseparierte Liste der Empfänger
*/
export function generateRecipientsList(dataPoints: DataPoint[]): string {
const recipients = new Set<string>()
dataPoints.forEach(dp => {
dp.thirdPartyRecipients?.forEach(r => recipients.add(r))
})
if (recipients.size === 0) {
return ''
}
return [...recipients].join(', ')
}
/**
* Generiert Abschnitt für Drittland-Übermittlungen
*
* @param dataPoints - Liste der Datenpunkte mit thirdCountryTransfer === true
* @param lang - Sprache für die Ausgabe
* @returns Markdown-Abschnitt als String
*/
export function generateThirdCountrySection(
dataPoints: DataPoint[],
lang: Language = 'de'
): string {
// Note: We assume dataPoints have been filtered for thirdCountryTransfer
// The actual flag would need to be added to the DataPoint interface
// For now, we check if any thirdPartyRecipients suggest third country
const thirdCountryIndicators = ['Google', 'AWS', 'Microsoft', 'Meta', 'Facebook', 'Cloudflare']
const thirdCountryPoints = dataPoints.filter(dp =>
dp.thirdPartyRecipients?.some(r =>
thirdCountryIndicators.some(indicator =>
r.toLowerCase().includes(indicator.toLowerCase())
)
)
)
if (thirdCountryPoints.length === 0) {
return ''
}
const recipients = new Set<string>()
thirdCountryPoints.forEach(dp => {
dp.thirdPartyRecipients?.forEach(r => {
if (thirdCountryIndicators.some(i => r.toLowerCase().includes(i.toLowerCase()))) {
recipients.add(r)
}
})
})
if (lang === 'de') {
return `## Übermittlung in Drittländer
Wir übermitteln personenbezogene Daten an folgende Empfänger in Drittländern (außerhalb der EU/des EWR):
${[...recipients].map(r => `- ${r}`).join('\n')}
Die Übermittlung erfolgt auf Grundlage von Standardvertragsklauseln (Art. 46 Abs. 2 lit. c DSGVO) bzw. einem Angemessenheitsbeschluss der EU-Kommission (Art. 45 DSGVO).`
} else {
return `## Transfers to Third Countries
We transfer personal data to the following recipients in third countries (outside the EU/EEA):
${[...recipients].map(r => `- ${r}`).join('\n')}
The transfer is based on Standard Contractual Clauses (Art. 46(2)(c) GDPR) or an adequacy decision by the EU Commission (Art. 45 GDPR).`
}
}
/**
* Generiert Risiko-Zusammenfassung
*
* @param dataPoints - Liste der Datenpunkte
* @param lang - Sprache für die Ausgabe
* @returns Formatierte Risiko-Zusammenfassung
*/
export function generateRiskSummary(
dataPoints: DataPoint[],
lang: Language = 'de'
): string {
const riskCounts: Record<RiskLevel, number> = {
LOW: 0,
MEDIUM: 0,
HIGH: 0
}
dataPoints.forEach(dp => {
riskCounts[dp.riskLevel]++
})
const parts = Object.entries(riskCounts)
.filter(([, count]) => count > 0)
.map(([level, count]) => {
const styling = RISK_LEVEL_STYLING[level as RiskLevel]
return `${count} ${getText(styling.label, lang).toLowerCase()}`
})
return parts.join(', ')
}
/**
* Generiert alle Platzhalter für den Dokumentengenerator
*
* @param dataPoints - Liste der ausgewählten Datenpunkte
* @param lang - Sprache für die Ausgabe
* @returns Objekt mit allen Platzhaltern
*/
export function generateAllPlaceholders(
dataPoints: DataPoint[],
lang: Language = 'de'
): DataPointPlaceholders {
return {
'[DATENPUNKTE_COUNT]': String(dataPoints.length),
'[DATENPUNKTE_LIST]': dataPoints.map(dp => getText(dp.name, lang)).join(', '),
'[DATENPUNKTE_TABLE]': generateDataPointsTable(dataPoints, lang),
'[VERARBEITUNGSZWECKE]': generatePurposesList(dataPoints, lang),
'[RECHTSGRUNDLAGEN]': generateLegalBasisList(dataPoints, lang),
'[SPEICHERFRISTEN]': generateRetentionList(dataPoints, lang),
'[EMPFAENGER]': generateRecipientsList(dataPoints),
'[BESONDERE_KATEGORIEN]': generateSpecialCategorySection(dataPoints, lang),
'[DRITTLAND_TRANSFERS]': generateThirdCountrySection(dataPoints, lang),
'[RISIKO_ZUSAMMENFASSUNG]': generateRiskSummary(dataPoints, lang)
}
}
// =============================================================================
// VALIDATION HELPERS
// =============================================================================
/**
* Validierungswarnung für den Dokumentengenerator
*/
export interface ValidationWarning {
type: 'error' | 'warning' | 'info'
code: string
message: string
suggestion: string
affectedDataPoints?: DataPoint[]
}
/**
* Prüft ob besondere Kategorien vorhanden sind aber kein entsprechender Abschnitt
*
* @param dataPoints - Liste der Datenpunkte
* @param documentContent - Der generierte Dokumentinhalt
* @param lang - Sprache
* @returns ValidationWarning oder null
*/
export function checkSpecialCategoriesWarning(
dataPoints: DataPoint[],
documentContent: string,
lang: Language = 'de'
): ValidationWarning | null {
const specialCategories = dataPoints.filter(dp => dp.isSpecialCategory)
if (specialCategories.length === 0) {
return null
}
const hasSection = lang === 'de'
? documentContent.includes('Art. 9') || documentContent.includes('Artikel 9') || documentContent.includes('besondere Kategorie')
: documentContent.includes('Art. 9') || documentContent.includes('Article 9') || documentContent.includes('special categor')
if (!hasSection) {
return {
type: 'error',
code: 'MISSING_ART9_SECTION',
message: lang === 'de'
? `${specialCategories.length} besondere Datenkategorien (Art. 9 DSGVO) ausgewählt, aber kein entsprechender Abschnitt im Dokument gefunden.`
: `${specialCategories.length} special data categories (Art. 9 GDPR) selected, but no corresponding section found in document.`,
suggestion: lang === 'de'
? 'Fügen Sie einen Abschnitt zu besonderen Kategorien personenbezogener Daten hinzu oder verwenden Sie [BESONDERE_KATEGORIEN] als Platzhalter.'
: 'Add a section about special categories of personal data or use [BESONDERE_KATEGORIEN] as placeholder.',
affectedDataPoints: specialCategories
}
}
return null
}
/**
* Prüft ob Drittland-Übermittlungen vorhanden sind aber keine SCC erwähnt werden
*
* @param dataPoints - Liste der Datenpunkte
* @param documentContent - Der generierte Dokumentinhalt
* @param lang - Sprache
* @returns ValidationWarning oder null
*/
export function checkThirdCountryWarning(
dataPoints: DataPoint[],
documentContent: string,
lang: Language = 'de'
): ValidationWarning | null {
const thirdCountryIndicators = ['Google', 'AWS', 'Microsoft', 'Meta', 'Facebook', 'Cloudflare', 'USA', 'US']
const thirdCountryPoints = dataPoints.filter(dp =>
dp.thirdPartyRecipients?.some(r =>
thirdCountryIndicators.some(i => r.toLowerCase().includes(i.toLowerCase()))
)
)
if (thirdCountryPoints.length === 0) {
return null
}
const hasSCCMention = lang === 'de'
? documentContent.includes('Standardvertragsklauseln') || documentContent.includes('SCC') || documentContent.includes('Art. 46')
: documentContent.includes('Standard Contractual Clauses') || documentContent.includes('SCC') || documentContent.includes('Art. 46')
if (!hasSCCMention) {
return {
type: 'warning',
code: 'MISSING_SCC_SECTION',
message: lang === 'de'
? `Drittland-Übermittlung für ${thirdCountryPoints.length} Datenpunkte erkannt, aber keine Standardvertragsklauseln (SCC) erwähnt.`
: `Third country transfer detected for ${thirdCountryPoints.length} data points, but no Standard Contractual Clauses (SCC) mentioned.`,
suggestion: lang === 'de'
? 'Erwägen Sie die Aufnahme eines Abschnitts zu Drittland-Übermittlungen und Standardvertragsklauseln oder verwenden Sie [DRITTLAND_TRANSFERS] als Platzhalter.'
: 'Consider adding a section about third country transfers and Standard Contractual Clauses or use [DRITTLAND_TRANSFERS] as placeholder.',
affectedDataPoints: thirdCountryPoints
}
}
return null
}
/**
* Prüft ob Datenpunkte mit expliziter Einwilligung korrekt behandelt werden
*
* @param dataPoints - Liste der Datenpunkte
* @param documentContent - Der generierte Dokumentinhalt
* @param lang - Sprache
* @returns ValidationWarning oder null
*/
export function checkExplicitConsentWarning(
dataPoints: DataPoint[],
documentContent: string,
lang: Language = 'de'
): ValidationWarning | null {
const explicitConsentPoints = dataPoints.filter(dp => dp.requiresExplicitConsent)
if (explicitConsentPoints.length === 0) {
return null
}
const hasConsentSection = lang === 'de'
? documentContent.includes('Einwilligung') || documentContent.includes('Widerruf') || documentContent.includes('Art. 7')
: documentContent.includes('consent') || documentContent.includes('withdraw') || documentContent.includes('Art. 7')
if (!hasConsentSection) {
return {
type: 'warning',
code: 'MISSING_CONSENT_SECTION',
message: lang === 'de'
? `${explicitConsentPoints.length} Datenpunkte erfordern ausdrückliche Einwilligung, aber kein Abschnitt zu Einwilligung/Widerruf gefunden.`
: `${explicitConsentPoints.length} data points require explicit consent, but no section about consent/withdrawal found.`,
suggestion: lang === 'de'
? 'Fügen Sie einen Abschnitt zum Widerrufsrecht hinzu.'
: 'Add a section about the right to withdraw consent.',
affectedDataPoints: explicitConsentPoints
}
}
return null
}
/**
* Führt alle Validierungsprüfungen durch
*
* @param dataPoints - Liste der Datenpunkte
* @param documentContent - Der generierte Dokumentinhalt
* @param lang - Sprache
* @returns Array aller Warnungen
*/
export function validateDocument(
dataPoints: DataPoint[],
documentContent: string,
lang: Language = 'de'
): ValidationWarning[] {
const warnings: ValidationWarning[] = []
const specialCatWarning = checkSpecialCategoriesWarning(dataPoints, documentContent, lang)
if (specialCatWarning) warnings.push(specialCatWarning)
const thirdCountryWarning = checkThirdCountryWarning(dataPoints, documentContent, lang)
if (thirdCountryWarning) warnings.push(thirdCountryWarning)
const consentWarning = checkExplicitConsentWarning(dataPoints, documentContent, lang)
if (consentWarning) warnings.push(consentWarning)
return warnings
}

View File

@@ -0,0 +1,8 @@
/**
* Document Generator Library
*
* Helper-Funktionen für die Integration von Einwilligungen-Datenpunkten
* in den Dokumentengenerator.
*/
export * from './datapoint-helpers'

View File

@@ -1,355 +0,0 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
// Mock fetch globally
const mockFetch = vi.fn()
global.fetch = mockFetch
describe('DSFA API Client', () => {
beforeEach(() => {
mockFetch.mockClear()
})
afterEach(() => {
vi.restoreAllMocks()
})
describe('listDSFAs', () => {
it('should fetch DSFAs without status filter', async () => {
const mockDSFAs = [
{ id: 'dsfa-1', name: 'Test DSFA 1', status: 'draft' },
{ id: 'dsfa-2', name: 'Test DSFA 2', status: 'approved' },
]
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ dsfas: mockDSFAs }),
})
const { listDSFAs } = await import('../api')
const result = await listDSFAs()
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result).toHaveLength(2)
expect(result[0].name).toBe('Test DSFA 1')
})
it('should fetch DSFAs with status filter', async () => {
const mockDSFAs = [{ id: 'dsfa-1', name: 'Draft DSFA', status: 'draft' }]
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ dsfas: mockDSFAs }),
})
const { listDSFAs } = await import('../api')
const result = await listDSFAs('draft')
expect(mockFetch).toHaveBeenCalledTimes(1)
const calledUrl = mockFetch.mock.calls[0][0]
expect(calledUrl).toContain('status=draft')
})
it('should return empty array when no DSFAs', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ dsfas: null }),
})
const { listDSFAs } = await import('../api')
const result = await listDSFAs()
expect(result).toEqual([])
})
})
describe('getDSFA', () => {
it('should fetch a single DSFA by ID', async () => {
const mockDSFA = {
id: 'dsfa-123',
name: 'Test DSFA',
status: 'draft',
risks: [],
mitigations: [],
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(mockDSFA),
})
const { getDSFA } = await import('../api')
const result = await getDSFA('dsfa-123')
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result.id).toBe('dsfa-123')
expect(result.name).toBe('Test DSFA')
})
it('should throw error for non-existent DSFA', async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
status: 404,
text: () => Promise.resolve('{"error": "DSFA not found"}'),
})
const { getDSFA } = await import('../api')
await expect(getDSFA('non-existent')).rejects.toThrow()
})
})
describe('createDSFA', () => {
it('should create a new DSFA', async () => {
const newDSFA = {
name: 'New DSFA',
description: 'Test description',
processing_purpose: 'Testing',
}
const createdDSFA = {
id: 'dsfa-new',
...newDSFA,
status: 'draft',
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(createdDSFA),
})
const { createDSFA } = await import('../api')
const result = await createDSFA(newDSFA)
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result.id).toBe('dsfa-new')
expect(result.name).toBe('New DSFA')
})
})
describe('updateDSFA', () => {
it('should update an existing DSFA', async () => {
const updates = {
name: 'Updated DSFA Name',
processing_purpose: 'Updated purpose',
}
const updatedDSFA = {
id: 'dsfa-123',
...updates,
status: 'draft',
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(updatedDSFA),
})
const { updateDSFA } = await import('../api')
const result = await updateDSFA('dsfa-123', updates)
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result.name).toBe('Updated DSFA Name')
})
})
describe('deleteDSFA', () => {
it('should delete a DSFA', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
})
const { deleteDSFA } = await import('../api')
await deleteDSFA('dsfa-123')
expect(mockFetch).toHaveBeenCalledTimes(1)
const calledConfig = mockFetch.mock.calls[0][1]
expect(calledConfig.method).toBe('DELETE')
})
it('should throw error when deletion fails', async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
statusText: 'Not Found',
})
const { deleteDSFA } = await import('../api')
await expect(deleteDSFA('non-existent')).rejects.toThrow()
})
})
describe('updateDSFASection', () => {
it('should update a specific section', async () => {
const sectionData = {
processing_purpose: 'Updated purpose',
data_categories: ['personal_data', 'contact_data'],
}
const updatedDSFA = {
id: 'dsfa-123',
section_progress: {
section_1_complete: true,
},
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(updatedDSFA),
})
const { updateDSFASection } = await import('../api')
const result = await updateDSFASection('dsfa-123', 1, sectionData)
expect(mockFetch).toHaveBeenCalledTimes(1)
const calledUrl = mockFetch.mock.calls[0][0]
expect(calledUrl).toContain('/sections/1')
})
})
describe('submitDSFAForReview', () => {
it('should submit DSFA for review', async () => {
const response = {
message: 'DSFA submitted for review',
dsfa: {
id: 'dsfa-123',
status: 'in_review',
},
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(response),
})
const { submitDSFAForReview } = await import('../api')
const result = await submitDSFAForReview('dsfa-123')
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result.dsfa.status).toBe('in_review')
})
})
describe('approveDSFA', () => {
it('should approve a DSFA', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ message: 'DSFA approved' }),
})
const { approveDSFA } = await import('../api')
const result = await approveDSFA('dsfa-123', {
dpo_opinion: 'Approved after review',
approved: true,
})
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result.message).toBe('DSFA approved')
})
it('should reject a DSFA', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ message: 'DSFA rejected' }),
})
const { approveDSFA } = await import('../api')
const result = await approveDSFA('dsfa-123', {
dpo_opinion: 'Needs more details',
approved: false,
})
expect(result.message).toBe('DSFA rejected')
})
})
describe('getDSFAStats', () => {
it('should fetch DSFA statistics', async () => {
const stats = {
total: 10,
status_stats: {
draft: 4,
in_review: 2,
approved: 3,
rejected: 1,
},
risk_stats: {
low: 3,
medium: 4,
high: 2,
very_high: 1,
},
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(stats),
})
const { getDSFAStats } = await import('../api')
const result = await getDSFAStats()
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result.total).toBe(10)
expect(result.status_stats.approved).toBe(3)
})
})
describe('createDSFAFromAssessment', () => {
it('should create DSFA from UCCA assessment', async () => {
const response = {
dsfa: {
id: 'dsfa-new',
name: 'AI Chatbot DSFA',
status: 'draft',
},
prefilled: true,
message: 'DSFA created from assessment',
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(response),
})
const { createDSFAFromAssessment } = await import('../api')
const result = await createDSFAFromAssessment('assessment-123')
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(result.prefilled).toBe(true)
expect(result.dsfa.id).toBe('dsfa-new')
})
})
describe('getDSFAByAssessment', () => {
it('should return DSFA linked to assessment', async () => {
const dsfa = {
id: 'dsfa-123',
assessment_id: 'assessment-123',
name: 'Linked DSFA',
}
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(dsfa),
})
const { getDSFAByAssessment } = await import('../api')
const result = await getDSFAByAssessment('assessment-123')
expect(result?.id).toBe('dsfa-123')
})
it('should return null when no DSFA exists for assessment', async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
status: 404,
text: () => Promise.resolve('Not found'),
})
const { getDSFAByAssessment } = await import('../api')
const result = await getDSFAByAssessment('no-dsfa-assessment')
expect(result).toBeNull()
})
})
})

View File

@@ -1,255 +0,0 @@
import { describe, it, expect } from 'vitest'
import {
calculateRiskLevel,
DSFA_SECTIONS,
DSFA_STATUS_LABELS,
DSFA_RISK_LEVEL_LABELS,
DSFA_LEGAL_BASES,
DSFA_AFFECTED_RIGHTS,
RISK_MATRIX,
type DSFARisk,
type DSFAMitigation,
type DSFASectionProgress,
type DSFA,
} from '../types'
describe('DSFA_SECTIONS', () => {
it('should have 5 sections defined', () => {
expect(DSFA_SECTIONS.length).toBe(5)
})
it('should have sections numbered 1-5', () => {
const numbers = DSFA_SECTIONS.map(s => s.number)
expect(numbers).toEqual([1, 2, 3, 4, 5])
})
it('should have GDPR references for all sections', () => {
DSFA_SECTIONS.forEach(section => {
expect(section.gdprRef).toBeDefined()
expect(section.gdprRef).toContain('Art. 35')
})
})
it('should mark first 4 sections as required', () => {
const requiredSections = DSFA_SECTIONS.filter(s => s.required)
expect(requiredSections.length).toBe(4)
expect(requiredSections.map(s => s.number)).toEqual([1, 2, 3, 4])
})
it('should mark section 5 as optional', () => {
const section5 = DSFA_SECTIONS.find(s => s.number === 5)
expect(section5?.required).toBe(false)
})
it('should have German titles for all sections', () => {
DSFA_SECTIONS.forEach(section => {
expect(section.titleDE).toBeDefined()
expect(section.titleDE.length).toBeGreaterThan(0)
})
})
})
describe('DSFA_STATUS_LABELS', () => {
it('should have all status labels defined', () => {
expect(DSFA_STATUS_LABELS.draft).toBe('Entwurf')
expect(DSFA_STATUS_LABELS.in_review).toBe('In Prüfung')
expect(DSFA_STATUS_LABELS.approved).toBe('Genehmigt')
expect(DSFA_STATUS_LABELS.rejected).toBe('Abgelehnt')
expect(DSFA_STATUS_LABELS.needs_update).toBe('Überarbeitung erforderlich')
})
})
describe('DSFA_RISK_LEVEL_LABELS', () => {
it('should have all risk level labels defined', () => {
expect(DSFA_RISK_LEVEL_LABELS.low).toBe('Niedrig')
expect(DSFA_RISK_LEVEL_LABELS.medium).toBe('Mittel')
expect(DSFA_RISK_LEVEL_LABELS.high).toBe('Hoch')
expect(DSFA_RISK_LEVEL_LABELS.very_high).toBe('Sehr Hoch')
})
})
describe('DSFA_LEGAL_BASES', () => {
it('should have 6 legal bases defined', () => {
expect(Object.keys(DSFA_LEGAL_BASES).length).toBe(6)
})
it('should reference GDPR Article 6', () => {
Object.values(DSFA_LEGAL_BASES).forEach(label => {
expect(label).toContain('Art. 6')
})
})
})
describe('DSFA_AFFECTED_RIGHTS', () => {
it('should have multiple affected rights defined', () => {
expect(DSFA_AFFECTED_RIGHTS.length).toBeGreaterThan(5)
})
it('should have id and label for each right', () => {
DSFA_AFFECTED_RIGHTS.forEach(right => {
expect(right.id).toBeDefined()
expect(right.label).toBeDefined()
})
})
it('should include GDPR data subject rights', () => {
const ids = DSFA_AFFECTED_RIGHTS.map(r => r.id)
expect(ids).toContain('right_of_access')
expect(ids).toContain('right_to_erasure')
expect(ids).toContain('right_to_data_portability')
})
})
describe('RISK_MATRIX', () => {
it('should have 9 cells defined (3x3 matrix)', () => {
expect(RISK_MATRIX.length).toBe(9)
})
it('should cover all combinations of likelihood and impact', () => {
const likelihoodValues = ['low', 'medium', 'high']
const impactValues = ['low', 'medium', 'high']
likelihoodValues.forEach(likelihood => {
impactValues.forEach(impact => {
const cell = RISK_MATRIX.find(
c => c.likelihood === likelihood && c.impact === impact
)
expect(cell).toBeDefined()
})
})
})
it('should have increasing scores for higher risks', () => {
const lowLow = RISK_MATRIX.find(c => c.likelihood === 'low' && c.impact === 'low')
const highHigh = RISK_MATRIX.find(c => c.likelihood === 'high' && c.impact === 'high')
expect(lowLow?.score).toBeLessThan(highHigh?.score || 0)
})
})
describe('calculateRiskLevel', () => {
it('should return low for low likelihood and low impact', () => {
const result = calculateRiskLevel('low', 'low')
expect(result.level).toBe('low')
expect(result.score).toBe(10)
})
it('should return very_high for high likelihood and high impact', () => {
const result = calculateRiskLevel('high', 'high')
expect(result.level).toBe('very_high')
expect(result.score).toBe(90)
})
it('should return medium for medium likelihood and medium impact', () => {
const result = calculateRiskLevel('medium', 'medium')
expect(result.level).toBe('medium')
expect(result.score).toBe(50)
})
it('should return high for high likelihood and medium impact', () => {
const result = calculateRiskLevel('high', 'medium')
expect(result.level).toBe('high')
expect(result.score).toBe(70)
})
it('should return medium for low likelihood and high impact', () => {
const result = calculateRiskLevel('low', 'high')
expect(result.level).toBe('medium')
expect(result.score).toBe(40)
})
})
describe('DSFARisk type', () => {
it('should accept valid risk data', () => {
const risk: DSFARisk = {
id: 'risk-001',
category: 'confidentiality',
description: 'Unauthorized access to personal data',
likelihood: 'medium',
impact: 'high',
risk_level: 'high',
affected_data: ['customer_data', 'financial_data'],
}
expect(risk.id).toBe('risk-001')
expect(risk.category).toBe('confidentiality')
expect(risk.likelihood).toBe('medium')
expect(risk.impact).toBe('high')
})
})
describe('DSFAMitigation type', () => {
it('should accept valid mitigation data', () => {
const mitigation: DSFAMitigation = {
id: 'mit-001',
risk_id: 'risk-001',
description: 'Implement encryption at rest',
type: 'technical',
status: 'implemented',
residual_risk: 'low',
responsible_party: 'IT Security Team',
}
expect(mitigation.id).toBe('mit-001')
expect(mitigation.type).toBe('technical')
expect(mitigation.status).toBe('implemented')
})
})
describe('DSFASectionProgress type', () => {
it('should track completion for all 5 sections', () => {
const progress: DSFASectionProgress = {
section_1_complete: true,
section_2_complete: true,
section_3_complete: false,
section_4_complete: false,
section_5_complete: false,
}
expect(progress.section_1_complete).toBe(true)
expect(progress.section_2_complete).toBe(true)
expect(progress.section_3_complete).toBe(false)
})
})
describe('DSFA type', () => {
it('should accept a complete DSFA object', () => {
const dsfa: DSFA = {
id: 'dsfa-001',
tenant_id: 'tenant-001',
name: 'AI Chatbot DSFA',
description: 'Data Protection Impact Assessment for AI Chatbot',
processing_description: 'Automated customer service using AI',
processing_purpose: 'Customer support automation',
data_categories: ['contact_data', 'inquiry_content'],
data_subjects: ['customers'],
recipients: ['internal_staff'],
legal_basis: 'legitimate_interest',
necessity_assessment: 'Required for efficient customer service',
proportionality_assessment: 'Minimal data processing for the purpose',
risks: [],
overall_risk_level: 'medium',
risk_score: 50,
mitigations: [],
dpo_consulted: false,
authority_consulted: false,
status: 'draft',
section_progress: {
section_1_complete: true,
section_2_complete: true,
section_3_complete: false,
section_4_complete: false,
section_5_complete: false,
},
conclusion: '',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
created_by: 'user-001',
}
expect(dsfa.id).toBe('dsfa-001')
expect(dsfa.name).toBe('AI Chatbot DSFA')
expect(dsfa.status).toBe('draft')
expect(dsfa.data_categories).toHaveLength(2)
})
})

View File

@@ -1,399 +0,0 @@
/**
* DSFA API Client
*
* API client functions for DSFA (Data Protection Impact Assessment) endpoints.
*/
import type {
DSFA,
DSFAListResponse,
DSFAStatsResponse,
CreateDSFARequest,
CreateDSFAFromAssessmentRequest,
CreateDSFAFromAssessmentResponse,
UpdateDSFASectionRequest,
SubmitForReviewResponse,
ApproveDSFARequest,
DSFATriggerInfo,
} from './types'
// =============================================================================
// CONFIGURATION
// =============================================================================
const getBaseUrl = () => {
if (typeof window !== 'undefined') {
// Browser environment
return process.env.NEXT_PUBLIC_SDK_API_URL || '/api/sdk/v1'
}
// Server environment
return process.env.SDK_API_URL || 'http://localhost:8080/api/sdk/v1'
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
async function handleResponse<T>(response: Response): Promise<T> {
if (!response.ok) {
const errorBody = await response.text()
let errorMessage = `HTTP ${response.status}`
try {
const errorJson = JSON.parse(errorBody)
errorMessage = errorJson.error || errorJson.message || errorMessage
} catch {
// Keep HTTP status message
}
throw new Error(errorMessage)
}
return response.json()
}
function getHeaders(): HeadersInit {
return {
'Content-Type': 'application/json',
}
}
// =============================================================================
// DSFA CRUD OPERATIONS
// =============================================================================
/**
* List all DSFAs for the current tenant
*/
export async function listDSFAs(status?: string): Promise<DSFA[]> {
const url = new URL(`${getBaseUrl()}/dsgvo/dsfas`, window.location.origin)
if (status) {
url.searchParams.set('status', status)
}
const response = await fetch(url.toString(), {
method: 'GET',
headers: getHeaders(),
credentials: 'include',
})
const data = await handleResponse<DSFAListResponse>(response)
return data.dsfas || []
}
/**
* Get a single DSFA by ID
*/
export async function getDSFA(id: string): Promise<DSFA> {
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}`, {
method: 'GET',
headers: getHeaders(),
credentials: 'include',
})
return handleResponse<DSFA>(response)
}
/**
* Create a new DSFA
*/
export async function createDSFA(data: CreateDSFARequest): Promise<DSFA> {
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas`, {
method: 'POST',
headers: getHeaders(),
credentials: 'include',
body: JSON.stringify(data),
})
return handleResponse<DSFA>(response)
}
/**
* Update an existing DSFA
*/
export async function updateDSFA(id: string, data: Partial<DSFA>): Promise<DSFA> {
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}`, {
method: 'PUT',
headers: getHeaders(),
credentials: 'include',
body: JSON.stringify(data),
})
return handleResponse<DSFA>(response)
}
/**
* Delete a DSFA
*/
export async function deleteDSFA(id: string): Promise<void> {
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}`, {
method: 'DELETE',
headers: getHeaders(),
credentials: 'include',
})
if (!response.ok) {
throw new Error(`Failed to delete DSFA: ${response.statusText}`)
}
}
// =============================================================================
// DSFA SECTION OPERATIONS
// =============================================================================
/**
* Update a specific section of a DSFA
*/
export async function updateDSFASection(
id: string,
sectionNumber: number,
data: UpdateDSFASectionRequest
): Promise<DSFA> {
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}/sections/${sectionNumber}`, {
method: 'PUT',
headers: getHeaders(),
credentials: 'include',
body: JSON.stringify(data),
})
return handleResponse<DSFA>(response)
}
// =============================================================================
// DSFA WORKFLOW OPERATIONS
// =============================================================================
/**
* Submit a DSFA for DPO review
*/
export async function submitDSFAForReview(id: string): Promise<SubmitForReviewResponse> {
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}/submit-for-review`, {
method: 'POST',
headers: getHeaders(),
credentials: 'include',
})
return handleResponse<SubmitForReviewResponse>(response)
}
/**
* Approve or reject a DSFA (DPO/CISO/GF action)
*/
export async function approveDSFA(id: string, data: ApproveDSFARequest): Promise<{ message: string }> {
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}/approve`, {
method: 'POST',
headers: getHeaders(),
credentials: 'include',
body: JSON.stringify(data),
})
return handleResponse<{ message: string }>(response)
}
// =============================================================================
// DSFA STATISTICS
// =============================================================================
/**
* Get DSFA statistics for the dashboard
*/
export async function getDSFAStats(): Promise<DSFAStatsResponse> {
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/stats`, {
method: 'GET',
headers: getHeaders(),
credentials: 'include',
})
return handleResponse<DSFAStatsResponse>(response)
}
// =============================================================================
// UCCA INTEGRATION
// =============================================================================
/**
* Create a DSFA from a UCCA assessment (pre-filled)
*/
export async function createDSFAFromAssessment(
assessmentId: string,
data?: CreateDSFAFromAssessmentRequest
): Promise<CreateDSFAFromAssessmentResponse> {
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/from-assessment/${assessmentId}`, {
method: 'POST',
headers: getHeaders(),
credentials: 'include',
body: JSON.stringify(data || {}),
})
return handleResponse<CreateDSFAFromAssessmentResponse>(response)
}
/**
* Get a DSFA by its linked UCCA assessment ID
*/
export async function getDSFAByAssessment(assessmentId: string): Promise<DSFA | null> {
try {
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/by-assessment/${assessmentId}`, {
method: 'GET',
headers: getHeaders(),
credentials: 'include',
})
if (response.status === 404) {
return null
}
return handleResponse<DSFA>(response)
} catch (error) {
// Return null if DSFA not found
return null
}
}
/**
* Check if a DSFA is required for a UCCA assessment
*/
export async function checkDSFARequired(assessmentId: string): Promise<DSFATriggerInfo> {
const response = await fetch(`${getBaseUrl()}/ucca/assessments/${assessmentId}/dsfa-required`, {
method: 'GET',
headers: getHeaders(),
credentials: 'include',
})
return handleResponse<DSFATriggerInfo>(response)
}
// =============================================================================
// EXPORT
// =============================================================================
/**
* Export a DSFA as JSON
*/
export async function exportDSFAAsJSON(id: string): Promise<Blob> {
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}/export?format=json`, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
credentials: 'include',
})
if (!response.ok) {
throw new Error(`Export failed: ${response.statusText}`)
}
return response.blob()
}
/**
* Export a DSFA as PDF
*/
export async function exportDSFAAsPDF(id: string): Promise<Blob> {
const response = await fetch(`${getBaseUrl()}/dsgvo/dsfas/${id}/export/pdf`, {
method: 'GET',
headers: {
'Accept': 'application/pdf',
},
credentials: 'include',
})
if (!response.ok) {
throw new Error(`PDF export failed: ${response.statusText}`)
}
return response.blob()
}
// =============================================================================
// RISK & MITIGATION OPERATIONS
// =============================================================================
/**
* Add a risk to a DSFA
*/
export async function addDSFARisk(dsfaId: string, risk: {
category: string
description: string
likelihood: 'low' | 'medium' | 'high'
impact: 'low' | 'medium' | 'high'
affected_data?: string[]
}): Promise<DSFA> {
const dsfa = await getDSFA(dsfaId)
const newRisk = {
id: crypto.randomUUID(),
...risk,
risk_level: calculateRiskLevelString(risk.likelihood, risk.impact),
affected_data: risk.affected_data || [],
}
const updatedRisks = [...(dsfa.risks || []), newRisk]
return updateDSFA(dsfaId, { risks: updatedRisks } as Partial<DSFA>)
}
/**
* Remove a risk from a DSFA
*/
export async function removeDSFARisk(dsfaId: string, riskId: string): Promise<DSFA> {
const dsfa = await getDSFA(dsfaId)
const updatedRisks = (dsfa.risks || []).filter(r => r.id !== riskId)
return updateDSFA(dsfaId, { risks: updatedRisks } as Partial<DSFA>)
}
/**
* Add a mitigation to a DSFA
*/
export async function addDSFAMitigation(dsfaId: string, mitigation: {
risk_id: string
description: string
type: 'technical' | 'organizational' | 'legal'
responsible_party: string
}): Promise<DSFA> {
const dsfa = await getDSFA(dsfaId)
const newMitigation = {
id: crypto.randomUUID(),
...mitigation,
status: 'planned' as const,
residual_risk: 'medium' as const,
}
const updatedMitigations = [...(dsfa.mitigations || []), newMitigation]
return updateDSFA(dsfaId, { mitigations: updatedMitigations } as Partial<DSFA>)
}
/**
* Update mitigation status
*/
export async function updateDSFAMitigationStatus(
dsfaId: string,
mitigationId: string,
status: 'planned' | 'in_progress' | 'implemented' | 'verified'
): Promise<DSFA> {
const dsfa = await getDSFA(dsfaId)
const updatedMitigations = (dsfa.mitigations || []).map(m => {
if (m.id === mitigationId) {
return {
...m,
status,
...(status === 'implemented' && { implemented_at: new Date().toISOString() }),
...(status === 'verified' && { verified_at: new Date().toISOString() }),
}
}
return m
})
return updateDSFA(dsfaId, { mitigations: updatedMitigations } as Partial<DSFA>)
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
function calculateRiskLevelString(
likelihood: 'low' | 'medium' | 'high',
impact: 'low' | 'medium' | 'high'
): string {
const matrix: Record<string, Record<string, string>> = {
low: { low: 'low', medium: 'low', high: 'medium' },
medium: { low: 'low', medium: 'medium', high: 'high' },
high: { low: 'medium', medium: 'high', high: 'very_high' },
}
return matrix[likelihood]?.[impact] || 'medium'
}

View File

@@ -1,8 +0,0 @@
/**
* DSFA Module
*
* Exports for DSFA (Data Protection Impact Assessment) functionality.
*/
export * from './types'
export * from './api'

View File

@@ -1,365 +0,0 @@
/**
* DSFA Types - Datenschutz-Folgenabschätzung (Art. 35 DSGVO)
*
* TypeScript type definitions for DSFA (Data Protection Impact Assessment)
* aligned with the backend Go models.
*/
// =============================================================================
// 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' },
]
// =============================================================================
// 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_1_complete: boolean
section_2_complete: boolean
section_3_complete: boolean
section_4_complete: boolean
section_5_complete: boolean
}
// =============================================================================
// 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 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[]
// Section 4: Abhilfemaßnahmen (Art. 35 Abs. 7 lit. d)
mitigations: DSFAMitigation[]
tom_references?: string[]
// 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
// Workflow & Approval
status: DSFAStatus
submitted_for_review_at?: string
submitted_by?: string
conclusion: string
review_comments?: DSFAReviewComment[]
// Section Progress Tracking
section_progress: DSFASectionProgress
// Metadata & Audit
metadata?: Record<string, unknown>
created_at: string
updated_at: string
created_by: string
approved_by?: string
approved_at?: string
}
// =============================================================================
// 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
}
// =============================================================================
// 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: 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'],
required: true,
},
{
number: 4,
title: 'Mitigation Measures',
titleDE: 'Abhilfemaßnahmen',
description: 'Definieren Sie technische und organisatorische Maßnahmen zur Risikominimierung.',
gdprRef: 'Art. 35 Abs. 7 lit. d DSGVO',
fields: ['mitigations', 'tom_references'],
required: true,
},
{
number: 5,
title: 'DPO Opinion',
titleDE: 'Stellungnahme DSB',
description: 'Dokumentieren Sie die Konsultation des Datenschutzbeauftragten und ggf. der Aufsichtsbehörde.',
gdprRef: 'Art. 35 Abs. 2 + Art. 36 DSGVO',
fields: ['dpo_consulted', 'dpo_opinion', 'authority_consulted', 'authority_reference'],
required: false,
},
]
// =============================================================================
// 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 }
}

664
admin-v2/lib/sdk/dsr/api.ts Normal file
View File

@@ -0,0 +1,664 @@
/**
* DSR API Client
*
* API client for Data Subject Request management
* Connects to the Go Consent Service backend
*/
import {
DSRRequest,
DSRListResponse,
DSRFilters,
DSRCreateRequest,
DSRUpdateRequest,
DSRVerifyIdentityRequest,
DSRCompleteRequest,
DSRRejectRequest,
DSRExtendDeadlineRequest,
DSRSendCommunicationRequest,
DSRCommunication,
DSRAuditEntry,
DSRStatistics,
DSRDataExport,
DSRErasureChecklist
} from './types'
// =============================================================================
// CONFIGURATION
// =============================================================================
const DSR_API_BASE = process.env.NEXT_PUBLIC_CONSENT_SERVICE_URL || 'http://localhost:8081'
const API_TIMEOUT = 30000 // 30 seconds
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
function getTenantId(): string {
// In a real app, this would come from auth context or localStorage
if (typeof window !== 'undefined') {
return localStorage.getItem('tenantId') || 'default-tenant'
}
return 'default-tenant'
}
function getAuthHeaders(): HeadersInit {
const headers: HeadersInit = {
'Content-Type': 'application/json',
'X-Tenant-ID': getTenantId()
}
// Add auth token if available
if (typeof window !== 'undefined') {
const token = localStorage.getItem('authToken')
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
}
return headers
}
async function fetchWithTimeout<T>(
url: string,
options: RequestInit = {},
timeout: number = API_TIMEOUT
): Promise<T> {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
headers: {
...getAuthHeaders(),
...options.headers
}
})
if (!response.ok) {
const errorBody = await response.text()
let errorMessage = `HTTP ${response.status}: ${response.statusText}`
try {
const errorJson = JSON.parse(errorBody)
errorMessage = errorJson.error || errorJson.message || errorMessage
} catch {
// Keep the HTTP status message
}
throw new Error(errorMessage)
}
// Handle empty responses
const contentType = response.headers.get('content-type')
if (contentType && contentType.includes('application/json')) {
return response.json()
}
return {} as T
} finally {
clearTimeout(timeoutId)
}
}
// =============================================================================
// DSR LIST & CRUD
// =============================================================================
/**
* Fetch all DSR requests with optional filters
*/
export async function fetchDSRList(filters?: DSRFilters): Promise<DSRListResponse> {
const params = new URLSearchParams()
if (filters) {
if (filters.status) {
const statuses = Array.isArray(filters.status) ? filters.status : [filters.status]
statuses.forEach(s => params.append('status', s))
}
if (filters.type) {
const types = Array.isArray(filters.type) ? filters.type : [filters.type]
types.forEach(t => params.append('type', t))
}
if (filters.priority) params.set('priority', filters.priority)
if (filters.assignedTo) params.set('assignedTo', filters.assignedTo)
if (filters.overdue !== undefined) params.set('overdue', String(filters.overdue))
if (filters.search) params.set('search', filters.search)
if (filters.dateFrom) params.set('dateFrom', filters.dateFrom)
if (filters.dateTo) params.set('dateTo', filters.dateTo)
}
const queryString = params.toString()
const url = `${DSR_API_BASE}/api/v1/admin/dsr${queryString ? `?${queryString}` : ''}`
return fetchWithTimeout<DSRListResponse>(url)
}
/**
* Fetch a single DSR request by ID
*/
export async function fetchDSR(id: string): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(`${DSR_API_BASE}/api/v1/admin/dsr/${id}`)
}
/**
* Create a new DSR request
*/
export async function createDSR(request: DSRCreateRequest): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(`${DSR_API_BASE}/api/v1/admin/dsr`, {
method: 'POST',
body: JSON.stringify(request)
})
}
/**
* Update a DSR request
*/
export async function updateDSR(id: string, update: DSRUpdateRequest): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(`${DSR_API_BASE}/api/v1/admin/dsr/${id}`, {
method: 'PUT',
body: JSON.stringify(update)
})
}
/**
* Delete a DSR request (soft delete - marks as cancelled)
*/
export async function deleteDSR(id: string): Promise<void> {
await fetchWithTimeout<void>(`${DSR_API_BASE}/api/v1/admin/dsr/${id}`, {
method: 'DELETE'
})
}
// =============================================================================
// DSR WORKFLOW ACTIONS
// =============================================================================
/**
* Verify the identity of the requester
*/
export async function verifyIdentity(
dsrId: string,
verification: DSRVerifyIdentityRequest
): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/verify-identity`,
{
method: 'POST',
body: JSON.stringify(verification)
}
)
}
/**
* Complete a DSR request
*/
export async function completeDSR(
dsrId: string,
completion?: DSRCompleteRequest
): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/complete`,
{
method: 'POST',
body: JSON.stringify(completion || {})
}
)
}
/**
* Reject a DSR request
*/
export async function rejectDSR(
dsrId: string,
rejection: DSRRejectRequest
): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/reject`,
{
method: 'POST',
body: JSON.stringify(rejection)
}
)
}
/**
* Extend the deadline for a DSR request
*/
export async function extendDeadline(
dsrId: string,
extension: DSRExtendDeadlineRequest
): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/extend`,
{
method: 'POST',
body: JSON.stringify(extension)
}
)
}
/**
* Assign a DSR request to a user
*/
export async function assignDSR(
dsrId: string,
assignedTo: string
): Promise<DSRRequest> {
return fetchWithTimeout<DSRRequest>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/assign`,
{
method: 'POST',
body: JSON.stringify({ assignedTo })
}
)
}
// =============================================================================
// COMMUNICATION
// =============================================================================
/**
* Get all communications for a DSR request
*/
export async function getCommunications(dsrId: string): Promise<DSRCommunication[]> {
return fetchWithTimeout<DSRCommunication[]>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/communications`
)
}
/**
* Send a communication (email, letter, internal note)
*/
export async function sendCommunication(
dsrId: string,
communication: DSRSendCommunicationRequest
): Promise<DSRCommunication> {
return fetchWithTimeout<DSRCommunication>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/send-communication`,
{
method: 'POST',
body: JSON.stringify(communication)
}
)
}
// =============================================================================
// AUDIT LOG
// =============================================================================
/**
* Get audit log entries for a DSR request
*/
export async function getAuditLog(dsrId: string): Promise<DSRAuditEntry[]> {
return fetchWithTimeout<DSRAuditEntry[]>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/audit`
)
}
// =============================================================================
// STATISTICS
// =============================================================================
/**
* Get DSR statistics
*/
export async function getDSRStatistics(): Promise<DSRStatistics> {
return fetchWithTimeout<DSRStatistics>(
`${DSR_API_BASE}/api/v1/admin/dsr/statistics`
)
}
// =============================================================================
// DATA EXPORT (Art. 15, 20)
// =============================================================================
/**
* Generate data export for Art. 15 (access) or Art. 20 (portability)
*/
export async function generateDataExport(
dsrId: string,
format: 'json' | 'csv' | 'xml' | 'pdf' = 'json'
): Promise<DSRDataExport> {
return fetchWithTimeout<DSRDataExport>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/export`,
{
method: 'POST',
body: JSON.stringify({ format })
}
)
}
/**
* Download generated data export
*/
export async function downloadDataExport(dsrId: string): Promise<Blob> {
const response = await fetch(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/export/download`,
{
headers: getAuthHeaders()
}
)
if (!response.ok) {
throw new Error(`Download failed: ${response.statusText}`)
}
return response.blob()
}
// =============================================================================
// ERASURE CHECKLIST (Art. 17)
// =============================================================================
/**
* Get the erasure checklist for an Art. 17 request
*/
export async function getErasureChecklist(dsrId: string): Promise<DSRErasureChecklist> {
return fetchWithTimeout<DSRErasureChecklist>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/erasure-checklist`
)
}
/**
* Update the erasure checklist
*/
export async function updateErasureChecklist(
dsrId: string,
checklist: DSRErasureChecklist
): Promise<DSRErasureChecklist> {
return fetchWithTimeout<DSRErasureChecklist>(
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/erasure-checklist`,
{
method: 'PUT',
body: JSON.stringify(checklist)
}
)
}
// =============================================================================
// EMAIL TEMPLATES
// =============================================================================
/**
* Get available email templates
*/
export async function getEmailTemplates(): Promise<{ id: string; name: string; stage: string }[]> {
return fetchWithTimeout<{ id: string; name: string; stage: string }[]>(
`${DSR_API_BASE}/api/v1/admin/dsr/email-templates`
)
}
/**
* Preview an email with variables filled in
*/
export async function previewEmail(
templateId: string,
dsrId: string
): Promise<{ subject: string; body: string }> {
return fetchWithTimeout<{ subject: string; body: string }>(
`${DSR_API_BASE}/api/v1/admin/dsr/email-templates/${templateId}/preview`,
{
method: 'POST',
body: JSON.stringify({ dsrId })
}
)
}
// =============================================================================
// MOCK DATA FUNCTIONS (for development without backend)
// =============================================================================
export function createMockDSRList(): DSRRequest[] {
const now = new Date()
return [
{
id: 'dsr-001',
referenceNumber: 'DSR-2025-000001',
type: 'access',
status: 'intake',
priority: 'high',
requester: {
name: 'Max Mustermann',
email: 'max.mustermann@example.de'
},
source: 'web_form',
sourceDetails: 'Kontaktformular auf breakpilot.de',
receivedAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(),
deadline: {
originalDeadline: new Date(now.getTime() + 28 * 24 * 60 * 60 * 1000).toISOString(),
currentDeadline: new Date(now.getTime() + 28 * 24 * 60 * 60 * 1000).toISOString(),
extended: false
},
identityVerification: {
verified: false
},
assignment: {
assignedTo: null
},
createdAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(),
createdBy: 'system',
updatedAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(),
tenantId: 'default-tenant'
},
{
id: 'dsr-002',
referenceNumber: 'DSR-2025-000002',
type: 'erasure',
status: 'identity_verification',
priority: 'high',
requester: {
name: 'Anna Schmidt',
email: 'anna.schmidt@example.de',
phone: '+49 170 1234567'
},
source: 'email',
requestText: 'Ich moechte, dass alle meine Daten geloescht werden.',
receivedAt: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(),
deadline: {
originalDeadline: new Date(now.getTime() + 9 * 24 * 60 * 60 * 1000).toISOString(),
currentDeadline: new Date(now.getTime() + 9 * 24 * 60 * 60 * 1000).toISOString(),
extended: false
},
identityVerification: {
verified: false
},
assignment: {
assignedTo: 'DSB Mueller',
assignedAt: new Date(now.getTime() - 4 * 24 * 60 * 60 * 1000).toISOString()
},
createdAt: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(),
createdBy: 'system',
updatedAt: new Date(now.getTime() - 4 * 24 * 60 * 60 * 1000).toISOString(),
tenantId: 'default-tenant'
},
{
id: 'dsr-003',
referenceNumber: 'DSR-2025-000003',
type: 'rectification',
status: 'processing',
priority: 'normal',
requester: {
name: 'Peter Meier',
email: 'peter.meier@example.de'
},
source: 'email',
requestText: 'Meine Adresse ist falsch gespeichert.',
receivedAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(),
deadline: {
originalDeadline: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(),
currentDeadline: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(),
extended: false
},
identityVerification: {
verified: true,
method: 'existing_account',
verifiedAt: new Date(now.getTime() - 6 * 24 * 60 * 60 * 1000).toISOString(),
verifiedBy: 'DSB Mueller'
},
assignment: {
assignedTo: 'DSB Mueller',
assignedAt: new Date(now.getTime() - 6 * 24 * 60 * 60 * 1000).toISOString()
},
rectificationDetails: {
fieldsToCorrect: [
{
field: 'Adresse',
currentValue: 'Musterstr. 1, 12345 Berlin',
requestedValue: 'Musterstr. 10, 12345 Berlin',
corrected: false
}
]
},
createdAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(),
createdBy: 'system',
updatedAt: new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000).toISOString(),
tenantId: 'default-tenant'
},
{
id: 'dsr-004',
referenceNumber: 'DSR-2025-000004',
type: 'portability',
status: 'processing',
priority: 'normal',
requester: {
name: 'Lisa Weber',
email: 'lisa.weber@example.de'
},
source: 'web_form',
receivedAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString(),
deadline: {
originalDeadline: new Date(now.getTime() + 20 * 24 * 60 * 60 * 1000).toISOString(),
currentDeadline: new Date(now.getTime() + 20 * 24 * 60 * 60 * 1000).toISOString(),
extended: false
},
identityVerification: {
verified: true,
method: 'id_document',
verifiedAt: new Date(now.getTime() - 8 * 24 * 60 * 60 * 1000).toISOString(),
verifiedBy: 'DSB Mueller'
},
assignment: {
assignedTo: 'IT Team',
assignedAt: new Date(now.getTime() - 8 * 24 * 60 * 60 * 1000).toISOString()
},
notes: 'JSON-Export wird vorbereitet',
createdAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString(),
createdBy: 'system',
updatedAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(),
tenantId: 'default-tenant'
},
{
id: 'dsr-005',
referenceNumber: 'DSR-2025-000005',
type: 'objection',
status: 'rejected',
priority: 'low',
requester: {
name: 'Thomas Klein',
email: 'thomas.klein@example.de'
},
source: 'letter',
requestText: 'Ich widerspreche der Verarbeitung meiner Daten fuer Marketingzwecke.',
receivedAt: new Date(now.getTime() - 35 * 24 * 60 * 60 * 1000).toISOString(),
deadline: {
originalDeadline: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(),
currentDeadline: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(),
extended: false
},
completedAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(),
identityVerification: {
verified: true,
method: 'postal',
verifiedAt: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString(),
verifiedBy: 'DSB Mueller'
},
assignment: {
assignedTo: 'Rechtsabteilung',
assignedAt: new Date(now.getTime() - 28 * 24 * 60 * 60 * 1000).toISOString()
},
objectionDetails: {
processingPurpose: 'Marketing',
legalBasis: 'Berechtigtes Interesse (Art. 6(1)(f))',
objectionGrounds: 'Keine konkreten Gruende genannt',
decision: 'rejected',
decisionReason: 'Zwingende schutzwuerdige Gruende fuer die Verarbeitung ueberwiegen',
decisionBy: 'Rechtsabteilung',
decisionAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString()
},
notes: 'Widerspruch unberechtigt - zwingende schutzwuerdige Gruende',
createdAt: new Date(now.getTime() - 35 * 24 * 60 * 60 * 1000).toISOString(),
createdBy: 'system',
updatedAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(),
tenantId: 'default-tenant'
},
{
id: 'dsr-006',
referenceNumber: 'DSR-2025-000006',
type: 'access',
status: 'completed',
priority: 'normal',
requester: {
name: 'Sarah Braun',
email: 'sarah.braun@example.de'
},
source: 'email',
receivedAt: new Date(now.getTime() - 45 * 24 * 60 * 60 * 1000).toISOString(),
deadline: {
originalDeadline: new Date(now.getTime() - 15 * 24 * 60 * 60 * 1000).toISOString(),
currentDeadline: new Date(now.getTime() - 15 * 24 * 60 * 60 * 1000).toISOString(),
extended: false
},
completedAt: new Date(now.getTime() - 20 * 24 * 60 * 60 * 1000).toISOString(),
identityVerification: {
verified: true,
method: 'id_document',
verifiedAt: new Date(now.getTime() - 42 * 24 * 60 * 60 * 1000).toISOString(),
verifiedBy: 'DSB Mueller'
},
assignment: {
assignedTo: 'DSB Mueller',
assignedAt: new Date(now.getTime() - 42 * 24 * 60 * 60 * 1000).toISOString()
},
dataExport: {
format: 'pdf',
generatedAt: new Date(now.getTime() - 20 * 24 * 60 * 60 * 1000).toISOString(),
generatedBy: 'DSB Mueller',
fileName: 'datenauskunft_sarah_braun.pdf',
fileSize: 245000,
includesThirdPartyData: false
},
createdAt: new Date(now.getTime() - 45 * 24 * 60 * 60 * 1000).toISOString(),
createdBy: 'system',
updatedAt: new Date(now.getTime() - 20 * 24 * 60 * 60 * 1000).toISOString(),
tenantId: 'default-tenant'
}
]
}
export function createMockStatistics(): DSRStatistics {
return {
total: 6,
byStatus: {
intake: 1,
identity_verification: 1,
processing: 2,
completed: 1,
rejected: 1,
cancelled: 0
},
byType: {
access: 2,
rectification: 1,
erasure: 1,
restriction: 0,
portability: 1,
objection: 1
},
overdue: 0,
dueThisWeek: 2,
averageProcessingDays: 18,
completedThisMonth: 1
}
}

View File

@@ -0,0 +1,6 @@
/**
* DSR Module Exports
*/
export * from './types'
export * from './api'

View File

@@ -0,0 +1,581 @@
/**
* DSR (Data Subject Request) Types
*
* TypeScript definitions for GDPR Art. 15-21 Data Subject Requests
* Based on the Go Consent Service backend API structure
*/
// =============================================================================
// ENUMS & CONSTANTS
// =============================================================================
export type DSRType =
| 'access' // Art. 15 - Auskunftsrecht
| 'rectification' // Art. 16 - Berichtigungsrecht
| 'erasure' // Art. 17 - Loeschungsrecht
| 'restriction' // Art. 18 - Einschraenkungsrecht
| 'portability' // Art. 20 - Datenuebertragbarkeit
| 'objection' // Art. 21 - Widerspruchsrecht
export type DSRStatus =
| 'intake' // Eingang - Anfrage dokumentiert
| 'identity_verification' // Identitaetspruefung
| 'processing' // In Bearbeitung
| 'completed' // Abgeschlossen
| 'rejected' // Abgelehnt
| 'cancelled' // Storniert
export type DSRPriority = 'low' | 'normal' | 'high' | 'critical'
export type DSRSource =
| 'web_form' // Kontaktformular/Portal
| 'email' // E-Mail
| 'letter' // Brief
| 'phone' // Telefon
| 'in_person' // Persoenlich
| 'other' // Sonstiges
export type IdentityVerificationMethod =
| 'id_document' // Ausweiskopie
| 'email' // E-Mail-Bestaetigung
| 'phone' // Telefonische Bestaetigung
| 'postal' // Postalische Bestaetigung
| 'existing_account' // Bestehendes Kundenkonto
| 'other' // Sonstiges
export type CommunicationType =
| 'incoming' // Eingehend (vom Betroffenen)
| 'outgoing' // Ausgehend (an Betroffenen)
| 'internal' // Intern (Notizen)
export type CommunicationChannel =
| 'email'
| 'letter'
| 'phone'
| 'portal'
| 'internal_note'
// =============================================================================
// DSR TYPE METADATA
// =============================================================================
export interface DSRTypeInfo {
type: DSRType
article: string
label: string
labelShort: string
description: string
defaultDeadlineDays: number
maxExtensionMonths: number
color: string
bgColor: string
processDocument?: string // Reference to process document
}
export const DSR_TYPE_INFO: Record<DSRType, DSRTypeInfo> = {
access: {
type: 'access',
article: 'Art. 15',
label: 'Auskunftsrecht',
labelShort: 'Auskunft',
description: 'Recht auf Auskunft ueber gespeicherte personenbezogene Daten',
defaultDeadlineDays: 30,
maxExtensionMonths: 2,
color: 'text-blue-700',
bgColor: 'bg-blue-100',
processDocument: 'Prozessbeschreibung Art. 15 DSGVO_v02.pdf'
},
rectification: {
type: 'rectification',
article: 'Art. 16',
label: 'Berichtigungsrecht',
labelShort: 'Berichtigung',
description: 'Recht auf Berichtigung unrichtiger personenbezogener Daten',
defaultDeadlineDays: 14,
maxExtensionMonths: 2,
color: 'text-yellow-700',
bgColor: 'bg-yellow-100',
processDocument: 'Prozessbeschriebung Art. 16 DSGVO_v02.pdf'
},
erasure: {
type: 'erasure',
article: 'Art. 17',
label: 'Loeschungsrecht',
labelShort: 'Loeschung',
description: 'Recht auf Loeschung personenbezogener Daten ("Recht auf Vergessenwerden")',
defaultDeadlineDays: 14,
maxExtensionMonths: 2,
color: 'text-red-700',
bgColor: 'bg-red-100',
processDocument: 'Prozessbeschreibung Art. 17 DSGVO_v03.pdf'
},
restriction: {
type: 'restriction',
article: 'Art. 18',
label: 'Einschraenkungsrecht',
labelShort: 'Einschraenkung',
description: 'Recht auf Einschraenkung der Verarbeitung',
defaultDeadlineDays: 14,
maxExtensionMonths: 2,
color: 'text-orange-700',
bgColor: 'bg-orange-100',
processDocument: 'Prozessbeschreibung Art. 18 DSGVO_v01.pdf'
},
portability: {
type: 'portability',
article: 'Art. 20',
label: 'Datenuebertragbarkeit',
labelShort: 'Uebertragung',
description: 'Recht auf Datenuebertragbarkeit in maschinenlesbarem Format',
defaultDeadlineDays: 30,
maxExtensionMonths: 2,
color: 'text-purple-700',
bgColor: 'bg-purple-100',
processDocument: 'Prozessbeschreibung Art. 20 DSGVO_v02.pdf'
},
objection: {
type: 'objection',
article: 'Art. 21',
label: 'Widerspruchsrecht',
labelShort: 'Widerspruch',
description: 'Recht auf Widerspruch gegen die Verarbeitung',
defaultDeadlineDays: 30,
maxExtensionMonths: 0, // No extension allowed for objections
color: 'text-gray-700',
bgColor: 'bg-gray-100'
}
}
export const DSR_STATUS_INFO: Record<DSRStatus, { label: string; color: string; bgColor: string; borderColor: string }> = {
intake: {
label: 'Eingang',
color: 'text-blue-700',
bgColor: 'bg-blue-100',
borderColor: 'border-blue-200'
},
identity_verification: {
label: 'ID-Pruefung',
color: 'text-yellow-700',
bgColor: 'bg-yellow-100',
borderColor: 'border-yellow-200'
},
processing: {
label: 'In Bearbeitung',
color: 'text-purple-700',
bgColor: 'bg-purple-100',
borderColor: 'border-purple-200'
},
completed: {
label: 'Abgeschlossen',
color: 'text-green-700',
bgColor: 'bg-green-100',
borderColor: 'border-green-200'
},
rejected: {
label: 'Abgelehnt',
color: 'text-red-700',
bgColor: 'bg-red-100',
borderColor: 'border-red-200'
},
cancelled: {
label: 'Storniert',
color: 'text-gray-700',
bgColor: 'bg-gray-100',
borderColor: 'border-gray-200'
}
}
// =============================================================================
// MAIN INTERFACES
// =============================================================================
export interface DSRRequester {
name: string
email: string
phone?: string
address?: string
customerId?: string // If existing customer
}
export interface DSRIdentityVerification {
verified: boolean
method?: IdentityVerificationMethod
verifiedAt?: string
verifiedBy?: string
notes?: string
documentRef?: string // Reference to uploaded ID document
}
export interface DSRAssignment {
assignedTo: string | null
assignedAt?: string
assignedBy?: string
}
export interface DSRDeadline {
originalDeadline: string
currentDeadline: string
extended: boolean
extensionReason?: string
extensionApprovedBy?: string
extensionApprovedAt?: string
}
export interface DSRRequest {
id: string
referenceNumber: string // e.g., "DSR-2025-000042"
type: DSRType
status: DSRStatus
priority: DSRPriority
// Requester info
requester: DSRRequester
// Request details
source: DSRSource
sourceDetails?: string // e.g., "Kontaktformular auf website.de"
requestText?: string // Original request text
// Dates
receivedAt: string
deadline: DSRDeadline
completedAt?: string
// Verification
identityVerification: DSRIdentityVerification
// Assignment
assignment: DSRAssignment
// Processing
notes?: string
internalNotes?: string
// Type-specific data
erasureChecklist?: DSRErasureChecklist // For Art. 17
dataExport?: DSRDataExport // For Art. 15, 20
rectificationDetails?: DSRRectificationDetails // For Art. 16
objectionDetails?: DSRObjectionDetails // For Art. 21
// Audit
createdAt: string
createdBy: string
updatedAt: string
updatedBy?: string
// Metadata
tenantId: string
}
// =============================================================================
// TYPE-SPECIFIC INTERFACES
// =============================================================================
// Art. 17(3) Erasure Exceptions Checklist
export interface DSRErasureChecklistItem {
id: string
article: string // e.g., "17(3)(a)"
label: string
description: string
checked: boolean
applies: boolean
notes?: string
}
export interface DSRErasureChecklist {
items: DSRErasureChecklistItem[]
canProceedWithErasure: boolean
reviewedBy?: string
reviewedAt?: string
}
export const ERASURE_EXCEPTIONS: Omit<DSRErasureChecklistItem, 'checked' | 'applies' | 'notes'>[] = [
{
id: 'art17_3_a',
article: '17(3)(a)',
label: 'Meinungs- und Informationsfreiheit',
description: 'Ausuebung des Rechts auf freie Meinungsaeusserung und Information'
},
{
id: 'art17_3_b',
article: '17(3)(b)',
label: 'Rechtliche Verpflichtung',
description: 'Erfuellung einer rechtlichen Verpflichtung (z.B. Aufbewahrungspflichten)'
},
{
id: 'art17_3_c',
article: '17(3)(c)',
label: 'Oeffentliches Interesse',
description: 'Gruende des oeffentlichen Interesses im Bereich Gesundheit'
},
{
id: 'art17_3_d',
article: '17(3)(d)',
label: 'Archivzwecke',
description: 'Archivzwecke, wissenschaftliche/historische Forschung, Statistik'
},
{
id: 'art17_3_e',
article: '17(3)(e)',
label: 'Rechtsansprueche',
description: 'Geltendmachung, Ausuebung oder Verteidigung von Rechtsanspruechen'
}
]
// Data Export for Art. 15, 20
export interface DSRDataExport {
format: 'json' | 'csv' | 'xml' | 'pdf'
generatedAt?: string
generatedBy?: string
fileUrl?: string
fileName?: string
fileSize?: number
includesThirdPartyData: boolean
anonymizedFields?: string[]
transferMethod?: 'download' | 'email' | 'third_party' // For Art. 20 transfer
transferRecipient?: string // For Art. 20 transfer to another controller
}
// Rectification Details for Art. 16
export interface DSRRectificationDetails {
fieldsToCorrect: {
field: string
currentValue: string
requestedValue: string
corrected: boolean
correctedAt?: string
correctedBy?: string
}[]
}
// Objection Details for Art. 21
export interface DSRObjectionDetails {
processingPurpose: string
legalBasis: string
objectionGrounds: string
decision: 'accepted' | 'rejected' | 'pending'
decisionReason?: string
decisionBy?: string
decisionAt?: string
}
// =============================================================================
// COMMUNICATION
// =============================================================================
export interface DSRCommunication {
id: string
dsrId: string
type: CommunicationType
channel: CommunicationChannel
subject?: string
content: string
templateUsed?: string // Reference to email template
attachments?: {
name: string
url: string
size: number
type: string
}[]
sentAt?: string
sentBy?: string
receivedAt?: string
createdAt: string
createdBy: string
}
// =============================================================================
// AUDIT LOG
// =============================================================================
export interface DSRAuditEntry {
id: string
dsrId: string
action: string // e.g., "status_changed", "identity_verified", "assigned"
previousValue?: string
newValue?: string
performedBy: string
performedAt: string
notes?: string
}
// =============================================================================
// EMAIL TEMPLATES
// =============================================================================
export interface DSREmailTemplate {
id: string
name: string
subject: string
body: string
type: DSRType | 'general'
stage: DSRStatus | 'identity_request' | 'deadline_extension' | 'completion'
language: 'de' | 'en'
variables: string[] // e.g., ["requesterName", "referenceNumber", "deadline"]
}
export const DSR_EMAIL_TEMPLATES: DSREmailTemplate[] = [
{
id: 'intake_confirmation',
name: 'Eingangsbestaetigung',
subject: 'Bestaetigung Ihrer Anfrage - {{referenceNumber}}',
body: `Sehr geehrte(r) {{requesterName}},
wir bestaetigen den Eingang Ihrer Anfrage vom {{receivedDate}}.
Referenznummer: {{referenceNumber}}
Art der Anfrage: {{requestType}}
Wir werden Ihre Anfrage innerhalb der gesetzlichen Frist von {{deadline}} bearbeiten.
Mit freundlichen Gruessen
{{senderName}}
Datenschutzbeauftragter`,
type: 'general',
stage: 'intake',
language: 'de',
variables: ['requesterName', 'receivedDate', 'referenceNumber', 'requestType', 'deadline', 'senderName']
},
{
id: 'identity_request',
name: 'Identitaetsanfrage',
subject: 'Identitaetspruefung erforderlich - {{referenceNumber}}',
body: `Sehr geehrte(r) {{requesterName}},
um Ihre Anfrage bearbeiten zu koennen, benoetigen wir einen Nachweis Ihrer Identitaet.
Bitte senden Sie uns eines der folgenden Dokumente:
- Kopie Ihres Personalausweises (Vorder- und Rueckseite)
- Kopie Ihres Reisepasses
Ihre Daten werden ausschliesslich zur Identitaetspruefung verwendet und anschliessend geloescht.
Mit freundlichen Gruessen
{{senderName}}
Datenschutzbeauftragter`,
type: 'general',
stage: 'identity_request',
language: 'de',
variables: ['requesterName', 'referenceNumber', 'senderName']
}
]
// =============================================================================
// API TYPES
// =============================================================================
export interface DSRFilters {
status?: DSRStatus | DSRStatus[]
type?: DSRType | DSRType[]
priority?: DSRPriority
assignedTo?: string
overdue?: boolean
search?: string
dateFrom?: string
dateTo?: string
}
export interface DSRListResponse {
requests: DSRRequest[]
total: number
page: number
pageSize: number
}
export interface DSRCreateRequest {
type: DSRType
requester: DSRRequester
source: DSRSource
sourceDetails?: string
requestText?: string
priority?: DSRPriority
}
export interface DSRUpdateRequest {
status?: DSRStatus
priority?: DSRPriority
notes?: string
internalNotes?: string
assignment?: DSRAssignment
}
export interface DSRVerifyIdentityRequest {
method: IdentityVerificationMethod
notes?: string
documentRef?: string
}
export interface DSRCompleteRequest {
completionNotes?: string
dataExport?: DSRDataExport
}
export interface DSRRejectRequest {
reason: string
legalBasis?: string // e.g., Art. 17(3) exception
}
export interface DSRExtendDeadlineRequest {
extensionMonths: 1 | 2
reason: string
}
export interface DSRSendCommunicationRequest {
type: CommunicationType
channel: CommunicationChannel
subject?: string
content: string
templateId?: string
}
// =============================================================================
// STATISTICS
// =============================================================================
export interface DSRStatistics {
total: number
byStatus: Record<DSRStatus, number>
byType: Record<DSRType, number>
overdue: number
dueThisWeek: number
averageProcessingDays: number
completedThisMonth: number
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
export function getDaysRemaining(deadline: string): number {
const deadlineDate = new Date(deadline)
const now = new Date()
const diff = deadlineDate.getTime() - now.getTime()
return Math.ceil(diff / (1000 * 60 * 60 * 24))
}
export function isOverdue(request: DSRRequest): boolean {
if (request.status === 'completed' || request.status === 'rejected' || request.status === 'cancelled') {
return false
}
return getDaysRemaining(request.deadline.currentDeadline) < 0
}
export function isUrgent(request: DSRRequest, thresholdDays: number = 7): boolean {
if (request.status === 'completed' || request.status === 'rejected' || request.status === 'cancelled') {
return false
}
const daysRemaining = getDaysRemaining(request.deadline.currentDeadline)
return daysRemaining >= 0 && daysRemaining <= thresholdDays
}
export function generateReferenceNumber(year: number, sequence: number): string {
return `DSR-${year}-${String(sequence).padStart(6, '0')}`
}
export function getTypeInfo(type: DSRType): DSRTypeInfo {
return DSR_TYPE_INFO[type]
}
export function getStatusInfo(status: DSRStatus) {
return DSR_STATUS_INFO[status]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,308 @@
/**
* YAML Catalog Loader - Erweiterte Version mit 128 Datenpunkten
*/
import {
DataPoint,
DataPointCategory,
RetentionMatrixEntry,
CookieBannerCategory,
LocalizedText,
DataPointCatalog,
} from '../types'
function l(de: string, en: string): LocalizedText {
return { de, en }
}
// =============================================================================
// VORDEFINIERTE DATENPUNKTE (128 Stueck in 18 Kategorien)
// =============================================================================
export const PREDEFINED_DATA_POINTS: DataPoint[] = [
// KATEGORIE A: STAMMDATEN (8)
{ id: 'dp-a1-firstname', code: 'A1', category: 'MASTER_DATA', name: l('Vorname', 'First Name'), description: l('Vorname der betroffenen Person', 'First name of the data subject'), purpose: l('Identifikation, Vertragserfuellung', 'Identification, contract fulfillment'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Erforderlich zur Vertragserfuellung', 'Required for contract performance'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange Konto aktiv', 'While account active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Verschluesselung', 'Zugriffskontrolle'], tags: ['identity', 'master-data'] },
{ id: 'dp-a2-lastname', code: 'A2', category: 'MASTER_DATA', name: l('Nachname', 'Last Name'), description: l('Nachname der betroffenen Person', 'Last name of the data subject'), purpose: l('Identifikation, Vertragserfuellung', 'Identification, contract fulfillment'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Erforderlich zur Vertragserfuellung', 'Required for contract performance'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange Konto aktiv', 'While account active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Verschluesselung', 'Zugriffskontrolle'], tags: ['identity', 'master-data'] },
{ id: 'dp-a3-birthdate', code: 'A3', category: 'MASTER_DATA', name: l('Geburtsdatum', 'Date of Birth'), description: l('Geburtsdatum zur Altersverifikation', 'Date of birth for age verification'), purpose: l('Altersverifikation, Identitaetspruefung', 'Age verification, identity check'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Erforderlich zur Altersverifikation', 'Required for age verification'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Fuer Identifikationszwecke', 'For identification purposes'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Verschluesselung'], tags: ['identity', 'master-data'] },
{ id: 'dp-a4-gender', code: 'A4', category: 'MASTER_DATA', name: l('Geschlecht', 'Gender'), description: l('Geschlechtsangabe (optional)', 'Gender (optional)'), purpose: l('Personalisierte Ansprache', 'Personalized communication'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Freiwillige Angabe', 'Voluntary'), retentionPeriod: 'UNTIL_REVOCATION', retentionJustification: l('Bis Widerruf', 'Until revocation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['personal', 'master-data'] },
{ id: 'dp-a5-title', code: 'A5', category: 'MASTER_DATA', name: l('Anrede/Titel', 'Salutation/Title'), description: l('Akademischer Titel oder Anrede', 'Academic title or salutation'), purpose: l('Korrekte Ansprache', 'Correct salutation'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Bestandteil der Kommunikation', 'Part of communication'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange Konto aktiv', 'While account active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['personal', 'master-data'] },
{ id: 'dp-a6-profile-picture', code: 'A6', category: 'MASTER_DATA', name: l('Profilbild', 'Profile Picture'), description: l('Vom Nutzer hochgeladenes Profilbild', 'User-uploaded profile picture'), purpose: l('Visuelle Identifikation', 'Visual identification'), riskLevel: 'MEDIUM', legalBasis: 'CONSENT', legalBasisJustification: l('Freiwilliger Upload', 'Voluntary upload'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Loeschung bei Kontoschliessung', 'Deletion on account closure'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['CDN'], technicalMeasures: ['Verschluesselung'], tags: ['image', 'master-data'] },
{ id: 'dp-a7-nationality', code: 'A7', category: 'MASTER_DATA', name: l('Staatsangehoerigkeit', 'Nationality'), description: l('Staatsangehoerigkeit der Person', 'Nationality of the person'), purpose: l('Compliance-Pruefung', 'Compliance check'), riskLevel: 'MEDIUM', legalBasis: 'LEGAL_OBLIGATION', legalBasisJustification: l('Compliance (Sanktionslisten)', 'Compliance (sanction lists)'), retentionPeriod: '10_YEARS', retentionJustification: l('Aufbewahrungspflichten', 'Retention obligations'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Verschluesselung'], tags: ['compliance', 'master-data'] },
{ id: 'dp-a8-username', code: 'A8', category: 'MASTER_DATA', name: l('Benutzername', 'Username'), description: l('Selbst gewaehlter Benutzername', 'Self-chosen username'), purpose: l('Identifikation, Login', 'Identification, login'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Kontoverwaltung', 'For account management'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange Konto aktiv', 'While account active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['account', 'master-data'] },
// KATEGORIE B: KONTAKTDATEN (10)
{ id: 'dp-b1-email', code: 'B1', category: 'CONTACT_DATA', name: l('E-Mail-Adresse', 'Email Address'), description: l('Primaere E-Mail-Adresse', 'Primary email address'), purpose: l('Kommunikation, Benachrichtigungen', 'Communication, notifications'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Vertragserfuellung', 'For contract performance'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange Konto aktiv', 'While account active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['E-Mail-Dienstleister'], technicalMeasures: ['TLS-Verschluesselung'], tags: ['contact', 'essential'] },
{ id: 'dp-b2-phone', code: 'B2', category: 'CONTACT_DATA', name: l('Telefonnummer', 'Phone Number'), description: l('Festnetz-Telefonnummer', 'Landline phone number'), purpose: l('Telefonische Kontaktaufnahme', 'Phone contact'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Kundensupport', 'For customer support'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange Konto aktiv', 'While account active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['contact', 'phone'] },
{ id: 'dp-b3-mobile', code: 'B3', category: 'CONTACT_DATA', name: l('Mobilnummer', 'Mobile Number'), description: l('Mobiltelefonnummer', 'Mobile phone number'), purpose: l('SMS-Benachrichtigungen, 2FA', 'SMS notifications, 2FA'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Sicherheit und Kommunikation', 'For security and communication'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange Konto aktiv', 'While account active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['SMS-Provider'], technicalMeasures: ['Verschluesselung'], tags: ['contact', 'mobile', '2fa'] },
{ id: 'dp-b4-address-street', code: 'B4', category: 'CONTACT_DATA', name: l('Strasse/Hausnummer', 'Street/House Number'), description: l('Strassenadresse', 'Street address'), purpose: l('Lieferung, Rechnungsstellung', 'Delivery, billing'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Vertragserfuellung', 'For contract performance'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange Konto aktiv', 'While account active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Versanddienstleister'], technicalMeasures: ['Verschluesselung'], tags: ['contact', 'address'] },
{ id: 'dp-b5-address-city', code: 'B5', category: 'CONTACT_DATA', name: l('PLZ/Ort', 'Postal Code/City'), description: l('Postleitzahl und Stadt', 'Postal code and city'), purpose: l('Lieferung, Rechnungsstellung', 'Delivery, billing'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Vertragserfuellung', 'For contract performance'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange Konto aktiv', 'While account active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Versanddienstleister'], technicalMeasures: ['Verschluesselung'], tags: ['contact', 'address'] },
{ id: 'dp-b6-address-country', code: 'B6', category: 'CONTACT_DATA', name: l('Land', 'Country'), description: l('Wohnsitzland', 'Country of residence'), purpose: l('Lieferung, Steuer', 'Delivery, tax'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Vertragserfuellung', 'For contract performance'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange Konto aktiv', 'While account active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['contact', 'address'] },
{ id: 'dp-b7-secondary-email', code: 'B7', category: 'CONTACT_DATA', name: l('Sekundaere E-Mail', 'Secondary Email'), description: l('Alternative E-Mail-Adresse', 'Alternative email address'), purpose: l('Backup-Kontakt, Wiederherstellung', 'Backup contact, recovery'), riskLevel: 'MEDIUM', legalBasis: 'CONSENT', legalBasisJustification: l('Freiwillige Angabe', 'Voluntary'), retentionPeriod: 'UNTIL_REVOCATION', retentionJustification: l('Bis Widerruf', 'Until revocation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['TLS'], tags: ['contact', 'backup'] },
{ id: 'dp-b8-fax', code: 'B8', category: 'CONTACT_DATA', name: l('Faxnummer', 'Fax Number'), description: l('Faxnummer (geschaeftlich)', 'Fax number (business)'), purpose: l('Geschaeftliche Kommunikation', 'Business communication'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer geschaeftliche Kommunikation', 'For business communication'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange Konto aktiv', 'While account active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['contact', 'business'] },
{ id: 'dp-b9-emergency-contact', code: 'B9', category: 'CONTACT_DATA', name: l('Notfallkontakt', 'Emergency Contact'), description: l('Kontaktdaten fuer Notfaelle', 'Emergency contact details'), purpose: l('Notfallbenachrichtigung', 'Emergency notification'), riskLevel: 'MEDIUM', legalBasis: 'CONSENT', legalBasisJustification: l('Freiwillige Angabe', 'Voluntary'), retentionPeriod: 'UNTIL_REVOCATION', retentionJustification: l('Bis Widerruf', 'Until revocation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Verschluesselung'], tags: ['contact', 'emergency'] },
{ id: 'dp-b10-social-profiles', code: 'B10', category: 'CONTACT_DATA', name: l('Social-Media-Profile', 'Social Media Profiles'), description: l('Links zu sozialen Netzwerken', 'Links to social networks'), purpose: l('Vernetzung, Kommunikation', 'Networking, communication'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Freiwillige Angabe', 'Voluntary'), retentionPeriod: 'UNTIL_REVOCATION', retentionJustification: l('Bis Widerruf', 'Until revocation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['contact', 'social'] },
// KATEGORIE C: AUTHENTIFIZIERUNGSDATEN (8)
{ id: 'dp-c1-password-hash', code: 'C1', category: 'AUTHENTICATION', name: l('Passwort-Hash', 'Password Hash'), description: l('Kryptografisch gehashtes Passwort', 'Cryptographically hashed password'), purpose: l('Sichere Authentifizierung', 'Secure authentication'), riskLevel: 'HIGH', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer sichere Kontoverwaltung', 'For secure account management'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange Konto aktiv', 'While account active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['bcrypt/Argon2', 'Salting'], tags: ['auth', 'security'] },
{ id: 'dp-c2-session-token', code: 'C2', category: 'AUTHENTICATION', name: l('Session-Token', 'Session Token'), description: l('JWT oder Session-ID', 'JWT or Session ID'), purpose: l('Aufrechterhaltung der Sitzung', 'Maintaining session'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Technisch erforderlich', 'Technically required'), retentionPeriod: '24_HOURS', retentionJustification: l('Kurze Lebensdauer', 'Short lifespan'), cookieCategory: 'ESSENTIAL', isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['JWT-Signatur', 'HttpOnly'], tags: ['auth', 'session'] },
{ id: 'dp-c3-refresh-token', code: 'C3', category: 'AUTHENTICATION', name: l('Refresh-Token', 'Refresh Token'), description: l('Token zur Session-Erneuerung', 'Token for session renewal'), purpose: l('Session-Erneuerung', 'Session renewal'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Benutzerfreundlichkeit', 'For user experience'), retentionPeriod: '30_DAYS', retentionJustification: l('Balance Sicherheit/UX', 'Balance security/UX'), cookieCategory: 'ESSENTIAL', isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Token-Rotation'], tags: ['auth', 'token'] },
{ id: 'dp-c4-2fa-secret', code: 'C4', category: 'AUTHENTICATION', name: l('2FA-Secret', '2FA Secret'), description: l('TOTP-Geheimnis fuer Zwei-Faktor-Auth', 'TOTP secret for two-factor auth'), purpose: l('Erhoehte Kontosicherheit', 'Enhanced account security'), riskLevel: 'HIGH', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Sicherheit', 'For security'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange 2FA aktiv', 'While 2FA active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Verschluesselung', 'HSM'], tags: ['auth', '2fa', 'security'] },
{ id: 'dp-c5-passkey', code: 'C5', category: 'AUTHENTICATION', name: l('Passkey/WebAuthn', 'Passkey/WebAuthn'), description: l('FIDO2/WebAuthn Credential', 'FIDO2/WebAuthn Credential'), purpose: l('Passwortlose Authentifizierung', 'Passwordless authentication'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer sichere Anmeldung', 'For secure login'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange Passkey aktiv', 'While passkey active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Asymmetrische Kryptografie'], tags: ['auth', 'passkey'] },
{ id: 'dp-c6-api-keys', code: 'C6', category: 'AUTHENTICATION', name: l('API-Keys', 'API Keys'), description: l('API-Schluessel fuer Integrationen', 'API keys for integrations'), purpose: l('API-Zugriff', 'API access'), riskLevel: 'HIGH', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer API-Nutzung', 'For API usage'), retentionPeriod: 'UNTIL_REVOCATION', retentionJustification: l('Bis Widerruf', 'Until revocation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Hashing', 'Rate-Limiting'], tags: ['auth', 'api'] },
{ id: 'dp-c7-oauth-provider', code: 'C7', category: 'AUTHENTICATION', name: l('OAuth-Provider-ID', 'OAuth Provider ID'), description: l('ID vom externen Auth-Provider', 'ID from external auth provider'), purpose: l('Social Login', 'Social Login'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Social Login', 'For social login'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange verknuepft', 'While linked'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['OAuth-Provider'], technicalMeasures: ['Minimale Daten'], tags: ['auth', 'oauth'] },
{ id: 'dp-c8-recovery-codes', code: 'C8', category: 'AUTHENTICATION', name: l('Wiederherstellungscodes', 'Recovery Codes'), description: l('Backup-Codes fuer 2FA', 'Backup codes for 2FA'), purpose: l('Kontowiederherstellung', 'Account recovery'), riskLevel: 'HIGH', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Notfallzugriff', 'For emergency access'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange 2FA aktiv', 'While 2FA active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Hashing', 'Einmalnutzung'], tags: ['auth', 'recovery'] },
// KATEGORIE D: EINWILLIGUNGSDATEN (6)
{ id: 'dp-d1-consent-records', code: 'D1', category: 'CONSENT', name: l('Consent-Protokolle', 'Consent Records'), description: l('Protokollierte Einwilligungen', 'Recorded consents'), purpose: l('Nachweis gegenueber Behoerden', 'Proof to authorities'), riskLevel: 'LOW', legalBasis: 'LEGAL_OBLIGATION', legalBasisJustification: l('Nachweispflicht Art. 7 DSGVO', 'Accountability Art. 7 GDPR'), retentionPeriod: '6_YEARS', retentionJustification: l('Audit-Zwecke', 'Audit purposes'), cookieCategory: 'ESSENTIAL', isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Unveraenderbare Logs'], tags: ['consent', 'compliance'] },
{ id: 'dp-d2-cookie-preferences', code: 'D2', category: 'CONSENT', name: l('Cookie-Praeferenzen', 'Cookie Preferences'), description: l('Cookie-Einstellungen des Nutzers', 'User cookie settings'), purpose: l('Speicherung der Consent-Entscheidung', 'Storage of consent decision'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Speicherung der Entscheidung', 'Storage of decision'), retentionPeriod: '12_MONTHS', retentionJustification: l('Branchenueblich', 'Industry standard'), cookieCategory: 'ESSENTIAL', isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['First-Party Cookie'], tags: ['consent', 'cookie'] },
{ id: 'dp-d3-marketing-consent', code: 'D3', category: 'CONSENT', name: l('Marketing-Einwilligung', 'Marketing Consent'), description: l('Einwilligung fuer Werbung', 'Consent for advertising'), purpose: l('Marketing-Kommunikation', 'Marketing communication'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Freiwillige Einwilligung', 'Voluntary consent'), retentionPeriod: 'UNTIL_REVOCATION', retentionJustification: l('Bis Widerruf', 'Until revocation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: [], technicalMeasures: ['Double-Opt-In'], tags: ['consent', 'marketing'] },
{ id: 'dp-d4-data-sharing', code: 'D4', category: 'CONSENT', name: l('Datenweitergabe-Einwilligung', 'Data Sharing Consent'), description: l('Einwilligung zur Datenweitergabe', 'Consent to data sharing'), purpose: l('Weitergabe an Partner', 'Sharing with partners'), riskLevel: 'MEDIUM', legalBasis: 'CONSENT', legalBasisJustification: l('Freiwillige Einwilligung', 'Voluntary consent'), retentionPeriod: 'UNTIL_REVOCATION', retentionJustification: l('Bis Widerruf', 'Until revocation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: [], technicalMeasures: ['Dokumentation'], tags: ['consent', 'sharing'] },
{ id: 'dp-d5-locale-preferences', code: 'D5', category: 'CONSENT', name: l('Sprach-/Regionspraeferenz', 'Language/Region Preference'), description: l('Bevorzugte Sprache und Region', 'Preferred language and region'), purpose: l('Lokalisierung', 'Localization'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Servicefunktionalitaet', 'Service functionality'), retentionPeriod: '12_MONTHS', retentionJustification: l('Nutzereinstellungen', 'User settings'), cookieCategory: 'ESSENTIAL', isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['First-Party Cookie'], tags: ['preferences', 'locale'] },
{ id: 'dp-d6-newsletter-consent', code: 'D6', category: 'CONSENT', name: l('Newsletter-Einwilligung', 'Newsletter Consent'), description: l('Einwilligung fuer Newsletter', 'Newsletter subscription consent'), purpose: l('E-Mail-Marketing', 'Email marketing'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Double-Opt-In erforderlich', 'Double opt-in required'), retentionPeriod: 'UNTIL_REVOCATION', retentionJustification: l('Bis Widerruf', 'Until revocation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['E-Mail-Provider'], technicalMeasures: ['Double-Opt-In', 'Abmelde-Link'], tags: ['consent', 'newsletter'] },
// KATEGORIE E: KOMMUNIKATIONSDATEN (7)
{ id: 'dp-e1-support-tickets', code: 'E1', category: 'COMMUNICATION', name: l('Support-Tickets', 'Support Tickets'), description: l('Inhalt von Kundenanfragen', 'Content of customer inquiries'), purpose: l('Kundenservice', 'Customer service'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Supportleistungen', 'For support services'), retentionPeriod: '24_MONTHS', retentionJustification: l('Nachvollziehbarkeit', 'Traceability'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Helpdesk-Software'], technicalMeasures: ['Verschluesselung'], tags: ['support', 'communication'] },
{ id: 'dp-e2-chat-history', code: 'E2', category: 'COMMUNICATION', name: l('Chat-Verlaeufe', 'Chat Histories'), description: l('Live-Chat und Chatbot-Verlaeufe', 'Live chat and chatbot histories'), purpose: l('Kundenservice, QA', 'Customer service, QA'), riskLevel: 'MEDIUM', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Servicequalitaet', 'Service quality'), retentionPeriod: '12_MONTHS', retentionJustification: l('Qualitaetssicherung', 'Quality assurance'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Chat-Software'], technicalMeasures: ['Pseudonymisierung'], tags: ['support', 'chat'] },
{ id: 'dp-e3-call-recordings', code: 'E3', category: 'COMMUNICATION', name: l('Anrufaufzeichnungen', 'Call Recordings'), description: l('Aufzeichnungen von Telefonaten', 'Recordings of phone calls'), purpose: l('Qualitaetssicherung, Schulung', 'Quality assurance, training'), riskLevel: 'HIGH', legalBasis: 'CONSENT', legalBasisJustification: l('Ausdrueckliche Einwilligung vor Aufzeichnung', 'Explicit consent before recording'), retentionPeriod: '90_DAYS', retentionJustification: l('Begrenzte Qualitaetspruefung', 'Limited quality review'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['Telefonie-Anbieter'], technicalMeasures: ['Verschluesselung', 'Auto-Loeschung'], tags: ['support', 'recording'] },
{ id: 'dp-e4-email-content', code: 'E4', category: 'COMMUNICATION', name: l('E-Mail-Inhalte', 'Email Content'), description: l('Inhalt von E-Mail-Korrespondenz', 'Content of email correspondence'), purpose: l('Kommunikation, Dokumentation', 'Communication, documentation'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Kommunikation', 'For communication'), retentionPeriod: '24_MONTHS', retentionJustification: l('Nachvollziehbarkeit', 'Traceability'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['E-Mail-Provider'], technicalMeasures: ['TLS', 'Archivierung'], tags: ['communication', 'email'] },
{ id: 'dp-e5-feedback', code: 'E5', category: 'COMMUNICATION', name: l('Feedback/Bewertungen', 'Feedback/Reviews'), description: l('Nutzerbewertungen und Feedback', 'User ratings and feedback'), purpose: l('Qualitaetsmessung', 'Quality measurement'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Produktqualitaet', 'Product quality'), retentionPeriod: '24_MONTHS', retentionJustification: l('Langzeitanalyse', 'Long-term analysis'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Aggregierung'], tags: ['feedback', 'quality'] },
{ id: 'dp-e6-notifications', code: 'E6', category: 'COMMUNICATION', name: l('Benachrichtigungsverlauf', 'Notification History'), description: l('Historie gesendeter Benachrichtigungen', 'History of sent notifications'), purpose: l('Nachvollziehbarkeit', 'Traceability'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Servicefunktionalitaet', 'Service functionality'), retentionPeriod: '12_MONTHS', retentionJustification: l('Support-Zwecke', 'Support purposes'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Logging'], tags: ['communication', 'notifications'] },
{ id: 'dp-e7-forum-posts', code: 'E7', category: 'COMMUNICATION', name: l('Forum-/Community-Beitraege', 'Forum/Community Posts'), description: l('Beitraege in Foren oder Communities', 'Posts in forums or communities'), purpose: l('Community-Interaktion', 'Community interaction'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Community-Nutzung', 'Community usage'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange Konto aktiv', 'While account active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Moderation'], tags: ['community', 'content'] },
// KATEGORIE F: ZAHLUNGSDATEN (8)
{ id: 'dp-f1-billing-address', code: 'F1', category: 'PAYMENT', name: l('Rechnungsadresse', 'Billing Address'), description: l('Vollstaendige Rechnungsanschrift', 'Complete billing address'), purpose: l('Rechnungsstellung, Steuer', 'Invoicing, tax'), riskLevel: 'MEDIUM', legalBasis: 'LEGAL_OBLIGATION', legalBasisJustification: l('§147 AO, §257 HGB', '§147 AO, §257 HGB'), retentionPeriod: '10_YEARS', retentionJustification: l('Steuerliche Aufbewahrung', 'Tax retention'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Steuerberater'], technicalMeasures: ['Verschluesselung', 'Archivierung'], tags: ['payment', 'billing'] },
{ id: 'dp-f2-payment-token', code: 'F2', category: 'PAYMENT', name: l('Zahlungs-Token', 'Payment Token'), description: l('Tokenisierte Zahlungsinformationen', 'Tokenized payment information'), purpose: l('Wiederkehrende Zahlungen', 'Recurring payments'), riskLevel: 'HIGH', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Zahlungsabwicklung', 'For payment processing'), retentionPeriod: '36_MONTHS', retentionJustification: l('Kundenbeziehung plus Rueckbuchungsfrist', 'Customer relationship plus chargeback period'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Payment Provider'], technicalMeasures: ['PCI-DSS', 'Tokenisierung'], tags: ['payment', 'token'] },
{ id: 'dp-f3-transactions', code: 'F3', category: 'PAYMENT', name: l('Transaktionshistorie', 'Transaction History'), description: l('Historie aller Transaktionen', 'History of all transactions'), purpose: l('Buchfuehrung, Nachweis', 'Accounting, proof'), riskLevel: 'MEDIUM', legalBasis: 'LEGAL_OBLIGATION', legalBasisJustification: l('§147 AO', '§147 AO'), retentionPeriod: '10_YEARS', retentionJustification: l('Steuerliche Aufbewahrung', 'Tax retention'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Steuerberater', 'Wirtschaftspruefer'], technicalMeasures: ['Revisionssichere Archivierung'], tags: ['payment', 'transactions'] },
{ id: 'dp-f4-iban', code: 'F4', category: 'PAYMENT', name: l('IBAN/Bankverbindung', 'IBAN/Bank Details'), description: l('Bankverbindung fuer Lastschrift', 'Bank details for direct debit'), purpose: l('Lastschrifteinzug', 'Direct debit'), riskLevel: 'HIGH', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer SEPA-Lastschrift', 'For SEPA direct debit'), retentionPeriod: '10_YEARS', retentionJustification: l('Steuerliche Aufbewahrung', 'Tax retention'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Bank'], technicalMeasures: ['Verschluesselung', 'Zugriffskontrolle'], tags: ['payment', 'bank'] },
{ id: 'dp-f5-invoices', code: 'F5', category: 'PAYMENT', name: l('Rechnungen', 'Invoices'), description: l('Ausgestellte Rechnungen', 'Issued invoices'), purpose: l('Buchfuehrung, Steuer', 'Accounting, tax'), riskLevel: 'MEDIUM', legalBasis: 'LEGAL_OBLIGATION', legalBasisJustification: l('§147 AO, §257 HGB', '§147 AO, §257 HGB'), retentionPeriod: '10_YEARS', retentionJustification: l('Steuerliche Aufbewahrung', 'Tax retention'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Steuerberater'], technicalMeasures: ['Revisionssichere Archivierung'], tags: ['payment', 'invoices'] },
{ id: 'dp-f6-tax-id', code: 'F6', category: 'PAYMENT', name: l('USt-IdNr./Steuernummer', 'VAT ID/Tax Number'), description: l('Umsatzsteuer-ID oder Steuernummer', 'VAT ID or tax number'), purpose: l('Steuerliche Dokumentation', 'Tax documentation'), riskLevel: 'MEDIUM', legalBasis: 'LEGAL_OBLIGATION', legalBasisJustification: l('Steuerrecht', 'Tax law'), retentionPeriod: '10_YEARS', retentionJustification: l('Steuerliche Aufbewahrung', 'Tax retention'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Steuerberater'], technicalMeasures: ['Verschluesselung'], tags: ['payment', 'tax'] },
{ id: 'dp-f7-subscription', code: 'F7', category: 'PAYMENT', name: l('Abonnement-Daten', 'Subscription Data'), description: l('Abonnement-Details und Status', 'Subscription details and status'), purpose: l('Abonnementverwaltung', 'Subscription management'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Vertragserfuellung', 'For contract performance'), retentionPeriod: '10_YEARS', retentionJustification: l('Steuerliche Aufbewahrung', 'Tax retention'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['payment', 'subscription'] },
{ id: 'dp-f8-refunds', code: 'F8', category: 'PAYMENT', name: l('Erstattungen', 'Refunds'), description: l('Erstattungshistorie', 'Refund history'), purpose: l('Buchfuehrung', 'Accounting'), riskLevel: 'LOW', legalBasis: 'LEGAL_OBLIGATION', legalBasisJustification: l('§147 AO', '§147 AO'), retentionPeriod: '10_YEARS', retentionJustification: l('Steuerliche Aufbewahrung', 'Tax retention'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Steuerberater'], technicalMeasures: ['Revisionssichere Archivierung'], tags: ['payment', 'refunds'] },
// KATEGORIE G: NUTZUNGSDATEN (8)
{ id: 'dp-g1-session-duration', code: 'G1', category: 'USAGE_DATA', name: l('Sitzungsdauer', 'Session Duration'), description: l('Dauer einzelner Sitzungen', 'Duration of individual sessions'), purpose: l('Nutzungsanalyse', 'Usage analysis'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Produktverbesserung', 'Product improvement'), retentionPeriod: '24_MONTHS', retentionJustification: l('Langzeitanalyse', 'Long-term analysis'), cookieCategory: 'PERFORMANCE', isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Aggregierung'], tags: ['usage', 'analytics'] },
{ id: 'dp-g2-page-views', code: 'G2', category: 'USAGE_DATA', name: l('Seitenaufrufe', 'Page Views'), description: l('Aufgerufene Seiten', 'Visited pages'), purpose: l('Nutzungsanalyse', 'Usage analysis'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Produktverbesserung', 'Product improvement'), retentionPeriod: '24_MONTHS', retentionJustification: l('Langzeitanalyse', 'Long-term analysis'), cookieCategory: 'PERFORMANCE', isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Aggregierung'], tags: ['usage', 'analytics'] },
{ id: 'dp-g3-click-paths', code: 'G3', category: 'USAGE_DATA', name: l('Klickpfade', 'Click Paths'), description: l('Navigationsverhalten', 'Navigation behavior'), purpose: l('UX-Optimierung', 'UX optimization'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Produktverbesserung', 'Product improvement'), retentionPeriod: '12_MONTHS', retentionJustification: l('UX-Analyse', 'UX analysis'), cookieCategory: 'PERFORMANCE', isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Pseudonymisierung'], tags: ['usage', 'ux'] },
{ id: 'dp-g4-search-queries', code: 'G4', category: 'USAGE_DATA', name: l('Suchanfragen', 'Search Queries'), description: l('Interne Suchanfragen', 'Internal search queries'), purpose: l('Suchoptimierung', 'Search optimization'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Produktverbesserung', 'Product improvement'), retentionPeriod: '90_DAYS', retentionJustification: l('Kurzfristige Analyse', 'Short-term analysis'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Anonymisierung'], tags: ['usage', 'search'] },
{ id: 'dp-g5-feature-usage', code: 'G5', category: 'USAGE_DATA', name: l('Feature-Nutzung', 'Feature Usage'), description: l('Nutzung einzelner Features', 'Usage of individual features'), purpose: l('Produktentwicklung', 'Product development'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Produktverbesserung', 'Product improvement'), retentionPeriod: '24_MONTHS', retentionJustification: l('Langzeitanalyse', 'Long-term analysis'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Aggregierung'], tags: ['usage', 'features'] },
{ id: 'dp-g6-error-logs', code: 'G6', category: 'USAGE_DATA', name: l('Fehlerprotokolle', 'Error Logs'), description: l('Client-seitige Fehler', 'Client-side errors'), purpose: l('Fehlerbehebung', 'Bug fixing'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Qualitaetssicherung', 'Quality assurance'), retentionPeriod: '90_DAYS', retentionJustification: l('Fehleranalyse', 'Error analysis'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Error-Tracking-Dienst'], technicalMeasures: ['Pseudonymisierung'], tags: ['usage', 'errors'] },
{ id: 'dp-g7-preferences', code: 'G7', category: 'USAGE_DATA', name: l('Nutzereinstellungen', 'User Preferences'), description: l('Individuelle Einstellungen', 'Individual settings'), purpose: l('Personalisierung', 'Personalization'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Servicefunktionalitaet', 'Service functionality'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange Konto aktiv', 'While account active'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['usage', 'preferences'] },
{ id: 'dp-g8-ab-tests', code: 'G8', category: 'USAGE_DATA', name: l('A/B-Test-Zuordnung', 'A/B Test Assignment'), description: l('Zuordnung zu Testvarianten', 'Assignment to test variants'), purpose: l('Produktoptimierung', 'Product optimization'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Produktverbesserung', 'Product improvement'), retentionPeriod: '90_DAYS', retentionJustification: l('Testdauer', 'Test duration'), cookieCategory: 'PERFORMANCE', isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Pseudonymisierung'], tags: ['usage', 'testing'] },
// KATEGORIE H: STANDORTDATEN (7)
{ id: 'dp-h1-gps', code: 'H1', category: 'LOCATION', name: l('GPS-Standort', 'GPS Location'), description: l('Praeziser GPS-Standort', 'Precise GPS location'), purpose: l('Standortbasierte Dienste', 'Location-based services'), riskLevel: 'HIGH', legalBasis: 'CONSENT', legalBasisJustification: l('Ausdrueckliche Einwilligung', 'Explicit consent'), retentionPeriod: '30_DAYS', retentionJustification: l('Datensparsamkeit', 'Data minimization'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: [], technicalMeasures: ['Verschluesselung', 'On-Device'], tags: ['location', 'gps'] },
{ id: 'dp-h2-ip-geo', code: 'H2', category: 'LOCATION', name: l('IP-Geolokation', 'IP Geolocation'), description: l('Ungefaehrer Standort aus IP', 'Approximate location from IP'), purpose: l('Regionalisierung', 'Regionalization'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Servicefunktionalitaet', 'Service functionality'), retentionPeriod: '90_DAYS', retentionJustification: l('Sicherheitsanalyse', 'Security analysis'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Nur Landesebene'], tags: ['location', 'ip'] },
{ id: 'dp-h3-timezone', code: 'H3', category: 'LOCATION', name: l('Zeitzone', 'Timezone'), description: l('Zeitzone des Nutzers', 'User timezone'), purpose: l('Lokalisierung', 'Localization'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Servicefunktionalitaet', 'Service functionality'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Nutzereinstellung', 'User setting'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: [], tags: ['location', 'timezone'] },
{ id: 'dp-h4-location-history', code: 'H4', category: 'LOCATION', name: l('Standortverlauf', 'Location History'), description: l('Historie von Standorten', 'History of locations'), purpose: l('Personalisierung', 'Personalization'), riskLevel: 'HIGH', legalBasis: 'CONSENT', legalBasisJustification: l('Freiwillige Funktion', 'Optional feature'), retentionPeriod: '30_DAYS', retentionJustification: l('Datensparsamkeit', 'Data minimization'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: [], technicalMeasures: ['Verschluesselung'], tags: ['location', 'history'] },
{ id: 'dp-h5-country', code: 'H5', category: 'LOCATION', name: l('Herkunftsland', 'Country of Origin'), description: l('Land basierend auf IP', 'Country based on IP'), purpose: l('Compliance, Geo-Blocking', 'Compliance, geo-blocking'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Compliance', 'Compliance'), retentionPeriod: '12_MONTHS', retentionJustification: l('Sicherheit', 'Security'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: [], tags: ['location', 'country'] },
{ id: 'dp-h6-wifi-networks', code: 'H6', category: 'LOCATION', name: l('WLAN-Netzwerke', 'WiFi Networks'), description: l('Erkannte WLAN-Netzwerke', 'Detected WiFi networks'), purpose: l('Standortbestimmung', 'Location detection'), riskLevel: 'MEDIUM', legalBasis: 'CONSENT', legalBasisJustification: l('Nur mit Einwilligung', 'Only with consent'), retentionPeriod: '24_HOURS', retentionJustification: l('Kurzlebig', 'Short-lived'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: [], technicalMeasures: ['On-Device'], tags: ['location', 'wifi'] },
{ id: 'dp-h7-travel-info', code: 'H7', category: 'LOCATION', name: l('Reiseinformationen', 'Travel Information'), description: l('Reiseziele und Plaene', 'Travel destinations and plans'), purpose: l('Reiseservices', 'Travel services'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer Reisedienste', 'For travel services'), retentionPeriod: '12_MONTHS', retentionJustification: l('Serviceerbringung', 'Service delivery'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Reiseanbieter'], technicalMeasures: ['Verschluesselung'], tags: ['location', 'travel'] },
// KATEGORIE I: GERAETEDATEN (10)
{ id: 'dp-i1-ip-address', code: 'I1', category: 'DEVICE_DATA', name: l('IP-Adresse', 'IP Address'), description: l('IP-Adresse des Nutzers', 'User IP address'), purpose: l('Sicherheit, Routing', 'Security, routing'), riskLevel: 'MEDIUM', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('IT-Sicherheit', 'IT security'), retentionPeriod: '90_DAYS', retentionJustification: l('Sicherheitsanalyse', 'Security analysis'), cookieCategory: 'ESSENTIAL', isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Security-Monitoring'], technicalMeasures: ['IP-Anonymisierung'], tags: ['device', 'network'] },
{ id: 'dp-i2-fingerprint', code: 'I2', category: 'DEVICE_DATA', name: l('Device Fingerprint', 'Device Fingerprint'), description: l('Hash aus Geraetemerkmalen', 'Hash of device characteristics'), purpose: l('Betrugspraevention', 'Fraud prevention'), riskLevel: 'MEDIUM', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Betrugspraevention', 'Fraud prevention'), retentionPeriod: '30_DAYS', retentionJustification: l('Kurze Speicherung', 'Short storage'), cookieCategory: 'ESSENTIAL', isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Einweg-Hashing'], tags: ['device', 'fingerprint'] },
{ id: 'dp-i3-browser', code: 'I3', category: 'DEVICE_DATA', name: l('Browser/User-Agent', 'Browser/User Agent'), description: l('Browser und Version', 'Browser and version'), purpose: l('Kompatibilitaet', 'Compatibility'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Technisch notwendig', 'Technically necessary'), retentionPeriod: '12_MONTHS', retentionJustification: l('Support', 'Support'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: [], tags: ['device', 'browser'] },
{ id: 'dp-i4-os', code: 'I4', category: 'DEVICE_DATA', name: l('Betriebssystem', 'Operating System'), description: l('OS und Version', 'OS and version'), purpose: l('Kompatibilitaet', 'Compatibility'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Technisch notwendig', 'Technically necessary'), retentionPeriod: '12_MONTHS', retentionJustification: l('Support', 'Support'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: [], tags: ['device', 'os'] },
{ id: 'dp-i5-screen', code: 'I5', category: 'DEVICE_DATA', name: l('Bildschirmaufloesung', 'Screen Resolution'), description: l('Bildschirmgroesse', 'Screen size'), purpose: l('Responsive Design', 'Responsive design'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('UX-Optimierung', 'UX optimization'), retentionPeriod: '12_MONTHS', retentionJustification: l('Analytics', 'Analytics'), cookieCategory: 'PERFORMANCE', isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: [], tags: ['device', 'screen'] },
{ id: 'dp-i6-language', code: 'I6', category: 'DEVICE_DATA', name: l('Browser-Sprache', 'Browser Language'), description: l('Spracheinstellung des Browsers', 'Browser language setting'), purpose: l('Lokalisierung', 'Localization'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Servicefunktionalitaet', 'Service functionality'), retentionPeriod: '12_MONTHS', retentionJustification: l('Nutzereinstellung', 'User setting'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: [], tags: ['device', 'language'] },
{ id: 'dp-i7-push-token', code: 'I7', category: 'DEVICE_DATA', name: l('Push-Token', 'Push Token'), description: l('Token fuer Push-Nachrichten', 'Token for push notifications'), purpose: l('Push-Benachrichtigungen', 'Push notifications'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Opt-In fuer Push', 'Opt-in for push'), retentionPeriod: 'UNTIL_REVOCATION', retentionJustification: l('Bis Deaktivierung', 'Until deactivation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['Push-Dienst'], technicalMeasures: ['Verschluesselung'], tags: ['device', 'push'] },
{ id: 'dp-i8-device-id', code: 'I8', category: 'DEVICE_DATA', name: l('Geraete-ID', 'Device ID'), description: l('Eindeutige Geraetekennung', 'Unique device identifier'), purpose: l('Geraeteverwaltung', 'Device management'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Multi-Device-Support', 'Multi-device support'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange verknuepft', 'While linked'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Hashing'], tags: ['device', 'id'] },
{ id: 'dp-i9-app-version', code: 'I9', category: 'DEVICE_DATA', name: l('App-Version', 'App Version'), description: l('Installierte App-Version', 'Installed app version'), purpose: l('Support, Updates', 'Support, updates'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Technisch notwendig', 'Technically necessary'), retentionPeriod: '12_MONTHS', retentionJustification: l('Support', 'Support'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: [], tags: ['device', 'app'] },
{ id: 'dp-i10-hardware', code: 'I10', category: 'DEVICE_DATA', name: l('Hardware-Info', 'Hardware Info'), description: l('Geraetetyp, Hersteller', 'Device type, manufacturer'), purpose: l('Kompatibilitaet', 'Compatibility'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Produktentwicklung', 'Product development'), retentionPeriod: '12_MONTHS', retentionJustification: l('Analytics', 'Analytics'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: [], tags: ['device', 'hardware'] },
// KATEGORIE J: MARKETINGDATEN (8)
{ id: 'dp-j1-tracking-pixel', code: 'J1', category: 'MARKETING', name: l('Tracking-Pixel', 'Tracking Pixel'), description: l('Conversion-Tracking-Pixel', 'Conversion tracking pixel'), purpose: l('Werbemessung', 'Ad measurement'), riskLevel: 'HIGH', legalBasis: 'CONSENT', legalBasisJustification: l('Cookie-Consent erforderlich', 'Cookie consent required'), retentionPeriod: '90_DAYS', retentionJustification: l('Conversion-Fenster', 'Conversion window'), cookieCategory: 'PERSONALIZATION', isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['Google Ads', 'Meta'], technicalMeasures: ['Nur bei Consent'], tags: ['marketing', 'tracking'] },
{ id: 'dp-j2-advertising-id', code: 'J2', category: 'MARKETING', name: l('Werbe-ID', 'Advertising ID'), description: l('Geraetuebergreifende Werbe-ID', 'Cross-device advertising ID'), purpose: l('Personalisierte Werbung', 'Personalized advertising'), riskLevel: 'HIGH', legalBasis: 'CONSENT', legalBasisJustification: l('Wegen Profilbildung', 'Due to profiling'), retentionPeriod: '90_DAYS', retentionJustification: l('Kampagnenzeitraum', 'Campaign period'), cookieCategory: 'PERSONALIZATION', isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['Werbenetzwerke'], technicalMeasures: ['Opt-out'], tags: ['marketing', 'advertising'] },
{ id: 'dp-j3-utm', code: 'J3', category: 'MARKETING', name: l('UTM-Parameter', 'UTM Parameters'), description: l('Kampagnen-Tracking-Parameter', 'Campaign tracking parameters'), purpose: l('Kampagnen-Attribution', 'Campaign attribution'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Kampagnenmessung', 'Campaign measurement'), retentionPeriod: '30_DAYS', retentionJustification: l('Session-Attribution', 'Session attribution'), cookieCategory: 'PERFORMANCE', isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Analytics'], technicalMeasures: ['Aggregierung'], tags: ['marketing', 'utm'] },
{ id: 'dp-j4-newsletter', code: 'J4', category: 'MARKETING', name: l('Newsletter-Daten', 'Newsletter Data'), description: l('E-Mail und Praeferenzen', 'Email and preferences'), purpose: l('Newsletter-Versand', 'Newsletter delivery'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Double-Opt-In', 'Double opt-in'), retentionPeriod: 'UNTIL_REVOCATION', retentionJustification: l('Bis Abmeldung', 'Until unsubscribe'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['E-Mail-Provider'], technicalMeasures: ['Double-Opt-In'], tags: ['marketing', 'newsletter'] },
{ id: 'dp-j5-remarketing', code: 'J5', category: 'MARKETING', name: l('Remarketing-Listen', 'Remarketing Lists'), description: l('Zielgruppen fuer Remarketing', 'Audiences for remarketing'), purpose: l('Remarketing', 'Remarketing'), riskLevel: 'HIGH', legalBasis: 'CONSENT', legalBasisJustification: l('Profilbildung', 'Profiling'), retentionPeriod: '90_DAYS', retentionJustification: l('Kampagnenzeitraum', 'Campaign period'), cookieCategory: 'PERSONALIZATION', isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['Werbenetzwerke'], technicalMeasures: ['Hashing'], tags: ['marketing', 'remarketing'] },
{ id: 'dp-j6-email-opens', code: 'J6', category: 'MARKETING', name: l('E-Mail-Oeffnungen', 'Email Opens'), description: l('Oeffnungsraten von E-Mails', 'Email open rates'), purpose: l('E-Mail-Optimierung', 'Email optimization'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Teil der Newsletter-Einwilligung', 'Part of newsletter consent'), retentionPeriod: '12_MONTHS', retentionJustification: l('Analyse', 'Analysis'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['E-Mail-Provider'], technicalMeasures: ['Aggregierung'], tags: ['marketing', 'email'] },
{ id: 'dp-j7-ad-clicks', code: 'J7', category: 'MARKETING', name: l('Anzeigen-Klicks', 'Ad Clicks'), description: l('Klicks auf Werbeanzeigen', 'Clicks on advertisements'), purpose: l('Werbemessung', 'Ad measurement'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Teil der Werbe-Einwilligung', 'Part of advertising consent'), retentionPeriod: '90_DAYS', retentionJustification: l('Conversion-Fenster', 'Conversion window'), cookieCategory: 'PERSONALIZATION', isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['Werbenetzwerke'], technicalMeasures: ['Aggregierung'], tags: ['marketing', 'advertising'] },
{ id: 'dp-j8-referrer', code: 'J8', category: 'MARKETING', name: l('Referrer-URL', 'Referrer URL'), description: l('Herkunftsseite', 'Source page'), purpose: l('Traffic-Analyse', 'Traffic analysis'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Marketing-Attribution', 'Marketing attribution'), retentionPeriod: '30_DAYS', retentionJustification: l('Kurzfristige Analyse', 'Short-term analysis'), cookieCategory: 'PERFORMANCE', isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Aggregierung'], tags: ['marketing', 'referrer'] },
// KATEGORIE K: ANALYSEDATEN (7)
{ id: 'dp-k1-google-analytics', code: 'K1', category: 'ANALYTICS', name: l('Google Analytics', 'Google Analytics'), description: l('GA4-Analysedaten', 'GA4 analytics data'), purpose: l('Web-Analyse', 'Web analytics'), riskLevel: 'MEDIUM', legalBasis: 'CONSENT', legalBasisJustification: l('Cookie-Consent erforderlich', 'Cookie consent required'), retentionPeriod: '26_MONTHS', retentionJustification: l('GA4-Standard', 'GA4 standard'), cookieCategory: 'PERFORMANCE', isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['Google'], technicalMeasures: ['IP-Anonymisierung', 'Consent-Mode'], tags: ['analytics', 'ga4'] },
{ id: 'dp-k2-heatmaps', code: 'K2', category: 'ANALYTICS', name: l('Heatmaps', 'Heatmaps'), description: l('Klick- und Scroll-Heatmaps', 'Click and scroll heatmaps'), purpose: l('UX-Analyse', 'UX analysis'), riskLevel: 'MEDIUM', legalBasis: 'CONSENT', legalBasisJustification: l('Cookie-Consent erforderlich', 'Cookie consent required'), retentionPeriod: '12_MONTHS', retentionJustification: l('UX-Optimierung', 'UX optimization'), cookieCategory: 'PERFORMANCE', isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['Hotjar/Clarity'], technicalMeasures: ['Anonymisierung'], tags: ['analytics', 'heatmaps'] },
{ id: 'dp-k3-session-recording', code: 'K3', category: 'ANALYTICS', name: l('Session-Recordings', 'Session Recordings'), description: l('Aufzeichnung von Sitzungen', 'Recording of sessions'), purpose: l('UX-Analyse, Debugging', 'UX analysis, debugging'), riskLevel: 'HIGH', legalBasis: 'CONSENT', legalBasisJustification: l('Ausdrueckliche Einwilligung', 'Explicit consent'), retentionPeriod: '90_DAYS', retentionJustification: l('Begrenzte Aufbewahrung', 'Limited retention'), cookieCategory: 'PERFORMANCE', isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['Recording-Dienst'], technicalMeasures: ['Passwort-Maskierung', 'PII-Filterung'], tags: ['analytics', 'recording'] },
{ id: 'dp-k4-events', code: 'K4', category: 'ANALYTICS', name: l('Event-Tracking', 'Event Tracking'), description: l('Benutzerdefinierte Events', 'Custom events'), purpose: l('Produktanalyse', 'Product analysis'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Cookie-Consent', 'Cookie consent'), retentionPeriod: '26_MONTHS', retentionJustification: l('Langzeitanalyse', 'Long-term analysis'), cookieCategory: 'PERFORMANCE', isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['Analytics-Dienst'], technicalMeasures: ['Aggregierung'], tags: ['analytics', 'events'] },
{ id: 'dp-k5-conversion', code: 'K5', category: 'ANALYTICS', name: l('Conversion-Daten', 'Conversion Data'), description: l('Konversions-Events', 'Conversion events'), purpose: l('Conversion-Optimierung', 'Conversion optimization'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Cookie-Consent', 'Cookie consent'), retentionPeriod: '26_MONTHS', retentionJustification: l('Business-Analyse', 'Business analysis'), cookieCategory: 'PERFORMANCE', isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['Analytics-Dienst'], technicalMeasures: ['Aggregierung'], tags: ['analytics', 'conversion'] },
{ id: 'dp-k6-funnel', code: 'K6', category: 'ANALYTICS', name: l('Funnel-Analyse', 'Funnel Analysis'), description: l('Trichterdaten', 'Funnel data'), purpose: l('Conversion-Optimierung', 'Conversion optimization'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Cookie-Consent', 'Cookie consent'), retentionPeriod: '26_MONTHS', retentionJustification: l('Business-Analyse', 'Business analysis'), cookieCategory: 'PERFORMANCE', isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['Analytics-Dienst'], technicalMeasures: ['Aggregierung'], tags: ['analytics', 'funnel'] },
{ id: 'dp-k7-cohort', code: 'K7', category: 'ANALYTICS', name: l('Kohorten-Analyse', 'Cohort Analysis'), description: l('Kohortenbasierte Daten', 'Cohort-based data'), purpose: l('Nutzeranalyse', 'User analysis'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Cookie-Consent', 'Cookie consent'), retentionPeriod: '26_MONTHS', retentionJustification: l('Langzeitanalyse', 'Long-term analysis'), cookieCategory: 'PERFORMANCE', isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['Analytics-Dienst'], technicalMeasures: ['Aggregierung'], tags: ['analytics', 'cohort'] },
// KATEGORIE L: SOCIAL-MEDIA-DATEN (6)
{ id: 'dp-l1-profile-id', code: 'L1', category: 'SOCIAL_MEDIA', name: l('Social-Profil-ID', 'Social Profile ID'), description: l('ID aus Social Login', 'ID from social login'), purpose: l('Social Login', 'Social login'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Freiwilliger Social Login', 'Voluntary social login'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange verknuepft', 'While linked'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Social-Network'], technicalMeasures: ['Minimale Daten'], tags: ['social', 'login'] },
{ id: 'dp-l2-avatar', code: 'L2', category: 'SOCIAL_MEDIA', name: l('Social-Avatar', 'Social Avatar'), description: l('Profilbild aus Social Network', 'Profile picture from social network'), purpose: l('Personalisierung', 'Personalization'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Freiwilliger Import', 'Voluntary import'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange gewuenscht', 'While desired'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Lokale Kopie'], tags: ['social', 'avatar'] },
{ id: 'dp-l3-connections', code: 'L3', category: 'SOCIAL_MEDIA', name: l('Social-Verbindungen', 'Social Connections'), description: l('Freunde/Follower', 'Friends/followers'), purpose: l('Social Features', 'Social features'), riskLevel: 'MEDIUM', legalBasis: 'CONSENT', legalBasisJustification: l('Freiwilliger Import', 'Voluntary import'), retentionPeriod: 'UNTIL_REVOCATION', retentionJustification: l('Bis Widerruf', 'Until revocation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: ['Social-Network'], technicalMeasures: ['Minimale Daten'], tags: ['social', 'connections'] },
{ id: 'dp-l4-shares', code: 'L4', category: 'SOCIAL_MEDIA', name: l('Geteilte Inhalte', 'Shared Content'), description: l('Auf Social Media geteilte Inhalte', 'Content shared on social media'), purpose: l('Social Sharing', 'Social sharing'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Freiwilliges Teilen', 'Voluntary sharing'), retentionPeriod: '12_MONTHS', retentionJustification: l('Nachvollziehbarkeit', 'Traceability'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Social-Networks'], technicalMeasures: ['Logging'], tags: ['social', 'sharing'] },
{ id: 'dp-l5-likes', code: 'L5', category: 'SOCIAL_MEDIA', name: l('Likes/Reaktionen', 'Likes/Reactions'), description: l('Social-Media-Interaktionen', 'Social media interactions'), purpose: l('Social Features', 'Social features'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Teil des Services', 'Part of service'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Nutzerfunktion', 'User feature'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: [], tags: ['social', 'interactions'] },
{ id: 'dp-l6-oauth-tokens', code: 'L6', category: 'SOCIAL_MEDIA', name: l('OAuth-Tokens', 'OAuth Tokens'), description: l('Zugangs-Token fuer Social APIs', 'Access tokens for social APIs'), purpose: l('API-Zugriff', 'API access'), riskLevel: 'MEDIUM', legalBasis: 'CONSENT', legalBasisJustification: l('Freiwillige Verknuepfung', 'Voluntary linking'), retentionPeriod: 'UNTIL_REVOCATION', retentionJustification: l('Bis Widerruf', 'Until revocation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Social-Network'], technicalMeasures: ['Verschluesselung', 'Token-Rotation'], tags: ['social', 'oauth'] },
// KATEGORIE M: GESUNDHEITSDATEN (7) - ART. 9 DSGVO!
{ id: 'dp-m1-health-status', code: 'M1', category: 'HEALTH_DATA', name: l('Gesundheitszustand', 'Health Status'), description: l('Allgemeiner Gesundheitszustand', 'General health status'), purpose: l('Gesundheitsdienste', 'Health services'), riskLevel: 'HIGH', legalBasis: 'EXPLICIT_CONSENT', legalBasisJustification: l('Art. 9 Abs. 2 lit. a DSGVO - Ausdrueckliche Einwilligung', 'Art. 9(2)(a) GDPR - Explicit consent'), retentionPeriod: '10_YEARS', retentionJustification: l('Medizinische Aufbewahrung', 'Medical retention'), cookieCategory: null, isSpecialCategory: true, requiresExplicitConsent: true, thirdPartyRecipients: [], technicalMeasures: ['Ende-zu-Ende-Verschluesselung', 'Zugriffskontrolle', 'Audit-Logging'], tags: ['health', 'article9', 'sensitive'] },
{ id: 'dp-m2-fitness-data', code: 'M2', category: 'HEALTH_DATA', name: l('Fitnessdaten', 'Fitness Data'), description: l('Schritte, Kalorien, Aktivitaet', 'Steps, calories, activity'), purpose: l('Fitness-Tracking', 'Fitness tracking'), riskLevel: 'MEDIUM', legalBasis: 'EXPLICIT_CONSENT', legalBasisJustification: l('Art. 9 Abs. 2 lit. a DSGVO', 'Art. 9(2)(a) GDPR'), retentionPeriod: '24_MONTHS', retentionJustification: l('Langzeit-Tracking', 'Long-term tracking'), cookieCategory: null, isSpecialCategory: true, requiresExplicitConsent: true, thirdPartyRecipients: ['Fitness-App'], technicalMeasures: ['Verschluesselung', 'Pseudonymisierung'], tags: ['health', 'fitness', 'article9'] },
{ id: 'dp-m3-medication', code: 'M3', category: 'HEALTH_DATA', name: l('Medikation', 'Medication'), description: l('Aktuelle Medikamente', 'Current medications'), purpose: l('Gesundheitsmanagement', 'Health management'), riskLevel: 'HIGH', legalBasis: 'EXPLICIT_CONSENT', legalBasisJustification: l('Art. 9 Abs. 2 lit. a DSGVO', 'Art. 9(2)(a) GDPR'), retentionPeriod: '10_YEARS', retentionJustification: l('Medizinische Dokumentation', 'Medical documentation'), cookieCategory: null, isSpecialCategory: true, requiresExplicitConsent: true, thirdPartyRecipients: [], technicalMeasures: ['Ende-zu-Ende-Verschluesselung', 'Strenge Zugriffskontrolle'], tags: ['health', 'medication', 'article9'] },
{ id: 'dp-m4-biometric', code: 'M4', category: 'HEALTH_DATA', name: l('Biometrische Daten', 'Biometric Data'), description: l('Fingerabdruck, Face-ID (zur Identifikation)', 'Fingerprint, Face ID (for identification)'), purpose: l('Biometrische Authentifizierung', 'Biometric authentication'), riskLevel: 'HIGH', legalBasis: 'EXPLICIT_CONSENT', legalBasisJustification: l('Art. 9 Abs. 2 lit. a DSGVO', 'Art. 9(2)(a) GDPR'), retentionPeriod: 'UNTIL_ACCOUNT_DELETION', retentionJustification: l('Solange gewuenscht', 'While desired'), cookieCategory: null, isSpecialCategory: true, requiresExplicitConsent: true, thirdPartyRecipients: [], technicalMeasures: ['On-Device-Speicherung', 'Keine Cloud-Uebertragung'], tags: ['health', 'biometric', 'article9'] },
{ id: 'dp-m5-allergies', code: 'M5', category: 'HEALTH_DATA', name: l('Allergien', 'Allergies'), description: l('Bekannte Allergien', 'Known allergies'), purpose: l('Gesundheitsschutz', 'Health protection'), riskLevel: 'HIGH', legalBasis: 'EXPLICIT_CONSENT', legalBasisJustification: l('Art. 9 Abs. 2 lit. a DSGVO', 'Art. 9(2)(a) GDPR'), retentionPeriod: '10_YEARS', retentionJustification: l('Medizinische Dokumentation', 'Medical documentation'), cookieCategory: null, isSpecialCategory: true, requiresExplicitConsent: true, thirdPartyRecipients: [], technicalMeasures: ['Ende-zu-Ende-Verschluesselung'], tags: ['health', 'allergies', 'article9'] },
{ id: 'dp-m6-vital-signs', code: 'M6', category: 'HEALTH_DATA', name: l('Vitalzeichen', 'Vital Signs'), description: l('Blutdruck, Puls, etc.', 'Blood pressure, pulse, etc.'), purpose: l('Gesundheitsmonitoring', 'Health monitoring'), riskLevel: 'HIGH', legalBasis: 'EXPLICIT_CONSENT', legalBasisJustification: l('Art. 9 Abs. 2 lit. a DSGVO', 'Art. 9(2)(a) GDPR'), retentionPeriod: '10_YEARS', retentionJustification: l('Medizinische Dokumentation', 'Medical documentation'), cookieCategory: null, isSpecialCategory: true, requiresExplicitConsent: true, thirdPartyRecipients: [], technicalMeasures: ['Ende-zu-Ende-Verschluesselung', 'Audit-Logging'], tags: ['health', 'vitals', 'article9'] },
{ id: 'dp-m7-disability', code: 'M7', category: 'HEALTH_DATA', name: l('Behinderung/Einschraenkung', 'Disability/Impairment'), description: l('Informationen zu Behinderungen', 'Information about disabilities'), purpose: l('Barrierefreiheit', 'Accessibility'), riskLevel: 'HIGH', legalBasis: 'EXPLICIT_CONSENT', legalBasisJustification: l('Art. 9 Abs. 2 lit. a DSGVO', 'Art. 9(2)(a) GDPR'), retentionPeriod: 'UNTIL_REVOCATION', retentionJustification: l('Bis Widerruf', 'Until revocation'), cookieCategory: null, isSpecialCategory: true, requiresExplicitConsent: true, thirdPartyRecipients: [], technicalMeasures: ['Strenge Zugriffskontrolle'], tags: ['health', 'disability', 'article9'] },
// KATEGORIE N: BESCHAEFTIGTENDATEN (10) - BDSG § 26
{ id: 'dp-n1-employee-id', code: 'N1', category: 'EMPLOYEE_DATA', name: l('Personalnummer', 'Employee ID'), description: l('Eindeutige Mitarbeiter-ID', 'Unique employee ID'), purpose: l('Personalverwaltung', 'HR management'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('BDSG § 26 - Beschaeftigungsverhaeltnis', 'BDSG § 26 - Employment relationship'), retentionPeriod: '10_YEARS', retentionJustification: l('Aufbewahrungspflichten', 'Retention obligations'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['employee', 'hr'] },
{ id: 'dp-n2-salary', code: 'N2', category: 'EMPLOYEE_DATA', name: l('Gehalt/Verguetung', 'Salary/Compensation'), description: l('Gehaltsinformationen', 'Salary information'), purpose: l('Lohnabrechnung', 'Payroll'), riskLevel: 'HIGH', legalBasis: 'CONTRACT', legalBasisJustification: l('BDSG § 26', 'BDSG § 26'), retentionPeriod: '10_YEARS', retentionJustification: l('§ 147 AO', '§ 147 AO'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Lohnbuero', 'Finanzamt'], technicalMeasures: ['Verschluesselung', 'Strenge Zugriffskontrolle'], tags: ['employee', 'payroll'] },
{ id: 'dp-n3-tax-id', code: 'N3', category: 'EMPLOYEE_DATA', name: l('Steuer-ID', 'Tax ID'), description: l('Steueridentifikationsnummer', 'Tax identification number'), purpose: l('Lohnsteuer', 'Payroll tax'), riskLevel: 'MEDIUM', legalBasis: 'LEGAL_OBLIGATION', legalBasisJustification: l('Steuerrecht', 'Tax law'), retentionPeriod: '10_YEARS', retentionJustification: l('Steuerliche Aufbewahrung', 'Tax retention'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Finanzamt'], technicalMeasures: ['Verschluesselung'], tags: ['employee', 'tax'] },
{ id: 'dp-n4-social-security', code: 'N4', category: 'EMPLOYEE_DATA', name: l('Sozialversicherungsnummer', 'Social Security Number'), description: l('SV-Nummer', 'Social security number'), purpose: l('Sozialversicherung', 'Social security'), riskLevel: 'MEDIUM', legalBasis: 'LEGAL_OBLIGATION', legalBasisJustification: l('Sozialversicherungsrecht', 'Social security law'), retentionPeriod: '10_YEARS', retentionJustification: l('Gesetzliche Pflicht', 'Legal obligation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Krankenkasse', 'Rentenversicherung'], technicalMeasures: ['Verschluesselung'], tags: ['employee', 'social-security'] },
{ id: 'dp-n5-working-hours', code: 'N5', category: 'EMPLOYEE_DATA', name: l('Arbeitszeiten', 'Working Hours'), description: l('Erfasste Arbeitszeiten', 'Recorded working hours'), purpose: l('Arbeitszeiterfassung', 'Time tracking'), riskLevel: 'LOW', legalBasis: 'LEGAL_OBLIGATION', legalBasisJustification: l('ArbZG', 'Working Time Act'), retentionPeriod: '6_YEARS', retentionJustification: l('Gesetzliche Aufbewahrung', 'Legal retention'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['employee', 'time-tracking'] },
{ id: 'dp-n6-vacation', code: 'N6', category: 'EMPLOYEE_DATA', name: l('Urlaubsdaten', 'Vacation Data'), description: l('Urlaubsanspruch und -nutzung', 'Vacation entitlement and usage'), purpose: l('Urlaubsverwaltung', 'Vacation management'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('BDSG § 26', 'BDSG § 26'), retentionPeriod: '6_YEARS', retentionJustification: l('Nachvollziehbarkeit', 'Traceability'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['employee', 'vacation'] },
{ id: 'dp-n7-sick-leave', code: 'N7', category: 'EMPLOYEE_DATA', name: l('Krankheitstage', 'Sick Leave'), description: l('Krankheitstage (ohne Diagnose)', 'Sick days (without diagnosis)'), purpose: l('Personalplanung', 'HR planning'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('BDSG § 26', 'BDSG § 26'), retentionPeriod: '6_YEARS', retentionJustification: l('Lohnfortzahlung', 'Sick pay'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Krankenkasse'], technicalMeasures: ['Zugriffskontrolle'], tags: ['employee', 'sick-leave'] },
{ id: 'dp-n8-performance', code: 'N8', category: 'EMPLOYEE_DATA', name: l('Leistungsbeurteilung', 'Performance Review'), description: l('Mitarbeiterbeurteilungen', 'Employee evaluations'), purpose: l('Personalentwicklung', 'HR development'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('BDSG § 26', 'BDSG § 26'), retentionPeriod: '6_YEARS', retentionJustification: l('Personalakte', 'Personnel file'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['employee', 'performance'] },
{ id: 'dp-n9-training', code: 'N9', category: 'EMPLOYEE_DATA', name: l('Schulungen/Weiterbildung', 'Training/Development'), description: l('Absolvierte Schulungen', 'Completed training'), purpose: l('Personalentwicklung', 'HR development'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('BDSG § 26', 'BDSG § 26'), retentionPeriod: '6_YEARS', retentionJustification: l('Personalakte', 'Personnel file'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['employee', 'training'] },
{ id: 'dp-n10-contract', code: 'N10', category: 'EMPLOYEE_DATA', name: l('Arbeitsvertrag', 'Employment Contract'), description: l('Arbeitsvertragsdaten', 'Employment contract data'), purpose: l('Vertragsverwaltung', 'Contract management'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('BDSG § 26', 'BDSG § 26'), retentionPeriod: '10_YEARS', retentionJustification: l('Verjaehrungsfristen', 'Limitation periods'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Verschluesselung', 'Archivierung'], tags: ['employee', 'contract'] },
// KATEGORIE O: VERTRAGSDATEN (7)
{ id: 'dp-o1-contract-number', code: 'O1', category: 'CONTRACT_DATA', name: l('Vertragsnummer', 'Contract Number'), description: l('Eindeutige Vertragsnummer', 'Unique contract number'), purpose: l('Vertragsverwaltung', 'Contract management'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Vertragserfuellung', 'Contract performance'), retentionPeriod: '10_YEARS', retentionJustification: l('§ 147 AO', '§ 147 AO'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['contract', 'id'] },
{ id: 'dp-o2-contract-duration', code: 'O2', category: 'CONTRACT_DATA', name: l('Vertragslaufzeit', 'Contract Duration'), description: l('Start- und Enddatum', 'Start and end date'), purpose: l('Vertragsverwaltung', 'Contract management'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Vertragserfuellung', 'Contract performance'), retentionPeriod: '10_YEARS', retentionJustification: l('§ 147 AO', '§ 147 AO'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: [], tags: ['contract', 'duration'] },
{ id: 'dp-o3-signature', code: 'O3', category: 'CONTRACT_DATA', name: l('Unterschrift', 'Signature'), description: l('Digitale oder gescannte Unterschrift', 'Digital or scanned signature'), purpose: l('Vertragsschluss', 'Contract conclusion'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Vertragserfuellung', 'Contract performance'), retentionPeriod: '10_YEARS', retentionJustification: l('Beweissicherung', 'Evidence preservation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Verschluesselung', 'Integritaetsschutz'], tags: ['contract', 'signature'] },
{ id: 'dp-o4-contract-documents', code: 'O4', category: 'CONTRACT_DATA', name: l('Vertragsdokumente', 'Contract Documents'), description: l('PDFs und Anlagen', 'PDFs and attachments'), purpose: l('Dokumentation', 'Documentation'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Vertragserfuellung', 'Contract performance'), retentionPeriod: '10_YEARS', retentionJustification: l('§ 147 AO', '§ 147 AO'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Revisionssichere Archivierung'], tags: ['contract', 'documents'] },
{ id: 'dp-o5-contract-terms', code: 'O5', category: 'CONTRACT_DATA', name: l('Vertragskonditionen', 'Contract Terms'), description: l('Preise, Rabatte, Bedingungen', 'Prices, discounts, conditions'), purpose: l('Vertragsabwicklung', 'Contract processing'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Vertragserfuellung', 'Contract performance'), retentionPeriod: '10_YEARS', retentionJustification: l('§ 147 AO', '§ 147 AO'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Zugriffskontrolle'], tags: ['contract', 'terms'] },
{ id: 'dp-o6-contract-history', code: 'O6', category: 'CONTRACT_DATA', name: l('Vertragshistorie', 'Contract History'), description: l('Aenderungen und Versionen', 'Changes and versions'), purpose: l('Nachvollziehbarkeit', 'Traceability'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Dokumentationspflicht', 'Documentation obligation'), retentionPeriod: '10_YEARS', retentionJustification: l('§ 147 AO', '§ 147 AO'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Versionierung'], tags: ['contract', 'history'] },
{ id: 'dp-o7-termination', code: 'O7', category: 'CONTRACT_DATA', name: l('Kuendigungsdaten', 'Termination Data'), description: l('Kuendigungen und Gruende', 'Terminations and reasons'), purpose: l('Vertragsbeendigung', 'Contract termination'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Vertragserfuellung', 'Contract performance'), retentionPeriod: '10_YEARS', retentionJustification: l('Beweissicherung', 'Evidence preservation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Archivierung'], tags: ['contract', 'termination'] },
// KATEGORIE P: PROTOKOLLDATEN (7)
{ id: 'dp-p1-login-logs', code: 'P1', category: 'LOG_DATA', name: l('Login-Protokolle', 'Login Logs'), description: l('Erfolgreiche und fehlgeschlagene Logins', 'Successful and failed logins'), purpose: l('Sicherheitsaudit', 'Security audit'), riskLevel: 'MEDIUM', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('IT-Sicherheit', 'IT security'), retentionPeriod: '12_MONTHS', retentionJustification: l('Sicherheitsforensik', 'Security forensics'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['SIEM'], technicalMeasures: ['Unveraenderbare Logs'], tags: ['logs', 'security'] },
{ id: 'dp-p2-access-logs', code: 'P2', category: 'LOG_DATA', name: l('Zugriffsprotokolle', 'Access Logs'), description: l('HTTP-Zugriffe', 'HTTP accesses'), purpose: l('Sicherheit, Debugging', 'Security, debugging'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('IT-Sicherheit', 'IT security'), retentionPeriod: '90_DAYS', retentionJustification: l('Fehleranalyse', 'Error analysis'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['IP-Anonymisierung'], tags: ['logs', 'access'] },
{ id: 'dp-p3-api-logs', code: 'P3', category: 'LOG_DATA', name: l('API-Protokolle', 'API Logs'), description: l('API-Aufrufe', 'API calls'), purpose: l('Debugging, Monitoring', 'Debugging, monitoring'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Servicequalitaet', 'Service quality'), retentionPeriod: '90_DAYS', retentionJustification: l('Fehleranalyse', 'Error analysis'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Pseudonymisierung'], tags: ['logs', 'api'] },
{ id: 'dp-p4-admin-logs', code: 'P4', category: 'LOG_DATA', name: l('Admin-Aktionen', 'Admin Actions'), description: l('Protokoll von Admin-Aktivitaeten', 'Log of admin activities'), purpose: l('Revisionssicherheit', 'Audit compliance'), riskLevel: 'MEDIUM', legalBasis: 'LEGAL_OBLIGATION', legalBasisJustification: l('Dokumentationspflicht', 'Documentation obligation'), retentionPeriod: '6_YEARS', retentionJustification: l('Revisionssicherheit', 'Audit compliance'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Unveraenderbare Logs', 'Signatur'], tags: ['logs', 'admin'] },
{ id: 'dp-p5-change-logs', code: 'P5', category: 'LOG_DATA', name: l('Aenderungshistorie', 'Change History'), description: l('Audit-Trail von Aenderungen', 'Audit trail of changes'), purpose: l('Nachvollziehbarkeit', 'Traceability'), riskLevel: 'LOW', legalBasis: 'LEGAL_OBLIGATION', legalBasisJustification: l('Dokumentationspflicht', 'Documentation obligation'), retentionPeriod: '6_YEARS', retentionJustification: l('Revisionssicherheit', 'Audit compliance'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Unveraenderbare Logs'], tags: ['logs', 'audit'] },
{ id: 'dp-p6-error-logs', code: 'P6', category: 'LOG_DATA', name: l('Fehlerprotokolle', 'Error Logs'), description: l('System- und Anwendungsfehler', 'System and application errors'), purpose: l('Fehlerbehebung', 'Bug fixing'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Servicequalitaet', 'Service quality'), retentionPeriod: '90_DAYS', retentionJustification: l('Fehleranalyse', 'Error analysis'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Error-Tracking'], technicalMeasures: ['PII-Filterung'], tags: ['logs', 'errors'] },
{ id: 'dp-p7-security-logs', code: 'P7', category: 'LOG_DATA', name: l('Sicherheitsprotokolle', 'Security Logs'), description: l('Security Events', 'Security events'), purpose: l('Sicherheitsmonitoring', 'Security monitoring'), riskLevel: 'MEDIUM', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('IT-Sicherheit', 'IT security'), retentionPeriod: '12_MONTHS', retentionJustification: l('Forensik', 'Forensics'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['SIEM'], technicalMeasures: ['Unveraenderbare Logs'], tags: ['logs', 'security'] },
// KATEGORIE Q: KI-DATEN (7) - AI ACT
{ id: 'dp-q1-ai-prompts', code: 'Q1', category: 'AI_DATA', name: l('KI-Prompts', 'AI Prompts'), description: l('Nutzereingaben an KI', 'User inputs to AI'), purpose: l('KI-Funktionalitaet', 'AI functionality'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer KI-Service', 'For AI service'), retentionPeriod: '90_DAYS', retentionJustification: l('Kontexterhaltung', 'Context preservation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['KI-Provider'], technicalMeasures: ['Keine Verwendung fuer Training', 'Verschluesselung'], tags: ['ai', 'prompts'] },
{ id: 'dp-q2-ai-responses', code: 'Q2', category: 'AI_DATA', name: l('KI-Antworten', 'AI Responses'), description: l('Generierte KI-Antworten', 'Generated AI responses'), purpose: l('Qualitaetssicherung', 'Quality assurance'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer KI-Service', 'For AI service'), retentionPeriod: '90_DAYS', retentionJustification: l('QA', 'QA'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Logging'], tags: ['ai', 'responses'] },
{ id: 'dp-q3-rag-context', code: 'Q3', category: 'AI_DATA', name: l('RAG-Kontext', 'RAG Context'), description: l('Retrieval-Kontext', 'Retrieval context'), purpose: l('Kontextuelle KI-Antworten', 'Contextual AI responses'), riskLevel: 'MEDIUM', legalBasis: 'CONTRACT', legalBasisJustification: l('Fuer RAG-Funktionalitaet', 'For RAG functionality'), retentionPeriod: '24_HOURS', retentionJustification: l('Session-Kontext', 'Session context'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['In-Memory', 'Auto-Loeschung'], tags: ['ai', 'rag'] },
{ id: 'dp-q4-ai-feedback', code: 'Q4', category: 'AI_DATA', name: l('KI-Feedback', 'AI Feedback'), description: l('Nutzerfeedback zu KI-Antworten', 'User feedback on AI responses'), purpose: l('KI-Verbesserung', 'AI improvement'), riskLevel: 'LOW', legalBasis: 'CONSENT', legalBasisJustification: l('Freiwilliges Feedback', 'Voluntary feedback'), retentionPeriod: '24_MONTHS', retentionJustification: l('Qualitaetsanalyse', 'Quality analysis'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Anonymisierung'], tags: ['ai', 'feedback'] },
{ id: 'dp-q5-training-data', code: 'Q5', category: 'AI_DATA', name: l('Trainingsdaten (mit Einwilligung)', 'Training Data (with consent)'), description: l('Fuer KI-Training freigegebene Daten', 'Data released for AI training'), purpose: l('Modellverbesserung', 'Model improvement'), riskLevel: 'HIGH', legalBasis: 'CONSENT', legalBasisJustification: l('Ausdrueckliche Einwilligung', 'Explicit consent'), retentionPeriod: '36_MONTHS', retentionJustification: l('Modellentwicklung', 'Model development'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: true, thirdPartyRecipients: [], technicalMeasures: ['Anonymisierung', 'Zugriffskontrolle'], tags: ['ai', 'training'] },
{ id: 'dp-q6-model-outputs', code: 'Q6', category: 'AI_DATA', name: l('Modell-Outputs', 'Model Outputs'), description: l('KI-generierte Inhalte', 'AI-generated content'), purpose: l('Dokumentation', 'Documentation'), riskLevel: 'LOW', legalBasis: 'CONTRACT', legalBasisJustification: l('Teil des Services', 'Part of service'), retentionPeriod: '12_MONTHS', retentionJustification: l('Nachvollziehbarkeit', 'Traceability'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Kennzeichnung als KI-generiert'], tags: ['ai', 'outputs'] },
{ id: 'dp-q7-ai-usage', code: 'Q7', category: 'AI_DATA', name: l('KI-Nutzungsstatistik', 'AI Usage Statistics'), description: l('Aggregierte KI-Nutzungsdaten', 'Aggregated AI usage data'), purpose: l('Kapazitaetsplanung', 'Capacity planning'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Serviceoptimierung', 'Service optimization'), retentionPeriod: '24_MONTHS', retentionJustification: l('Langzeitanalyse', 'Long-term analysis'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Aggregierung'], tags: ['ai', 'usage'] },
// KATEGORIE R: SICHERHEITSDATEN (7)
{ id: 'dp-r1-failed-logins', code: 'R1', category: 'SECURITY', name: l('Fehlgeschlagene Logins', 'Failed Logins'), description: l('Fehlgeschlagene Anmeldeversuche', 'Failed login attempts'), purpose: l('Angriffserkennung', 'Attack detection'), riskLevel: 'MEDIUM', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('IT-Sicherheit', 'IT security'), retentionPeriod: '12_MONTHS', retentionJustification: l('Forensik', 'Forensics'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['SIEM'], technicalMeasures: ['Alerting', 'Rate-Limiting'], tags: ['security', 'auth'] },
{ id: 'dp-r2-fraud-score', code: 'R2', category: 'SECURITY', name: l('Betrugsrisiko-Score', 'Fraud Risk Score'), description: l('Berechnetes Betrugsrisiko', 'Calculated fraud risk'), purpose: l('Betrugspraevention', 'Fraud prevention'), riskLevel: 'MEDIUM', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('Betrugspraevention', 'Fraud prevention'), retentionPeriod: '12_MONTHS', retentionJustification: l('Analyse', 'Analysis'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Maschinelles Lernen'], tags: ['security', 'fraud'] },
{ id: 'dp-r3-incident-reports', code: 'R3', category: 'SECURITY', name: l('Vorfallberichte', 'Incident Reports'), description: l('Sicherheitsvorfaelle', 'Security incidents'), purpose: l('Vorfallmanagement', 'Incident management'), riskLevel: 'MEDIUM', legalBasis: 'LEGAL_OBLIGATION', legalBasisJustification: l('Art. 33 DSGVO Meldepflicht', 'Art. 33 GDPR notification obligation'), retentionPeriod: '6_YEARS', retentionJustification: l('Dokumentationspflicht', 'Documentation obligation'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: ['Aufsichtsbehoerde'], technicalMeasures: ['Verschluesselung', 'Zugriffskontrolle'], tags: ['security', 'incidents'] },
{ id: 'dp-r4-threat-intel', code: 'R4', category: 'SECURITY', name: l('Bedrohungsinformationen', 'Threat Intelligence'), description: l('Erkannte Bedrohungen', 'Detected threats'), purpose: l('Sicherheitsmonitoring', 'Security monitoring'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('IT-Sicherheit', 'IT security'), retentionPeriod: '12_MONTHS', retentionJustification: l('Analyse', 'Analysis'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Aggregierung'], tags: ['security', 'threats'] },
{ id: 'dp-r5-blocked-ips', code: 'R5', category: 'SECURITY', name: l('Gesperrte IPs', 'Blocked IPs'), description: l('Blacklist von IP-Adressen', 'IP address blacklist'), purpose: l('Angriffspraevention', 'Attack prevention'), riskLevel: 'LOW', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('IT-Sicherheit', 'IT security'), retentionPeriod: '12_MONTHS', retentionJustification: l('Sicherheit', 'Security'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Automatische Bereinigung'], tags: ['security', 'blocking'] },
{ id: 'dp-r6-vulnerability-scans', code: 'R6', category: 'SECURITY', name: l('Schwachstellen-Scans', 'Vulnerability Scans'), description: l('Ergebnisse von Security-Scans', 'Results of security scans'), purpose: l('Schwachstellenmanagement', 'Vulnerability management'), riskLevel: 'MEDIUM', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('IT-Sicherheit', 'IT security'), retentionPeriod: '12_MONTHS', retentionJustification: l('Trend-Analyse', 'Trend analysis'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Verschluesselung'], tags: ['security', 'vulnerabilities'] },
{ id: 'dp-r7-penetration-tests', code: 'R7', category: 'SECURITY', name: l('Penetrationstests', 'Penetration Tests'), description: l('Ergebnisse von Pentests', 'Results of pentests'), purpose: l('Sicherheitspruefung', 'Security testing'), riskLevel: 'MEDIUM', legalBasis: 'LEGITIMATE_INTEREST', legalBasisJustification: l('IT-Sicherheit', 'IT security'), retentionPeriod: '6_YEARS', retentionJustification: l('Compliance-Nachweis', 'Compliance evidence'), cookieCategory: null, isSpecialCategory: false, requiresExplicitConsent: false, thirdPartyRecipients: [], technicalMeasures: ['Strenge Zugriffskontrolle'], tags: ['security', 'pentests'] },
]
// =============================================================================
// RETENTION MATRIX (18 Kategorien)
// =============================================================================
export const RETENTION_MATRIX: RetentionMatrixEntry[] = [
{ category: 'MASTER_DATA', categoryName: l('Stammdaten', 'Master Data'), standardPeriod: 'UNTIL_ACCOUNT_DELETION', legalBasis: 'Art. 6 Abs. 1 lit. b DSGVO', exceptions: [] },
{ category: 'CONTACT_DATA', categoryName: l('Kontaktdaten', 'Contact Data'), standardPeriod: 'UNTIL_ACCOUNT_DELETION', legalBasis: 'Art. 6 Abs. 1 lit. b DSGVO', exceptions: [] },
{ category: 'AUTHENTICATION', categoryName: l('Authentifizierung', 'Authentication'), standardPeriod: 'UNTIL_ACCOUNT_DELETION', legalBasis: 'Art. 6 Abs. 1 lit. b DSGVO', exceptions: [{ condition: l('Session-Token', 'Session token'), period: '24_HOURS', reason: l('Sicherheit', 'Security') }] },
{ category: 'CONSENT', categoryName: l('Einwilligungsdaten', 'Consent Data'), standardPeriod: '6_YEARS', legalBasis: 'Art. 7 DSGVO, § 147 AO', exceptions: [] },
{ category: 'COMMUNICATION', categoryName: l('Kommunikation', 'Communication'), standardPeriod: '24_MONTHS', legalBasis: 'Art. 6 Abs. 1 lit. b/f DSGVO', exceptions: [] },
{ category: 'PAYMENT', categoryName: l('Zahlungsdaten', 'Payment Data'), standardPeriod: '10_YEARS', legalBasis: '§ 147 AO, § 257 HGB', exceptions: [] },
{ category: 'USAGE_DATA', categoryName: l('Nutzungsdaten', 'Usage Data'), standardPeriod: '24_MONTHS', legalBasis: 'Art. 6 Abs. 1 lit. f DSGVO', exceptions: [] },
{ category: 'LOCATION', categoryName: l('Standortdaten', 'Location Data'), standardPeriod: '90_DAYS', legalBasis: 'Art. 6 Abs. 1 lit. a DSGVO', exceptions: [] },
{ category: 'DEVICE_DATA', categoryName: l('Geraetedaten', 'Device Data'), standardPeriod: '12_MONTHS', legalBasis: 'Art. 6 Abs. 1 lit. f DSGVO', exceptions: [] },
{ category: 'MARKETING', categoryName: l('Marketingdaten', 'Marketing Data'), standardPeriod: '90_DAYS', legalBasis: 'Art. 6 Abs. 1 lit. a DSGVO', exceptions: [] },
{ category: 'ANALYTICS', categoryName: l('Analysedaten', 'Analytics Data'), standardPeriod: '26_MONTHS', legalBasis: 'Art. 6 Abs. 1 lit. a DSGVO', exceptions: [] },
{ category: 'SOCIAL_MEDIA', categoryName: l('Social-Media-Daten', 'Social Media Data'), standardPeriod: 'UNTIL_ACCOUNT_DELETION', legalBasis: 'Art. 6 Abs. 1 lit. a DSGVO', exceptions: [] },
{ category: 'HEALTH_DATA', categoryName: l('Gesundheitsdaten', 'Health Data'), standardPeriod: '10_YEARS', legalBasis: 'Art. 9 Abs. 2 lit. a DSGVO', exceptions: [] },
{ category: 'EMPLOYEE_DATA', categoryName: l('Beschaeftigtendaten', 'Employee Data'), standardPeriod: '10_YEARS', legalBasis: 'BDSG § 26', exceptions: [] },
{ category: 'CONTRACT_DATA', categoryName: l('Vertragsdaten', 'Contract Data'), standardPeriod: '10_YEARS', legalBasis: '§ 147 AO, § 257 HGB', exceptions: [] },
{ category: 'LOG_DATA', categoryName: l('Protokolldaten', 'Log Data'), standardPeriod: '12_MONTHS', legalBasis: 'Art. 6 Abs. 1 lit. f DSGVO', exceptions: [] },
{ category: 'AI_DATA', categoryName: l('KI-Daten', 'AI Data'), standardPeriod: '90_DAYS', legalBasis: 'Art. 6 Abs. 1 lit. a/b DSGVO', exceptions: [] },
{ category: 'SECURITY', categoryName: l('Sicherheitsdaten', 'Security Data'), standardPeriod: '12_MONTHS', legalBasis: 'Art. 6 Abs. 1 lit. f DSGVO', exceptions: [] },
]
// =============================================================================
// COOKIE CATEGORIES
// =============================================================================
export const DEFAULT_COOKIE_CATEGORIES: CookieBannerCategory[] = [
{ id: 'ESSENTIAL', name: l('Technisch notwendig', 'Essential'), description: l('Diese Cookies sind fuer den Betrieb erforderlich', 'These cookies are required for operation'), isRequired: true, defaultEnabled: true, dataPointIds: ['dp-c2-session-token', 'dp-c3-refresh-token', 'dp-d1-consent-records', 'dp-d2-cookie-preferences'], cookies: [] },
{ id: 'PERFORMANCE', name: l('Analyse & Performance', 'Analytics & Performance'), description: l('Helfen uns die Nutzung zu verstehen', 'Help us understand usage'), isRequired: false, defaultEnabled: false, dataPointIds: ['dp-g1-session-duration', 'dp-g2-page-views'], cookies: [] },
{ id: 'PERSONALIZATION', name: l('Personalisierung', 'Personalization'), description: l('Ermoeglichen personalisierte Werbung', 'Enable personalized advertising'), isRequired: false, defaultEnabled: false, dataPointIds: [], cookies: [] },
{ id: 'EXTERNAL_MEDIA', name: l('Externe Medien', 'External Media'), description: l('Erlauben Einbindung externer Medien', 'Allow embedding external media'), isRequired: false, defaultEnabled: false, dataPointIds: [], cookies: [] },
]
// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================
export function getDataPointById(id: string): DataPoint | undefined {
return PREDEFINED_DATA_POINTS.find((dp) => dp.id === id)
}
export function getDataPointByCode(code: string): DataPoint | undefined {
return PREDEFINED_DATA_POINTS.find((dp) => dp.code === code)
}
export function getDataPointsByCategory(category: DataPointCategory): DataPoint[] {
return PREDEFINED_DATA_POINTS.filter((dp) => dp.category === category)
}
export function getDataPointsByLegalBasis(legalBasis: string): DataPoint[] {
return PREDEFINED_DATA_POINTS.filter((dp) => dp.legalBasis === legalBasis)
}
export function getDataPointsByCookieCategory(cookieCategory: string): DataPoint[] {
return PREDEFINED_DATA_POINTS.filter((dp) => dp.cookieCategory === cookieCategory)
}
export function getDataPointsRequiringConsent(): DataPoint[] {
return PREDEFINED_DATA_POINTS.filter((dp) => dp.requiresExplicitConsent)
}
export function getHighRiskDataPoints(): DataPoint[] {
return PREDEFINED_DATA_POINTS.filter((dp) => dp.riskLevel === 'HIGH')
}
export function getSpecialCategoryDataPoints(): DataPoint[] {
return PREDEFINED_DATA_POINTS.filter((dp) => dp.isSpecialCategory)
}
export function countDataPointsByCategory(): Record<DataPointCategory, number> {
const counts = {} as Record<DataPointCategory, number>
for (const dp of PREDEFINED_DATA_POINTS) {
counts[dp.category] = (counts[dp.category] || 0) + 1
}
return counts
}
export function countDataPointsByRiskLevel(): Record<'LOW' | 'MEDIUM' | 'HIGH', number> {
const counts = { LOW: 0, MEDIUM: 0, HIGH: 0 }
for (const dp of PREDEFINED_DATA_POINTS) {
counts[dp.riskLevel]++
}
return counts
}
export function createDefaultCatalog(tenantId: string): DataPointCatalog {
return {
id: `catalog-${tenantId}`,
tenantId,
version: '2.0.0',
dataPoints: PREDEFINED_DATA_POINTS.map((dp) => ({ ...dp, isActive: true })),
customDataPoints: [],
retentionMatrix: RETENTION_MATRIX,
createdAt: new Date(),
updatedAt: new Date(),
}
}
export function searchDataPoints(dataPoints: DataPoint[], query: string, language: 'de' | 'en' = 'de'): DataPoint[] {
const lowerQuery = query.toLowerCase()
return dataPoints.filter(
(dp) =>
dp.code.toLowerCase().includes(lowerQuery) ||
dp.name[language].toLowerCase().includes(lowerQuery) ||
dp.description[language].toLowerCase().includes(lowerQuery) ||
dp.tags.some((tag) => tag.toLowerCase().includes(lowerQuery))
)
}

View File

@@ -0,0 +1,669 @@
'use client'
/**
* Einwilligungen Context & Reducer
*
* Zentrale State-Verwaltung fuer das Datenpunktkatalog & DSI-Generator Modul.
* Verwendet React Context + useReducer fuer vorhersehbare State-Updates.
*/
import {
createContext,
useContext,
useReducer,
useCallback,
useMemo,
ReactNode,
Dispatch,
} from 'react'
import {
EinwilligungenState,
EinwilligungenAction,
EinwilligungenTab,
DataPoint,
DataPointCatalog,
GeneratedPrivacyPolicy,
CookieBannerConfig,
CompanyInfo,
ConsentStatistics,
PrivacyPolicySection,
SupportedLanguage,
ExportFormat,
DataPointCategory,
LegalBasis,
RiskLevel,
} from './types'
import {
PREDEFINED_DATA_POINTS,
RETENTION_MATRIX,
DEFAULT_COOKIE_CATEGORIES,
createDefaultCatalog,
getDataPointById,
getDataPointsByCategory,
countDataPointsByCategory,
countDataPointsByRiskLevel,
} from './catalog/loader'
// =============================================================================
// INITIAL STATE
// =============================================================================
const initialState: EinwilligungenState = {
// Data
catalog: null,
selectedDataPoints: [],
privacyPolicy: null,
cookieBannerConfig: null,
companyInfo: null,
consentStatistics: null,
// UI State
activeTab: 'catalog',
isLoading: false,
isSaving: false,
error: null,
// Editor State
editingDataPoint: null,
editingSection: null,
// Preview
previewLanguage: 'de',
previewFormat: 'HTML',
}
// =============================================================================
// REDUCER
// =============================================================================
function einwilligungenReducer(
state: EinwilligungenState,
action: EinwilligungenAction
): EinwilligungenState {
switch (action.type) {
case 'SET_CATALOG':
return {
...state,
catalog: action.payload,
// Automatisch alle aktiven Datenpunkte auswaehlen
selectedDataPoints: [
...action.payload.dataPoints.filter((dp) => dp.isActive !== false).map((dp) => dp.id),
...action.payload.customDataPoints.filter((dp) => dp.isActive !== false).map((dp) => dp.id),
],
}
case 'SET_SELECTED_DATA_POINTS':
return {
...state,
selectedDataPoints: action.payload,
}
case 'TOGGLE_DATA_POINT': {
const id = action.payload
const isSelected = state.selectedDataPoints.includes(id)
return {
...state,
selectedDataPoints: isSelected
? state.selectedDataPoints.filter((dpId) => dpId !== id)
: [...state.selectedDataPoints, id],
}
}
case 'ADD_CUSTOM_DATA_POINT':
if (!state.catalog) return state
return {
...state,
catalog: {
...state.catalog,
customDataPoints: [...state.catalog.customDataPoints, action.payload],
updatedAt: new Date(),
},
selectedDataPoints: [...state.selectedDataPoints, action.payload.id],
}
case 'UPDATE_DATA_POINT': {
if (!state.catalog) return state
const { id, data } = action.payload
// Pruefe ob es ein vordefinierter oder kundenspezifischer Datenpunkt ist
const isCustom = state.catalog.customDataPoints.some((dp) => dp.id === id)
if (isCustom) {
return {
...state,
catalog: {
...state.catalog,
customDataPoints: state.catalog.customDataPoints.map((dp) =>
dp.id === id ? { ...dp, ...data } : dp
),
updatedAt: new Date(),
},
}
} else {
// Vordefinierte Datenpunkte: nur isActive aendern
return {
...state,
catalog: {
...state.catalog,
dataPoints: state.catalog.dataPoints.map((dp) =>
dp.id === id ? { ...dp, ...data } : dp
),
updatedAt: new Date(),
},
}
}
}
case 'DELETE_CUSTOM_DATA_POINT':
if (!state.catalog) return state
return {
...state,
catalog: {
...state.catalog,
customDataPoints: state.catalog.customDataPoints.filter((dp) => dp.id !== action.payload),
updatedAt: new Date(),
},
selectedDataPoints: state.selectedDataPoints.filter((id) => id !== action.payload),
}
case 'SET_PRIVACY_POLICY':
return {
...state,
privacyPolicy: action.payload,
}
case 'SET_COOKIE_BANNER_CONFIG':
return {
...state,
cookieBannerConfig: action.payload,
}
case 'UPDATE_COOKIE_BANNER_STYLING':
if (!state.cookieBannerConfig) return state
return {
...state,
cookieBannerConfig: {
...state.cookieBannerConfig,
styling: {
...state.cookieBannerConfig.styling,
...action.payload,
},
updatedAt: new Date(),
},
}
case 'UPDATE_COOKIE_BANNER_TEXTS':
if (!state.cookieBannerConfig) return state
return {
...state,
cookieBannerConfig: {
...state.cookieBannerConfig,
texts: {
...state.cookieBannerConfig.texts,
...action.payload,
},
updatedAt: new Date(),
},
}
case 'SET_COMPANY_INFO':
return {
...state,
companyInfo: action.payload,
}
case 'SET_CONSENT_STATISTICS':
return {
...state,
consentStatistics: action.payload,
}
case 'SET_ACTIVE_TAB':
return {
...state,
activeTab: action.payload,
}
case 'SET_LOADING':
return {
...state,
isLoading: action.payload,
}
case 'SET_SAVING':
return {
...state,
isSaving: action.payload,
}
case 'SET_ERROR':
return {
...state,
error: action.payload,
}
case 'SET_EDITING_DATA_POINT':
return {
...state,
editingDataPoint: action.payload,
}
case 'SET_EDITING_SECTION':
return {
...state,
editingSection: action.payload,
}
case 'SET_PREVIEW_LANGUAGE':
return {
...state,
previewLanguage: action.payload,
}
case 'SET_PREVIEW_FORMAT':
return {
...state,
previewFormat: action.payload,
}
case 'RESET_STATE':
return initialState
default:
return state
}
}
// =============================================================================
// CONTEXT
// =============================================================================
interface EinwilligungenContextValue {
state: EinwilligungenState
dispatch: Dispatch<EinwilligungenAction>
// Computed Values
allDataPoints: DataPoint[]
selectedDataPointsData: DataPoint[]
dataPointsByCategory: Record<DataPointCategory, DataPoint[]>
categoryStats: Record<DataPointCategory, number>
riskStats: Record<RiskLevel, number>
legalBasisStats: Record<LegalBasis, number>
// Actions
initializeCatalog: (tenantId: string) => void
loadCatalog: (tenantId: string) => Promise<void>
saveCatalog: () => Promise<void>
toggleDataPoint: (id: string) => void
addCustomDataPoint: (dataPoint: DataPoint) => void
updateDataPoint: (id: string, data: Partial<DataPoint>) => void
deleteCustomDataPoint: (id: string) => void
setActiveTab: (tab: EinwilligungenTab) => void
setPreviewLanguage: (language: SupportedLanguage) => void
setPreviewFormat: (format: ExportFormat) => void
setCompanyInfo: (info: CompanyInfo) => void
generatePrivacyPolicy: () => Promise<void>
generateCookieBannerConfig: () => void
}
const EinwilligungenContext = createContext<EinwilligungenContextValue | null>(null)
// =============================================================================
// PROVIDER
// =============================================================================
interface EinwilligungenProviderProps {
children: ReactNode
tenantId?: string
}
export function EinwilligungenProvider({ children, tenantId }: EinwilligungenProviderProps) {
const [state, dispatch] = useReducer(einwilligungenReducer, initialState)
// ---------------------------------------------------------------------------
// COMPUTED VALUES
// ---------------------------------------------------------------------------
const allDataPoints = useMemo(() => {
if (!state.catalog) return PREDEFINED_DATA_POINTS
return [...state.catalog.dataPoints, ...state.catalog.customDataPoints]
}, [state.catalog])
const selectedDataPointsData = useMemo(() => {
return allDataPoints.filter((dp) => state.selectedDataPoints.includes(dp.id))
}, [allDataPoints, state.selectedDataPoints])
const dataPointsByCategory = useMemo(() => {
const result: Partial<Record<DataPointCategory, DataPoint[]>> = {}
// 18 Kategorien (A-R)
const categories: DataPointCategory[] = [
'MASTER_DATA', // A
'CONTACT_DATA', // B
'AUTHENTICATION', // C
'CONSENT', // D
'COMMUNICATION', // E
'PAYMENT', // F
'USAGE_DATA', // G
'LOCATION', // H
'DEVICE_DATA', // I
'MARKETING', // J
'ANALYTICS', // K
'SOCIAL_MEDIA', // L
'HEALTH_DATA', // M - Art. 9 DSGVO
'EMPLOYEE_DATA', // N - BDSG § 26
'CONTRACT_DATA', // O
'LOG_DATA', // P
'AI_DATA', // Q - AI Act
'SECURITY', // R
]
for (const cat of categories) {
result[cat] = selectedDataPointsData.filter((dp) => dp.category === cat)
}
return result as Record<DataPointCategory, DataPoint[]>
}, [selectedDataPointsData])
const categoryStats = useMemo(() => {
const counts: Partial<Record<DataPointCategory, number>> = {}
for (const dp of selectedDataPointsData) {
counts[dp.category] = (counts[dp.category] || 0) + 1
}
return counts as Record<DataPointCategory, number>
}, [selectedDataPointsData])
const riskStats = useMemo(() => {
const counts: Record<RiskLevel, number> = { LOW: 0, MEDIUM: 0, HIGH: 0 }
for (const dp of selectedDataPointsData) {
counts[dp.riskLevel]++
}
return counts
}, [selectedDataPointsData])
const legalBasisStats = useMemo(() => {
// Alle 7 Rechtsgrundlagen
const counts: Record<LegalBasis, number> = {
CONTRACT: 0,
CONSENT: 0,
EXPLICIT_CONSENT: 0,
LEGITIMATE_INTEREST: 0,
LEGAL_OBLIGATION: 0,
VITAL_INTERESTS: 0,
PUBLIC_INTEREST: 0,
}
for (const dp of selectedDataPointsData) {
counts[dp.legalBasis]++
}
return counts
}, [selectedDataPointsData])
// ---------------------------------------------------------------------------
// ACTIONS
// ---------------------------------------------------------------------------
const initializeCatalog = useCallback(
(tid: string) => {
const catalog = createDefaultCatalog(tid)
dispatch({ type: 'SET_CATALOG', payload: catalog })
},
[dispatch]
)
const loadCatalog = useCallback(
async (tid: string) => {
dispatch({ type: 'SET_LOADING', payload: true })
dispatch({ type: 'SET_ERROR', payload: null })
try {
const response = await fetch(`/api/sdk/v1/einwilligungen/catalog`, {
headers: {
'X-Tenant-ID': tid,
},
})
if (response.ok) {
const data = await response.json()
dispatch({ type: 'SET_CATALOG', payload: data.catalog })
if (data.companyInfo) {
dispatch({ type: 'SET_COMPANY_INFO', payload: data.companyInfo })
}
if (data.cookieBannerConfig) {
dispatch({ type: 'SET_COOKIE_BANNER_CONFIG', payload: data.cookieBannerConfig })
}
} else if (response.status === 404) {
// Katalog existiert noch nicht - erstelle Default
initializeCatalog(tid)
} else {
throw new Error('Failed to load catalog')
}
} catch (error) {
console.error('Error loading catalog:', error)
dispatch({ type: 'SET_ERROR', payload: 'Fehler beim Laden des Katalogs' })
// Fallback zu Default
initializeCatalog(tid)
} finally {
dispatch({ type: 'SET_LOADING', payload: false })
}
},
[dispatch, initializeCatalog]
)
const saveCatalog = useCallback(async () => {
if (!state.catalog) return
dispatch({ type: 'SET_SAVING', payload: true })
dispatch({ type: 'SET_ERROR', payload: null })
try {
const response = await fetch(`/api/sdk/v1/einwilligungen/catalog`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Tenant-ID': state.catalog.tenantId,
},
body: JSON.stringify({
catalog: state.catalog,
companyInfo: state.companyInfo,
cookieBannerConfig: state.cookieBannerConfig,
}),
})
if (!response.ok) {
throw new Error('Failed to save catalog')
}
} catch (error) {
console.error('Error saving catalog:', error)
dispatch({ type: 'SET_ERROR', payload: 'Fehler beim Speichern des Katalogs' })
} finally {
dispatch({ type: 'SET_SAVING', payload: false })
}
}, [state.catalog, state.companyInfo, state.cookieBannerConfig, dispatch])
const toggleDataPoint = useCallback(
(id: string) => {
dispatch({ type: 'TOGGLE_DATA_POINT', payload: id })
},
[dispatch]
)
const addCustomDataPoint = useCallback(
(dataPoint: DataPoint) => {
dispatch({ type: 'ADD_CUSTOM_DATA_POINT', payload: { ...dataPoint, isCustom: true } })
},
[dispatch]
)
const updateDataPoint = useCallback(
(id: string, data: Partial<DataPoint>) => {
dispatch({ type: 'UPDATE_DATA_POINT', payload: { id, data } })
},
[dispatch]
)
const deleteCustomDataPoint = useCallback(
(id: string) => {
dispatch({ type: 'DELETE_CUSTOM_DATA_POINT', payload: id })
},
[dispatch]
)
const setActiveTab = useCallback(
(tab: EinwilligungenTab) => {
dispatch({ type: 'SET_ACTIVE_TAB', payload: tab })
},
[dispatch]
)
const setPreviewLanguage = useCallback(
(language: SupportedLanguage) => {
dispatch({ type: 'SET_PREVIEW_LANGUAGE', payload: language })
},
[dispatch]
)
const setPreviewFormat = useCallback(
(format: ExportFormat) => {
dispatch({ type: 'SET_PREVIEW_FORMAT', payload: format })
},
[dispatch]
)
const setCompanyInfo = useCallback(
(info: CompanyInfo) => {
dispatch({ type: 'SET_COMPANY_INFO', payload: info })
},
[dispatch]
)
const generatePrivacyPolicy = useCallback(async () => {
if (!state.catalog || !state.companyInfo) {
dispatch({ type: 'SET_ERROR', payload: 'Bitte zuerst Firmendaten eingeben' })
return
}
dispatch({ type: 'SET_LOADING', payload: true })
dispatch({ type: 'SET_ERROR', payload: null })
try {
const response = await fetch(`/api/sdk/v1/einwilligungen/privacy-policy/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Tenant-ID': state.catalog.tenantId,
},
body: JSON.stringify({
dataPointIds: state.selectedDataPoints,
companyInfo: state.companyInfo,
language: state.previewLanguage,
format: state.previewFormat,
}),
})
if (response.ok) {
const policy = await response.json()
dispatch({ type: 'SET_PRIVACY_POLICY', payload: policy })
} else {
throw new Error('Failed to generate privacy policy')
}
} catch (error) {
console.error('Error generating privacy policy:', error)
dispatch({ type: 'SET_ERROR', payload: 'Fehler bei der Generierung der Datenschutzerklaerung' })
} finally {
dispatch({ type: 'SET_LOADING', payload: false })
}
}, [
state.catalog,
state.companyInfo,
state.selectedDataPoints,
state.previewLanguage,
state.previewFormat,
dispatch,
])
const generateCookieBannerConfig = useCallback(() => {
if (!state.catalog) return
const config: CookieBannerConfig = {
id: `cookie-banner-${state.catalog.tenantId}`,
tenantId: state.catalog.tenantId,
categories: DEFAULT_COOKIE_CATEGORIES.map((cat) => ({
...cat,
// Filtere nur die ausgewaehlten Datenpunkte
dataPointIds: cat.dataPointIds.filter((id) => state.selectedDataPoints.includes(id)),
})),
styling: {
position: 'BOTTOM',
theme: 'LIGHT',
primaryColor: '#6366f1',
borderRadius: 12,
},
texts: {
title: { de: 'Cookie-Einstellungen', en: 'Cookie Settings' },
description: {
de: 'Wir verwenden Cookies, um Ihnen die bestmoegliche Nutzung unserer Website zu ermoeglichen.',
en: 'We use cookies to provide you with the best possible experience on our website.',
},
acceptAll: { de: 'Alle akzeptieren', en: 'Accept All' },
rejectAll: { de: 'Alle ablehnen', en: 'Reject All' },
customize: { de: 'Anpassen', en: 'Customize' },
save: { de: 'Auswahl speichern', en: 'Save Selection' },
privacyPolicyLink: { de: 'Datenschutzerklaerung', en: 'Privacy Policy' },
},
updatedAt: new Date(),
}
dispatch({ type: 'SET_COOKIE_BANNER_CONFIG', payload: config })
}, [state.catalog, state.selectedDataPoints, dispatch])
// ---------------------------------------------------------------------------
// CONTEXT VALUE
// ---------------------------------------------------------------------------
const value: EinwilligungenContextValue = {
state,
dispatch,
// Computed Values
allDataPoints,
selectedDataPointsData,
dataPointsByCategory,
categoryStats,
riskStats,
legalBasisStats,
// Actions
initializeCatalog,
loadCatalog,
saveCatalog,
toggleDataPoint,
addCustomDataPoint,
updateDataPoint,
deleteCustomDataPoint,
setActiveTab,
setPreviewLanguage,
setPreviewFormat,
setCompanyInfo,
generatePrivacyPolicy,
generateCookieBannerConfig,
}
return (
<EinwilligungenContext.Provider value={value}>{children}</EinwilligungenContext.Provider>
)
}
// =============================================================================
// HOOK
// =============================================================================
export function useEinwilligungen(): EinwilligungenContextValue {
const context = useContext(EinwilligungenContext)
if (!context) {
throw new Error('useEinwilligungen must be used within EinwilligungenProvider')
}
return context
}
// =============================================================================
// EXPORTS
// =============================================================================
export { initialState, einwilligungenReducer }

View File

@@ -0,0 +1,493 @@
// =============================================================================
// Privacy Policy DOCX Export
// Export Datenschutzerklaerung to Microsoft Word format
// =============================================================================
import {
GeneratedPrivacyPolicy,
PrivacyPolicySection,
CompanyInfo,
SupportedLanguage,
DataPoint,
CATEGORY_METADATA,
RETENTION_PERIOD_INFO,
} from '../types'
// =============================================================================
// TYPES
// =============================================================================
export interface DOCXExportOptions {
language: SupportedLanguage
includeTableOfContents: boolean
includeDataPointList: boolean
companyLogo?: string
primaryColor?: string
}
const DEFAULT_OPTIONS: DOCXExportOptions = {
language: 'de',
includeTableOfContents: true,
includeDataPointList: true,
primaryColor: '#6366f1',
}
// =============================================================================
// DOCX CONTENT STRUCTURE
// =============================================================================
export interface DocxParagraph {
type: 'paragraph' | 'heading1' | 'heading2' | 'heading3' | 'bullet' | 'title'
content: string
style?: Record<string, string>
}
export interface DocxTableRow {
cells: string[]
isHeader?: boolean
}
export interface DocxTable {
type: 'table'
headers: string[]
rows: DocxTableRow[]
}
export type DocxElement = DocxParagraph | DocxTable
// =============================================================================
// DOCX CONTENT GENERATION
// =============================================================================
/**
* Generate DOCX content structure for Privacy Policy
*/
export function generateDOCXContent(
policy: GeneratedPrivacyPolicy,
companyInfo: CompanyInfo,
dataPoints: DataPoint[],
options: Partial<DOCXExportOptions> = {}
): DocxElement[] {
const opts = { ...DEFAULT_OPTIONS, ...options }
const elements: DocxElement[] = []
const lang = opts.language
// Title
elements.push({
type: 'title',
content: lang === 'de' ? 'Datenschutzerklaerung' : 'Privacy Policy',
})
elements.push({
type: 'paragraph',
content: lang === 'de'
? 'gemaess Art. 13, 14 DSGVO'
: 'according to Art. 13, 14 GDPR',
style: { fontStyle: 'italic', textAlign: 'center' },
})
// Company Info
elements.push({
type: 'heading2',
content: lang === 'de' ? 'Verantwortlicher' : 'Controller',
})
elements.push({
type: 'paragraph',
content: companyInfo.name,
style: { fontWeight: 'bold' },
})
elements.push({
type: 'paragraph',
content: `${companyInfo.address}`,
})
elements.push({
type: 'paragraph',
content: `${companyInfo.postalCode} ${companyInfo.city}`,
})
if (companyInfo.country) {
elements.push({
type: 'paragraph',
content: companyInfo.country,
})
}
elements.push({
type: 'paragraph',
content: `${lang === 'de' ? 'E-Mail' : 'Email'}: ${companyInfo.email}`,
})
if (companyInfo.phone) {
elements.push({
type: 'paragraph',
content: `${lang === 'de' ? 'Telefon' : 'Phone'}: ${companyInfo.phone}`,
})
}
if (companyInfo.website) {
elements.push({
type: 'paragraph',
content: `Website: ${companyInfo.website}`,
})
}
// DPO Info
if (companyInfo.dpoName || companyInfo.dpoEmail) {
elements.push({
type: 'heading3',
content: lang === 'de' ? 'Datenschutzbeauftragter' : 'Data Protection Officer',
})
if (companyInfo.dpoName) {
elements.push({
type: 'paragraph',
content: companyInfo.dpoName,
})
}
if (companyInfo.dpoEmail) {
elements.push({
type: 'paragraph',
content: `${lang === 'de' ? 'E-Mail' : 'Email'}: ${companyInfo.dpoEmail}`,
})
}
if (companyInfo.dpoPhone) {
elements.push({
type: 'paragraph',
content: `${lang === 'de' ? 'Telefon' : 'Phone'}: ${companyInfo.dpoPhone}`,
})
}
}
// Document metadata
elements.push({
type: 'paragraph',
content: lang === 'de'
? `Stand: ${new Date(policy.generatedAt).toLocaleDateString('de-DE')}`
: `Date: ${new Date(policy.generatedAt).toLocaleDateString('en-US')}`,
style: { marginTop: '20px' },
})
elements.push({
type: 'paragraph',
content: `Version: ${policy.version}`,
})
// Table of Contents
if (opts.includeTableOfContents) {
elements.push({
type: 'heading2',
content: lang === 'de' ? 'Inhaltsverzeichnis' : 'Table of Contents',
})
policy.sections.forEach((section, idx) => {
elements.push({
type: 'bullet',
content: `${idx + 1}. ${section.title[lang]}`,
})
})
if (opts.includeDataPointList) {
elements.push({
type: 'bullet',
content: lang === 'de' ? 'Anhang: Datenpunktkatalog' : 'Appendix: Data Point Catalog',
})
}
}
// Privacy Policy Sections
policy.sections.forEach((section, idx) => {
elements.push({
type: 'heading1',
content: `${idx + 1}. ${section.title[lang]}`,
})
// Parse content
const content = section.content[lang]
const paragraphs = content.split('\n\n')
for (const para of paragraphs) {
if (para.startsWith('- ')) {
// List items
const items = para.split('\n').filter(l => l.startsWith('- '))
for (const item of items) {
elements.push({
type: 'bullet',
content: item.substring(2),
})
}
} else if (para.startsWith('### ')) {
elements.push({
type: 'heading3',
content: para.substring(4),
})
} else if (para.startsWith('## ')) {
elements.push({
type: 'heading2',
content: para.substring(3),
})
} else if (para.trim()) {
elements.push({
type: 'paragraph',
content: para.replace(/\*\*(.*?)\*\*/g, '$1'),
})
}
}
})
// Data Point Catalog Appendix
if (opts.includeDataPointList && dataPoints.length > 0) {
elements.push({
type: 'heading1',
content: lang === 'de' ? 'Anhang: Datenpunktkatalog' : 'Appendix: Data Point Catalog',
})
elements.push({
type: 'paragraph',
content: lang === 'de'
? 'Die folgende Tabelle zeigt alle verarbeiteten personenbezogenen Daten:'
: 'The following table shows all processed personal data:',
})
// Group by category
const categories = [...new Set(dataPoints.map(dp => dp.category))]
for (const category of categories) {
const categoryDPs = dataPoints.filter(dp => dp.category === category)
const categoryMeta = CATEGORY_METADATA[category]
elements.push({
type: 'heading3',
content: `${categoryMeta.code}. ${categoryMeta.name[lang]}`,
})
elements.push({
type: 'table',
headers: lang === 'de'
? ['Code', 'Datenpunkt', 'Rechtsgrundlage', 'Loeschfrist']
: ['Code', 'Data Point', 'Legal Basis', 'Retention'],
rows: categoryDPs.map(dp => ({
cells: [
dp.code,
dp.name[lang],
formatLegalBasis(dp.legalBasis, lang),
RETENTION_PERIOD_INFO[dp.retentionPeriod]?.label[lang] || dp.retentionPeriod,
],
})),
})
}
}
// Footer
elements.push({
type: 'paragraph',
content: lang === 'de'
? `Dieses Dokument wurde automatisch generiert mit dem Datenschutzerklaerung-Generator am ${new Date().toLocaleDateString('de-DE')}.`
: `This document was automatically generated with the Privacy Policy Generator on ${new Date().toLocaleDateString('en-US')}.`,
style: { fontStyle: 'italic', fontSize: '9pt' },
})
return elements
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
function formatLegalBasis(basis: string, language: SupportedLanguage): string {
const bases: Record<string, Record<SupportedLanguage, string>> = {
CONTRACT: { de: 'Vertrag (Art. 6 Abs. 1 lit. b)', en: 'Contract (Art. 6(1)(b))' },
CONSENT: { de: 'Einwilligung (Art. 6 Abs. 1 lit. a)', en: 'Consent (Art. 6(1)(a))' },
LEGITIMATE_INTEREST: { de: 'Ber. Interesse (Art. 6 Abs. 1 lit. f)', en: 'Legitimate Interest (Art. 6(1)(f))' },
LEGAL_OBLIGATION: { de: 'Rechtspflicht (Art. 6 Abs. 1 lit. c)', en: 'Legal Obligation (Art. 6(1)(c))' },
}
return bases[basis]?.[language] || basis
}
// =============================================================================
// DOCX BLOB GENERATION
// =============================================================================
/**
* Generate a DOCX file as a Blob
* This generates HTML that Word can open
*/
export async function generateDOCXBlob(
policy: GeneratedPrivacyPolicy,
companyInfo: CompanyInfo,
dataPoints: DataPoint[],
options: Partial<DOCXExportOptions> = {}
): Promise<Blob> {
const content = generateDOCXContent(policy, companyInfo, dataPoints, options)
const opts = { ...DEFAULT_OPTIONS, ...options }
const html = generateHTMLFromContent(content, opts)
return new Blob([html], {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
})
}
function generateHTMLFromContent(
content: DocxElement[],
options: DOCXExportOptions
): string {
let html = `
<!DOCTYPE html>
<html lang="${options.language}">
<head>
<meta charset="utf-8">
<title>${options.language === 'de' ? 'Datenschutzerklaerung' : 'Privacy Policy'}</title>
<style>
body {
font-family: Calibri, Arial, sans-serif;
font-size: 11pt;
line-height: 1.5;
color: #1e293b;
max-width: 800px;
margin: 0 auto;
padding: 40px;
}
h1 {
font-size: 18pt;
color: ${options.primaryColor};
border-bottom: 1px solid ${options.primaryColor};
padding-bottom: 8px;
margin-top: 24pt;
}
h2 {
font-size: 14pt;
color: ${options.primaryColor};
margin-top: 18pt;
}
h3 {
font-size: 12pt;
color: #334155;
margin-top: 14pt;
}
.title {
font-size: 24pt;
color: ${options.primaryColor};
text-align: center;
font-weight: bold;
margin-bottom: 12pt;
}
p {
margin: 8pt 0;
text-align: justify;
}
table {
width: 100%;
border-collapse: collapse;
margin: 12pt 0;
font-size: 10pt;
}
th, td {
border: 1px solid #cbd5e1;
padding: 6pt 10pt;
text-align: left;
}
th {
background-color: ${options.primaryColor};
color: white;
font-weight: 600;
}
tr:nth-child(even) {
background-color: #f8fafc;
}
ul {
margin: 8pt 0;
padding-left: 20pt;
}
li {
margin: 4pt 0;
}
</style>
</head>
<body>
`
for (const element of content) {
if (element.type === 'table') {
html += '<table>\n<thead><tr>\n'
for (const header of element.headers) {
html += ` <th>${escapeHtml(header)}</th>\n`
}
html += '</tr></thead>\n<tbody>\n'
for (const row of element.rows) {
html += '<tr>\n'
for (const cell of row.cells) {
html += ` <td>${escapeHtml(cell)}</td>\n`
}
html += '</tr>\n'
}
html += '</tbody></table>\n'
} else {
const tag = getHtmlTag(element.type)
const className = element.type === 'title' ? ' class="title"' : ''
const processedContent = escapeHtml(element.content)
html += `<${tag}${className}>${processedContent}</${tag}>\n`
}
}
html += '</body></html>'
return html
}
function getHtmlTag(type: string): string {
switch (type) {
case 'title':
return 'div'
case 'heading1':
return 'h1'
case 'heading2':
return 'h2'
case 'heading3':
return 'h3'
case 'bullet':
return 'li'
default:
return 'p'
}
}
function escapeHtml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
}
// =============================================================================
// FILENAME GENERATION
// =============================================================================
/**
* Generate a filename for the DOCX export
*/
export function generateDOCXFilename(
companyInfo: CompanyInfo,
language: SupportedLanguage = 'de'
): string {
const companyName = companyInfo.name.replace(/[^a-zA-Z0-9]/g, '-') || 'unknown'
const date = new Date().toISOString().split('T')[0]
const prefix = language === 'de' ? 'Datenschutzerklaerung' : 'Privacy-Policy'
return `${prefix}-${companyName}-${date}.doc`
}

View File

@@ -0,0 +1,8 @@
/**
* Einwilligungen Export Module
*
* PDF and DOCX export functionality for Privacy Policy documents.
*/
export * from './pdf'
export * from './docx'

View File

@@ -0,0 +1,505 @@
// =============================================================================
// Privacy Policy PDF Export
// Export Datenschutzerklaerung to PDF format
// =============================================================================
import {
GeneratedPrivacyPolicy,
PrivacyPolicySection,
CompanyInfo,
SupportedLanguage,
DataPoint,
CATEGORY_METADATA,
RETENTION_PERIOD_INFO,
} from '../types'
// =============================================================================
// TYPES
// =============================================================================
export interface PDFExportOptions {
language: SupportedLanguage
includeTableOfContents: boolean
includeDataPointList: boolean
companyLogo?: string
primaryColor?: string
pageSize?: 'A4' | 'LETTER'
orientation?: 'portrait' | 'landscape'
fontSize?: number
}
const DEFAULT_OPTIONS: PDFExportOptions = {
language: 'de',
includeTableOfContents: true,
includeDataPointList: true,
primaryColor: '#6366f1',
pageSize: 'A4',
orientation: 'portrait',
fontSize: 11,
}
// =============================================================================
// PDF CONTENT STRUCTURE
// =============================================================================
export interface PDFSection {
type: 'title' | 'heading' | 'subheading' | 'paragraph' | 'table' | 'list' | 'pagebreak'
content?: string
items?: string[]
table?: {
headers: string[]
rows: string[][]
}
style?: {
color?: string
fontSize?: number
bold?: boolean
italic?: boolean
align?: 'left' | 'center' | 'right'
}
}
// =============================================================================
// PDF CONTENT GENERATION
// =============================================================================
/**
* Generate PDF content structure for Privacy Policy
*/
export function generatePDFContent(
policy: GeneratedPrivacyPolicy,
companyInfo: CompanyInfo,
dataPoints: DataPoint[],
options: Partial<PDFExportOptions> = {}
): PDFSection[] {
const opts = { ...DEFAULT_OPTIONS, ...options }
const sections: PDFSection[] = []
const lang = opts.language
// Title page
sections.push({
type: 'title',
content: lang === 'de' ? 'Datenschutzerklaerung' : 'Privacy Policy',
style: { color: opts.primaryColor, fontSize: 28, bold: true, align: 'center' },
})
sections.push({
type: 'paragraph',
content: lang === 'de'
? 'gemaess Art. 13, 14 DSGVO'
: 'according to Art. 13, 14 GDPR',
style: { fontSize: 14, align: 'center', italic: true },
})
// Company information
sections.push({
type: 'paragraph',
content: companyInfo.name,
style: { fontSize: 16, bold: true, align: 'center' },
})
sections.push({
type: 'paragraph',
content: `${companyInfo.address}, ${companyInfo.postalCode} ${companyInfo.city}`,
style: { align: 'center' },
})
sections.push({
type: 'paragraph',
content: `${lang === 'de' ? 'Stand' : 'Date'}: ${new Date(policy.generatedAt).toLocaleDateString(lang === 'de' ? 'de-DE' : 'en-US')}`,
style: { align: 'center' },
})
sections.push({
type: 'paragraph',
content: `Version: ${policy.version}`,
style: { align: 'center', fontSize: 10 },
})
sections.push({ type: 'pagebreak' })
// Table of Contents
if (opts.includeTableOfContents) {
sections.push({
type: 'heading',
content: lang === 'de' ? 'Inhaltsverzeichnis' : 'Table of Contents',
style: { color: opts.primaryColor },
})
const tocItems = policy.sections.map((section, idx) =>
`${idx + 1}. ${section.title[lang]}`
)
if (opts.includeDataPointList) {
tocItems.push(lang === 'de' ? 'Anhang: Datenpunktkatalog' : 'Appendix: Data Point Catalog')
}
sections.push({
type: 'list',
items: tocItems,
})
sections.push({ type: 'pagebreak' })
}
// Privacy Policy Sections
policy.sections.forEach((section, idx) => {
sections.push({
type: 'heading',
content: `${idx + 1}. ${section.title[lang]}`,
style: { color: opts.primaryColor },
})
// Convert markdown-like content to paragraphs
const content = section.content[lang]
const paragraphs = content.split('\n\n')
for (const para of paragraphs) {
if (para.startsWith('- ')) {
// List items
const items = para.split('\n').filter(l => l.startsWith('- ')).map(l => l.substring(2))
sections.push({
type: 'list',
items,
})
} else if (para.startsWith('### ')) {
sections.push({
type: 'subheading',
content: para.substring(4),
})
} else if (para.startsWith('## ')) {
sections.push({
type: 'subheading',
content: para.substring(3),
style: { bold: true },
})
} else if (para.trim()) {
sections.push({
type: 'paragraph',
content: para.replace(/\*\*(.*?)\*\*/g, '$1'), // Remove markdown bold for plain text
})
}
}
// Add related data points if this section has them
if (section.dataPointIds.length > 0 && opts.includeDataPointList) {
const relatedDPs = dataPoints.filter(dp => section.dataPointIds.includes(dp.id))
if (relatedDPs.length > 0) {
sections.push({
type: 'paragraph',
content: lang === 'de'
? `Betroffene Datenkategorien: ${relatedDPs.map(dp => dp.name[lang]).join(', ')}`
: `Affected data categories: ${relatedDPs.map(dp => dp.name[lang]).join(', ')}`,
style: { italic: true, fontSize: 10 },
})
}
}
})
// Data Point Catalog Appendix
if (opts.includeDataPointList && dataPoints.length > 0) {
sections.push({ type: 'pagebreak' })
sections.push({
type: 'heading',
content: lang === 'de' ? 'Anhang: Datenpunktkatalog' : 'Appendix: Data Point Catalog',
style: { color: opts.primaryColor },
})
sections.push({
type: 'paragraph',
content: lang === 'de'
? 'Die folgende Tabelle zeigt alle verarbeiteten personenbezogenen Daten:'
: 'The following table shows all processed personal data:',
})
// Group by category
const categories = [...new Set(dataPoints.map(dp => dp.category))]
for (const category of categories) {
const categoryDPs = dataPoints.filter(dp => dp.category === category)
const categoryMeta = CATEGORY_METADATA[category]
sections.push({
type: 'subheading',
content: `${categoryMeta.code}. ${categoryMeta.name[lang]}`,
})
sections.push({
type: 'table',
table: {
headers: lang === 'de'
? ['Code', 'Datenpunkt', 'Zweck', 'Loeschfrist']
: ['Code', 'Data Point', 'Purpose', 'Retention'],
rows: categoryDPs.map(dp => [
dp.code,
dp.name[lang],
dp.purpose[lang].substring(0, 50) + (dp.purpose[lang].length > 50 ? '...' : ''),
RETENTION_PERIOD_INFO[dp.retentionPeriod]?.label[lang] || dp.retentionPeriod,
]),
},
})
}
}
// Footer
sections.push({
type: 'paragraph',
content: lang === 'de'
? `Generiert am ${new Date().toLocaleDateString('de-DE')} mit dem Datenschutzerklaerung-Generator`
: `Generated on ${new Date().toLocaleDateString('en-US')} with the Privacy Policy Generator`,
style: { italic: true, align: 'center', fontSize: 9 },
})
return sections
}
// =============================================================================
// PDF BLOB GENERATION
// =============================================================================
/**
* Generate a PDF file as a Blob
* This generates HTML that can be printed to PDF or used with a PDF library
*/
export async function generatePDFBlob(
policy: GeneratedPrivacyPolicy,
companyInfo: CompanyInfo,
dataPoints: DataPoint[],
options: Partial<PDFExportOptions> = {}
): Promise<Blob> {
const content = generatePDFContent(policy, companyInfo, dataPoints, options)
const opts = { ...DEFAULT_OPTIONS, ...options }
// Generate HTML for PDF conversion
const html = generateHTMLFromContent(content, opts)
return new Blob([html], { type: 'text/html' })
}
/**
* Generate printable HTML from PDF content
*/
function generateHTMLFromContent(
content: PDFSection[],
options: PDFExportOptions
): string {
const pageWidth = options.pageSize === 'A4' ? '210mm' : '8.5in'
const pageHeight = options.pageSize === 'A4' ? '297mm' : '11in'
let html = `
<!DOCTYPE html>
<html lang="${options.language}">
<head>
<meta charset="utf-8">
<title>${options.language === 'de' ? 'Datenschutzerklaerung' : 'Privacy Policy'}</title>
<style>
@page {
size: ${pageWidth} ${pageHeight};
margin: 20mm;
}
body {
font-family: 'Segoe UI', Calibri, Arial, sans-serif;
font-size: ${options.fontSize}pt;
line-height: 1.6;
color: #1e293b;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1 {
font-size: 24pt;
color: ${options.primaryColor};
border-bottom: 2px solid ${options.primaryColor};
padding-bottom: 10px;
margin-top: 30px;
}
h2 {
font-size: 16pt;
color: ${options.primaryColor};
margin-top: 24px;
}
h3 {
font-size: 13pt;
color: #334155;
margin-top: 18px;
}
p {
margin: 12px 0;
text-align: justify;
}
.title {
font-size: 28pt;
text-align: center;
color: ${options.primaryColor};
font-weight: bold;
margin-bottom: 10px;
}
.subtitle {
text-align: center;
font-style: italic;
color: #64748b;
}
.center {
text-align: center;
}
table {
width: 100%;
border-collapse: collapse;
margin: 16px 0;
font-size: 10pt;
}
th, td {
border: 1px solid #e2e8f0;
padding: 8px 12px;
text-align: left;
}
th {
background-color: ${options.primaryColor};
color: white;
font-weight: 600;
}
tr:nth-child(even) {
background-color: #f8fafc;
}
ul {
margin: 12px 0;
padding-left: 24px;
}
li {
margin: 6px 0;
}
.pagebreak {
page-break-after: always;
}
.footer {
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid #e2e8f0;
font-size: 9pt;
color: #94a3b8;
text-align: center;
}
@media print {
body {
padding: 0;
}
.pagebreak {
page-break-after: always;
}
}
</style>
</head>
<body>
`
for (const section of content) {
switch (section.type) {
case 'title':
html += `<div class="title" style="${getStyleString(section.style)}">${escapeHtml(section.content || '')}</div>\n`
break
case 'heading':
html += `<h1 style="${getStyleString(section.style)}">${escapeHtml(section.content || '')}</h1>\n`
break
case 'subheading':
html += `<h3 style="${getStyleString(section.style)}">${escapeHtml(section.content || '')}</h3>\n`
break
case 'paragraph':
const alignClass = section.style?.align === 'center' ? ' class="center"' : ''
html += `<p${alignClass} style="${getStyleString(section.style)}">${escapeHtml(section.content || '')}</p>\n`
break
case 'list':
html += '<ul>\n'
for (const item of section.items || []) {
html += ` <li>${escapeHtml(item)}</li>\n`
}
html += '</ul>\n'
break
case 'table':
if (section.table) {
html += '<table>\n<thead><tr>\n'
for (const header of section.table.headers) {
html += ` <th>${escapeHtml(header)}</th>\n`
}
html += '</tr></thead>\n<tbody>\n'
for (const row of section.table.rows) {
html += '<tr>\n'
for (const cell of row) {
html += ` <td>${escapeHtml(cell)}</td>\n`
}
html += '</tr>\n'
}
html += '</tbody></table>\n'
}
break
case 'pagebreak':
html += '<div class="pagebreak"></div>\n'
break
}
}
html += '</body></html>'
return html
}
function getStyleString(style?: PDFSection['style']): string {
if (!style) return ''
const parts: string[] = []
if (style.color) parts.push(`color: ${style.color}`)
if (style.fontSize) parts.push(`font-size: ${style.fontSize}pt`)
if (style.bold) parts.push('font-weight: bold')
if (style.italic) parts.push('font-style: italic')
if (style.align) parts.push(`text-align: ${style.align}`)
return parts.join('; ')
}
function escapeHtml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
}
// =============================================================================
// FILENAME GENERATION
// =============================================================================
/**
* Generate a filename for the PDF export
*/
export function generatePDFFilename(
companyInfo: CompanyInfo,
language: SupportedLanguage = 'de'
): string {
const companyName = companyInfo.name.replace(/[^a-zA-Z0-9]/g, '-') || 'unknown'
const date = new Date().toISOString().split('T')[0]
const prefix = language === 'de' ? 'Datenschutzerklaerung' : 'Privacy-Policy'
return `${prefix}-${companyName}-${date}.html`
}

View File

@@ -0,0 +1,595 @@
/**
* Cookie Banner Generator
*
* Generiert Cookie-Banner Konfigurationen und Embed-Code aus dem Datenpunktkatalog.
* Die Cookie-Kategorien werden automatisch aus den Datenpunkten abgeleitet.
*/
import {
DataPoint,
CookieCategory,
CookieBannerCategory,
CookieBannerConfig,
CookieBannerStyling,
CookieBannerTexts,
CookieBannerEmbedCode,
CookieInfo,
LocalizedText,
SupportedLanguage,
} from '../types'
import { DEFAULT_COOKIE_CATEGORIES } from '../catalog/loader'
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
/**
* Holt den lokalisierten Text
*/
function t(text: LocalizedText, language: SupportedLanguage): string {
return text[language]
}
// =============================================================================
// COOKIE BANNER CONFIGURATION
// =============================================================================
/**
* Standard Cookie Banner Texte
*/
export const DEFAULT_COOKIE_BANNER_TEXTS: CookieBannerTexts = {
title: {
de: 'Cookie-Einstellungen',
en: 'Cookie Settings',
},
description: {
de: 'Wir verwenden Cookies, um Ihnen die bestmoegliche Nutzung unserer Website zu ermoeglichen. Einige Cookies sind technisch notwendig, waehrend andere uns helfen, Ihre Nutzererfahrung zu verbessern.',
en: 'We use cookies to provide you with the best possible experience on our website. Some cookies are technically necessary, while others help us improve your user experience.',
},
acceptAll: {
de: 'Alle akzeptieren',
en: 'Accept All',
},
rejectAll: {
de: 'Nur notwendige',
en: 'Essential Only',
},
customize: {
de: 'Einstellungen',
en: 'Customize',
},
save: {
de: 'Auswahl speichern',
en: 'Save Selection',
},
privacyPolicyLink: {
de: 'Mehr in unserer Datenschutzerklaerung',
en: 'More in our Privacy Policy',
},
}
/**
* Standard Styling fuer Cookie Banner
*/
export const DEFAULT_COOKIE_BANNER_STYLING: CookieBannerStyling = {
position: 'BOTTOM',
theme: 'LIGHT',
primaryColor: '#6366f1', // Indigo
secondaryColor: '#f1f5f9', // Slate-100
textColor: '#1e293b', // Slate-800
backgroundColor: '#ffffff',
borderRadius: 12,
maxWidth: 480,
}
// =============================================================================
// GENERATOR FUNCTIONS
// =============================================================================
/**
* Generiert Cookie-Banner Kategorien aus Datenpunkten
*/
export function generateCookieCategories(
dataPoints: DataPoint[]
): CookieBannerCategory[] {
// Filtere nur Datenpunkte mit Cookie-Kategorie
const cookieDataPoints = dataPoints.filter((dp) => dp.cookieCategory !== null)
// Erstelle die Kategorien basierend auf den Defaults
return DEFAULT_COOKIE_CATEGORIES.map((defaultCat) => {
// Filtere die Datenpunkte fuer diese Kategorie
const categoryDataPoints = cookieDataPoints.filter(
(dp) => dp.cookieCategory === defaultCat.id
)
// Erstelle Cookie-Infos aus den Datenpunkten
const cookies: CookieInfo[] = categoryDataPoints.map((dp) => ({
name: dp.code,
provider: 'First Party',
purpose: dp.purpose,
expiry: getExpiryFromRetention(dp.retentionPeriod),
type: 'FIRST_PARTY',
}))
return {
...defaultCat,
dataPointIds: categoryDataPoints.map((dp) => dp.id),
cookies,
}
}).filter((cat) => cat.dataPointIds.length > 0 || cat.isRequired)
}
/**
* Konvertiert Retention Period zu Cookie-Expiry String
*/
function getExpiryFromRetention(retention: string): string {
const mapping: Record<string, string> = {
'24_HOURS': '24 Stunden / 24 hours',
'30_DAYS': '30 Tage / 30 days',
'90_DAYS': '90 Tage / 90 days',
'12_MONTHS': '1 Jahr / 1 year',
'24_MONTHS': '2 Jahre / 2 years',
'36_MONTHS': '3 Jahre / 3 years',
'UNTIL_REVOCATION': 'Bis Widerruf / Until revocation',
'UNTIL_PURPOSE_FULFILLED': 'Session',
'UNTIL_ACCOUNT_DELETION': 'Bis Kontoschliessung / Until account deletion',
}
return mapping[retention] || 'Session'
}
/**
* Generiert die vollstaendige Cookie Banner Konfiguration
*/
export function generateCookieBannerConfig(
tenantId: string,
dataPoints: DataPoint[],
customTexts?: Partial<CookieBannerTexts>,
customStyling?: Partial<CookieBannerStyling>
): CookieBannerConfig {
const categories = generateCookieCategories(dataPoints)
return {
id: `cookie-banner-${tenantId}`,
tenantId,
categories,
styling: {
...DEFAULT_COOKIE_BANNER_STYLING,
...customStyling,
},
texts: {
...DEFAULT_COOKIE_BANNER_TEXTS,
...customTexts,
},
updatedAt: new Date(),
}
}
// =============================================================================
// EMBED CODE GENERATION
// =============================================================================
/**
* Generiert den Embed-Code fuer den Cookie Banner
*/
export function generateEmbedCode(
config: CookieBannerConfig,
privacyPolicyUrl: string = '/datenschutz'
): CookieBannerEmbedCode {
const css = generateCSS(config.styling)
const html = generateHTML(config, privacyPolicyUrl)
const js = generateJS(config)
const scriptTag = `<script src="/cookie-banner.js" data-tenant="${config.tenantId}"></script>`
return {
html,
css,
js,
scriptTag,
}
}
/**
* Generiert das CSS fuer den Cookie Banner
*/
function generateCSS(styling: CookieBannerStyling): string {
const positionStyles: Record<string, string> = {
BOTTOM: 'bottom: 0; left: 0; right: 0;',
TOP: 'top: 0; left: 0; right: 0;',
CENTER: 'top: 50%; left: 50%; transform: translate(-50%, -50%);',
}
const isDark = styling.theme === 'DARK'
const bgColor = isDark ? '#1e293b' : styling.backgroundColor || '#ffffff'
const textColor = isDark ? '#f1f5f9' : styling.textColor || '#1e293b'
const borderColor = isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)'
return `
/* Cookie Banner Styles */
.cookie-banner-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 9998;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.cookie-banner-overlay.active {
opacity: 1;
visibility: visible;
}
.cookie-banner {
position: fixed;
${positionStyles[styling.position]}
z-index: 9999;
background: ${bgColor};
color: ${textColor};
border-radius: ${styling.borderRadius || 12}px;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
padding: 24px;
max-width: ${styling.maxWidth}px;
margin: ${styling.position === 'CENTER' ? '0' : '16px'};
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
transform: translateY(100%);
opacity: 0;
transition: all 0.3s ease;
}
.cookie-banner.active {
transform: translateY(0);
opacity: 1;
}
.cookie-banner-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 12px;
}
.cookie-banner-description {
font-size: 14px;
line-height: 1.5;
margin-bottom: 16px;
opacity: 0.8;
}
.cookie-banner-buttons {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.cookie-banner-btn {
flex: 1;
min-width: 120px;
padding: 12px 20px;
border-radius: ${(styling.borderRadius || 12) / 2}px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
border: none;
}
.cookie-banner-btn-primary {
background: ${styling.primaryColor};
color: white;
}
.cookie-banner-btn-primary:hover {
filter: brightness(1.1);
}
.cookie-banner-btn-secondary {
background: ${styling.secondaryColor || borderColor};
color: ${textColor};
}
.cookie-banner-btn-secondary:hover {
filter: brightness(0.95);
}
.cookie-banner-link {
display: block;
margin-top: 16px;
font-size: 12px;
color: ${styling.primaryColor};
text-decoration: none;
}
.cookie-banner-link:hover {
text-decoration: underline;
}
/* Category Details */
.cookie-banner-details {
margin-top: 16px;
border-top: 1px solid ${borderColor};
padding-top: 16px;
display: none;
}
.cookie-banner-details.active {
display: block;
}
.cookie-banner-category {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid ${borderColor};
}
.cookie-banner-category:last-child {
border-bottom: none;
}
.cookie-banner-category-info {
flex: 1;
}
.cookie-banner-category-name {
font-weight: 500;
font-size: 14px;
}
.cookie-banner-category-desc {
font-size: 12px;
opacity: 0.7;
margin-top: 4px;
}
.cookie-banner-toggle {
position: relative;
width: 48px;
height: 28px;
background: ${borderColor};
border-radius: 14px;
cursor: pointer;
transition: all 0.2s ease;
}
.cookie-banner-toggle.active {
background: ${styling.primaryColor};
}
.cookie-banner-toggle.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.cookie-banner-toggle::after {
content: '';
position: absolute;
top: 4px;
left: 4px;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
transition: all 0.2s ease;
}
.cookie-banner-toggle.active::after {
left: 24px;
}
@media (max-width: 640px) {
.cookie-banner {
margin: 0;
border-radius: ${styling.position === 'CENTER' ? (styling.borderRadius || 12) : 0}px;
max-width: 100%;
}
.cookie-banner-buttons {
flex-direction: column;
}
.cookie-banner-btn {
width: 100%;
}
}
`.trim()
}
/**
* Generiert das HTML fuer den Cookie Banner
*/
function generateHTML(config: CookieBannerConfig, privacyPolicyUrl: string): string {
const categoriesHTML = config.categories
.map((cat) => {
const isRequired = cat.isRequired
return `
<div class="cookie-banner-category" data-category="${cat.id}">
<div class="cookie-banner-category-info">
<div class="cookie-banner-category-name">${cat.name.de}</div>
<div class="cookie-banner-category-desc">${cat.description.de}</div>
</div>
<div class="cookie-banner-toggle ${cat.defaultEnabled ? 'active' : ''} ${isRequired ? 'disabled' : ''}"
data-category="${cat.id}"
data-required="${isRequired}"></div>
</div>
`
})
.join('')
return `
<div class="cookie-banner-overlay" id="cookieBannerOverlay"></div>
<div class="cookie-banner" id="cookieBanner" role="dialog" aria-labelledby="cookieBannerTitle" aria-modal="true">
<div class="cookie-banner-title" id="cookieBannerTitle">${config.texts.title.de}</div>
<div class="cookie-banner-description">${config.texts.description.de}</div>
<div class="cookie-banner-buttons">
<button class="cookie-banner-btn cookie-banner-btn-secondary" id="cookieBannerReject">
${config.texts.rejectAll.de}
</button>
<button class="cookie-banner-btn cookie-banner-btn-secondary" id="cookieBannerCustomize">
${config.texts.customize.de}
</button>
<button class="cookie-banner-btn cookie-banner-btn-primary" id="cookieBannerAccept">
${config.texts.acceptAll.de}
</button>
</div>
<div class="cookie-banner-details" id="cookieBannerDetails">
${categoriesHTML}
<div class="cookie-banner-buttons" style="margin-top: 16px;">
<button class="cookie-banner-btn cookie-banner-btn-primary" id="cookieBannerSave">
${config.texts.save.de}
</button>
</div>
</div>
<a href="${privacyPolicyUrl}" class="cookie-banner-link" target="_blank">
${config.texts.privacyPolicyLink.de}
</a>
</div>
`.trim()
}
/**
* Generiert das JavaScript fuer den Cookie Banner
*/
function generateJS(config: CookieBannerConfig): string {
const categoryIds = config.categories.map((c) => c.id)
const requiredCategories = config.categories.filter((c) => c.isRequired).map((c) => c.id)
return `
(function() {
'use strict';
const COOKIE_NAME = 'cookie_consent';
const COOKIE_EXPIRY_DAYS = 365;
const CATEGORIES = ${JSON.stringify(categoryIds)};
const REQUIRED_CATEGORIES = ${JSON.stringify(requiredCategories)};
// Get consent from cookie
function getConsent() {
const cookie = document.cookie.split('; ').find(row => row.startsWith(COOKIE_NAME + '='));
if (!cookie) return null;
try {
return JSON.parse(decodeURIComponent(cookie.split('=')[1]));
} catch {
return null;
}
}
// Save consent to cookie
function saveConsent(consent) {
const date = new Date();
date.setTime(date.getTime() + (COOKIE_EXPIRY_DAYS * 24 * 60 * 60 * 1000));
document.cookie = COOKIE_NAME + '=' + encodeURIComponent(JSON.stringify(consent)) +
';expires=' + date.toUTCString() +
';path=/;SameSite=Lax';
// Dispatch event
window.dispatchEvent(new CustomEvent('cookieConsentUpdated', { detail: consent }));
}
// Check if category is consented
function hasConsent(category) {
const consent = getConsent();
if (!consent) return REQUIRED_CATEGORIES.includes(category);
return consent[category] === true;
}
// Initialize banner
function initBanner() {
const banner = document.getElementById('cookieBanner');
const overlay = document.getElementById('cookieBannerOverlay');
const details = document.getElementById('cookieBannerDetails');
if (!banner) return;
const consent = getConsent();
if (consent) {
// User has already consented
return;
}
// Show banner
setTimeout(() => {
banner.classList.add('active');
overlay.classList.add('active');
}, 500);
// Accept all
document.getElementById('cookieBannerAccept')?.addEventListener('click', () => {
const consent = {};
CATEGORIES.forEach(cat => consent[cat] = true);
saveConsent(consent);
closeBanner();
});
// Reject all (only essential)
document.getElementById('cookieBannerReject')?.addEventListener('click', () => {
const consent = {};
CATEGORIES.forEach(cat => consent[cat] = REQUIRED_CATEGORIES.includes(cat));
saveConsent(consent);
closeBanner();
});
// Customize
document.getElementById('cookieBannerCustomize')?.addEventListener('click', () => {
details.classList.toggle('active');
});
// Save selection
document.getElementById('cookieBannerSave')?.addEventListener('click', () => {
const consent = {};
CATEGORIES.forEach(cat => {
const toggle = document.querySelector('.cookie-banner-toggle[data-category="' + cat + '"]');
consent[cat] = toggle?.classList.contains('active') || REQUIRED_CATEGORIES.includes(cat);
});
saveConsent(consent);
closeBanner();
});
// Toggle handlers
document.querySelectorAll('.cookie-banner-toggle').forEach(toggle => {
if (toggle.dataset.required === 'true') return;
toggle.addEventListener('click', () => {
toggle.classList.toggle('active');
});
});
// Close on overlay click
overlay?.addEventListener('click', () => {
// Don't close - user must make a choice
});
}
function closeBanner() {
const banner = document.getElementById('cookieBanner');
const overlay = document.getElementById('cookieBannerOverlay');
banner?.classList.remove('active');
overlay?.classList.remove('active');
}
// Expose API
window.CookieConsent = {
getConsent,
saveConsent,
hasConsent,
show: () => {
document.getElementById('cookieBanner')?.classList.add('active');
document.getElementById('cookieBannerOverlay')?.classList.add('active');
},
hide: closeBanner
};
// Initialize on DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initBanner);
} else {
initBanner();
}
})();
`.trim()
}
// Note: All exports are defined inline with 'export const' and 'export function'

View File

@@ -0,0 +1,965 @@
/**
* Privacy Policy Generator
*
* Generiert Datenschutzerklaerungen (DSI) aus dem Datenpunktkatalog.
* Die DSI wird aus 9 Abschnitten generiert:
*
* 1. Verantwortlicher (companyInfo)
* 2. Erhobene Daten (dataPoints nach Kategorie)
* 3. Verarbeitungszwecke (dataPoints.purpose)
* 4. Rechtsgrundlagen (dataPoints.legalBasis)
* 5. Empfaenger/Dritte (dataPoints.thirdPartyRecipients)
* 6. Speicherdauer (retentionMatrix)
* 7. Betroffenenrechte (statischer Text + Links)
* 8. Cookies (cookieCategory-basiert)
* 9. Aenderungen (statischer Text + Versionierung)
*/
import {
DataPoint,
DataPointCategory,
CompanyInfo,
PrivacyPolicySection,
GeneratedPrivacyPolicy,
SupportedLanguage,
ExportFormat,
LocalizedText,
RetentionMatrixEntry,
LegalBasis,
CATEGORY_METADATA,
LEGAL_BASIS_INFO,
RETENTION_PERIOD_INFO,
ARTICLE_9_WARNING,
} from '../types'
import { RETENTION_MATRIX } from '../catalog/loader'
// =============================================================================
// KONSTANTEN - 18 Kategorien in der richtigen Reihenfolge
// =============================================================================
const ALL_CATEGORIES: DataPointCategory[] = [
'MASTER_DATA', // A
'CONTACT_DATA', // B
'AUTHENTICATION', // C
'CONSENT', // D
'COMMUNICATION', // E
'PAYMENT', // F
'USAGE_DATA', // G
'LOCATION', // H
'DEVICE_DATA', // I
'MARKETING', // J
'ANALYTICS', // K
'SOCIAL_MEDIA', // L
'HEALTH_DATA', // M - Art. 9 DSGVO
'EMPLOYEE_DATA', // N - BDSG § 26
'CONTRACT_DATA', // O
'LOG_DATA', // P
'AI_DATA', // Q - AI Act
'SECURITY', // R
]
// Alle Rechtsgrundlagen in der richtigen Reihenfolge
const ALL_LEGAL_BASES: LegalBasis[] = [
'CONTRACT',
'CONSENT',
'EXPLICIT_CONSENT',
'LEGITIMATE_INTEREST',
'LEGAL_OBLIGATION',
'VITAL_INTERESTS',
'PUBLIC_INTEREST',
]
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
/**
* Holt den lokalisierten Text
*/
function t(text: LocalizedText, language: SupportedLanguage): string {
return text[language]
}
/**
* Gruppiert Datenpunkte nach Kategorie
*/
function groupByCategory(dataPoints: DataPoint[]): Map<DataPointCategory, DataPoint[]> {
const grouped = new Map<DataPointCategory, DataPoint[]>()
for (const dp of dataPoints) {
const existing = grouped.get(dp.category) || []
grouped.set(dp.category, [...existing, dp])
}
return grouped
}
/**
* Gruppiert Datenpunkte nach Rechtsgrundlage
*/
function groupByLegalBasis(dataPoints: DataPoint[]): Map<LegalBasis, DataPoint[]> {
const grouped = new Map<LegalBasis, DataPoint[]>()
for (const dp of dataPoints) {
const existing = grouped.get(dp.legalBasis) || []
grouped.set(dp.legalBasis, [...existing, dp])
}
return grouped
}
/**
* Extrahiert alle einzigartigen Drittanbieter
*/
function extractThirdParties(dataPoints: DataPoint[]): string[] {
const thirdParties = new Set<string>()
for (const dp of dataPoints) {
for (const recipient of dp.thirdPartyRecipients) {
thirdParties.add(recipient)
}
}
return Array.from(thirdParties).sort()
}
/**
* Formatiert ein Datum fuer die Anzeige
*/
function formatDate(date: Date, language: SupportedLanguage): string {
return date.toLocaleDateString(language === 'de' ? 'de-DE' : 'en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})
}
// =============================================================================
// SECTION GENERATORS
// =============================================================================
/**
* Abschnitt 1: Verantwortlicher
*/
function generateControllerSection(
companyInfo: CompanyInfo,
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '1. Verantwortlicher',
en: '1. Data Controller',
}
const dpoSection = companyInfo.dpoName
? language === 'de'
? `\n\n**Datenschutzbeauftragter:**\n${companyInfo.dpoName}${companyInfo.dpoEmail ? `\nE-Mail: ${companyInfo.dpoEmail}` : ''}${companyInfo.dpoPhone ? `\nTelefon: ${companyInfo.dpoPhone}` : ''}`
: `\n\n**Data Protection Officer:**\n${companyInfo.dpoName}${companyInfo.dpoEmail ? `\nEmail: ${companyInfo.dpoEmail}` : ''}${companyInfo.dpoPhone ? `\nPhone: ${companyInfo.dpoPhone}` : ''}`
: ''
const content: LocalizedText = {
de: `Verantwortlich fuer die Datenverarbeitung auf dieser Website ist:
**${companyInfo.name}**
${companyInfo.address}
${companyInfo.postalCode} ${companyInfo.city}
${companyInfo.country}
E-Mail: ${companyInfo.email}${companyInfo.phone ? `\nTelefon: ${companyInfo.phone}` : ''}${companyInfo.website ? `\nWebsite: ${companyInfo.website}` : ''}${companyInfo.registrationNumber ? `\n\nHandelsregister: ${companyInfo.registrationNumber}` : ''}${companyInfo.vatId ? `\nUSt-IdNr.: ${companyInfo.vatId}` : ''}${dpoSection}`,
en: `The controller responsible for data processing on this website is:
**${companyInfo.name}**
${companyInfo.address}
${companyInfo.postalCode} ${companyInfo.city}
${companyInfo.country}
Email: ${companyInfo.email}${companyInfo.phone ? `\nPhone: ${companyInfo.phone}` : ''}${companyInfo.website ? `\nWebsite: ${companyInfo.website}` : ''}${companyInfo.registrationNumber ? `\n\nCommercial Register: ${companyInfo.registrationNumber}` : ''}${companyInfo.vatId ? `\nVAT ID: ${companyInfo.vatId}` : ''}${dpoSection}`,
}
return {
id: 'controller',
order: 1,
title,
content,
dataPointIds: [],
isRequired: true,
isGenerated: false,
}
}
/**
* Abschnitt 2: Erhobene Daten (18 Kategorien)
*/
function generateDataCollectionSection(
dataPoints: DataPoint[],
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '2. Erhobene personenbezogene Daten',
en: '2. Personal Data We Collect',
}
const grouped = groupByCategory(dataPoints)
const sections: string[] = []
// Prüfe ob Art. 9 Daten enthalten sind
const hasSpecialCategoryData = dataPoints.some(dp => dp.isSpecialCategory || dp.category === 'HEALTH_DATA')
for (const category of ALL_CATEGORIES) {
const categoryData = grouped.get(category)
if (!categoryData || categoryData.length === 0) continue
const categoryMeta = CATEGORY_METADATA[category]
if (!categoryMeta) continue
const categoryTitle = t(categoryMeta.name, language)
// Spezielle Warnung für Art. 9 DSGVO Daten (Gesundheitsdaten)
let categoryNote = ''
if (category === 'HEALTH_DATA') {
categoryNote = language === 'de'
? `\n\n> **Hinweis:** Diese Daten gehoeren zu den besonderen Kategorien personenbezogener Daten gemaess Art. 9 DSGVO und erfordern eine ausdrueckliche Einwilligung.`
: `\n\n> **Note:** This data belongs to special categories of personal data under Art. 9 GDPR and requires explicit consent.`
} else if (category === 'EMPLOYEE_DATA') {
categoryNote = language === 'de'
? `\n\n> **Hinweis:** Die Verarbeitung von Beschaeftigtendaten erfolgt gemaess § 26 BDSG.`
: `\n\n> **Note:** Processing of employee data is carried out in accordance with § 26 BDSG (German Federal Data Protection Act).`
} else if (category === 'AI_DATA') {
categoryNote = language === 'de'
? `\n\n> **Hinweis:** Die Verarbeitung von KI-bezogenen Daten unterliegt den Transparenzpflichten des AI Acts.`
: `\n\n> **Note:** Processing of AI-related data is subject to AI Act transparency requirements.`
}
const dataList = categoryData
.map((dp) => {
const specialTag = dp.isSpecialCategory
? (language === 'de' ? ' *(Art. 9 DSGVO)*' : ' *(Art. 9 GDPR)*')
: ''
return `- **${t(dp.name, language)}**${specialTag}: ${t(dp.description, language)}`
})
.join('\n')
sections.push(`### ${categoryMeta.code}. ${categoryTitle}\n\n${dataList}${categoryNote}`)
}
const intro: LocalizedText = {
de: 'Wir erheben und verarbeiten die folgenden personenbezogenen Daten:',
en: 'We collect and process the following personal data:',
}
// Zusätzlicher Hinweis für Art. 9 Daten
const specialCategoryNote: LocalizedText = hasSpecialCategoryData
? {
de: '\n\n**Wichtig:** Einige der unten aufgefuehrten Daten gehoeren zu den besonderen Kategorien personenbezogener Daten nach Art. 9 DSGVO und werden nur mit Ihrer ausdruecklichen Einwilligung verarbeitet.',
en: '\n\n**Important:** Some of the data listed below belongs to special categories of personal data under Art. 9 GDPR and is only processed with your explicit consent.',
}
: { de: '', en: '' }
const content: LocalizedText = {
de: `${intro.de}${specialCategoryNote.de}\n\n${sections.join('\n\n')}`,
en: `${intro.en}${specialCategoryNote.en}\n\n${sections.join('\n\n')}`,
}
return {
id: 'data-collection',
order: 2,
title,
content,
dataPointIds: dataPoints.map((dp) => dp.id),
isRequired: true,
isGenerated: true,
}
}
/**
* Abschnitt 3: Verarbeitungszwecke
*/
function generatePurposesSection(
dataPoints: DataPoint[],
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '3. Zwecke der Datenverarbeitung',
en: '3. Purposes of Data Processing',
}
// Gruppiere nach Zweck (unique purposes)
const purposes = new Map<string, DataPoint[]>()
for (const dp of dataPoints) {
const purpose = t(dp.purpose, language)
const existing = purposes.get(purpose) || []
purposes.set(purpose, [...existing, dp])
}
const purposeList = Array.from(purposes.entries())
.map(([purpose, dps]) => {
const dataNames = dps.map((dp) => t(dp.name, language)).join(', ')
return `- **${purpose}**\n Betroffene Daten: ${dataNames}`
})
.join('\n\n')
const content: LocalizedText = {
de: `Wir verarbeiten Ihre personenbezogenen Daten fuer folgende Zwecke:\n\n${purposeList}`,
en: `We process your personal data for the following purposes:\n\n${purposeList}`,
}
return {
id: 'purposes',
order: 3,
title,
content,
dataPointIds: dataPoints.map((dp) => dp.id),
isRequired: true,
isGenerated: true,
}
}
/**
* Abschnitt 4: Rechtsgrundlagen (alle 7 Rechtsgrundlagen)
*/
function generateLegalBasisSection(
dataPoints: DataPoint[],
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '4. Rechtsgrundlagen der Verarbeitung',
en: '4. Legal Basis for Processing',
}
const grouped = groupByLegalBasis(dataPoints)
const sections: string[] = []
// Alle 7 Rechtsgrundlagen in der richtigen Reihenfolge
for (const basis of ALL_LEGAL_BASES) {
const basisData = grouped.get(basis)
if (!basisData || basisData.length === 0) continue
const basisInfo = LEGAL_BASIS_INFO[basis]
if (!basisInfo) continue
const basisTitle = `${t(basisInfo.name, language)} (${basisInfo.article})`
const basisDesc = t(basisInfo.description, language)
// Für Art. 9 Daten (EXPLICIT_CONSENT) zusätzliche Warnung hinzufügen
let additionalWarning = ''
if (basis === 'EXPLICIT_CONSENT') {
additionalWarning = language === 'de'
? `\n\n> **Wichtig:** Fuer die Verarbeitung dieser besonderen Kategorien personenbezogener Daten (Art. 9 DSGVO) ist eine separate, ausdrueckliche Einwilligung erforderlich, die Sie jederzeit widerrufen koennen.`
: `\n\n> **Important:** Processing of these special categories of personal data (Art. 9 GDPR) requires a separate, explicit consent that you can withdraw at any time.`
}
const dataLabel = language === 'de' ? 'Betroffene Daten' : 'Affected Data'
const dataList = basisData
.map((dp) => {
const specialTag = dp.isSpecialCategory
? (language === 'de' ? ' *(Art. 9 DSGVO)*' : ' *(Art. 9 GDPR)*')
: ''
return `- ${t(dp.name, language)}${specialTag}: ${t(dp.legalBasisJustification, language)}`
})
.join('\n')
sections.push(`### ${basisTitle}\n\n${basisDesc}${additionalWarning}\n\n**${dataLabel}:**\n${dataList}`)
}
const content: LocalizedText = {
de: `Die Verarbeitung Ihrer personenbezogenen Daten erfolgt auf Grundlage folgender Rechtsgrundlagen:\n\n${sections.join('\n\n')}`,
en: `The processing of your personal data is based on the following legal grounds:\n\n${sections.join('\n\n')}`,
}
return {
id: 'legal-basis',
order: 4,
title,
content,
dataPointIds: dataPoints.map((dp) => dp.id),
isRequired: true,
isGenerated: true,
}
}
/**
* Abschnitt 5: Empfaenger / Dritte
*/
function generateRecipientsSection(
dataPoints: DataPoint[],
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '5. Empfaenger und Datenweitergabe',
en: '5. Recipients and Data Sharing',
}
const thirdParties = extractThirdParties(dataPoints)
if (thirdParties.length === 0) {
const content: LocalizedText = {
de: 'Wir geben Ihre personenbezogenen Daten grundsaetzlich nicht an Dritte weiter, es sei denn, dies ist zur Vertragserfuellung erforderlich oder Sie haben ausdruecklich eingewilligt.',
en: 'We generally do not share your personal data with third parties unless this is necessary for contract performance or you have expressly consented.',
}
return {
id: 'recipients',
order: 5,
title,
content,
dataPointIds: [],
isRequired: true,
isGenerated: false,
}
}
// Gruppiere nach Drittanbieter
const recipientDetails = new Map<string, DataPoint[]>()
for (const dp of dataPoints) {
for (const recipient of dp.thirdPartyRecipients) {
const existing = recipientDetails.get(recipient) || []
recipientDetails.set(recipient, [...existing, dp])
}
}
const recipientList = Array.from(recipientDetails.entries())
.map(([recipient, dps]) => {
const dataNames = dps.map((dp) => t(dp.name, language)).join(', ')
return `- **${recipient}**: ${dataNames}`
})
.join('\n')
const content: LocalizedText = {
de: `Wir uebermitteln Ihre personenbezogenen Daten an folgende Empfaenger bzw. Kategorien von Empfaengern:\n\n${recipientList}\n\nMit allen Auftragsverarbeitern haben wir Auftragsverarbeitungsvertraege nach Art. 28 DSGVO abgeschlossen.`,
en: `We share your personal data with the following recipients or categories of recipients:\n\n${recipientList}\n\nWe have concluded data processing agreements pursuant to Art. 28 GDPR with all processors.`,
}
return {
id: 'recipients',
order: 5,
title,
content,
dataPointIds: dataPoints.filter((dp) => dp.thirdPartyRecipients.length > 0).map((dp) => dp.id),
isRequired: true,
isGenerated: true,
}
}
/**
* Abschnitt 6: Speicherdauer
*/
function generateRetentionSection(
dataPoints: DataPoint[],
retentionMatrix: RetentionMatrixEntry[],
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '6. Speicherdauer',
en: '6. Data Retention',
}
const grouped = groupByCategory(dataPoints)
const sections: string[] = []
for (const entry of retentionMatrix) {
const categoryData = grouped.get(entry.category)
if (!categoryData || categoryData.length === 0) continue
const categoryName = t(entry.categoryName, language)
const standardPeriod = t(RETENTION_PERIOD_INFO[entry.standardPeriod].label, language)
const dataRetention = categoryData
.map((dp) => {
const period = t(RETENTION_PERIOD_INFO[dp.retentionPeriod].label, language)
return `- ${t(dp.name, language)}: ${period}`
})
.join('\n')
sections.push(`### ${categoryName}\n\n**Standardfrist:** ${standardPeriod}\n\n${dataRetention}`)
}
const content: LocalizedText = {
de: `Wir speichern Ihre personenbezogenen Daten nur so lange, wie dies fuer die jeweiligen Zwecke erforderlich ist oder gesetzliche Aufbewahrungsfristen bestehen.\n\n${sections.join('\n\n')}`,
en: `We store your personal data only for as long as is necessary for the respective purposes or as required by statutory retention periods.\n\n${sections.join('\n\n')}`,
}
return {
id: 'retention',
order: 6,
title,
content,
dataPointIds: dataPoints.map((dp) => dp.id),
isRequired: true,
isGenerated: true,
}
}
/**
* Abschnitt 6a: Besondere Kategorien (Art. 9 DSGVO)
* Wird nur generiert, wenn Art. 9 Daten vorhanden sind
*/
function generateSpecialCategoriesSection(
dataPoints: DataPoint[],
language: SupportedLanguage
): PrivacyPolicySection | null {
// Filtere Art. 9 Datenpunkte
const specialCategoryDataPoints = dataPoints.filter(dp => dp.isSpecialCategory || dp.category === 'HEALTH_DATA')
if (specialCategoryDataPoints.length === 0) {
return null
}
const title: LocalizedText = {
de: '6a. Besondere Kategorien personenbezogener Daten (Art. 9 DSGVO)',
en: '6a. Special Categories of Personal Data (Art. 9 GDPR)',
}
const dataList = specialCategoryDataPoints
.map((dp) => `- **${t(dp.name, language)}**: ${t(dp.description, language)}`)
.join('\n')
const content: LocalizedText = {
de: `Gemaess Art. 9 DSGVO verarbeiten wir auch besondere Kategorien personenbezogener Daten, die einen erhoehten Schutz geniessen. Diese Daten umfassen:
${dataList}
### Ihre ausdrueckliche Einwilligung
Die Verarbeitung dieser besonderen Kategorien personenbezogener Daten erfolgt nur auf Grundlage Ihrer **ausdruecklichen Einwilligung** gemaess Art. 9 Abs. 2 lit. a DSGVO.
### Ihre Rechte bei Art. 9 Daten
- Sie koennen Ihre Einwilligung **jederzeit widerrufen**
- Der Widerruf beruehrt nicht die Rechtmaessigkeit der bisherigen Verarbeitung
- Bei Widerruf werden Ihre Daten unverzueglich geloescht
- Sie haben das Recht auf **Auskunft, Berichtigung und Loeschung**
### Besondere Schutzmassnahmen
Fuer diese sensiblen Daten haben wir besondere technische und organisatorische Massnahmen implementiert:
- Ende-zu-Ende-Verschluesselung
- Strenge Zugriffskontrolle (Need-to-Know-Prinzip)
- Audit-Logging aller Zugriffe
- Regelmaessige Datenschutz-Folgenabschaetzungen`,
en: `In accordance with Art. 9 GDPR, we also process special categories of personal data that enjoy enhanced protection. This data includes:
${dataList}
### Your Explicit Consent
Processing of these special categories of personal data only takes place on the basis of your **explicit consent** pursuant to Art. 9(2)(a) GDPR.
### Your Rights Regarding Art. 9 Data
- You can **withdraw your consent at any time**
- Withdrawal does not affect the lawfulness of previous processing
- Upon withdrawal, your data will be deleted immediately
- You have the right to **access, rectification, and erasure**
### Special Protection Measures
For this sensitive data, we have implemented special technical and organizational measures:
- End-to-end encryption
- Strict access control (need-to-know principle)
- Audit logging of all access
- Regular data protection impact assessments`,
}
return {
id: 'special-categories',
order: 6.5, // Zwischen Speicherdauer (6) und Rechte (7)
title,
content,
dataPointIds: specialCategoryDataPoints.map((dp) => dp.id),
isRequired: false,
isGenerated: true,
}
}
/**
* Abschnitt 7: Betroffenenrechte
*/
function generateRightsSection(language: SupportedLanguage): PrivacyPolicySection {
const title: LocalizedText = {
de: '7. Ihre Rechte als betroffene Person',
en: '7. Your Rights as a Data Subject',
}
const content: LocalizedText = {
de: `Sie haben gegenueber uns folgende Rechte hinsichtlich der Sie betreffenden personenbezogenen Daten:
### Auskunftsrecht (Art. 15 DSGVO)
Sie haben das Recht, Auskunft ueber die von uns verarbeiteten personenbezogenen Daten zu verlangen.
### Recht auf Berichtigung (Art. 16 DSGVO)
Sie haben das Recht, die Berichtigung unrichtiger oder die Vervollstaendigung unvollstaendiger Daten zu verlangen.
### Recht auf Loeschung (Art. 17 DSGVO)
Sie haben das Recht, die Loeschung Ihrer personenbezogenen Daten zu verlangen, sofern keine gesetzlichen Aufbewahrungspflichten entgegenstehen.
### Recht auf Einschraenkung der Verarbeitung (Art. 18 DSGVO)
Sie haben das Recht, die Einschraenkung der Verarbeitung Ihrer Daten zu verlangen.
### Recht auf Datenuebertragbarkeit (Art. 20 DSGVO)
Sie haben das Recht, Ihre Daten in einem strukturierten, gaengigen und maschinenlesbaren Format zu erhalten.
### Widerspruchsrecht (Art. 21 DSGVO)
Sie haben das Recht, der Verarbeitung Ihrer Daten jederzeit zu widersprechen, soweit die Verarbeitung auf berechtigtem Interesse beruht.
### Recht auf Widerruf der Einwilligung (Art. 7 Abs. 3 DSGVO)
Sie haben das Recht, Ihre erteilte Einwilligung jederzeit zu widerrufen. Die Rechtmaessigkeit der aufgrund der Einwilligung bis zum Widerruf erfolgten Verarbeitung wird dadurch nicht beruehrt.
### Beschwerderecht bei der Aufsichtsbehoerde (Art. 77 DSGVO)
Sie haben das Recht, sich bei einer Datenschutz-Aufsichtsbehoerde ueber die Verarbeitung Ihrer personenbezogenen Daten zu beschweren.
**Zur Ausuebung Ihrer Rechte wenden Sie sich bitte an die oben angegebenen Kontaktdaten.**`,
en: `You have the following rights regarding your personal data:
### Right of Access (Art. 15 GDPR)
You have the right to request information about the personal data we process about you.
### Right to Rectification (Art. 16 GDPR)
You have the right to request the correction of inaccurate data or the completion of incomplete data.
### Right to Erasure (Art. 17 GDPR)
You have the right to request the deletion of your personal data, unless statutory retention obligations apply.
### Right to Restriction of Processing (Art. 18 GDPR)
You have the right to request the restriction of processing of your data.
### Right to Data Portability (Art. 20 GDPR)
You have the right to receive your data in a structured, commonly used, and machine-readable format.
### Right to Object (Art. 21 GDPR)
You have the right to object to the processing of your data at any time, insofar as the processing is based on legitimate interest.
### Right to Withdraw Consent (Art. 7(3) GDPR)
You have the right to withdraw your consent at any time. The lawfulness of processing based on consent before its withdrawal is not affected.
### Right to Lodge a Complaint with a Supervisory Authority (Art. 77 GDPR)
You have the right to lodge a complaint with a data protection supervisory authority about the processing of your personal data.
**To exercise your rights, please contact us using the contact details provided above.**`,
}
return {
id: 'rights',
order: 7,
title,
content,
dataPointIds: [],
isRequired: true,
isGenerated: false,
}
}
/**
* Abschnitt 8: Cookies
*/
function generateCookiesSection(
dataPoints: DataPoint[],
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '8. Cookies und aehnliche Technologien',
en: '8. Cookies and Similar Technologies',
}
// Filtere Datenpunkte mit Cookie-Kategorie
const cookieDataPoints = dataPoints.filter((dp) => dp.cookieCategory !== null)
if (cookieDataPoints.length === 0) {
const content: LocalizedText = {
de: 'Wir verwenden auf dieser Website keine Cookies.',
en: 'We do not use cookies on this website.',
}
return {
id: 'cookies',
order: 8,
title,
content,
dataPointIds: [],
isRequired: false,
isGenerated: false,
}
}
// Gruppiere nach Cookie-Kategorie
const essential = cookieDataPoints.filter((dp) => dp.cookieCategory === 'ESSENTIAL')
const performance = cookieDataPoints.filter((dp) => dp.cookieCategory === 'PERFORMANCE')
const personalization = cookieDataPoints.filter((dp) => dp.cookieCategory === 'PERSONALIZATION')
const externalMedia = cookieDataPoints.filter((dp) => dp.cookieCategory === 'EXTERNAL_MEDIA')
const sections: string[] = []
if (essential.length > 0) {
const list = essential.map((dp) => `- **${t(dp.name, language)}**: ${t(dp.purpose, language)}`).join('\n')
sections.push(
language === 'de'
? `### Technisch notwendige Cookies\n\nDiese Cookies sind fuer den Betrieb der Website erforderlich und koennen nicht deaktiviert werden.\n\n${list}`
: `### Essential Cookies\n\nThese cookies are required for the website to function and cannot be disabled.\n\n${list}`
)
}
if (performance.length > 0) {
const list = performance.map((dp) => `- **${t(dp.name, language)}**: ${t(dp.purpose, language)}`).join('\n')
sections.push(
language === 'de'
? `### Analyse- und Performance-Cookies\n\nDiese Cookies helfen uns, die Nutzung der Website zu verstehen und zu verbessern.\n\n${list}`
: `### Analytics and Performance Cookies\n\nThese cookies help us understand and improve website usage.\n\n${list}`
)
}
if (personalization.length > 0) {
const list = personalization.map((dp) => `- **${t(dp.name, language)}**: ${t(dp.purpose, language)}`).join('\n')
sections.push(
language === 'de'
? `### Personalisierungs-Cookies\n\nDiese Cookies ermoeglichen personalisierte Werbung und Inhalte.\n\n${list}`
: `### Personalization Cookies\n\nThese cookies enable personalized advertising and content.\n\n${list}`
)
}
if (externalMedia.length > 0) {
const list = externalMedia.map((dp) => `- **${t(dp.name, language)}**: ${t(dp.purpose, language)}`).join('\n')
sections.push(
language === 'de'
? `### Cookies fuer externe Medien\n\nDiese Cookies erlauben die Einbindung externer Medien wie Videos und Karten.\n\n${list}`
: `### External Media Cookies\n\nThese cookies allow embedding external media like videos and maps.\n\n${list}`
)
}
const intro: LocalizedText = {
de: `Wir verwenden Cookies und aehnliche Technologien, um Ihnen die bestmoegliche Nutzung unserer Website zu ermoeglichen. Sie koennen Ihre Cookie-Einstellungen jederzeit ueber unseren Cookie-Banner anpassen.`,
en: `We use cookies and similar technologies to provide you with the best possible experience on our website. You can adjust your cookie settings at any time through our cookie banner.`,
}
const content: LocalizedText = {
de: `${intro.de}\n\n${sections.join('\n\n')}`,
en: `${intro.en}\n\n${sections.join('\n\n')}`,
}
return {
id: 'cookies',
order: 8,
title,
content,
dataPointIds: cookieDataPoints.map((dp) => dp.id),
isRequired: true,
isGenerated: true,
}
}
/**
* Abschnitt 9: Aenderungen
*/
function generateChangesSection(
version: string,
date: Date,
language: SupportedLanguage
): PrivacyPolicySection {
const title: LocalizedText = {
de: '9. Aenderungen dieser Datenschutzerklaerung',
en: '9. Changes to this Privacy Policy',
}
const formattedDate = formatDate(date, language)
const content: LocalizedText = {
de: `Diese Datenschutzerklaerung ist aktuell gueltig und hat den Stand: **${formattedDate}** (Version ${version}).
Wir behalten uns vor, diese Datenschutzerklaerung anzupassen, damit sie stets den aktuellen rechtlichen Anforderungen entspricht oder um Aenderungen unserer Leistungen umzusetzen.
Fuer Ihren erneuten Besuch gilt dann die neue Datenschutzerklaerung.`,
en: `This privacy policy is currently valid and was last updated: **${formattedDate}** (Version ${version}).
We reserve the right to amend this privacy policy to ensure it always complies with current legal requirements or to implement changes to our services.
The new privacy policy will then apply for your next visit.`,
}
return {
id: 'changes',
order: 9,
title,
content,
dataPointIds: [],
isRequired: true,
isGenerated: false,
}
}
// =============================================================================
// MAIN GENERATOR FUNCTIONS
// =============================================================================
/**
* Generiert alle Abschnitte der Privacy Policy (18 Kategorien + Art. 9)
*/
export function generatePrivacyPolicySections(
dataPoints: DataPoint[],
companyInfo: CompanyInfo,
language: SupportedLanguage,
version: string = '1.0.0'
): PrivacyPolicySection[] {
const now = new Date()
const sections: PrivacyPolicySection[] = [
generateControllerSection(companyInfo, language),
generateDataCollectionSection(dataPoints, language),
generatePurposesSection(dataPoints, language),
generateLegalBasisSection(dataPoints, language),
generateRecipientsSection(dataPoints, language),
generateRetentionSection(dataPoints, RETENTION_MATRIX, language),
]
// Art. 9 DSGVO Abschnitt nur einfügen, wenn besondere Kategorien vorhanden
const specialCategoriesSection = generateSpecialCategoriesSection(dataPoints, language)
if (specialCategoriesSection) {
sections.push(specialCategoriesSection)
}
sections.push(
generateRightsSection(language),
generateCookiesSection(dataPoints, language),
generateChangesSection(version, now, language)
)
// Abschnittsnummern neu vergeben
sections.forEach((section, index) => {
section.order = index + 1
// Titel-Nummer aktualisieren
const titleDe = section.title.de
const titleEn = section.title.en
if (titleDe.match(/^\d+[a-z]?\./)) {
section.title.de = titleDe.replace(/^\d+[a-z]?\./, `${index + 1}.`)
}
if (titleEn.match(/^\d+[a-z]?\./)) {
section.title.en = titleEn.replace(/^\d+[a-z]?\./, `${index + 1}.`)
}
})
return sections
}
/**
* Generiert die vollstaendige Privacy Policy
*/
export function generatePrivacyPolicy(
tenantId: string,
dataPoints: DataPoint[],
companyInfo: CompanyInfo,
language: SupportedLanguage,
format: ExportFormat = 'HTML'
): GeneratedPrivacyPolicy {
const version = '1.0.0'
const sections = generatePrivacyPolicySections(dataPoints, companyInfo, language, version)
// Generiere den Inhalt
const content = renderPrivacyPolicy(sections, language, format)
return {
id: `privacy-policy-${tenantId}-${Date.now()}`,
tenantId,
language,
sections,
companyInfo,
generatedAt: new Date(),
version,
format,
content,
}
}
/**
* Rendert die Privacy Policy im gewuenschten Format
*/
function renderPrivacyPolicy(
sections: PrivacyPolicySection[],
language: SupportedLanguage,
format: ExportFormat
): string {
switch (format) {
case 'HTML':
return renderAsHTML(sections, language)
case 'MARKDOWN':
return renderAsMarkdown(sections, language)
default:
return renderAsMarkdown(sections, language)
}
}
/**
* Rendert als HTML
*/
function renderAsHTML(sections: PrivacyPolicySection[], language: SupportedLanguage): string {
const title = language === 'de' ? 'Datenschutzerklaerung' : 'Privacy Policy'
const sectionsHTML = sections
.map((section) => {
const content = t(section.content, language)
.replace(/\n\n/g, '</p><p>')
.replace(/\n/g, '<br>')
.replace(/### (.+)/g, '<h3>$1</h3>')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/- (.+)(?:<br>|$)/g, '<li>$1</li>')
return `
<section id="${section.id}">
<h2>${t(section.title, language)}</h2>
<p>${content}</p>
</section>
`
})
.join('\n')
return `<!DOCTYPE html>
<html lang="${language}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
color: #333;
}
h1 { font-size: 2rem; margin-bottom: 2rem; }
h2 { font-size: 1.5rem; margin-top: 2rem; color: #1a1a1a; }
h3 { font-size: 1.25rem; margin-top: 1.5rem; color: #333; }
p { margin: 1rem 0; }
ul, ol { margin: 1rem 0; padding-left: 2rem; }
li { margin: 0.5rem 0; }
strong { font-weight: 600; }
</style>
</head>
<body>
<h1>${title}</h1>
${sectionsHTML}
</body>
</html>`
}
/**
* Rendert als Markdown
*/
function renderAsMarkdown(sections: PrivacyPolicySection[], language: SupportedLanguage): string {
const title = language === 'de' ? 'Datenschutzerklaerung' : 'Privacy Policy'
const sectionsMarkdown = sections
.map((section) => {
return `## ${t(section.title, language)}\n\n${t(section.content, language)}`
})
.join('\n\n---\n\n')
return `# ${title}\n\n${sectionsMarkdown}`
}
// =============================================================================
// EXPORTS
// =============================================================================
export {
generateControllerSection,
generateDataCollectionSection,
generatePurposesSection,
generateLegalBasisSection,
generateRecipientsSection,
generateRetentionSection,
generateSpecialCategoriesSection,
generateRightsSection,
generateCookiesSection,
generateChangesSection,
renderAsHTML,
renderAsMarkdown,
}

View File

@@ -0,0 +1,79 @@
/**
* Datenpunktkatalog & Datenschutzinformationen-Generator
*
* Dieses Modul erweitert das SDK Einwilligungen-Modul um:
* - Datenpunktkatalog mit 28 vordefinierten + kundenspezifischen Datenpunkten
* - Automatische Privacy Policy Generierung
* - Cookie Banner Konfiguration
* - Retention Matrix Visualisierung
*
* @module lib/sdk/einwilligungen
*/
// =============================================================================
// TYPES
// =============================================================================
export * from './types'
// =============================================================================
// CATALOG
// =============================================================================
export {
PREDEFINED_DATA_POINTS,
RETENTION_MATRIX,
DEFAULT_COOKIE_CATEGORIES,
getDataPointById,
getDataPointByCode,
getDataPointsByCategory,
getDataPointsByLegalBasis,
getDataPointsByCookieCategory,
getDataPointsRequiringConsent,
getHighRiskDataPoints,
countDataPointsByCategory,
countDataPointsByRiskLevel,
createDefaultCatalog,
searchDataPoints,
} from './catalog/loader'
// =============================================================================
// CONTEXT
// =============================================================================
export {
EinwilligungenProvider,
useEinwilligungen,
initialState as einwilligungenInitialState,
einwilligungenReducer,
} from './context'
// =============================================================================
// GENERATORS (to be implemented)
// =============================================================================
// Privacy Policy Generator
export { generatePrivacyPolicy, generatePrivacyPolicySections } from './generator/privacy-policy'
// Cookie Banner Generator
export { generateCookieBannerConfig, generateEmbedCode } from './generator/cookie-banner'
// =============================================================================
// EXPORT
// =============================================================================
// PDF Export
export {
generatePDFContent as generatePrivacyPolicyPDFContent,
generatePDFBlob as generatePrivacyPolicyPDFBlob,
generatePDFFilename as generatePrivacyPolicyPDFFilename,
} from './export/pdf'
export type { PDFExportOptions as PrivacyPolicyPDFExportOptions } from './export/pdf'
// DOCX Export
export {
generateDOCXContent as generatePrivacyPolicyDOCXContent,
generateDOCXBlob as generatePrivacyPolicyDOCXBlob,
generateDOCXFilename as generatePrivacyPolicyDOCXFilename,
} from './export/docx'
export type { DOCXExportOptions as PrivacyPolicyDOCXExportOptions } from './export/docx'

View File

@@ -0,0 +1,838 @@
/**
* Datenpunktkatalog & Datenschutzinformationen-Generator
* TypeScript Interfaces
*
* Dieses Modul definiert alle Typen für:
* - Datenpunktkatalog (32 vordefinierte + kundenspezifische)
* - Privacy Policy Generator
* - Cookie Banner Configuration
* - Retention Matrix
*/
// =============================================================================
// ENUMS
// =============================================================================
/**
* Kategorien für Datenpunkte (18 Kategorien: A-R)
*/
export type DataPointCategory =
| 'MASTER_DATA' // A: Stammdaten
| 'CONTACT_DATA' // B: Kontaktdaten
| 'AUTHENTICATION' // C: Authentifizierungsdaten
| 'CONSENT' // D: Einwilligungsdaten
| 'COMMUNICATION' // E: Kommunikationsdaten
| 'PAYMENT' // F: Zahlungsdaten
| 'USAGE_DATA' // G: Nutzungsdaten
| 'LOCATION' // H: Standortdaten
| 'DEVICE_DATA' // I: Gerätedaten
| 'MARKETING' // J: Marketingdaten
| 'ANALYTICS' // K: Analysedaten
| 'SOCIAL_MEDIA' // L: Social-Media-Daten
| 'HEALTH_DATA' // M: Gesundheitsdaten (Art. 9 DSGVO)
| 'EMPLOYEE_DATA' // N: Beschäftigtendaten
| 'CONTRACT_DATA' // O: Vertragsdaten
| 'LOG_DATA' // P: Protokolldaten
| 'AI_DATA' // Q: KI-Daten
| 'SECURITY' // R: Sicherheitsdaten
/**
* Risikoniveau für Datenpunkte
*/
export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH'
/**
* Rechtsgrundlagen nach DSGVO Art. 6 und Art. 9
*/
export type LegalBasis =
| 'CONTRACT' // Art. 6 Abs. 1 lit. b DSGVO
| 'CONSENT' // Art. 6 Abs. 1 lit. a DSGVO
| 'EXPLICIT_CONSENT' // Art. 9 Abs. 2 lit. a DSGVO (für Art. 9 Daten)
| 'LEGITIMATE_INTEREST' // Art. 6 Abs. 1 lit. f DSGVO
| 'LEGAL_OBLIGATION' // Art. 6 Abs. 1 lit. c DSGVO
| 'VITAL_INTERESTS' // Art. 6 Abs. 1 lit. d DSGVO
| 'PUBLIC_INTEREST' // Art. 6 Abs. 1 lit. e DSGVO
/**
* Aufbewahrungsfristen
*/
export type RetentionPeriod =
| '24_HOURS'
| '30_DAYS'
| '90_DAYS'
| '12_MONTHS'
| '24_MONTHS'
| '26_MONTHS' // Google Analytics Standard
| '36_MONTHS'
| '48_MONTHS'
| '6_YEARS'
| '10_YEARS'
| 'UNTIL_REVOCATION'
| 'UNTIL_PURPOSE_FULFILLED'
| 'UNTIL_ACCOUNT_DELETION'
/**
* Cookie-Kategorien für Cookie-Banner
*/
export type CookieCategory =
| 'ESSENTIAL' // Technisch notwendig
| 'PERFORMANCE' // Analyse & Performance
| 'PERSONALIZATION' // Personalisierung
| 'EXTERNAL_MEDIA' // Externe Medien
/**
* Export-Formate für Privacy Policy
*/
export type ExportFormat = 'HTML' | 'MARKDOWN' | 'PDF' | 'DOCX'
/**
* Sprachen
*/
export type SupportedLanguage = 'de' | 'en'
// =============================================================================
// DATA POINT
// =============================================================================
/**
* Lokalisierter Text (DE/EN)
*/
export interface LocalizedText {
de: string
en: string
}
/**
* Einzelner Datenpunkt im Katalog
*/
export interface DataPoint {
id: string
code: string // z.B. "A1", "B2", "C3"
category: DataPointCategory
name: LocalizedText
description: LocalizedText
purpose: LocalizedText
riskLevel: RiskLevel
legalBasis: LegalBasis
legalBasisJustification: LocalizedText
retentionPeriod: RetentionPeriod
retentionJustification: LocalizedText
cookieCategory: CookieCategory | null // null = kein Cookie
isSpecialCategory: boolean // Art. 9 DSGVO (sensible Daten)
requiresExplicitConsent: boolean
thirdPartyRecipients: string[]
technicalMeasures: string[]
tags: string[]
isCustom?: boolean // Kundenspezifischer Datenpunkt
isActive?: boolean // Aktiviert fuer diesen Tenant
}
/**
* YAML-Struktur fuer Datenpunkte (fuer Loader)
*/
export interface DataPointYAML {
id: string
code: string
category: string
name_de: string
name_en: string
description_de: string
description_en: string
purpose_de: string
purpose_en: string
risk_level: string
legal_basis: string
legal_basis_justification_de: string
legal_basis_justification_en: string
retention_period: string
retention_justification_de: string
retention_justification_en: string
cookie_category: string | null
is_special_category: boolean
requires_explicit_consent: boolean
third_party_recipients: string[]
technical_measures: string[]
tags: string[]
}
// =============================================================================
// CATALOG & RETENTION MATRIX
// =============================================================================
/**
* Gesamter Datenpunktkatalog eines Tenants
*/
export interface DataPointCatalog {
id: string
tenantId: string
version: string
dataPoints: DataPoint[] // Vordefinierte (32)
customDataPoints: DataPoint[] // Kundenspezifische
retentionMatrix: RetentionMatrixEntry[]
createdAt: Date
updatedAt: Date
}
/**
* Eintrag in der Retention Matrix
*/
export interface RetentionMatrixEntry {
category: DataPointCategory
categoryName: LocalizedText
standardPeriod: RetentionPeriod
legalBasis: string
exceptions: RetentionException[]
}
/**
* Ausnahme von der Standard-Loeschfrist
*/
export interface RetentionException {
condition: LocalizedText
period: RetentionPeriod
reason: LocalizedText
}
// =============================================================================
// PRIVACY POLICY GENERATION
// =============================================================================
/**
* Abschnitt in der Privacy Policy
*/
export interface PrivacyPolicySection {
id: string
order: number
title: LocalizedText
content: LocalizedText
dataPointIds: string[]
isRequired: boolean
isGenerated: boolean // true = aus Datenpunkten generiert
}
/**
* Unternehmensinfo fuer Privacy Policy
*/
export interface CompanyInfo {
name: string
address: string
city: string
postalCode: string
country: string
email: string
phone?: string
website?: string
dpoName?: string // Datenschutzbeauftragter
dpoEmail?: string
dpoPhone?: string
registrationNumber?: string // Handelsregister
vatId?: string // USt-IdNr
}
/**
* Generierte Privacy Policy
*/
export interface GeneratedPrivacyPolicy {
id: string
tenantId: string
language: SupportedLanguage
sections: PrivacyPolicySection[]
companyInfo: CompanyInfo
generatedAt: Date
version: string
format: ExportFormat
content?: string // Rendered content (HTML/MD)
}
/**
* Optionen fuer Privacy Policy Generierung
*/
export interface PrivacyPolicyGenerationOptions {
language: SupportedLanguage
format: ExportFormat
includeDataPoints: string[] // Welche Datenpunkte einschliessen
customSections?: PrivacyPolicySection[] // Zusaetzliche Abschnitte
styling?: PrivacyPolicyStyling
}
/**
* Styling-Optionen fuer PDF/HTML Export
*/
export interface PrivacyPolicyStyling {
primaryColor?: string
fontFamily?: string
fontSize?: number
headerFontSize?: number
includeTableOfContents?: boolean
includeDateFooter?: boolean
logoUrl?: string
}
// =============================================================================
// COOKIE BANNER CONFIG
// =============================================================================
/**
* Einzelner Cookie in einer Kategorie
*/
export interface CookieInfo {
name: string
provider: string
purpose: LocalizedText
expiry: string
type: 'FIRST_PARTY' | 'THIRD_PARTY'
}
/**
* Cookie-Banner Kategorie
*/
export interface CookieBannerCategory {
id: CookieCategory
name: LocalizedText
description: LocalizedText
isRequired: boolean // Essentiell = required
defaultEnabled: boolean
dataPointIds: string[] // Verknuepfte Datenpunkte
cookies: CookieInfo[]
}
/**
* Styling fuer Cookie Banner
*/
export interface CookieBannerStyling {
position: 'BOTTOM' | 'TOP' | 'CENTER'
theme: 'LIGHT' | 'DARK' | 'CUSTOM'
primaryColor?: string
secondaryColor?: string
textColor?: string
backgroundColor?: string
borderRadius?: number
maxWidth?: number
}
/**
* Texte fuer Cookie Banner
*/
export interface CookieBannerTexts {
title: LocalizedText
description: LocalizedText
acceptAll: LocalizedText
rejectAll: LocalizedText
customize: LocalizedText
save: LocalizedText
privacyPolicyLink: LocalizedText
}
/**
* Generierter Code fuer Cookie Banner
*/
export interface CookieBannerEmbedCode {
html: string
css: string
js: string
scriptTag: string // Fertiger Script-Tag zum Einbinden
}
/**
* Vollstaendige Cookie Banner Konfiguration
*/
export interface CookieBannerConfig {
id: string
tenantId: string
categories: CookieBannerCategory[]
styling: CookieBannerStyling
texts: CookieBannerTexts
embedCode?: CookieBannerEmbedCode
updatedAt: Date
}
// =============================================================================
// CONSENT MANAGEMENT
// =============================================================================
/**
* Einzelne Einwilligung eines Nutzers
*/
export interface ConsentEntry {
id: string
userId: string
dataPointId: string
granted: boolean
grantedAt: Date
revokedAt?: Date
ipAddress?: string
userAgent?: string
consentVersion: string
}
/**
* Aggregierte Consent-Statistiken
*/
export interface ConsentStatistics {
totalConsents: number
activeConsents: number
revokedConsents: number
byCategory: Record<DataPointCategory, {
total: number
active: number
revoked: number
}>
byLegalBasis: Record<LegalBasis, {
total: number
active: number
}>
conversionRate: number // Prozent der Nutzer mit Consent
}
// =============================================================================
// EINWILLIGUNGEN STATE & ACTIONS
// =============================================================================
/**
* Aktiver Tab in der Einwilligungen-Ansicht
*/
export type EinwilligungenTab =
| 'catalog'
| 'privacy-policy'
| 'cookie-banner'
| 'retention'
| 'consents'
/**
* State fuer Einwilligungen-Modul
*/
export interface EinwilligungenState {
// Data
catalog: DataPointCatalog | null
selectedDataPoints: string[]
privacyPolicy: GeneratedPrivacyPolicy | null
cookieBannerConfig: CookieBannerConfig | null
companyInfo: CompanyInfo | null
consentStatistics: ConsentStatistics | null
// UI State
activeTab: EinwilligungenTab
isLoading: boolean
isSaving: boolean
error: string | null
// Editor State
editingDataPoint: DataPoint | null
editingSection: PrivacyPolicySection | null
// Preview
previewLanguage: SupportedLanguage
previewFormat: ExportFormat
}
/**
* Actions fuer Einwilligungen-Reducer
*/
export type EinwilligungenAction =
| { type: 'SET_CATALOG'; payload: DataPointCatalog }
| { type: 'SET_SELECTED_DATA_POINTS'; payload: string[] }
| { type: 'TOGGLE_DATA_POINT'; payload: string }
| { type: 'ADD_CUSTOM_DATA_POINT'; payload: DataPoint }
| { type: 'UPDATE_DATA_POINT'; payload: { id: string; data: Partial<DataPoint> } }
| { type: 'DELETE_CUSTOM_DATA_POINT'; payload: string }
| { type: 'SET_PRIVACY_POLICY'; payload: GeneratedPrivacyPolicy }
| { type: 'SET_COOKIE_BANNER_CONFIG'; payload: CookieBannerConfig }
| { type: 'UPDATE_COOKIE_BANNER_STYLING'; payload: Partial<CookieBannerStyling> }
| { type: 'UPDATE_COOKIE_BANNER_TEXTS'; payload: Partial<CookieBannerTexts> }
| { type: 'SET_COMPANY_INFO'; payload: CompanyInfo }
| { type: 'SET_CONSENT_STATISTICS'; payload: ConsentStatistics }
| { type: 'SET_ACTIVE_TAB'; payload: EinwilligungenTab }
| { type: 'SET_LOADING'; payload: boolean }
| { type: 'SET_SAVING'; payload: boolean }
| { type: 'SET_ERROR'; payload: string | null }
| { type: 'SET_EDITING_DATA_POINT'; payload: DataPoint | null }
| { type: 'SET_EDITING_SECTION'; payload: PrivacyPolicySection | null }
| { type: 'SET_PREVIEW_LANGUAGE'; payload: SupportedLanguage }
| { type: 'SET_PREVIEW_FORMAT'; payload: ExportFormat }
| { type: 'RESET_STATE' }
// =============================================================================
// HELPER TYPES
// =============================================================================
/**
* Kategorie-Metadaten
*/
export interface CategoryMetadata {
id: DataPointCategory
code: string // A, B, C, etc.
name: LocalizedText
description: LocalizedText
icon: string // Icon name
color: string // Tailwind color class
}
/**
* Mapping von Kategorie zu Metadaten (18 Kategorien)
*/
export const CATEGORY_METADATA: Record<DataPointCategory, CategoryMetadata> = {
MASTER_DATA: {
id: 'MASTER_DATA',
code: 'A',
name: { de: 'Stammdaten', en: 'Master Data' },
description: { de: 'Grundlegende personenbezogene Daten', en: 'Basic personal data' },
icon: 'User',
color: 'blue'
},
CONTACT_DATA: {
id: 'CONTACT_DATA',
code: 'B',
name: { de: 'Kontaktdaten', en: 'Contact Data' },
description: { de: 'Kontaktinformationen und Erreichbarkeit', en: 'Contact information and availability' },
icon: 'Mail',
color: 'sky'
},
AUTHENTICATION: {
id: 'AUTHENTICATION',
code: 'C',
name: { de: 'Authentifizierungsdaten', en: 'Authentication Data' },
description: { de: 'Daten zur Benutzeranmeldung und Session-Verwaltung', en: 'Data for user login and session management' },
icon: 'Key',
color: 'slate'
},
CONSENT: {
id: 'CONSENT',
code: 'D',
name: { de: 'Einwilligungsdaten', en: 'Consent Data' },
description: { de: 'Einwilligungen und Datenschutzpraeferenzen', en: 'Consents and privacy preferences' },
icon: 'CheckCircle',
color: 'green'
},
COMMUNICATION: {
id: 'COMMUNICATION',
code: 'E',
name: { de: 'Kommunikationsdaten', en: 'Communication Data' },
description: { de: 'Kundenservice und Kommunikationsdaten', en: 'Customer service and communication data' },
icon: 'MessageSquare',
color: 'cyan'
},
PAYMENT: {
id: 'PAYMENT',
code: 'F',
name: { de: 'Zahlungsdaten', en: 'Payment Data' },
description: { de: 'Rechnungs- und Zahlungsinformationen', en: 'Billing and payment information' },
icon: 'CreditCard',
color: 'amber'
},
USAGE_DATA: {
id: 'USAGE_DATA',
code: 'G',
name: { de: 'Nutzungsdaten', en: 'Usage Data' },
description: { de: 'Daten zur Nutzung des Dienstes', en: 'Data about service usage' },
icon: 'Activity',
color: 'violet'
},
LOCATION: {
id: 'LOCATION',
code: 'H',
name: { de: 'Standortdaten', en: 'Location Data' },
description: { de: 'Geografische Standortinformationen', en: 'Geographic location information' },
icon: 'MapPin',
color: 'emerald'
},
DEVICE_DATA: {
id: 'DEVICE_DATA',
code: 'I',
name: { de: 'Geraetedaten', en: 'Device Data' },
description: { de: 'Technische Geraete- und Browserinformationen', en: 'Technical device and browser information' },
icon: 'Smartphone',
color: 'zinc'
},
MARKETING: {
id: 'MARKETING',
code: 'J',
name: { de: 'Marketingdaten', en: 'Marketing Data' },
description: { de: 'Marketing- und Werbedaten', en: 'Marketing and advertising data' },
icon: 'Megaphone',
color: 'purple'
},
ANALYTICS: {
id: 'ANALYTICS',
code: 'K',
name: { de: 'Analysedaten', en: 'Analytics Data' },
description: { de: 'Web-Analyse und Nutzungsstatistiken', en: 'Web analytics and usage statistics' },
icon: 'BarChart3',
color: 'indigo'
},
SOCIAL_MEDIA: {
id: 'SOCIAL_MEDIA',
code: 'L',
name: { de: 'Social-Media-Daten', en: 'Social Media Data' },
description: { de: 'Daten aus sozialen Netzwerken', en: 'Data from social networks' },
icon: 'Share2',
color: 'pink'
},
HEALTH_DATA: {
id: 'HEALTH_DATA',
code: 'M',
name: { de: 'Gesundheitsdaten', en: 'Health Data' },
description: { de: 'Besondere Kategorie nach Art. 9 DSGVO - Gesundheitsbezogene Daten', en: 'Special category under Art. 9 GDPR - Health-related data' },
icon: 'Heart',
color: 'rose'
},
EMPLOYEE_DATA: {
id: 'EMPLOYEE_DATA',
code: 'N',
name: { de: 'Beschaeftigtendaten', en: 'Employee Data' },
description: { de: 'Personalverwaltung und Arbeitnehmerinformationen (BDSG § 26)', en: 'HR management and employee information' },
icon: 'Briefcase',
color: 'orange'
},
CONTRACT_DATA: {
id: 'CONTRACT_DATA',
code: 'O',
name: { de: 'Vertragsdaten', en: 'Contract Data' },
description: { de: 'Vertragsinformationen und -dokumente', en: 'Contract information and documents' },
icon: 'FileText',
color: 'teal'
},
LOG_DATA: {
id: 'LOG_DATA',
code: 'P',
name: { de: 'Protokolldaten', en: 'Log Data' },
description: { de: 'System- und Zugriffsprotokolle', en: 'System and access logs' },
icon: 'FileCode',
color: 'gray'
},
AI_DATA: {
id: 'AI_DATA',
code: 'Q',
name: { de: 'KI-Daten', en: 'AI Data' },
description: { de: 'KI-Interaktionen, Prompts und generierte Inhalte (AI Act)', en: 'AI interactions, prompts and generated content (AI Act)' },
icon: 'Bot',
color: 'fuchsia'
},
SECURITY: {
id: 'SECURITY',
code: 'R',
name: { de: 'Sicherheitsdaten', en: 'Security Data' },
description: { de: 'Sicherheitsrelevante Daten und Vorfallberichte', en: 'Security-relevant data and incident reports' },
icon: 'Shield',
color: 'red'
}
}
/**
* Mapping von Rechtsgrundlage zu Beschreibung
*/
export const LEGAL_BASIS_INFO: Record<LegalBasis, { article: string; name: LocalizedText; description: LocalizedText }> = {
CONTRACT: {
article: 'Art. 6 Abs. 1 lit. b DSGVO',
name: { de: 'Vertragserfuellung', en: 'Contract Performance' },
description: {
de: 'Die Verarbeitung ist erforderlich fuer die Erfuellung eines Vertrags oder zur Durchfuehrung vorvertraglicher Massnahmen.',
en: 'Processing is necessary for the performance of a contract or pre-contractual measures.'
}
},
CONSENT: {
article: 'Art. 6 Abs. 1 lit. a DSGVO',
name: { de: 'Einwilligung', en: 'Consent' },
description: {
de: 'Die betroffene Person hat ihre Einwilligung zu der Verarbeitung gegeben.',
en: 'The data subject has given consent to the processing.'
}
},
EXPLICIT_CONSENT: {
article: 'Art. 9 Abs. 2 lit. a DSGVO',
name: { de: 'Ausdrueckliche Einwilligung', en: 'Explicit Consent' },
description: {
de: 'Die betroffene Person hat ausdruecklich in die Verarbeitung besonderer Kategorien personenbezogener Daten (Art. 9 DSGVO) eingewilligt. Dies betrifft Gesundheitsdaten, biometrische Daten, Daten zur ethnischen Herkunft, politische Meinungen, religiöse Überzeugungen etc.',
en: 'The data subject has given explicit consent to the processing of special categories of personal data (Art. 9 GDPR). This includes health data, biometric data, racial or ethnic origin, political opinions, religious beliefs, etc.'
}
},
LEGITIMATE_INTEREST: {
article: 'Art. 6 Abs. 1 lit. f DSGVO',
name: { de: 'Berechtigtes Interesse', en: 'Legitimate Interest' },
description: {
de: 'Die Verarbeitung ist zur Wahrung berechtigter Interessen des Verantwortlichen erforderlich.',
en: 'Processing is necessary for legitimate interests pursued by the controller.'
}
},
LEGAL_OBLIGATION: {
article: 'Art. 6 Abs. 1 lit. c DSGVO',
name: { de: 'Rechtliche Verpflichtung', en: 'Legal Obligation' },
description: {
de: 'Die Verarbeitung ist zur Erfuellung einer rechtlichen Verpflichtung erforderlich.',
en: 'Processing is necessary for compliance with a legal obligation.'
}
},
VITAL_INTERESTS: {
article: 'Art. 6 Abs. 1 lit. d DSGVO',
name: { de: 'Lebenswichtige Interessen', en: 'Vital Interests' },
description: {
de: 'Die Verarbeitung ist erforderlich, um lebenswichtige Interessen der betroffenen Person oder einer anderen natuerlichen Person zu schuetzen.',
en: 'Processing is necessary to protect the vital interests of the data subject or another natural person.'
}
},
PUBLIC_INTEREST: {
article: 'Art. 6 Abs. 1 lit. e DSGVO',
name: { de: 'Oeffentliches Interesse', en: 'Public Interest' },
description: {
de: 'Die Verarbeitung ist fuer die Wahrnehmung einer Aufgabe erforderlich, die im oeffentlichen Interesse liegt oder in Ausuebung oeffentlicher Gewalt erfolgt.',
en: 'Processing is necessary for the performance of a task carried out in the public interest or in the exercise of official authority.'
}
}
}
/**
* Mapping von Aufbewahrungsfrist zu Beschreibung
*/
export const RETENTION_PERIOD_INFO: Record<RetentionPeriod, { label: LocalizedText; days: number | null }> = {
'24_HOURS': { label: { de: '24 Stunden', en: '24 Hours' }, days: 1 },
'30_DAYS': { label: { de: '30 Tage', en: '30 Days' }, days: 30 },
'90_DAYS': { label: { de: '90 Tage', en: '90 Days' }, days: 90 },
'12_MONTHS': { label: { de: '12 Monate', en: '12 Months' }, days: 365 },
'24_MONTHS': { label: { de: '24 Monate', en: '24 Months' }, days: 730 },
'26_MONTHS': { label: { de: '26 Monate (Google Analytics)', en: '26 Months (Google Analytics)' }, days: 790 },
'36_MONTHS': { label: { de: '36 Monate', en: '36 Months' }, days: 1095 },
'48_MONTHS': { label: { de: '48 Monate', en: '48 Months' }, days: 1460 },
'6_YEARS': { label: { de: '6 Jahre', en: '6 Years' }, days: 2190 },
'10_YEARS': { label: { de: '10 Jahre', en: '10 Years' }, days: 3650 },
'UNTIL_REVOCATION': { label: { de: 'Bis Widerruf', en: 'Until Revocation' }, days: null },
'UNTIL_PURPOSE_FULFILLED': { label: { de: 'Bis Zweckerfuellung', en: 'Until Purpose Fulfilled' }, days: null },
'UNTIL_ACCOUNT_DELETION': { label: { de: 'Bis Kontoschliessung', en: 'Until Account Deletion' }, days: null }
}
/**
* Spezielle Hinweise für Art. 9 DSGVO Kategorien
*/
export interface Article9Warning {
title: LocalizedText
description: LocalizedText
requirements: LocalizedText[]
}
export const ARTICLE_9_WARNING: Article9Warning = {
title: {
de: 'Besondere Kategorie personenbezogener Daten (Art. 9 DSGVO)',
en: 'Special Category of Personal Data (Art. 9 GDPR)'
},
description: {
de: 'Die Verarbeitung dieser Daten unterliegt besonderen Anforderungen nach Art. 9 DSGVO. Diese Daten sind besonders schuetzenswert.',
en: 'Processing of this data is subject to special requirements under Art. 9 GDPR. This data requires special protection.'
},
requirements: [
{
de: 'Ausdrueckliche Einwilligung erforderlich (Art. 9 Abs. 2 lit. a DSGVO)',
en: 'Explicit consent required (Art. 9(2)(a) GDPR)'
},
{
de: 'Separate Einwilligungserklaerung im UI notwendig',
en: 'Separate consent declaration required in UI'
},
{
de: 'Hoehere Dokumentationspflichten',
en: 'Higher documentation requirements'
},
{
de: 'Spezielle Loeschverfahren erforderlich',
en: 'Special deletion procedures required'
},
{
de: 'Datenschutz-Folgenabschaetzung (DSFA) empfohlen',
en: 'Data Protection Impact Assessment (DPIA) recommended'
}
]
}
/**
* Spezielle Hinweise für Beschäftigtendaten (BDSG § 26)
*/
export interface EmployeeDataWarning {
title: LocalizedText
description: LocalizedText
requirements: LocalizedText[]
}
export const EMPLOYEE_DATA_WARNING: EmployeeDataWarning = {
title: {
de: 'Beschaeftigtendaten (BDSG § 26)',
en: 'Employee Data (BDSG § 26)'
},
description: {
de: 'Die Verarbeitung von Beschaeftigtendaten unterliegt besonderen Anforderungen nach § 26 BDSG.',
en: 'Processing of employee data is subject to special requirements under § 26 BDSG (German Federal Data Protection Act).'
},
requirements: [
{
de: 'Aufbewahrungspflichten fuer Lohnunterlagen (6-10 Jahre)',
en: 'Retention obligations for payroll records (6-10 years)'
},
{
de: 'Betriebsrat-Beteiligung ggf. erforderlich',
en: 'Works council involvement may be required'
},
{
de: 'Verarbeitung nur fuer Zwecke des Beschaeftigungsverhaeltnisses',
en: 'Processing only for employment purposes'
},
{
de: 'Besondere Vertraulichkeit bei Gesundheitsdaten',
en: 'Special confidentiality for health data'
}
]
}
/**
* Spezielle Hinweise für KI-Daten (AI Act)
*/
export interface AIDataWarning {
title: LocalizedText
description: LocalizedText
requirements: LocalizedText[]
}
export const AI_DATA_WARNING: AIDataWarning = {
title: {
de: 'KI-Daten (AI Act)',
en: 'AI Data (AI Act)'
},
description: {
de: 'Die Verarbeitung von KI-bezogenen Daten unterliegt den Transparenzpflichten des AI Acts.',
en: 'Processing of AI-related data is subject to AI Act transparency requirements.'
},
requirements: [
{
de: 'Transparenzpflichten bei KI-Verarbeitung',
en: 'Transparency obligations for AI processing'
},
{
de: 'Kennzeichnung von KI-generierten Inhalten',
en: 'Labeling of AI-generated content'
},
{
de: 'Dokumentation der KI-Modell-Nutzung',
en: 'Documentation of AI model usage'
},
{
de: 'Keine Verwendung fuer unerlaubtes Training ohne Einwilligung',
en: 'No use for unauthorized training without consent'
}
]
}
/**
* Risk Level Styling
*/
export const RISK_LEVEL_STYLING: Record<RiskLevel, { label: LocalizedText; color: string; bgColor: string }> = {
LOW: {
label: { de: 'Niedrig', en: 'Low' },
color: 'text-green-700',
bgColor: 'bg-green-100'
},
MEDIUM: {
label: { de: 'Mittel', en: 'Medium' },
color: 'text-yellow-700',
bgColor: 'bg-yellow-100'
},
HIGH: {
label: { de: 'Hoch', en: 'High' },
color: 'text-red-700',
bgColor: 'bg-red-100'
}
}

753
admin-v2/lib/sdk/export.ts Normal file
View File

@@ -0,0 +1,753 @@
/**
* SDK Export Utilities
* Handles PDF and ZIP export of SDK state and documents
*/
import jsPDF from 'jspdf'
import JSZip from 'jszip'
import { SDKState, SDK_STEPS, getStepById } from './types'
// =============================================================================
// 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
// =============================================================================
export async function downloadExport(
state: SDKState,
format: 'json' | 'pdf' | 'zip',
options: ExportOptions = {}
): Promise<void> {
let blob: Blob
let filename: string
const timestamp = new Date().toISOString().slice(0, 10)
switch (format) {
case 'json':
blob = new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' })
filename = `ai-compliance-sdk-${timestamp}.json`
break
case 'pdf':
blob = await exportToPDF(state, options)
filename = `ai-compliance-sdk-${timestamp}.pdf`
break
case 'zip':
blob = await exportToZIP(state, options)
filename = `ai-compliance-sdk-${timestamp}.zip`
break
default:
throw new Error(`Unknown export format: ${format}`)
}
// Create download link
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
}

73
admin-v2/lib/sdk/index.ts Normal file
View File

@@ -0,0 +1,73 @@
/**
* AI Compliance SDK - Main Export
*/
// Types
export * from './types'
// Context & Provider
export { SDKProvider, useSDK, SDKContext, initialState } from './context'
// Export utilities
export { exportToPDF, exportToZIP, downloadExport } from './export'
export type { ExportOptions } from './export'
// API Client
export {
SDKApiClient,
getSDKApiClient,
resetSDKApiClient,
} from './api-client'
export type {
APIResponse,
StateResponse,
SaveStateRequest,
CheckpointValidationResult,
APIError,
} from './api-client'
// Sync Manager
export {
StateSyncManager,
createStateSyncManager,
} from './sync'
export type {
SyncStatus,
SyncState,
ConflictResolution,
SyncOptions,
SyncCallbacks,
} from './sync'
// SDK Backend Client (RAG + LLM)
export {
SDKBackendClient,
getSDKBackendClient,
resetSDKBackendClient,
isLegalQuery,
extractRegulationReferences,
} from './sdk-client'
export type {
SearchResult,
SearchResponse,
CorpusStatus,
GenerateRequest,
GenerateResponse,
} from './sdk-client'
// Demo Data Seeding (stored via API like real customer data)
export {
generateDemoState,
seedDemoData,
seedDemoDataDirect,
hasDemoData,
clearDemoData,
// Seed data templates (for testing/reference only)
getDemoUseCases,
getDemoRisks,
getDemoControls,
getDemoDSFA,
getDemoTOMs,
getDemoProcessingActivities,
getDemoRetentionPolicies,
} from './demo-data'

View File

@@ -0,0 +1,327 @@
/**
* SDK Backend Client
*
* Client for communicating with the SDK Backend (Go service)
* for RAG search and document generation.
*/
// =============================================================================
// TYPES
// =============================================================================
export interface SearchResult {
id: string
content: string
source: string
score: number
metadata?: Record<string, string>
highlights?: string[]
}
export interface SearchResponse {
query: string
topK: number
results: SearchResult[]
source: 'qdrant' | 'mock'
}
export interface CorpusStatus {
status: 'ready' | 'unavailable' | 'indexing'
collections: string[]
documents: number
lastUpdated?: string
}
export interface GenerateRequest {
tenantId: string
context: Record<string, unknown>
template?: string
language?: 'de' | 'en'
useRag?: boolean
ragQuery?: string
maxTokens?: number
temperature?: number
}
export interface GenerateResponse {
content: string
generatedAt: string
model: string
tokensUsed: number
ragSources?: SearchResult[]
confidence?: number
}
export interface SDKBackendResponse<T> {
success: boolean
data?: T
error?: string
code?: string
}
// =============================================================================
// CONFIGURATION
// =============================================================================
const SDK_BACKEND_URL = process.env.NEXT_PUBLIC_SDK_BACKEND_URL || 'http://localhost:8085'
const DEFAULT_TIMEOUT = 60000 // 60 seconds for generation
// =============================================================================
// SDK BACKEND CLIENT
// =============================================================================
export class SDKBackendClient {
private baseUrl: string
private timeout: number
constructor(options: {
baseUrl?: string
timeout?: number
} = {}) {
this.baseUrl = options.baseUrl || SDK_BACKEND_URL
this.timeout = options.timeout || DEFAULT_TIMEOUT
}
// ---------------------------------------------------------------------------
// Private Methods
// ---------------------------------------------------------------------------
private async fetch<T>(
path: string,
options: RequestInit = {}
): Promise<T> {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), this.timeout)
try {
const response = await fetch(`${this.baseUrl}${path}`, {
...options,
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
})
const data = await response.json() as SDKBackendResponse<T>
if (!response.ok || !data.success) {
throw new Error(data.error || `HTTP ${response.status}`)
}
return data.data as T
} finally {
clearTimeout(timeoutId)
}
}
// ---------------------------------------------------------------------------
// RAG Search
// ---------------------------------------------------------------------------
/**
* Search the legal corpus using semantic search
*/
async search(
query: string,
options: {
topK?: number
collection?: string
filter?: string
} = {}
): Promise<SearchResponse> {
const params = new URLSearchParams({
q: query,
top_k: String(options.topK || 5),
})
if (options.collection) {
params.set('collection', options.collection)
}
if (options.filter) {
params.set('filter', options.filter)
}
return this.fetch<SearchResponse>(`/sdk/v1/rag/search?${params}`)
}
/**
* Get the status of the legal corpus
*/
async getCorpusStatus(): Promise<CorpusStatus> {
return this.fetch<CorpusStatus>('/sdk/v1/rag/status')
}
/**
* Index a new document into the corpus
*/
async indexDocument(
collection: string,
id: string,
content: string,
metadata?: Record<string, string>
): Promise<{ indexed: boolean; id: string }> {
return this.fetch('/sdk/v1/rag/index', {
method: 'POST',
body: JSON.stringify({
collection,
id,
content,
metadata,
}),
})
}
// ---------------------------------------------------------------------------
// Document Generation
// ---------------------------------------------------------------------------
/**
* Generate a Data Protection Impact Assessment (DSFA)
*/
async generateDSFA(request: GenerateRequest): Promise<GenerateResponse> {
return this.fetch<GenerateResponse>('/sdk/v1/generate/dsfa', {
method: 'POST',
body: JSON.stringify(request),
})
}
/**
* Generate Technical and Organizational Measures (TOM)
*/
async generateTOM(request: GenerateRequest): Promise<GenerateResponse> {
return this.fetch<GenerateResponse>('/sdk/v1/generate/tom', {
method: 'POST',
body: JSON.stringify(request),
})
}
/**
* Generate a Processing Activity Register (VVT)
*/
async generateVVT(request: GenerateRequest): Promise<GenerateResponse> {
return this.fetch<GenerateResponse>('/sdk/v1/generate/vvt', {
method: 'POST',
body: JSON.stringify(request),
})
}
/**
* Generate an expert opinion/assessment (Gutachten)
*/
async generateGutachten(request: GenerateRequest): Promise<GenerateResponse> {
return this.fetch<GenerateResponse>('/sdk/v1/generate/gutachten', {
method: 'POST',
body: JSON.stringify(request),
})
}
// ---------------------------------------------------------------------------
// Health Check
// ---------------------------------------------------------------------------
/**
* Check if the SDK backend is healthy
*/
async healthCheck(): Promise<{
status: string
timestamp: string
services: {
database: boolean
rag: boolean
llm: boolean
}
}> {
try {
const response = await fetch(`${this.baseUrl}/health`, {
method: 'GET',
})
return response.json()
} catch {
return {
status: 'unhealthy',
timestamp: new Date().toISOString(),
services: {
database: false,
rag: false,
llm: false,
},
}
}
}
}
// =============================================================================
// SINGLETON INSTANCE
// =============================================================================
let clientInstance: SDKBackendClient | null = null
export function getSDKBackendClient(): SDKBackendClient {
if (!clientInstance) {
clientInstance = new SDKBackendClient()
}
return clientInstance
}
export function resetSDKBackendClient(): void {
clientInstance = null
}
// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================
/**
* Check if a query is likely a legal/compliance search
*/
export function isLegalQuery(query: string): boolean {
const legalKeywords = [
'dsgvo', 'gdpr', 'datenschutz', 'privacy',
'ai act', 'ki-gesetz', 'ai-verordnung',
'nis2', 'cybersicherheit',
'artikel', 'article', 'art.',
'verordnung', 'regulation', 'richtlinie', 'directive',
'gesetz', 'law', 'compliance',
'dsfa', 'dpia', 'folgenabschätzung',
'tom', 'maßnahmen', 'measures',
'vvt', 'verarbeitungsverzeichnis',
]
const normalizedQuery = query.toLowerCase()
return legalKeywords.some(keyword => normalizedQuery.includes(keyword))
}
/**
* Extract regulation references from text
*/
export function extractRegulationReferences(text: string): Array<{
regulation: string
article: string
fullReference: string
}> {
const references: Array<{
regulation: string
article: string
fullReference: string
}> = []
// Match patterns like "Art. 5 DSGVO", "Article 6 GDPR", "Art. 35 Abs. 1 DSGVO"
const patterns = [
/Art(?:ikel|\.|\s)*(\d+)(?:\s*Abs\.\s*(\d+))?\s*(DSGVO|GDPR|AI\s*Act|NIS2)/gi,
/Article\s*(\d+)(?:\s*para(?:graph)?\s*(\d+))?\s*(DSGVO|GDPR|AI\s*Act|NIS2)/gi,
]
for (const pattern of patterns) {
let match
while ((match = pattern.exec(text)) !== null) {
references.push({
regulation: match[3].toUpperCase().replace(/\s+/g, ' '),
article: match[2] ? `${match[1]} Abs. ${match[2]}` : match[1],
fullReference: match[0],
})
}
}
return references
}

482
admin-v2/lib/sdk/sync.ts Normal file
View File

@@ -0,0 +1,482 @@
/**
* SDK State Synchronization
*
* Handles offline/online sync, multi-tab coordination,
* and conflict resolution for SDK state.
*/
import { SDKState } from './types'
import { SDKApiClient, StateResponse } from './api-client'
// =============================================================================
// TYPES
// =============================================================================
export type SyncStatus = 'idle' | 'syncing' | 'error' | 'offline' | 'conflict'
export interface SyncState {
status: SyncStatus
lastSyncedAt: Date | null
localVersion: number
serverVersion: number
pendingChanges: number
error: string | null
}
export interface ConflictResolution {
strategy: 'local' | 'server' | 'merge'
mergedState?: SDKState
}
export interface SyncOptions {
debounceMs?: number
maxRetries?: number
conflictHandler?: (local: SDKState, server: SDKState) => Promise<ConflictResolution>
}
export interface SyncCallbacks {
onSyncStart?: () => void
onSyncComplete?: (state: SDKState) => void
onSyncError?: (error: Error) => void
onConflict?: (local: SDKState, server: SDKState) => void
onOffline?: () => void
onOnline?: () => void
}
// =============================================================================
// CONSTANTS
// =============================================================================
const STORAGE_KEY_PREFIX = 'ai-compliance-sdk-state'
const SYNC_CHANNEL = 'sdk-state-sync'
const DEFAULT_DEBOUNCE_MS = 2000
const DEFAULT_MAX_RETRIES = 3
// =============================================================================
// STATE SYNC MANAGER
// =============================================================================
export class StateSyncManager {
private apiClient: SDKApiClient
private tenantId: string
private options: Required<SyncOptions>
private callbacks: SyncCallbacks
private syncState: SyncState
private broadcastChannel: BroadcastChannel | null = null
private debounceTimeout: ReturnType<typeof setTimeout> | null = null
private pendingState: SDKState | null = null
private isOnline: boolean = true
constructor(
apiClient: SDKApiClient,
tenantId: string,
options: SyncOptions = {},
callbacks: SyncCallbacks = {}
) {
this.apiClient = apiClient
this.tenantId = tenantId
this.callbacks = callbacks
this.options = {
debounceMs: options.debounceMs ?? DEFAULT_DEBOUNCE_MS,
maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
conflictHandler: options.conflictHandler ?? this.defaultConflictHandler,
}
this.syncState = {
status: 'idle',
lastSyncedAt: null,
localVersion: 0,
serverVersion: 0,
pendingChanges: 0,
error: null,
}
this.setupBroadcastChannel()
this.setupOnlineListener()
}
// ---------------------------------------------------------------------------
// Setup Methods
// ---------------------------------------------------------------------------
private setupBroadcastChannel(): void {
if (typeof window === 'undefined' || !('BroadcastChannel' in window)) {
return
}
try {
this.broadcastChannel = new BroadcastChannel(`${SYNC_CHANNEL}-${this.tenantId}`)
this.broadcastChannel.onmessage = this.handleBroadcastMessage.bind(this)
} catch (error) {
console.warn('BroadcastChannel not available:', error)
}
}
private setupOnlineListener(): void {
if (typeof window === 'undefined') {
return
}
window.addEventListener('online', () => {
this.isOnline = true
this.syncState.status = 'idle'
this.callbacks.onOnline?.()
// Attempt to sync any pending changes
if (this.pendingState) {
this.syncToServer(this.pendingState)
}
})
window.addEventListener('offline', () => {
this.isOnline = false
this.syncState.status = 'offline'
this.callbacks.onOffline?.()
})
// Check initial online status
this.isOnline = navigator.onLine
if (!this.isOnline) {
this.syncState.status = 'offline'
}
}
// ---------------------------------------------------------------------------
// Broadcast Channel Methods
// ---------------------------------------------------------------------------
private handleBroadcastMessage(event: MessageEvent): void {
const { type, state, version, tabId } = event.data
switch (type) {
case 'STATE_UPDATED':
// Another tab updated the state
if (version > this.syncState.localVersion) {
this.syncState.localVersion = version
this.saveToLocalStorage(state)
this.callbacks.onSyncComplete?.(state)
}
break
case 'SYNC_COMPLETE':
// Another tab completed a sync
this.syncState.serverVersion = version
break
case 'REQUEST_STATE':
// Another tab is requesting the current state
this.broadcastState()
break
}
}
private broadcastState(): void {
if (!this.broadcastChannel) return
const state = this.loadFromLocalStorage()
if (state) {
this.broadcastChannel.postMessage({
type: 'STATE_UPDATED',
state,
version: this.syncState.localVersion,
tabId: this.getTabId(),
})
}
}
private broadcastSyncComplete(version: number): void {
if (!this.broadcastChannel) return
this.broadcastChannel.postMessage({
type: 'SYNC_COMPLETE',
version,
tabId: this.getTabId(),
})
}
private getTabId(): string {
if (typeof window === 'undefined') return 'server'
let tabId = sessionStorage.getItem('sdk-tab-id')
if (!tabId) {
tabId = `tab-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
sessionStorage.setItem('sdk-tab-id', tabId)
}
return tabId
}
// ---------------------------------------------------------------------------
// Local Storage Methods
// ---------------------------------------------------------------------------
private getStorageKey(): string {
return `${STORAGE_KEY_PREFIX}-${this.tenantId}`
}
saveToLocalStorage(state: SDKState): void {
if (typeof window === 'undefined') return
try {
const data = {
state,
version: this.syncState.localVersion,
savedAt: new Date().toISOString(),
}
localStorage.setItem(this.getStorageKey(), JSON.stringify(data))
} catch (error) {
console.error('Failed to save to localStorage:', error)
}
}
loadFromLocalStorage(): SDKState | null {
if (typeof window === 'undefined') return null
try {
const stored = localStorage.getItem(this.getStorageKey())
if (stored) {
const data = JSON.parse(stored)
this.syncState.localVersion = data.version || 0
return data.state
}
} catch (error) {
console.error('Failed to load from localStorage:', error)
}
return null
}
clearLocalStorage(): void {
if (typeof window === 'undefined') return
try {
localStorage.removeItem(this.getStorageKey())
} catch (error) {
console.error('Failed to clear localStorage:', error)
}
}
// ---------------------------------------------------------------------------
// Sync Methods
// ---------------------------------------------------------------------------
/**
* Queue a state change for syncing (debounced)
*/
queueSync(state: SDKState): void {
this.pendingState = state
this.syncState.pendingChanges++
// Save to localStorage immediately
this.syncState.localVersion++
this.saveToLocalStorage(state)
// Broadcast to other tabs
this.broadcastState()
// Debounce server sync
if (this.debounceTimeout) {
clearTimeout(this.debounceTimeout)
}
this.debounceTimeout = setTimeout(() => {
this.syncToServer(state)
}, this.options.debounceMs)
}
/**
* Force immediate sync to server
*/
async forcSync(state: SDKState): Promise<void> {
if (this.debounceTimeout) {
clearTimeout(this.debounceTimeout)
this.debounceTimeout = null
}
await this.syncToServer(state)
}
/**
* Sync state to server
*/
private async syncToServer(state: SDKState): Promise<void> {
if (!this.isOnline) {
this.syncState.status = 'offline'
return
}
this.syncState.status = 'syncing'
this.callbacks.onSyncStart?.()
try {
const response = await this.apiClient.saveState(state, this.syncState.serverVersion)
this.syncState = {
...this.syncState,
status: 'idle',
lastSyncedAt: new Date(),
serverVersion: response.version,
pendingChanges: 0,
error: null,
}
this.pendingState = null
this.broadcastSyncComplete(response.version)
this.callbacks.onSyncComplete?.(state)
} catch (error) {
// Handle version conflict (409)
if ((error as { status?: number }).status === 409) {
await this.handleConflict(state)
} else {
this.syncState.status = 'error'
this.syncState.error = (error as Error).message
this.callbacks.onSyncError?.(error as Error)
}
}
}
/**
* Load state from server
*/
async loadFromServer(): Promise<SDKState | null> {
if (!this.isOnline) {
return this.loadFromLocalStorage()
}
try {
const response = await this.apiClient.getState()
if (response) {
this.syncState.serverVersion = response.version
this.syncState.localVersion = response.version
this.saveToLocalStorage(response.state)
return response.state
}
// No server state, return local if available
return this.loadFromLocalStorage()
} catch (error) {
console.error('Failed to load from server:', error)
// Fallback to local storage
return this.loadFromLocalStorage()
}
}
// ---------------------------------------------------------------------------
// Conflict Resolution
// ---------------------------------------------------------------------------
private async handleConflict(localState: SDKState): Promise<void> {
this.syncState.status = 'conflict'
try {
// Fetch server state
const serverResponse = await this.apiClient.getState()
if (!serverResponse) {
// Server has no state, use local
await this.apiClient.saveState(localState)
return
}
const serverState = serverResponse.state
this.callbacks.onConflict?.(localState, serverState)
// Resolve conflict
const resolution = await this.options.conflictHandler(localState, serverState)
let resolvedState: SDKState
switch (resolution.strategy) {
case 'local':
resolvedState = localState
break
case 'server':
resolvedState = serverState
break
case 'merge':
resolvedState = resolution.mergedState || localState
break
}
// Save resolved state
const response = await this.apiClient.saveState(resolvedState)
this.syncState.serverVersion = response.version
this.syncState.localVersion = response.version
this.saveToLocalStorage(resolvedState)
this.syncState.status = 'idle'
this.callbacks.onSyncComplete?.(resolvedState)
} catch (error) {
this.syncState.status = 'error'
this.syncState.error = (error as Error).message
this.callbacks.onSyncError?.(error as Error)
}
}
private async defaultConflictHandler(
local: SDKState,
server: SDKState
): Promise<ConflictResolution> {
// Default: Server wins, but we preserve certain local-only data
const localTime = new Date(local.lastModified).getTime()
const serverTime = new Date(server.lastModified).getTime()
if (localTime > serverTime) {
// Local is newer, use local
return { strategy: 'local' }
}
// Merge: Use server state but preserve local UI preferences
const mergedState: SDKState = {
...server,
preferences: local.preferences,
commandBarHistory: [
...local.commandBarHistory,
...server.commandBarHistory.filter(
h => !local.commandBarHistory.some(lh => lh.id === h.id)
),
].slice(0, 50),
recentSearches: [...new Set([...local.recentSearches, ...server.recentSearches])].slice(0, 20),
}
return { strategy: 'merge', mergedState }
}
// ---------------------------------------------------------------------------
// Getters & Cleanup
// ---------------------------------------------------------------------------
getSyncState(): SyncState {
return { ...this.syncState }
}
isOnlineStatus(): boolean {
return this.isOnline
}
hasPendingChanges(): boolean {
return this.syncState.pendingChanges > 0 || this.pendingState !== null
}
/**
* Cleanup resources
*/
destroy(): void {
if (this.debounceTimeout) {
clearTimeout(this.debounceTimeout)
}
if (this.broadcastChannel) {
this.broadcastChannel.close()
}
}
}
// =============================================================================
// FACTORY FUNCTION
// =============================================================================
export function createStateSyncManager(
apiClient: SDKApiClient,
tenantId: string,
options?: SyncOptions,
callbacks?: SyncCallbacks
): StateSyncManager {
return new StateSyncManager(apiClient, tenantId, options, callbacks)
}

View File

@@ -0,0 +1,414 @@
// =============================================================================
// TOM Generator Document Analyzer
// AI-powered analysis of uploaded evidence documents
// =============================================================================
import {
EvidenceDocument,
AIDocumentAnalysis,
ExtractedClause,
DocumentType,
} from '../types'
import {
getDocumentAnalysisPrompt,
getDocumentTypeDetectionPrompt,
DocumentAnalysisPromptContext,
} from './prompts'
import { getAllControls } from '../controls/loader'
// =============================================================================
// TYPES
// =============================================================================
export interface AnalysisResult {
success: boolean
analysis: AIDocumentAnalysis | null
error?: string
}
export interface DocumentTypeDetectionResult {
documentType: DocumentType
confidence: number
reasoning: string
}
// =============================================================================
// DOCUMENT ANALYZER CLASS
// =============================================================================
export class TOMDocumentAnalyzer {
private apiEndpoint: string
private apiKey: string | null
constructor(options?: { apiEndpoint?: string; apiKey?: string }) {
this.apiEndpoint = options?.apiEndpoint || '/api/sdk/v1/tom-generator/evidence/analyze'
this.apiKey = options?.apiKey || null
}
/**
* Analyze a document and extract relevant TOM information
*/
async analyzeDocument(
document: EvidenceDocument,
documentText: string,
language: 'de' | 'en' = 'de'
): Promise<AnalysisResult> {
try {
// Get all control IDs for context
const allControls = getAllControls()
const controlIds = allControls.map((c) => c.id)
// Build the prompt context
const promptContext: DocumentAnalysisPromptContext = {
documentType: document.documentType,
documentText,
controlIds,
language,
}
const prompt = getDocumentAnalysisPrompt(promptContext)
// Call the AI API
const response = await this.callAI(prompt)
if (!response.success || !response.data) {
return {
success: false,
analysis: null,
error: response.error || 'Failed to analyze document',
}
}
// Parse the AI response
const parsedResponse = this.parseAnalysisResponse(response.data)
const analysis: AIDocumentAnalysis = {
summary: parsedResponse.summary,
extractedClauses: parsedResponse.extractedClauses,
applicableControls: parsedResponse.applicableControls,
gaps: parsedResponse.gaps,
confidence: parsedResponse.confidence,
analyzedAt: new Date(),
}
return {
success: true,
analysis,
}
} catch (error) {
return {
success: false,
analysis: null,
error: error instanceof Error ? error.message : 'Unknown error',
}
}
}
/**
* Detect the document type from content
*/
async detectDocumentType(
documentText: string,
filename: string
): Promise<DocumentTypeDetectionResult> {
try {
const prompt = getDocumentTypeDetectionPrompt(documentText, filename)
const response = await this.callAI(prompt)
if (!response.success || !response.data) {
return {
documentType: 'OTHER',
confidence: 0,
reasoning: 'Could not detect document type',
}
}
const parsed = this.parseJSONResponse(response.data)
return {
documentType: this.mapDocumentType(String(parsed.documentType || 'OTHER')),
confidence: typeof parsed.confidence === 'number' ? parsed.confidence : 0,
reasoning: typeof parsed.reasoning === 'string' ? parsed.reasoning : '',
}
} catch (error) {
return {
documentType: 'OTHER',
confidence: 0,
reasoning: error instanceof Error ? error.message : 'Detection failed',
}
}
}
/**
* Link document to applicable controls based on analysis
*/
async suggestControlLinks(
analysis: AIDocumentAnalysis
): Promise<string[]> {
// Use the applicable controls from the analysis
const suggestedControls = [...analysis.applicableControls]
// Also check extracted clauses for related controls
for (const clause of analysis.extractedClauses) {
if (clause.relatedControlId && !suggestedControls.includes(clause.relatedControlId)) {
suggestedControls.push(clause.relatedControlId)
}
}
return suggestedControls
}
/**
* Calculate evidence coverage for a control
*/
calculateEvidenceCoverage(
controlId: string,
documents: EvidenceDocument[]
): {
coverage: number
linkedDocuments: string[]
missingEvidence: string[]
} {
const control = getAllControls().find((c) => c.id === controlId)
if (!control) {
return { coverage: 0, linkedDocuments: [], missingEvidence: [] }
}
const linkedDocuments: string[] = []
const coveredRequirements = new Set<string>()
for (const doc of documents) {
// Check if document is explicitly linked
if (doc.linkedControlIds.includes(controlId)) {
linkedDocuments.push(doc.id)
}
// Check if AI analysis suggests this document covers the control
if (doc.aiAnalysis?.applicableControls.includes(controlId)) {
if (!linkedDocuments.includes(doc.id)) {
linkedDocuments.push(doc.id)
}
}
// Check which evidence requirements are covered
if (doc.aiAnalysis) {
for (const requirement of control.evidenceRequirements) {
const reqLower = requirement.toLowerCase()
if (
doc.aiAnalysis.summary.toLowerCase().includes(reqLower) ||
doc.aiAnalysis.extractedClauses.some((c) =>
c.text.toLowerCase().includes(reqLower)
)
) {
coveredRequirements.add(requirement)
}
}
}
}
const missingEvidence = control.evidenceRequirements.filter(
(req) => !coveredRequirements.has(req)
)
const coverage =
control.evidenceRequirements.length > 0
? Math.round(
(coveredRequirements.size / control.evidenceRequirements.length) * 100
)
: 100
return {
coverage,
linkedDocuments,
missingEvidence,
}
}
/**
* Call the AI API
*/
private async callAI(
prompt: string
): Promise<{ success: boolean; data?: string; error?: string }> {
try {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (this.apiKey) {
headers['Authorization'] = `Bearer ${this.apiKey}`
}
const response = await fetch(this.apiEndpoint, {
method: 'POST',
headers,
body: JSON.stringify({ prompt }),
})
if (!response.ok) {
return {
success: false,
error: `API error: ${response.status} ${response.statusText}`,
}
}
const data = await response.json()
return {
success: true,
data: data.response || data.content || JSON.stringify(data),
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'API call failed',
}
}
}
/**
* Parse the AI analysis response
*/
private parseAnalysisResponse(response: string): {
summary: string
extractedClauses: ExtractedClause[]
applicableControls: string[]
gaps: string[]
confidence: number
} {
const parsed = this.parseJSONResponse(response)
return {
summary: typeof parsed.summary === 'string' ? parsed.summary : '',
extractedClauses: (Array.isArray(parsed.extractedClauses) ? parsed.extractedClauses : []).map(
(clause: Record<string, unknown>) => ({
id: String(clause.id || ''),
text: String(clause.text || ''),
type: String(clause.type || ''),
relatedControlId: clause.relatedControlId
? String(clause.relatedControlId)
: null,
})
),
applicableControls: Array.isArray(parsed.applicableControls)
? parsed.applicableControls.map(String)
: [],
gaps: Array.isArray(parsed.gaps) ? parsed.gaps.map(String) : [],
confidence: typeof parsed.confidence === 'number' ? parsed.confidence : 0,
}
}
/**
* Parse JSON from AI response (handles markdown code blocks)
*/
private parseJSONResponse(response: string): Record<string, unknown> {
let jsonStr = response.trim()
// Remove markdown code blocks if present
if (jsonStr.startsWith('```json')) {
jsonStr = jsonStr.slice(7)
} else if (jsonStr.startsWith('```')) {
jsonStr = jsonStr.slice(3)
}
if (jsonStr.endsWith('```')) {
jsonStr = jsonStr.slice(0, -3)
}
jsonStr = jsonStr.trim()
try {
return JSON.parse(jsonStr)
} catch {
// Try to extract JSON from the response
const jsonMatch = jsonStr.match(/\{[\s\S]*\}/)
if (jsonMatch) {
try {
return JSON.parse(jsonMatch[0])
} catch {
return {}
}
}
return {}
}
}
/**
* Map string to DocumentType
*/
private mapDocumentType(type: string): DocumentType {
const typeMap: Record<string, DocumentType> = {
AVV: 'AVV',
DPA: 'DPA',
SLA: 'SLA',
NDA: 'NDA',
POLICY: 'POLICY',
CERTIFICATE: 'CERTIFICATE',
AUDIT_REPORT: 'AUDIT_REPORT',
OTHER: 'OTHER',
}
return typeMap[type.toUpperCase()] || 'OTHER'
}
}
// =============================================================================
// SINGLETON INSTANCE
// =============================================================================
let analyzerInstance: TOMDocumentAnalyzer | null = null
export function getDocumentAnalyzer(
options?: { apiEndpoint?: string; apiKey?: string }
): TOMDocumentAnalyzer {
if (!analyzerInstance) {
analyzerInstance = new TOMDocumentAnalyzer(options)
}
return analyzerInstance
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
/**
* Quick document analysis
*/
export async function analyzeEvidenceDocument(
document: EvidenceDocument,
documentText: string,
language: 'de' | 'en' = 'de'
): Promise<AnalysisResult> {
return getDocumentAnalyzer().analyzeDocument(document, documentText, language)
}
/**
* Quick document type detection
*/
export async function detectEvidenceDocumentType(
documentText: string,
filename: string
): Promise<DocumentTypeDetectionResult> {
return getDocumentAnalyzer().detectDocumentType(documentText, filename)
}
/**
* Get evidence gaps for all controls
*/
export function getEvidenceGapsForAllControls(
documents: EvidenceDocument[]
): Map<string, { coverage: number; missing: string[] }> {
const analyzer = getDocumentAnalyzer()
const allControls = getAllControls()
const gaps = new Map<string, { coverage: number; missing: string[] }>()
for (const control of allControls) {
const result = analyzer.calculateEvidenceCoverage(control.id, documents)
gaps.set(control.id, {
coverage: result.coverage,
missing: result.missingEvidence,
})
}
return gaps
}

View File

@@ -0,0 +1,427 @@
// =============================================================================
// TOM Generator AI Prompts
// Prompts for document analysis and TOM description generation
// =============================================================================
import {
CompanyProfile,
DataProfile,
ArchitectureProfile,
RiskProfile,
DocumentType,
ControlLibraryEntry,
} from '../types'
// =============================================================================
// DOCUMENT ANALYSIS PROMPT
// =============================================================================
export interface DocumentAnalysisPromptContext {
documentType: DocumentType
documentText: string
controlIds?: string[]
language?: 'de' | 'en'
}
export function getDocumentAnalysisPrompt(
context: DocumentAnalysisPromptContext
): string {
const { documentType, documentText, controlIds, language = 'de' } = context
const controlContext = controlIds?.length
? `\nRELEVANT CONTROL IDS: ${controlIds.join(', ')}`
: ''
if (language === 'de') {
return `Du bist ein Experte für Datenschutz-Compliance und analysierst ein Dokument für die TOM-Dokumentation nach DSGVO Art. 32.
DOKUMENTTYP: ${documentType}
${controlContext}
DOKUMENTTEXT:
${documentText}
AUFGABE: Analysiere das Dokument und extrahiere die folgenden Informationen:
1. SUMMARY: Eine Zusammenfassung in 2-3 Sätzen, die die Relevanz für den Datenschutz beschreibt.
2. EXTRACTED_CLAUSES: Alle Klauseln, die sich auf technische und organisatorische Sicherheitsmaßnahmen beziehen. Für jede Klausel:
- id: Eindeutige ID (z.B. "clause-1")
- text: Der extrahierte Text
- type: Art der Maßnahme (z.B. "encryption", "access-control", "backup", "training")
- relatedControlId: Falls zutreffend, die TOM-Control-ID (z.B. "TOM-ENC-01")
3. APPLICABLE_CONTROLS: Liste der TOM-Control-IDs, die durch dieses Dokument belegt werden könnten.
4. GAPS: Identifizierte Lücken oder fehlende Maßnahmen, die im Dokument nicht adressiert werden.
5. CONFIDENCE: Dein Vertrauenswert für die Analyse (0.0 bis 1.0).
Antworte im JSON-Format:
{
"summary": "...",
"extractedClauses": [
{ "id": "...", "text": "...", "type": "...", "relatedControlId": "..." }
],
"applicableControls": ["TOM-..."],
"gaps": ["..."],
"confidence": 0.85
}`
}
return `You are a data protection compliance expert analyzing a document for TOM documentation according to GDPR Art. 32.
DOCUMENT TYPE: ${documentType}
${controlContext}
DOCUMENT TEXT:
${documentText}
TASK: Analyze the document and extract the following information:
1. SUMMARY: A 2-3 sentence summary describing the relevance for data protection.
2. EXTRACTED_CLAUSES: All clauses related to technical and organizational security measures. For each clause:
- id: Unique ID (e.g., "clause-1")
- text: The extracted text
- type: Type of measure (e.g., "encryption", "access-control", "backup", "training")
- relatedControlId: If applicable, the TOM control ID (e.g., "TOM-ENC-01")
3. APPLICABLE_CONTROLS: List of TOM control IDs that could be evidenced by this document.
4. GAPS: Identified gaps or missing measures not addressed in the document.
5. CONFIDENCE: Your confidence score for the analysis (0.0 to 1.0).
Respond in JSON format:
{
"summary": "...",
"extractedClauses": [
{ "id": "...", "text": "...", "type": "...", "relatedControlId": "..." }
],
"applicableControls": ["TOM-..."],
"gaps": ["..."],
"confidence": 0.85
}`
}
// =============================================================================
// TOM DESCRIPTION GENERATION PROMPT
// =============================================================================
export interface TOMDescriptionPromptContext {
control: ControlLibraryEntry
companyProfile: CompanyProfile
dataProfile: DataProfile
architectureProfile: ArchitectureProfile
riskProfile: RiskProfile
language?: 'de' | 'en'
}
export function getTOMDescriptionPrompt(
context: TOMDescriptionPromptContext
): string {
const {
control,
companyProfile,
dataProfile,
architectureProfile,
riskProfile,
language = 'de',
} = context
if (language === 'de') {
return `Du bist ein Experte für Datenschutz-Compliance und erstellst eine unternehmensspezifische TOM-Beschreibung.
KONTROLLE:
- Name: ${control.name.de}
- Beschreibung: ${control.description.de}
- Kategorie: ${control.category}
- Typ: ${control.type}
UNTERNEHMENSPROFIL:
- Branche: ${companyProfile.industry}
- Größe: ${companyProfile.size}
- Rolle: ${companyProfile.role}
- Produkte/Services: ${companyProfile.products.join(', ')}
DATENPROFIL:
- Datenkategorien: ${dataProfile.categories.join(', ')}
- Besondere Kategorien: ${dataProfile.hasSpecialCategories ? 'Ja' : 'Nein'}
- Betroffene: ${dataProfile.subjects.join(', ')}
- Datenvolumen: ${dataProfile.dataVolume}
ARCHITEKTUR:
- Hosting-Modell: ${architectureProfile.hostingModel}
- Standort: ${architectureProfile.hostingLocation}
- Mandantentrennung: ${architectureProfile.multiTenancy}
SCHUTZBEDARF: ${riskProfile.protectionLevel}
AUFGABE: Erstelle eine unternehmensspezifische Beschreibung dieser TOM in 3-5 Sätzen.
Die Beschreibung soll:
- Auf das spezifische Unternehmensprofil zugeschnitten sein
- Konkrete Maßnahmen beschreiben, die für dieses Unternehmen relevant sind
- In formeller Geschäftssprache verfasst sein
- Keine Platzhalter oder generischen Formulierungen enthalten
Antworte nur mit der Beschreibung, ohne zusätzliche Erklärungen.`
}
return `You are a data protection compliance expert creating a company-specific TOM description.
CONTROL:
- Name: ${control.name.en}
- Description: ${control.description.en}
- Category: ${control.category}
- Type: ${control.type}
COMPANY PROFILE:
- Industry: ${companyProfile.industry}
- Size: ${companyProfile.size}
- Role: ${companyProfile.role}
- Products/Services: ${companyProfile.products.join(', ')}
DATA PROFILE:
- Data Categories: ${dataProfile.categories.join(', ')}
- Special Categories: ${dataProfile.hasSpecialCategories ? 'Yes' : 'No'}
- Data Subjects: ${dataProfile.subjects.join(', ')}
- Data Volume: ${dataProfile.dataVolume}
ARCHITECTURE:
- Hosting Model: ${architectureProfile.hostingModel}
- Location: ${architectureProfile.hostingLocation}
- Multi-tenancy: ${architectureProfile.multiTenancy}
PROTECTION LEVEL: ${riskProfile.protectionLevel}
TASK: Create a company-specific description of this TOM in 3-5 sentences.
The description should:
- Be tailored to the specific company profile
- Describe concrete measures relevant to this company
- Be written in formal business language
- Contain no placeholders or generic formulations
Respond only with the description, without additional explanations.`
}
// =============================================================================
// GAP RECOMMENDATIONS PROMPT
// =============================================================================
export interface GapRecommendationsPromptContext {
missingControls: Array<{ controlId: string; name: string; priority: string }>
partialControls: Array<{ controlId: string; name: string; missingAspects: string[] }>
companyProfile: CompanyProfile
riskProfile: RiskProfile
language?: 'de' | 'en'
}
export function getGapRecommendationsPrompt(
context: GapRecommendationsPromptContext
): string {
const {
missingControls,
partialControls,
companyProfile,
riskProfile,
language = 'de',
} = context
const missingList = missingControls
.map((c) => `- ${c.name} (${c.controlId}, Priorität: ${c.priority})`)
.join('\n')
const partialList = partialControls
.map((c) => `- ${c.name} (${c.controlId}): Fehlend: ${c.missingAspects.join(', ')}`)
.join('\n')
if (language === 'de') {
return `Du bist ein Experte für Datenschutz-Compliance und erstellst Handlungsempfehlungen für TOM-Lücken.
UNTERNEHMEN:
- Branche: ${companyProfile.industry}
- Größe: ${companyProfile.size}
- Rolle: ${companyProfile.role}
SCHUTZBEDARF: ${riskProfile.protectionLevel}
FEHLENDE KONTROLLEN:
${missingList || 'Keine'}
TEILWEISE IMPLEMENTIERTE KONTROLLEN:
${partialList || 'Keine'}
AUFGABE: Erstelle konkrete Handlungsempfehlungen, um die Lücken zu schließen.
Für jede Empfehlung:
1. Priorisiere nach Schutzbedarf und DSGVO-Relevanz
2. Berücksichtige die Unternehmensgröße und Branche
3. Gib konkrete, umsetzbare Schritte an
4. Schätze den Aufwand ein (niedrig/mittel/hoch)
Antworte im JSON-Format:
{
"recommendations": [
{
"priority": "HIGH",
"title": "...",
"description": "...",
"steps": ["..."],
"effort": "MEDIUM",
"relatedControls": ["TOM-..."]
}
],
"summary": "Kurze Zusammenfassung der wichtigsten Maßnahmen"
}`
}
return `You are a data protection compliance expert creating recommendations for TOM gaps.
COMPANY:
- Industry: ${companyProfile.industry}
- Size: ${companyProfile.size}
- Role: ${companyProfile.role}
PROTECTION LEVEL: ${riskProfile.protectionLevel}
MISSING CONTROLS:
${missingList || 'None'}
PARTIALLY IMPLEMENTED CONTROLS:
${partialList || 'None'}
TASK: Create concrete recommendations to close the gaps.
For each recommendation:
1. Prioritize by protection level and GDPR relevance
2. Consider company size and industry
3. Provide concrete, actionable steps
4. Estimate effort (low/medium/high)
Respond in JSON format:
{
"recommendations": [
{
"priority": "HIGH",
"title": "...",
"description": "...",
"steps": ["..."],
"effort": "MEDIUM",
"relatedControls": ["TOM-..."]
}
],
"summary": "Brief summary of the most important measures"
}`
}
// =============================================================================
// DOCUMENT TYPE DETECTION PROMPT
// =============================================================================
export function getDocumentTypeDetectionPrompt(
documentText: string,
filename: string
): string {
return `Du bist ein Experte für Datenschutz-Dokumente und sollst den Dokumenttyp erkennen.
DATEINAME: ${filename}
DOKUMENTTEXT (Auszug):
${documentText.substring(0, 2000)}
MÖGLICHE DOKUMENTTYPEN:
- AVV: Auftragsverarbeitungsvertrag
- DPA: Data Processing Agreement (englisch)
- SLA: Service Level Agreement
- NDA: Geheimhaltungsvereinbarung
- POLICY: Interne Richtlinie (z.B. Passwortrichtlinie, IT-Sicherheitsrichtlinie)
- CERTIFICATE: Zertifikat (z.B. ISO 27001, SOC 2)
- AUDIT_REPORT: Audit-Bericht oder Prüfbericht
- OTHER: Sonstiges Dokument
Antworte im JSON-Format:
{
"documentType": "...",
"confidence": 0.85,
"reasoning": "Kurze Begründung"
}`
}
// =============================================================================
// CLAUSE EXTRACTION PROMPT
// =============================================================================
export function getClauseExtractionPrompt(
documentText: string,
controlCategory: string
): string {
return `Du bist ein Experte für Datenschutz-Compliance und extrahierst Klauseln aus einem Dokument.
GESUCHTE KATEGORIE: ${controlCategory}
DOKUMENTTEXT:
${documentText}
AUFGABE: Extrahiere alle Klauseln, die sich auf die Kategorie "${controlCategory}" beziehen.
Antworte im JSON-Format:
{
"clauses": [
{
"id": "clause-1",
"text": "Der extrahierte Text der Klausel",
"section": "Abschnittsnummer oder -name falls vorhanden",
"relevance": "Kurze Erklärung der Relevanz",
"matchScore": 0.9
}
],
"totalFound": 3
}`
}
// =============================================================================
// COMPLIANCE ASSESSMENT PROMPT
// =============================================================================
export function getComplianceAssessmentPrompt(
tomDescription: string,
evidenceDescriptions: string[],
controlRequirements: string[]
): string {
return `Du bist ein Experte für Datenschutz-Compliance und bewertest die Umsetzung einer TOM.
TOM-BESCHREIBUNG:
${tomDescription}
ANFORDERUNGEN AN NACHWEISE:
${controlRequirements.map((r, i) => `${i + 1}. ${r}`).join('\n')}
VORHANDENE NACHWEISE:
${evidenceDescriptions.map((e, i) => `${i + 1}. ${e}`).join('\n') || 'Keine Nachweise vorhanden'}
AUFGABE: Bewerte den Umsetzungsgrad dieser TOM.
Antworte im JSON-Format:
{
"implementationStatus": "NOT_IMPLEMENTED" | "PARTIAL" | "IMPLEMENTED",
"score": 0-100,
"coveredRequirements": ["..."],
"missingRequirements": ["..."],
"recommendations": ["..."],
"reasoning": "Begründung der Bewertung"
}`
}
// =============================================================================
// EXPORT FUNCTIONS
// =============================================================================
export const AI_PROMPTS = {
documentAnalysis: getDocumentAnalysisPrompt,
tomDescription: getTOMDescriptionPrompt,
gapRecommendations: getGapRecommendationsPrompt,
documentTypeDetection: getDocumentTypeDetectionPrompt,
clauseExtraction: getClauseExtractionPrompt,
complianceAssessment: getComplianceAssessmentPrompt,
}

View File

@@ -0,0 +1,698 @@
'use client'
// =============================================================================
// TOM Generator Context
// State management for the TOM Generator Wizard
// =============================================================================
import React, {
createContext,
useContext,
useReducer,
useCallback,
useEffect,
useRef,
ReactNode,
} from 'react'
import {
TOMGeneratorState,
TOMGeneratorStepId,
CompanyProfile,
DataProfile,
ArchitectureProfile,
SecurityProfile,
RiskProfile,
EvidenceDocument,
DerivedTOM,
GapAnalysisResult,
ExportRecord,
WizardStep,
createInitialTOMGeneratorState,
TOM_GENERATOR_STEPS,
getStepIndex,
calculateProtectionLevel,
isDSFARequired,
hasSpecialCategories,
} from './types'
import { TOMRulesEngine } from './rules-engine'
// =============================================================================
// ACTION TYPES
// =============================================================================
type TOMGeneratorAction =
| { type: 'INITIALIZE'; payload: { tenantId: string; state?: TOMGeneratorState } }
| { type: 'RESET'; payload: { tenantId: string } }
| { type: 'SET_CURRENT_STEP'; payload: TOMGeneratorStepId }
| { type: 'SET_COMPANY_PROFILE'; payload: CompanyProfile }
| { type: 'UPDATE_COMPANY_PROFILE'; payload: Partial<CompanyProfile> }
| { type: 'SET_DATA_PROFILE'; payload: DataProfile }
| { type: 'UPDATE_DATA_PROFILE'; payload: Partial<DataProfile> }
| { type: 'SET_ARCHITECTURE_PROFILE'; payload: ArchitectureProfile }
| { type: 'UPDATE_ARCHITECTURE_PROFILE'; payload: Partial<ArchitectureProfile> }
| { type: 'SET_SECURITY_PROFILE'; payload: SecurityProfile }
| { type: 'UPDATE_SECURITY_PROFILE'; payload: Partial<SecurityProfile> }
| { type: 'SET_RISK_PROFILE'; payload: RiskProfile }
| { type: 'UPDATE_RISK_PROFILE'; payload: Partial<RiskProfile> }
| { type: 'COMPLETE_STEP'; payload: { stepId: TOMGeneratorStepId; data: unknown } }
| { type: 'UNCOMPLETE_STEP'; payload: TOMGeneratorStepId }
| { type: 'ADD_EVIDENCE'; payload: EvidenceDocument }
| { type: 'UPDATE_EVIDENCE'; payload: { id: string; data: Partial<EvidenceDocument> } }
| { type: 'DELETE_EVIDENCE'; payload: string }
| { type: 'SET_DERIVED_TOMS'; payload: DerivedTOM[] }
| { type: 'UPDATE_DERIVED_TOM'; payload: { id: string; data: Partial<DerivedTOM> } }
| { type: 'SET_GAP_ANALYSIS'; payload: GapAnalysisResult }
| { type: 'ADD_EXPORT'; payload: ExportRecord }
| { type: 'LOAD_STATE'; payload: TOMGeneratorState }
// =============================================================================
// REDUCER
// =============================================================================
function tomGeneratorReducer(
state: TOMGeneratorState,
action: TOMGeneratorAction
): TOMGeneratorState {
const updateState = (updates: Partial<TOMGeneratorState>): TOMGeneratorState => ({
...state,
...updates,
updatedAt: new Date(),
})
switch (action.type) {
case 'INITIALIZE': {
if (action.payload.state) {
return action.payload.state
}
return createInitialTOMGeneratorState(action.payload.tenantId)
}
case 'RESET': {
return createInitialTOMGeneratorState(action.payload.tenantId)
}
case 'SET_CURRENT_STEP': {
return updateState({ currentStep: action.payload })
}
case 'SET_COMPANY_PROFILE': {
return updateState({ companyProfile: action.payload })
}
case 'UPDATE_COMPANY_PROFILE': {
if (!state.companyProfile) return state
return updateState({
companyProfile: { ...state.companyProfile, ...action.payload },
})
}
case 'SET_DATA_PROFILE': {
// Automatically set hasSpecialCategories based on categories
const profile: DataProfile = {
...action.payload,
hasSpecialCategories: hasSpecialCategories(action.payload.categories),
}
return updateState({ dataProfile: profile })
}
case 'UPDATE_DATA_PROFILE': {
if (!state.dataProfile) return state
const updatedProfile = { ...state.dataProfile, ...action.payload }
// Recalculate hasSpecialCategories if categories changed
if (action.payload.categories) {
updatedProfile.hasSpecialCategories = hasSpecialCategories(
action.payload.categories
)
}
return updateState({ dataProfile: updatedProfile })
}
case 'SET_ARCHITECTURE_PROFILE': {
return updateState({ architectureProfile: action.payload })
}
case 'UPDATE_ARCHITECTURE_PROFILE': {
if (!state.architectureProfile) return state
return updateState({
architectureProfile: { ...state.architectureProfile, ...action.payload },
})
}
case 'SET_SECURITY_PROFILE': {
return updateState({ securityProfile: action.payload })
}
case 'UPDATE_SECURITY_PROFILE': {
if (!state.securityProfile) return state
return updateState({
securityProfile: { ...state.securityProfile, ...action.payload },
})
}
case 'SET_RISK_PROFILE': {
// Automatically calculate protection level and DSFA requirement
const profile: RiskProfile = {
...action.payload,
protectionLevel: calculateProtectionLevel(action.payload.ciaAssessment),
dsfaRequired: isDSFARequired(state.dataProfile, action.payload),
}
return updateState({ riskProfile: profile })
}
case 'UPDATE_RISK_PROFILE': {
if (!state.riskProfile) return state
const updatedProfile = { ...state.riskProfile, ...action.payload }
// Recalculate protection level if CIA assessment changed
if (action.payload.ciaAssessment) {
updatedProfile.protectionLevel = calculateProtectionLevel(
action.payload.ciaAssessment
)
}
// Recalculate DSFA requirement
updatedProfile.dsfaRequired = isDSFARequired(state.dataProfile, updatedProfile)
return updateState({ riskProfile: updatedProfile })
}
case 'COMPLETE_STEP': {
const updatedSteps = state.steps.map((step) =>
step.id === action.payload.stepId
? {
...step,
completed: true,
data: action.payload.data,
validatedAt: new Date(),
}
: step
)
return updateState({ steps: updatedSteps })
}
case 'UNCOMPLETE_STEP': {
const updatedSteps = state.steps.map((step) =>
step.id === action.payload
? { ...step, completed: false, validatedAt: null }
: step
)
return updateState({ steps: updatedSteps })
}
case 'ADD_EVIDENCE': {
return updateState({
documents: [...state.documents, action.payload],
})
}
case 'UPDATE_EVIDENCE': {
const updatedDocuments = state.documents.map((doc) =>
doc.id === action.payload.id ? { ...doc, ...action.payload.data } : doc
)
return updateState({ documents: updatedDocuments })
}
case 'DELETE_EVIDENCE': {
return updateState({
documents: state.documents.filter((doc) => doc.id !== action.payload),
})
}
case 'SET_DERIVED_TOMS': {
return updateState({ derivedTOMs: action.payload })
}
case 'UPDATE_DERIVED_TOM': {
const updatedTOMs = state.derivedTOMs.map((tom) =>
tom.id === action.payload.id ? { ...tom, ...action.payload.data } : tom
)
return updateState({ derivedTOMs: updatedTOMs })
}
case 'SET_GAP_ANALYSIS': {
return updateState({ gapAnalysis: action.payload })
}
case 'ADD_EXPORT': {
return updateState({
exports: [...state.exports, action.payload],
})
}
case 'LOAD_STATE': {
return action.payload
}
default:
return state
}
}
// =============================================================================
// CONTEXT VALUE INTERFACE
// =============================================================================
interface TOMGeneratorContextValue {
state: TOMGeneratorState
dispatch: React.Dispatch<TOMGeneratorAction>
// Navigation
currentStepIndex: number
totalSteps: number
canGoNext: boolean
canGoPrevious: boolean
goToStep: (stepId: TOMGeneratorStepId) => void
goToNextStep: () => void
goToPreviousStep: () => void
completeCurrentStep: (data: unknown) => void
// Profile setters
setCompanyProfile: (profile: CompanyProfile) => void
updateCompanyProfile: (data: Partial<CompanyProfile>) => void
setDataProfile: (profile: DataProfile) => void
updateDataProfile: (data: Partial<DataProfile>) => void
setArchitectureProfile: (profile: ArchitectureProfile) => void
updateArchitectureProfile: (data: Partial<ArchitectureProfile>) => void
setSecurityProfile: (profile: SecurityProfile) => void
updateSecurityProfile: (data: Partial<SecurityProfile>) => void
setRiskProfile: (profile: RiskProfile) => void
updateRiskProfile: (data: Partial<RiskProfile>) => void
// Evidence management
addEvidence: (document: EvidenceDocument) => void
updateEvidence: (id: string, data: Partial<EvidenceDocument>) => void
deleteEvidence: (id: string) => void
// TOM derivation
deriveTOMs: () => void
updateDerivedTOM: (id: string, data: Partial<DerivedTOM>) => void
// Gap analysis
runGapAnalysis: () => void
// Export
addExport: (record: ExportRecord) => void
// Persistence
saveState: () => Promise<void>
loadState: () => Promise<void>
resetState: () => void
// Status
isStepCompleted: (stepId: TOMGeneratorStepId) => boolean
getCompletionPercentage: () => number
isLoading: boolean
error: string | null
}
// =============================================================================
// CONTEXT
// =============================================================================
const TOMGeneratorContext = createContext<TOMGeneratorContextValue | null>(null)
// =============================================================================
// STORAGE KEYS
// =============================================================================
const STORAGE_KEY_PREFIX = 'tom-generator-state-'
function getStorageKey(tenantId: string): string {
return `${STORAGE_KEY_PREFIX}${tenantId}`
}
// =============================================================================
// PROVIDER COMPONENT
// =============================================================================
interface TOMGeneratorProviderProps {
children: ReactNode
tenantId: string
initialState?: TOMGeneratorState
enablePersistence?: boolean
}
export function TOMGeneratorProvider({
children,
tenantId,
initialState,
enablePersistence = true,
}: TOMGeneratorProviderProps) {
const [state, dispatch] = useReducer(
tomGeneratorReducer,
initialState ?? createInitialTOMGeneratorState(tenantId)
)
const [isLoading, setIsLoading] = React.useState(false)
const [error, setError] = React.useState<string | null>(null)
const rulesEngineRef = useRef<TOMRulesEngine | null>(null)
// Initialize rules engine
useEffect(() => {
if (!rulesEngineRef.current) {
rulesEngineRef.current = new TOMRulesEngine()
}
}, [])
// Load state from localStorage on mount
useEffect(() => {
if (enablePersistence && typeof window !== 'undefined') {
try {
const stored = localStorage.getItem(getStorageKey(tenantId))
if (stored) {
const parsed = JSON.parse(stored)
// Convert date strings back to Date objects
if (parsed.createdAt) parsed.createdAt = new Date(parsed.createdAt)
if (parsed.updatedAt) parsed.updatedAt = new Date(parsed.updatedAt)
if (parsed.steps) {
parsed.steps = parsed.steps.map((step: WizardStep) => ({
...step,
validatedAt: step.validatedAt ? new Date(step.validatedAt) : null,
}))
}
if (parsed.documents) {
parsed.documents = parsed.documents.map((doc: EvidenceDocument) => ({
...doc,
uploadedAt: new Date(doc.uploadedAt),
validFrom: doc.validFrom ? new Date(doc.validFrom) : null,
validUntil: doc.validUntil ? new Date(doc.validUntil) : null,
aiAnalysis: doc.aiAnalysis
? {
...doc.aiAnalysis,
analyzedAt: new Date(doc.aiAnalysis.analyzedAt),
}
: null,
}))
}
if (parsed.derivedTOMs) {
parsed.derivedTOMs = parsed.derivedTOMs.map((tom: DerivedTOM) => ({
...tom,
implementationDate: tom.implementationDate
? new Date(tom.implementationDate)
: null,
reviewDate: tom.reviewDate ? new Date(tom.reviewDate) : null,
}))
}
if (parsed.gapAnalysis?.generatedAt) {
parsed.gapAnalysis.generatedAt = new Date(parsed.gapAnalysis.generatedAt)
}
if (parsed.exports) {
parsed.exports = parsed.exports.map((exp: ExportRecord) => ({
...exp,
generatedAt: new Date(exp.generatedAt),
}))
}
dispatch({ type: 'LOAD_STATE', payload: parsed })
}
} catch (e) {
console.error('Failed to load TOM Generator state from localStorage:', e)
}
}
}, [tenantId, enablePersistence])
// Save state to localStorage on changes
useEffect(() => {
if (enablePersistence && typeof window !== 'undefined') {
try {
localStorage.setItem(getStorageKey(tenantId), JSON.stringify(state))
} catch (e) {
console.error('Failed to save TOM Generator state to localStorage:', e)
}
}
}, [state, tenantId, enablePersistence])
// Navigation helpers
const currentStepIndex = getStepIndex(state.currentStep)
const totalSteps = TOM_GENERATOR_STEPS.length
const canGoNext = currentStepIndex < totalSteps - 1
const canGoPrevious = currentStepIndex > 0
const goToStep = useCallback((stepId: TOMGeneratorStepId) => {
dispatch({ type: 'SET_CURRENT_STEP', payload: stepId })
}, [])
const goToNextStep = useCallback(() => {
if (canGoNext) {
const nextStep = TOM_GENERATOR_STEPS[currentStepIndex + 1]
dispatch({ type: 'SET_CURRENT_STEP', payload: nextStep.id })
}
}, [canGoNext, currentStepIndex])
const goToPreviousStep = useCallback(() => {
if (canGoPrevious) {
const prevStep = TOM_GENERATOR_STEPS[currentStepIndex - 1]
dispatch({ type: 'SET_CURRENT_STEP', payload: prevStep.id })
}
}, [canGoPrevious, currentStepIndex])
const completeCurrentStep = useCallback(
(data: unknown) => {
dispatch({
type: 'COMPLETE_STEP',
payload: { stepId: state.currentStep, data },
})
},
[state.currentStep]
)
// Profile setters
const setCompanyProfile = useCallback((profile: CompanyProfile) => {
dispatch({ type: 'SET_COMPANY_PROFILE', payload: profile })
}, [])
const updateCompanyProfile = useCallback((data: Partial<CompanyProfile>) => {
dispatch({ type: 'UPDATE_COMPANY_PROFILE', payload: data })
}, [])
const setDataProfile = useCallback((profile: DataProfile) => {
dispatch({ type: 'SET_DATA_PROFILE', payload: profile })
}, [])
const updateDataProfile = useCallback((data: Partial<DataProfile>) => {
dispatch({ type: 'UPDATE_DATA_PROFILE', payload: data })
}, [])
const setArchitectureProfile = useCallback((profile: ArchitectureProfile) => {
dispatch({ type: 'SET_ARCHITECTURE_PROFILE', payload: profile })
}, [])
const updateArchitectureProfile = useCallback(
(data: Partial<ArchitectureProfile>) => {
dispatch({ type: 'UPDATE_ARCHITECTURE_PROFILE', payload: data })
},
[]
)
const setSecurityProfile = useCallback((profile: SecurityProfile) => {
dispatch({ type: 'SET_SECURITY_PROFILE', payload: profile })
}, [])
const updateSecurityProfile = useCallback((data: Partial<SecurityProfile>) => {
dispatch({ type: 'UPDATE_SECURITY_PROFILE', payload: data })
}, [])
const setRiskProfile = useCallback((profile: RiskProfile) => {
dispatch({ type: 'SET_RISK_PROFILE', payload: profile })
}, [])
const updateRiskProfile = useCallback((data: Partial<RiskProfile>) => {
dispatch({ type: 'UPDATE_RISK_PROFILE', payload: data })
}, [])
// Evidence management
const addEvidence = useCallback((document: EvidenceDocument) => {
dispatch({ type: 'ADD_EVIDENCE', payload: document })
}, [])
const updateEvidence = useCallback(
(id: string, data: Partial<EvidenceDocument>) => {
dispatch({ type: 'UPDATE_EVIDENCE', payload: { id, data } })
},
[]
)
const deleteEvidence = useCallback((id: string) => {
dispatch({ type: 'DELETE_EVIDENCE', payload: id })
}, [])
// TOM derivation
const deriveTOMs = useCallback(() => {
if (!rulesEngineRef.current) return
const derivedTOMs = rulesEngineRef.current.deriveAllTOMs({
companyProfile: state.companyProfile,
dataProfile: state.dataProfile,
architectureProfile: state.architectureProfile,
securityProfile: state.securityProfile,
riskProfile: state.riskProfile,
})
dispatch({ type: 'SET_DERIVED_TOMS', payload: derivedTOMs })
}, [
state.companyProfile,
state.dataProfile,
state.architectureProfile,
state.securityProfile,
state.riskProfile,
])
const updateDerivedTOM = useCallback(
(id: string, data: Partial<DerivedTOM>) => {
dispatch({ type: 'UPDATE_DERIVED_TOM', payload: { id, data } })
},
[]
)
// Gap analysis
const runGapAnalysis = useCallback(() => {
if (!rulesEngineRef.current) return
const result = rulesEngineRef.current.performGapAnalysis(
state.derivedTOMs,
state.documents
)
dispatch({ type: 'SET_GAP_ANALYSIS', payload: result })
}, [state.derivedTOMs, state.documents])
// Export
const addExport = useCallback((record: ExportRecord) => {
dispatch({ type: 'ADD_EXPORT', payload: record })
}, [])
// Persistence
const saveState = useCallback(async () => {
setIsLoading(true)
setError(null)
try {
// API call to save state
const response = await fetch('/api/sdk/v1/tom-generator/state', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tenantId, state }),
})
if (!response.ok) {
throw new Error('Failed to save state')
}
} catch (e) {
setError(e instanceof Error ? e.message : 'Unknown error')
throw e
} finally {
setIsLoading(false)
}
}, [tenantId, state])
const loadState = useCallback(async () => {
setIsLoading(true)
setError(null)
try {
const response = await fetch(
`/api/sdk/v1/tom-generator/state?tenantId=${tenantId}`
)
if (!response.ok) {
throw new Error('Failed to load state')
}
const data = await response.json()
if (data.state) {
dispatch({ type: 'LOAD_STATE', payload: data.state })
}
} catch (e) {
setError(e instanceof Error ? e.message : 'Unknown error')
throw e
} finally {
setIsLoading(false)
}
}, [tenantId])
const resetState = useCallback(() => {
dispatch({ type: 'RESET', payload: { tenantId } })
}, [tenantId])
// Status helpers
const isStepCompleted = useCallback(
(stepId: TOMGeneratorStepId) => {
const step = state.steps.find((s) => s.id === stepId)
return step?.completed ?? false
},
[state.steps]
)
const getCompletionPercentage = useCallback(() => {
const completedSteps = state.steps.filter((s) => s.completed).length
return Math.round((completedSteps / totalSteps) * 100)
}, [state.steps, totalSteps])
const contextValue: TOMGeneratorContextValue = {
state,
dispatch,
currentStepIndex,
totalSteps,
canGoNext,
canGoPrevious,
goToStep,
goToNextStep,
goToPreviousStep,
completeCurrentStep,
setCompanyProfile,
updateCompanyProfile,
setDataProfile,
updateDataProfile,
setArchitectureProfile,
updateArchitectureProfile,
setSecurityProfile,
updateSecurityProfile,
setRiskProfile,
updateRiskProfile,
addEvidence,
updateEvidence,
deleteEvidence,
deriveTOMs,
updateDerivedTOM,
runGapAnalysis,
addExport,
saveState,
loadState,
resetState,
isStepCompleted,
getCompletionPercentage,
isLoading,
error,
}
return (
<TOMGeneratorContext.Provider value={contextValue}>
{children}
</TOMGeneratorContext.Provider>
)
}
// =============================================================================
// HOOK
// =============================================================================
export function useTOMGenerator(): TOMGeneratorContextValue {
const context = useContext(TOMGeneratorContext)
if (!context) {
throw new Error(
'useTOMGenerator must be used within a TOMGeneratorProvider'
)
}
return context
}
// =============================================================================
// EXPORTS
// =============================================================================
export { TOMGeneratorContext }
export type { TOMGeneratorAction, TOMGeneratorContextValue }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,518 @@
// =============================================================================
// TOM Generator Demo Data
// Sample data for demonstration and testing
// =============================================================================
import {
TOMGeneratorState,
CompanyProfile,
DataProfile,
ArchitectureProfile,
SecurityProfile,
RiskProfile,
EvidenceDocument,
DerivedTOM,
GapAnalysisResult,
TOM_GENERATOR_STEPS,
} from '../types'
import { getTOMRulesEngine } from '../rules-engine'
// =============================================================================
// DEMO COMPANY PROFILES
// =============================================================================
export const DEMO_COMPANY_PROFILES: Record<string, CompanyProfile> = {
saas: {
id: 'demo-company-saas',
name: 'CloudTech Solutions GmbH',
industry: 'Software / SaaS',
size: 'MEDIUM',
role: 'PROCESSOR',
products: ['Cloud CRM', 'Analytics Platform', 'API Services'],
dpoPerson: 'Dr. Maria Schmidt',
dpoEmail: 'dpo@cloudtech.de',
itSecurityContact: 'Thomas Müller',
},
healthcare: {
id: 'demo-company-health',
name: 'MediCare Digital GmbH',
industry: 'Gesundheitswesen / HealthTech',
size: 'SMALL',
role: 'CONTROLLER',
products: ['Patientenportal', 'Telemedizin-App', 'Terminbuchung'],
dpoPerson: 'Dr. Klaus Weber',
dpoEmail: 'datenschutz@medicare.de',
itSecurityContact: 'Anna Bauer',
},
enterprise: {
id: 'demo-company-enterprise',
name: 'GlobalCorp AG',
industry: 'Finanzdienstleistungen',
size: 'ENTERPRISE',
role: 'CONTROLLER',
products: ['Online Banking', 'Investment Platform', 'Payment Services'],
dpoPerson: 'Prof. Dr. Hans Meyer',
dpoEmail: 'privacy@globalcorp.de',
itSecurityContact: 'Security Team',
},
}
// =============================================================================
// DEMO DATA PROFILES
// =============================================================================
export const DEMO_DATA_PROFILES: Record<string, DataProfile> = {
saas: {
categories: ['IDENTIFICATION', 'CONTACT', 'PROFESSIONAL', 'BEHAVIORAL'],
subjects: ['CUSTOMERS', 'EMPLOYEES'],
hasSpecialCategories: false,
processesMinors: false,
dataVolume: 'HIGH',
thirdCountryTransfers: true,
thirdCountryList: ['USA'],
},
healthcare: {
categories: ['IDENTIFICATION', 'CONTACT', 'HEALTH', 'BIOMETRIC'],
subjects: ['PATIENTS', 'EMPLOYEES'],
hasSpecialCategories: true,
processesMinors: true,
dataVolume: 'MEDIUM',
thirdCountryTransfers: false,
thirdCountryList: [],
},
enterprise: {
categories: ['IDENTIFICATION', 'CONTACT', 'FINANCIAL', 'BEHAVIORAL'],
subjects: ['CUSTOMERS', 'EMPLOYEES', 'PROSPECTS'],
hasSpecialCategories: false,
processesMinors: false,
dataVolume: 'VERY_HIGH',
thirdCountryTransfers: true,
thirdCountryList: ['USA', 'UK', 'Schweiz'],
},
}
// =============================================================================
// DEMO ARCHITECTURE PROFILES
// =============================================================================
export const DEMO_ARCHITECTURE_PROFILES: Record<string, ArchitectureProfile> = {
saas: {
hostingModel: 'PUBLIC_CLOUD',
hostingLocation: 'EU',
providers: [
{ name: 'AWS', location: 'EU', certifications: ['ISO 27001', 'SOC 2', 'C5'] },
{ name: 'Cloudflare', location: 'EU', certifications: ['ISO 27001'] },
],
multiTenancy: 'MULTI_TENANT',
hasSubprocessors: true,
subprocessorCount: 5,
encryptionAtRest: true,
encryptionInTransit: true,
},
healthcare: {
hostingModel: 'PRIVATE_CLOUD',
hostingLocation: 'DE',
providers: [
{ name: 'Telekom Cloud', location: 'DE', certifications: ['ISO 27001', 'C5', 'TISAX'] },
],
multiTenancy: 'SINGLE_TENANT',
hasSubprocessors: true,
subprocessorCount: 2,
encryptionAtRest: true,
encryptionInTransit: true,
},
enterprise: {
hostingModel: 'HYBRID',
hostingLocation: 'DE',
providers: [
{ name: 'Private Datacenter', location: 'DE', certifications: ['ISO 27001', 'SOC 2'] },
{ name: 'Azure', location: 'EU', certifications: ['ISO 27001', 'C5', 'SOC 2'] },
],
multiTenancy: 'DEDICATED',
hasSubprocessors: true,
subprocessorCount: 10,
encryptionAtRest: true,
encryptionInTransit: true,
},
}
// =============================================================================
// DEMO SECURITY PROFILES
// =============================================================================
export const DEMO_SECURITY_PROFILES: Record<string, SecurityProfile> = {
saas: {
authMethods: [
{ type: 'PASSWORD', provider: null },
{ type: 'MFA', provider: 'Auth0' },
{ type: 'SSO', provider: 'Auth0' },
],
hasMFA: true,
hasSSO: true,
hasIAM: true,
hasPAM: false,
hasEncryptionAtRest: true,
hasEncryptionInTransit: true,
hasLogging: true,
logRetentionDays: 90,
hasBackup: true,
backupFrequency: 'DAILY',
backupRetentionDays: 30,
hasDRPlan: true,
rtoHours: 4,
rpoHours: 1,
hasVulnerabilityManagement: true,
hasPenetrationTests: true,
hasSecurityTraining: true,
},
healthcare: {
authMethods: [
{ type: 'PASSWORD', provider: null },
{ type: 'MFA', provider: 'Microsoft Authenticator' },
{ type: 'CERTIFICATE', provider: 'Internal PKI' },
],
hasMFA: true,
hasSSO: false,
hasIAM: true,
hasPAM: true,
hasEncryptionAtRest: true,
hasEncryptionInTransit: true,
hasLogging: true,
logRetentionDays: 365,
hasBackup: true,
backupFrequency: 'HOURLY',
backupRetentionDays: 90,
hasDRPlan: true,
rtoHours: 2,
rpoHours: 0.5,
hasVulnerabilityManagement: true,
hasPenetrationTests: true,
hasSecurityTraining: true,
},
enterprise: {
authMethods: [
{ type: 'PASSWORD', provider: null },
{ type: 'MFA', provider: 'Okta' },
{ type: 'SSO', provider: 'Okta' },
{ type: 'BIOMETRIC', provider: 'Windows Hello' },
],
hasMFA: true,
hasSSO: true,
hasIAM: true,
hasPAM: true,
hasEncryptionAtRest: true,
hasEncryptionInTransit: true,
hasLogging: true,
logRetentionDays: 730,
hasBackup: true,
backupFrequency: 'HOURLY',
backupRetentionDays: 365,
hasDRPlan: true,
rtoHours: 1,
rpoHours: 0.25,
hasVulnerabilityManagement: true,
hasPenetrationTests: true,
hasSecurityTraining: true,
},
}
// =============================================================================
// DEMO RISK PROFILES
// =============================================================================
export const DEMO_RISK_PROFILES: Record<string, RiskProfile> = {
saas: {
ciaAssessment: {
confidentiality: 3,
integrity: 3,
availability: 4,
justification: 'Als SaaS-Anbieter ist die Verfügbarkeit kritisch für unsere Kunden. Vertraulichkeit und Integrität sind wichtig aufgrund der verarbeiteten Geschäftsdaten.',
},
protectionLevel: 'HIGH',
specialRisks: ['Cloud-Abhängigkeit', 'Multi-Mandanten-Umgebung'],
regulatoryRequirements: ['DSGVO', 'Kundenvorgaben'],
hasHighRiskProcessing: false,
dsfaRequired: false,
},
healthcare: {
ciaAssessment: {
confidentiality: 5,
integrity: 5,
availability: 4,
justification: 'Gesundheitsdaten erfordern höchsten Schutz. Fehlerhafte Daten können Patientensicherheit gefährden.',
},
protectionLevel: 'VERY_HIGH',
specialRisks: ['Gesundheitsdaten', 'Minderjährige', 'Telemedizin'],
regulatoryRequirements: ['DSGVO', 'SGB', 'MDR'],
hasHighRiskProcessing: true,
dsfaRequired: true,
},
enterprise: {
ciaAssessment: {
confidentiality: 4,
integrity: 5,
availability: 5,
justification: 'Finanzdienstleistungen erfordern höchste Integrität und Verfügbarkeit. Vertraulichkeit ist kritisch für Kundendaten und Transaktionen.',
},
protectionLevel: 'VERY_HIGH',
specialRisks: ['Finanztransaktionen', 'Regulatorische Auflagen', 'Cyber-Risiken'],
regulatoryRequirements: ['DSGVO', 'MaRisk', 'BAIT', 'PSD2'],
hasHighRiskProcessing: true,
dsfaRequired: true,
},
}
// =============================================================================
// DEMO EVIDENCE DOCUMENTS
// =============================================================================
export const DEMO_EVIDENCE_DOCUMENTS: EvidenceDocument[] = [
{
id: 'demo-evidence-1',
filename: 'iso27001-certificate.pdf',
originalName: 'ISO 27001 Zertifikat.pdf',
mimeType: 'application/pdf',
size: 245678,
uploadedAt: new Date('2025-01-15'),
uploadedBy: 'admin@company.de',
documentType: 'CERTIFICATE',
detectedType: 'CERTIFICATE',
hash: 'sha256:abc123def456',
validFrom: new Date('2024-06-01'),
validUntil: new Date('2027-05-31'),
linkedControlIds: ['TOM-RV-04', 'TOM-AZ-01'],
aiAnalysis: {
summary: 'ISO 27001:2022 Zertifikat bestätigt die Implementierung eines Informationssicherheits-Managementsystems.',
extractedClauses: [
{
id: 'clause-1',
text: 'Zertifiziert nach ISO/IEC 27001:2022',
type: 'certification',
relatedControlId: 'TOM-RV-04',
},
],
applicableControls: ['TOM-RV-04', 'TOM-AZ-01', 'TOM-RV-01'],
gaps: [],
confidence: 0.95,
analyzedAt: new Date('2025-01-15'),
},
status: 'VERIFIED',
},
{
id: 'demo-evidence-2',
filename: 'passwort-richtlinie.pdf',
originalName: 'Passwortrichtlinie v2.1.pdf',
mimeType: 'application/pdf',
size: 128456,
uploadedAt: new Date('2025-01-10'),
uploadedBy: 'admin@company.de',
documentType: 'POLICY',
detectedType: 'POLICY',
hash: 'sha256:xyz789abc012',
validFrom: new Date('2024-09-01'),
validUntil: null,
linkedControlIds: ['TOM-ADM-02'],
aiAnalysis: {
summary: 'Interne Passwortrichtlinie definiert Anforderungen an Passwortlänge, Komplexität und Wechselintervalle.',
extractedClauses: [
{
id: 'clause-1',
text: 'Mindestlänge 12 Zeichen, Groß-/Kleinbuchstaben, Zahlen und Sonderzeichen erforderlich',
type: 'password-policy',
relatedControlId: 'TOM-ADM-02',
},
{
id: 'clause-2',
text: 'Passwörter müssen alle 90 Tage geändert werden',
type: 'password-policy',
relatedControlId: 'TOM-ADM-02',
},
],
applicableControls: ['TOM-ADM-02'],
gaps: ['Keine Regelung zur Passwort-Historie gefunden'],
confidence: 0.85,
analyzedAt: new Date('2025-01-10'),
},
status: 'ANALYZED',
},
{
id: 'demo-evidence-3',
filename: 'aws-avv.pdf',
originalName: 'AWS Data Processing Addendum.pdf',
mimeType: 'application/pdf',
size: 456789,
uploadedAt: new Date('2025-01-05'),
uploadedBy: 'admin@company.de',
documentType: 'AVV',
detectedType: 'DPA',
hash: 'sha256:qwe123rty456',
validFrom: new Date('2024-01-01'),
validUntil: null,
linkedControlIds: ['TOM-OR-01', 'TOM-OR-02'],
aiAnalysis: {
summary: 'AWS Data Processing Addendum regelt die Auftragsverarbeitung durch AWS als Unterauftragsverarbeiter.',
extractedClauses: [
{
id: 'clause-1',
text: 'AWS verpflichtet sich zur Einhaltung der DSGVO-Anforderungen',
type: 'data-processing',
relatedControlId: 'TOM-OR-01',
},
{
id: 'clause-2',
text: 'Jährliche SOC 2 und ISO 27001 Audits werden durchgeführt',
type: 'audit',
relatedControlId: 'TOM-OR-02',
},
],
applicableControls: ['TOM-OR-01', 'TOM-OR-02', 'TOM-OR-04'],
gaps: [],
confidence: 0.9,
analyzedAt: new Date('2025-01-05'),
},
status: 'VERIFIED',
},
]
// =============================================================================
// DEMO STATE GENERATOR
// =============================================================================
export type DemoScenario = 'saas' | 'healthcare' | 'enterprise'
/**
* Generate a complete demo state for a given scenario
*/
export function generateDemoState(
tenantId: string,
scenario: DemoScenario = 'saas'
): TOMGeneratorState {
const companyProfile = DEMO_COMPANY_PROFILES[scenario]
const dataProfile = DEMO_DATA_PROFILES[scenario]
const architectureProfile = DEMO_ARCHITECTURE_PROFILES[scenario]
const securityProfile = DEMO_SECURITY_PROFILES[scenario]
const riskProfile = DEMO_RISK_PROFILES[scenario]
// Generate derived TOMs using the rules engine
const rulesEngine = getTOMRulesEngine()
const derivedTOMs = rulesEngine.deriveAllTOMs({
companyProfile,
dataProfile,
architectureProfile,
securityProfile,
riskProfile,
})
// Set some TOMs as implemented for demo
const implementedTOMs = derivedTOMs.map((tom, index) => ({
...tom,
implementationStatus:
index % 3 === 0
? 'IMPLEMENTED' as const
: index % 3 === 1
? 'PARTIAL' as const
: 'NOT_IMPLEMENTED' as const,
responsiblePerson:
index % 2 === 0 ? 'IT Security Team' : 'Datenschutzbeauftragter',
implementationDate:
index % 3 === 0 ? new Date('2024-06-15') : null,
}))
// Generate gap analysis
const gapAnalysis = rulesEngine.performGapAnalysis(
implementedTOMs,
DEMO_EVIDENCE_DOCUMENTS
)
const now = new Date()
return {
id: `demo-state-${scenario}-${Date.now()}`,
tenantId,
companyProfile,
dataProfile,
architectureProfile,
securityProfile,
riskProfile,
currentStep: 'review-export',
steps: TOM_GENERATOR_STEPS.map((step) => ({
id: step.id,
completed: true,
data: null,
validatedAt: now,
})),
documents: DEMO_EVIDENCE_DOCUMENTS,
derivedTOMs: implementedTOMs,
gapAnalysis,
exports: [],
createdAt: now,
updatedAt: now,
}
}
/**
* Generate an empty starter state
*/
export function generateEmptyState(tenantId: string): TOMGeneratorState {
const now = new Date()
return {
id: `new-state-${Date.now()}`,
tenantId,
companyProfile: null,
dataProfile: null,
architectureProfile: null,
securityProfile: null,
riskProfile: null,
currentStep: 'scope-roles',
steps: TOM_GENERATOR_STEPS.map((step) => ({
id: step.id,
completed: false,
data: null,
validatedAt: null,
})),
documents: [],
derivedTOMs: [],
gapAnalysis: null,
exports: [],
createdAt: now,
updatedAt: now,
}
}
/**
* Generate partial state (first 3 steps completed)
*/
export function generatePartialState(
tenantId: string,
scenario: DemoScenario = 'saas'
): TOMGeneratorState {
const state = generateEmptyState(tenantId)
const now = new Date()
state.companyProfile = DEMO_COMPANY_PROFILES[scenario]
state.dataProfile = DEMO_DATA_PROFILES[scenario]
state.architectureProfile = DEMO_ARCHITECTURE_PROFILES[scenario]
state.currentStep = 'security-profile'
state.steps = state.steps.map((step, index) => ({
...step,
completed: index < 3,
validatedAt: index < 3 ? now : null,
}))
return state
}
// =============================================================================
// EXPORTS
// =============================================================================
export {
DEMO_COMPANY_PROFILES as demoCompanyProfiles,
DEMO_DATA_PROFILES as demoDataProfiles,
DEMO_ARCHITECTURE_PROFILES as demoArchitectureProfiles,
DEMO_SECURITY_PROFILES as demoSecurityProfiles,
DEMO_RISK_PROFILES as demoRiskProfiles,
DEMO_EVIDENCE_DOCUMENTS as demoEvidenceDocuments,
}

View File

@@ -0,0 +1,67 @@
// =============================================================================
// TOM Generator Evidence Store
// Shared in-memory storage for evidence documents
// =============================================================================
import { EvidenceDocument, DocumentType } from './types'
interface StoredEvidence {
tenantId: string
documents: EvidenceDocument[]
}
class InMemoryEvidenceStore {
private store: Map<string, StoredEvidence> = new Map()
async getAll(tenantId: string): Promise<EvidenceDocument[]> {
const stored = this.store.get(tenantId)
return stored?.documents || []
}
async getById(tenantId: string, documentId: string): Promise<EvidenceDocument | null> {
const stored = this.store.get(tenantId)
return stored?.documents.find((d) => d.id === documentId) || null
}
async add(tenantId: string, document: EvidenceDocument): Promise<EvidenceDocument> {
const stored = this.store.get(tenantId) || { tenantId, documents: [] }
stored.documents.push(document)
this.store.set(tenantId, stored)
return document
}
async update(tenantId: string, documentId: string, updates: Partial<EvidenceDocument>): Promise<EvidenceDocument | null> {
const stored = this.store.get(tenantId)
if (!stored) return null
const index = stored.documents.findIndex((d) => d.id === documentId)
if (index === -1) return null
stored.documents[index] = { ...stored.documents[index], ...updates }
this.store.set(tenantId, stored)
return stored.documents[index]
}
async delete(tenantId: string, documentId: string): Promise<boolean> {
const stored = this.store.get(tenantId)
if (!stored) return false
const initialLength = stored.documents.length
stored.documents = stored.documents.filter((d) => d.id !== documentId)
this.store.set(tenantId, stored)
return stored.documents.length < initialLength
}
async getByType(tenantId: string, type: DocumentType): Promise<EvidenceDocument[]> {
const stored = this.store.get(tenantId)
return stored?.documents.filter((d) => d.documentType === type) || []
}
async getByStatus(tenantId: string, status: string): Promise<EvidenceDocument[]> {
const stored = this.store.get(tenantId)
return stored?.documents.filter((d) => d.status === status) || []
}
}
// Singleton instance for the application
export const evidenceStore = new InMemoryEvidenceStore()

View File

@@ -0,0 +1,525 @@
// =============================================================================
// TOM Generator DOCX Export
// Export TOMs to Microsoft Word format
// =============================================================================
import {
TOMGeneratorState,
DerivedTOM,
ControlCategory,
CONTROL_CATEGORIES,
} from '../types'
import { getControlById, getCategoryMetadata } from '../controls/loader'
// =============================================================================
// TYPES
// =============================================================================
export interface DOCXExportOptions {
language: 'de' | 'en'
includeNotApplicable: boolean
includeEvidence: boolean
includeGapAnalysis: boolean
companyLogo?: string
primaryColor?: string
}
const DEFAULT_OPTIONS: DOCXExportOptions = {
language: 'de',
includeNotApplicable: false,
includeEvidence: true,
includeGapAnalysis: true,
primaryColor: '#1a56db',
}
// =============================================================================
// DOCX CONTENT GENERATION
// =============================================================================
export interface DocxParagraph {
type: 'paragraph' | 'heading1' | 'heading2' | 'heading3' | 'bullet'
content: string
style?: Record<string, string>
}
export interface DocxTableRow {
cells: string[]
isHeader?: boolean
}
export interface DocxTable {
type: 'table'
headers: string[]
rows: DocxTableRow[]
}
export type DocxElement = DocxParagraph | DocxTable
/**
* Generate DOCX content structure for TOMs
*/
export function generateDOCXContent(
state: TOMGeneratorState,
options: Partial<DOCXExportOptions> = {}
): DocxElement[] {
const opts = { ...DEFAULT_OPTIONS, ...options }
const elements: DocxElement[] = []
// Title page
elements.push({
type: 'heading1',
content: opts.language === 'de'
? 'Technische und Organisatorische Maßnahmen (TOMs)'
: 'Technical and Organizational Measures (TOMs)',
})
elements.push({
type: 'paragraph',
content: opts.language === 'de'
? `gemäß Art. 32 DSGVO`
: 'according to Art. 32 GDPR',
})
// Company info
if (state.companyProfile) {
elements.push({
type: 'heading2',
content: opts.language === 'de' ? 'Unternehmen' : 'Company',
})
elements.push({
type: 'paragraph',
content: `${state.companyProfile.name}`,
})
elements.push({
type: 'paragraph',
content: opts.language === 'de'
? `Branche: ${state.companyProfile.industry}`
: `Industry: ${state.companyProfile.industry}`,
})
elements.push({
type: 'paragraph',
content: opts.language === 'de'
? `Rolle: ${formatRole(state.companyProfile.role, opts.language)}`
: `Role: ${formatRole(state.companyProfile.role, opts.language)}`,
})
if (state.companyProfile.dpoPerson) {
elements.push({
type: 'paragraph',
content: opts.language === 'de'
? `Datenschutzbeauftragter: ${state.companyProfile.dpoPerson}`
: `Data Protection Officer: ${state.companyProfile.dpoPerson}`,
})
}
}
// Document metadata
elements.push({
type: 'paragraph',
content: opts.language === 'de'
? `Stand: ${new Date().toLocaleDateString('de-DE')}`
: `Date: ${new Date().toLocaleDateString('en-US')}`,
})
// Protection level summary
if (state.riskProfile) {
elements.push({
type: 'heading2',
content: opts.language === 'de' ? 'Schutzbedarf' : 'Protection Level',
})
elements.push({
type: 'paragraph',
content: opts.language === 'de'
? `Ermittelter Schutzbedarf: ${formatProtectionLevel(state.riskProfile.protectionLevel, opts.language)}`
: `Determined Protection Level: ${formatProtectionLevel(state.riskProfile.protectionLevel, opts.language)}`,
})
elements.push({
type: 'paragraph',
content: opts.language === 'de'
? `CIA-Bewertung: Vertraulichkeit ${state.riskProfile.ciaAssessment.confidentiality}/5, Integrität ${state.riskProfile.ciaAssessment.integrity}/5, Verfügbarkeit ${state.riskProfile.ciaAssessment.availability}/5`
: `CIA Assessment: Confidentiality ${state.riskProfile.ciaAssessment.confidentiality}/5, Integrity ${state.riskProfile.ciaAssessment.integrity}/5, Availability ${state.riskProfile.ciaAssessment.availability}/5`,
})
if (state.riskProfile.dsfaRequired) {
elements.push({
type: 'paragraph',
content: opts.language === 'de'
? '⚠️ Eine Datenschutz-Folgenabschätzung (DSFA) ist erforderlich.'
: '⚠️ A Data Protection Impact Assessment (DPIA) is required.',
})
}
}
// TOMs by category
elements.push({
type: 'heading2',
content: opts.language === 'de'
? 'Übersicht der Maßnahmen'
: 'Measures Overview',
})
// Group TOMs by category
const tomsByCategory = groupTOMsByCategory(state.derivedTOMs, opts.includeNotApplicable)
for (const category of CONTROL_CATEGORIES) {
const categoryTOMs = tomsByCategory.get(category.id)
if (!categoryTOMs || categoryTOMs.length === 0) continue
const categoryName = category.name[opts.language]
elements.push({
type: 'heading3',
content: `${categoryName} (${category.gdprReference})`,
})
// Create table for this category
const tableHeaders = opts.language === 'de'
? ['ID', 'Maßnahme', 'Typ', 'Status', 'Anwendbarkeit']
: ['ID', 'Measure', 'Type', 'Status', 'Applicability']
const tableRows: DocxTableRow[] = categoryTOMs.map((tom) => ({
cells: [
tom.controlId,
tom.name,
formatType(getControlById(tom.controlId)?.type || 'TECHNICAL', opts.language),
formatImplementationStatus(tom.implementationStatus, opts.language),
formatApplicability(tom.applicability, opts.language),
],
}))
elements.push({
type: 'table',
headers: tableHeaders,
rows: tableRows,
})
// Add detailed descriptions
for (const tom of categoryTOMs) {
if (tom.applicability === 'NOT_APPLICABLE' && !opts.includeNotApplicable) {
continue
}
elements.push({
type: 'paragraph',
content: `**${tom.controlId}: ${tom.name}**`,
})
elements.push({
type: 'paragraph',
content: tom.aiGeneratedDescription || tom.description,
})
elements.push({
type: 'bullet',
content: opts.language === 'de'
? `Anwendbarkeit: ${formatApplicability(tom.applicability, opts.language)}`
: `Applicability: ${formatApplicability(tom.applicability, opts.language)}`,
})
elements.push({
type: 'bullet',
content: opts.language === 'de'
? `Begründung: ${tom.applicabilityReason}`
: `Reason: ${tom.applicabilityReason}`,
})
elements.push({
type: 'bullet',
content: opts.language === 'de'
? `Umsetzungsstatus: ${formatImplementationStatus(tom.implementationStatus, opts.language)}`
: `Implementation Status: ${formatImplementationStatus(tom.implementationStatus, opts.language)}`,
})
if (tom.responsiblePerson) {
elements.push({
type: 'bullet',
content: opts.language === 'de'
? `Verantwortlich: ${tom.responsiblePerson}`
: `Responsible: ${tom.responsiblePerson}`,
})
}
if (opts.includeEvidence && tom.linkedEvidence.length > 0) {
elements.push({
type: 'bullet',
content: opts.language === 'de'
? `Nachweise: ${tom.linkedEvidence.length} Dokument(e) verknüpft`
: `Evidence: ${tom.linkedEvidence.length} document(s) linked`,
})
}
if (tom.evidenceGaps.length > 0) {
elements.push({
type: 'bullet',
content: opts.language === 'de'
? `Fehlende Nachweise: ${tom.evidenceGaps.join(', ')}`
: `Missing Evidence: ${tom.evidenceGaps.join(', ')}`,
})
}
}
}
// Gap Analysis
if (opts.includeGapAnalysis && state.gapAnalysis) {
elements.push({
type: 'heading2',
content: opts.language === 'de' ? 'Lückenanalyse' : 'Gap Analysis',
})
elements.push({
type: 'paragraph',
content: opts.language === 'de'
? `Gesamtscore: ${state.gapAnalysis.overallScore}%`
: `Overall Score: ${state.gapAnalysis.overallScore}%`,
})
if (state.gapAnalysis.missingControls.length > 0) {
elements.push({
type: 'heading3',
content: opts.language === 'de'
? 'Fehlende Maßnahmen'
: 'Missing Measures',
})
for (const missing of state.gapAnalysis.missingControls) {
const control = getControlById(missing.controlId)
elements.push({
type: 'bullet',
content: `${missing.controlId}: ${control?.name[opts.language] || 'Unknown'} (${missing.priority})`,
})
}
}
if (state.gapAnalysis.recommendations.length > 0) {
elements.push({
type: 'heading3',
content: opts.language === 'de' ? 'Empfehlungen' : 'Recommendations',
})
for (const rec of state.gapAnalysis.recommendations) {
elements.push({
type: 'bullet',
content: rec,
})
}
}
}
// Footer
elements.push({
type: 'paragraph',
content: opts.language === 'de'
? `Dieses Dokument wurde automatisch generiert mit dem TOM Generator am ${new Date().toLocaleDateString('de-DE')}.`
: `This document was automatically generated with the TOM Generator on ${new Date().toLocaleDateString('en-US')}.`,
})
return elements
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
function groupTOMsByCategory(
toms: DerivedTOM[],
includeNotApplicable: boolean
): Map<ControlCategory, DerivedTOM[]> {
const grouped = new Map<ControlCategory, DerivedTOM[]>()
for (const tom of toms) {
if (!includeNotApplicable && tom.applicability === 'NOT_APPLICABLE') {
continue
}
const control = getControlById(tom.controlId)
if (!control) continue
const category = control.category
const existing = grouped.get(category) || []
existing.push(tom)
grouped.set(category, existing)
}
return grouped
}
function formatRole(role: string, language: 'de' | 'en'): string {
const roles: Record<string, Record<'de' | 'en', string>> = {
CONTROLLER: { de: 'Verantwortlicher', en: 'Controller' },
PROCESSOR: { de: 'Auftragsverarbeiter', en: 'Processor' },
JOINT_CONTROLLER: { de: 'Gemeinsam Verantwortlicher', en: 'Joint Controller' },
}
return roles[role]?.[language] || role
}
function formatProtectionLevel(level: string, language: 'de' | 'en'): string {
const levels: Record<string, Record<'de' | 'en', string>> = {
NORMAL: { de: 'Normal', en: 'Normal' },
HIGH: { de: 'Hoch', en: 'High' },
VERY_HIGH: { de: 'Sehr hoch', en: 'Very High' },
}
return levels[level]?.[language] || level
}
function formatType(type: string, language: 'de' | 'en'): string {
const types: Record<string, Record<'de' | 'en', string>> = {
TECHNICAL: { de: 'Technisch', en: 'Technical' },
ORGANIZATIONAL: { de: 'Organisatorisch', en: 'Organizational' },
}
return types[type]?.[language] || type
}
function formatImplementationStatus(status: string, language: 'de' | 'en'): string {
const statuses: Record<string, Record<'de' | 'en', string>> = {
NOT_IMPLEMENTED: { de: 'Nicht umgesetzt', en: 'Not Implemented' },
PARTIAL: { de: 'Teilweise umgesetzt', en: 'Partially Implemented' },
IMPLEMENTED: { de: 'Umgesetzt', en: 'Implemented' },
}
return statuses[status]?.[language] || status
}
function formatApplicability(applicability: string, language: 'de' | 'en'): string {
const apps: Record<string, Record<'de' | 'en', string>> = {
REQUIRED: { de: 'Erforderlich', en: 'Required' },
RECOMMENDED: { de: 'Empfohlen', en: 'Recommended' },
OPTIONAL: { de: 'Optional', en: 'Optional' },
NOT_APPLICABLE: { de: 'Nicht anwendbar', en: 'Not Applicable' },
}
return apps[applicability]?.[language] || applicability
}
// =============================================================================
// DOCX BLOB GENERATION
// Uses simple XML structure compatible with docx libraries
// =============================================================================
/**
* Generate a DOCX file as a Blob
* Note: For production, use docx library (npm install docx)
* This is a simplified version that generates XML-based content
*/
export async function generateDOCXBlob(
state: TOMGeneratorState,
options: Partial<DOCXExportOptions> = {}
): Promise<Blob> {
const content = generateDOCXContent(state, options)
// Generate simple HTML that can be converted to DOCX
// In production, use the docx library for proper DOCX generation
const html = generateHTMLFromContent(content, options)
// Return as a Word-compatible HTML blob
// The proper way would be to use the docx library
const blob = new Blob([html], {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
})
return blob
}
function generateHTMLFromContent(
content: DocxElement[],
options: Partial<DOCXExportOptions>
): string {
const opts = { ...DEFAULT_OPTIONS, ...options }
let html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: Calibri, Arial, sans-serif; font-size: 11pt; line-height: 1.5; }
h1 { font-size: 24pt; color: ${opts.primaryColor}; border-bottom: 2px solid ${opts.primaryColor}; }
h2 { font-size: 18pt; color: ${opts.primaryColor}; margin-top: 24pt; }
h3 { font-size: 14pt; color: #333; margin-top: 18pt; }
table { border-collapse: collapse; width: 100%; margin: 12pt 0; }
th, td { border: 1px solid #ddd; padding: 8pt; text-align: left; }
th { background-color: ${opts.primaryColor}; color: white; }
tr:nth-child(even) { background-color: #f9f9f9; }
ul { margin: 6pt 0; }
li { margin: 3pt 0; }
.warning { color: #dc2626; font-weight: bold; }
</style>
</head>
<body>
`
for (const element of content) {
if (element.type === 'table') {
html += '<table>'
html += '<tr>'
for (const header of element.headers) {
html += `<th>${escapeHtml(header)}</th>`
}
html += '</tr>'
for (const row of element.rows) {
html += '<tr>'
for (const cell of row.cells) {
html += `<td>${escapeHtml(cell)}</td>`
}
html += '</tr>'
}
html += '</table>'
} else {
const tag = getHtmlTag(element.type)
const processedContent = processContent(element.content)
html += `<${tag}>${processedContent}</${tag}>\n`
}
}
html += '</body></html>'
return html
}
function getHtmlTag(type: string): string {
switch (type) {
case 'heading1':
return 'h1'
case 'heading2':
return 'h2'
case 'heading3':
return 'h3'
case 'bullet':
return 'li'
default:
return 'p'
}
}
function escapeHtml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
}
function processContent(content: string): string {
// Convert markdown-style bold to HTML
return escapeHtml(content).replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
}
// =============================================================================
// FILENAME GENERATION
// =============================================================================
/**
* Generate a filename for the DOCX export
*/
export function generateDOCXFilename(
state: TOMGeneratorState,
language: 'de' | 'en' = 'de'
): string {
const companyName = state.companyProfile?.name?.replace(/[^a-zA-Z0-9]/g, '-') || 'unknown'
const date = new Date().toISOString().split('T')[0]
const prefix = language === 'de' ? 'TOMs' : 'TOMs'
return `${prefix}-${companyName}-${date}.docx`
}
// Types are exported at their definition site above

View File

@@ -0,0 +1,517 @@
// =============================================================================
// TOM Generator PDF Export
// Export TOMs to PDF format
// =============================================================================
import {
TOMGeneratorState,
DerivedTOM,
CONTROL_CATEGORIES,
} from '../types'
import { getControlById } from '../controls/loader'
// =============================================================================
// TYPES
// =============================================================================
export interface PDFExportOptions {
language: 'de' | 'en'
includeNotApplicable: boolean
includeEvidence: boolean
includeGapAnalysis: boolean
companyLogo?: string
primaryColor?: string
pageSize?: 'A4' | 'LETTER'
orientation?: 'portrait' | 'landscape'
}
const DEFAULT_OPTIONS: PDFExportOptions = {
language: 'de',
includeNotApplicable: false,
includeEvidence: true,
includeGapAnalysis: true,
primaryColor: '#1a56db',
pageSize: 'A4',
orientation: 'portrait',
}
// =============================================================================
// PDF CONTENT STRUCTURE
// =============================================================================
export interface PDFSection {
type: 'title' | 'heading' | 'subheading' | 'paragraph' | 'table' | 'list' | 'pagebreak'
content?: string
items?: string[]
table?: {
headers: string[]
rows: string[][]
}
style?: {
color?: string
fontSize?: number
bold?: boolean
italic?: boolean
align?: 'left' | 'center' | 'right'
}
}
/**
* Generate PDF content structure for TOMs
*/
export function generatePDFContent(
state: TOMGeneratorState,
options: Partial<PDFExportOptions> = {}
): PDFSection[] {
const opts = { ...DEFAULT_OPTIONS, ...options }
const sections: PDFSection[] = []
// Title page
sections.push({
type: 'title',
content: opts.language === 'de'
? 'Technische und Organisatorische Maßnahmen (TOMs)'
: 'Technical and Organizational Measures (TOMs)',
style: { color: opts.primaryColor, fontSize: 24, bold: true, align: 'center' },
})
sections.push({
type: 'paragraph',
content: opts.language === 'de'
? 'gemäß Art. 32 DSGVO'
: 'according to Art. 32 GDPR',
style: { fontSize: 14, align: 'center' },
})
// Company information
if (state.companyProfile) {
sections.push({
type: 'paragraph',
content: state.companyProfile.name,
style: { fontSize: 16, bold: true, align: 'center' },
})
sections.push({
type: 'paragraph',
content: `${opts.language === 'de' ? 'Branche' : 'Industry'}: ${state.companyProfile.industry}`,
style: { align: 'center' },
})
sections.push({
type: 'paragraph',
content: `${opts.language === 'de' ? 'Stand' : 'Date'}: ${new Date().toLocaleDateString(opts.language === 'de' ? 'de-DE' : 'en-US')}`,
style: { align: 'center' },
})
}
sections.push({ type: 'pagebreak' })
// Table of Contents
sections.push({
type: 'heading',
content: opts.language === 'de' ? 'Inhaltsverzeichnis' : 'Table of Contents',
style: { color: opts.primaryColor },
})
const tocItems = [
opts.language === 'de' ? '1. Zusammenfassung' : '1. Summary',
opts.language === 'de' ? '2. Schutzbedarf' : '2. Protection Level',
opts.language === 'de' ? '3. Maßnahmenübersicht' : '3. Measures Overview',
]
let sectionNum = 4
for (const category of CONTROL_CATEGORIES) {
const categoryTOMs = state.derivedTOMs.filter((tom) => {
const control = getControlById(tom.controlId)
return control?.category === category.id &&
(opts.includeNotApplicable || tom.applicability !== 'NOT_APPLICABLE')
})
if (categoryTOMs.length > 0) {
tocItems.push(`${sectionNum}. ${category.name[opts.language]}`)
sectionNum++
}
}
if (opts.includeGapAnalysis && state.gapAnalysis) {
tocItems.push(`${sectionNum}. ${opts.language === 'de' ? 'Lückenanalyse' : 'Gap Analysis'}`)
}
sections.push({
type: 'list',
items: tocItems,
})
sections.push({ type: 'pagebreak' })
// Executive Summary
sections.push({
type: 'heading',
content: opts.language === 'de' ? '1. Zusammenfassung' : '1. Summary',
style: { color: opts.primaryColor },
})
const totalTOMs = state.derivedTOMs.length
const requiredTOMs = state.derivedTOMs.filter((t) => t.applicability === 'REQUIRED').length
const implementedTOMs = state.derivedTOMs.filter((t) => t.implementationStatus === 'IMPLEMENTED').length
sections.push({
type: 'paragraph',
content: opts.language === 'de'
? `Dieses Dokument beschreibt die technischen und organisatorischen Maßnahmen (TOMs) gemäß Art. 32 DSGVO. Insgesamt wurden ${totalTOMs} Kontrollen bewertet, davon ${requiredTOMs} als erforderlich eingestuft. Aktuell sind ${implementedTOMs} Maßnahmen vollständig umgesetzt.`
: `This document describes the technical and organizational measures (TOMs) according to Art. 32 GDPR. A total of ${totalTOMs} controls were evaluated, of which ${requiredTOMs} are classified as required. Currently, ${implementedTOMs} measures are fully implemented.`,
})
// Summary statistics table
sections.push({
type: 'table',
table: {
headers: opts.language === 'de'
? ['Kategorie', 'Anzahl', 'Erforderlich', 'Umgesetzt']
: ['Category', 'Count', 'Required', 'Implemented'],
rows: generateCategorySummary(state.derivedTOMs, opts),
},
})
// Protection Level
sections.push({
type: 'heading',
content: opts.language === 'de' ? '2. Schutzbedarf' : '2. Protection Level',
style: { color: opts.primaryColor },
})
if (state.riskProfile) {
sections.push({
type: 'paragraph',
content: opts.language === 'de'
? `Der ermittelte Schutzbedarf beträgt: **${formatProtectionLevel(state.riskProfile.protectionLevel, opts.language)}**`
: `The determined protection level is: **${formatProtectionLevel(state.riskProfile.protectionLevel, opts.language)}**`,
})
sections.push({
type: 'table',
table: {
headers: opts.language === 'de'
? ['Schutzziel', 'Bewertung (1-5)', 'Bedeutung']
: ['Protection Goal', 'Rating (1-5)', 'Meaning'],
rows: [
[
opts.language === 'de' ? 'Vertraulichkeit' : 'Confidentiality',
String(state.riskProfile.ciaAssessment.confidentiality),
getCIAMeaning(state.riskProfile.ciaAssessment.confidentiality, opts.language),
],
[
opts.language === 'de' ? 'Integrität' : 'Integrity',
String(state.riskProfile.ciaAssessment.integrity),
getCIAMeaning(state.riskProfile.ciaAssessment.integrity, opts.language),
],
[
opts.language === 'de' ? 'Verfügbarkeit' : 'Availability',
String(state.riskProfile.ciaAssessment.availability),
getCIAMeaning(state.riskProfile.ciaAssessment.availability, opts.language),
],
],
},
})
if (state.riskProfile.dsfaRequired) {
sections.push({
type: 'paragraph',
content: opts.language === 'de'
? '⚠️ HINWEIS: Aufgrund der Verarbeitung ist eine Datenschutz-Folgenabschätzung (DSFA) nach Art. 35 DSGVO erforderlich.'
: '⚠️ NOTE: Due to the processing, a Data Protection Impact Assessment (DPIA) according to Art. 35 GDPR is required.',
style: { bold: true, color: '#dc2626' },
})
}
}
// Measures Overview
sections.push({
type: 'heading',
content: opts.language === 'de' ? '3. Maßnahmenübersicht' : '3. Measures Overview',
style: { color: opts.primaryColor },
})
sections.push({
type: 'table',
table: {
headers: opts.language === 'de'
? ['ID', 'Maßnahme', 'Anwendbarkeit', 'Status']
: ['ID', 'Measure', 'Applicability', 'Status'],
rows: state.derivedTOMs
.filter((tom) => opts.includeNotApplicable || tom.applicability !== 'NOT_APPLICABLE')
.map((tom) => [
tom.controlId,
tom.name,
formatApplicability(tom.applicability, opts.language),
formatImplementationStatus(tom.implementationStatus, opts.language),
]),
},
})
// Detailed sections by category
let currentSection = 4
for (const category of CONTROL_CATEGORIES) {
const categoryTOMs = state.derivedTOMs.filter((tom) => {
const control = getControlById(tom.controlId)
return control?.category === category.id &&
(opts.includeNotApplicable || tom.applicability !== 'NOT_APPLICABLE')
})
if (categoryTOMs.length === 0) continue
sections.push({ type: 'pagebreak' })
sections.push({
type: 'heading',
content: `${currentSection}. ${category.name[opts.language]}`,
style: { color: opts.primaryColor },
})
sections.push({
type: 'paragraph',
content: `${opts.language === 'de' ? 'Rechtsgrundlage' : 'Legal Basis'}: ${category.gdprReference}`,
style: { italic: true },
})
for (const tom of categoryTOMs) {
sections.push({
type: 'subheading',
content: `${tom.controlId}: ${tom.name}`,
})
sections.push({
type: 'paragraph',
content: tom.aiGeneratedDescription || tom.description,
})
sections.push({
type: 'list',
items: [
`${opts.language === 'de' ? 'Typ' : 'Type'}: ${formatType(getControlById(tom.controlId)?.type || 'TECHNICAL', opts.language)}`,
`${opts.language === 'de' ? 'Anwendbarkeit' : 'Applicability'}: ${formatApplicability(tom.applicability, opts.language)}`,
`${opts.language === 'de' ? 'Begründung' : 'Reason'}: ${tom.applicabilityReason}`,
`${opts.language === 'de' ? 'Umsetzungsstatus' : 'Implementation Status'}: ${formatImplementationStatus(tom.implementationStatus, opts.language)}`,
...(tom.responsiblePerson ? [`${opts.language === 'de' ? 'Verantwortlich' : 'Responsible'}: ${tom.responsiblePerson}`] : []),
...(opts.includeEvidence && tom.linkedEvidence.length > 0
? [`${opts.language === 'de' ? 'Verknüpfte Nachweise' : 'Linked Evidence'}: ${tom.linkedEvidence.length}`]
: []),
],
})
}
currentSection++
}
// Gap Analysis
if (opts.includeGapAnalysis && state.gapAnalysis) {
sections.push({ type: 'pagebreak' })
sections.push({
type: 'heading',
content: `${currentSection}. ${opts.language === 'de' ? 'Lückenanalyse' : 'Gap Analysis'}`,
style: { color: opts.primaryColor },
})
sections.push({
type: 'paragraph',
content: opts.language === 'de'
? `Gesamtscore: ${state.gapAnalysis.overallScore}%`
: `Overall Score: ${state.gapAnalysis.overallScore}%`,
style: { fontSize: 16, bold: true },
})
if (state.gapAnalysis.missingControls.length > 0) {
sections.push({
type: 'subheading',
content: opts.language === 'de' ? 'Fehlende Maßnahmen' : 'Missing Measures',
})
sections.push({
type: 'table',
table: {
headers: opts.language === 'de'
? ['ID', 'Maßnahme', 'Priorität']
: ['ID', 'Measure', 'Priority'],
rows: state.gapAnalysis.missingControls.map((mc) => {
const control = getControlById(mc.controlId)
return [
mc.controlId,
control?.name[opts.language] || 'Unknown',
mc.priority,
]
}),
},
})
}
if (state.gapAnalysis.recommendations.length > 0) {
sections.push({
type: 'subheading',
content: opts.language === 'de' ? 'Empfehlungen' : 'Recommendations',
})
sections.push({
type: 'list',
items: state.gapAnalysis.recommendations,
})
}
}
// Footer
sections.push({
type: 'paragraph',
content: opts.language === 'de'
? `Generiert am ${new Date().toLocaleDateString('de-DE')} mit dem TOM Generator`
: `Generated on ${new Date().toLocaleDateString('en-US')} with the TOM Generator`,
style: { italic: true, align: 'center', fontSize: 10 },
})
return sections
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
function generateCategorySummary(
toms: DerivedTOM[],
opts: PDFExportOptions
): string[][] {
const summary: string[][] = []
for (const category of CONTROL_CATEGORIES) {
const categoryTOMs = toms.filter((tom) => {
const control = getControlById(tom.controlId)
return control?.category === category.id
})
if (categoryTOMs.length === 0) continue
const required = categoryTOMs.filter((t) => t.applicability === 'REQUIRED').length
const implemented = categoryTOMs.filter((t) => t.implementationStatus === 'IMPLEMENTED').length
summary.push([
category.name[opts.language],
String(categoryTOMs.length),
String(required),
String(implemented),
])
}
return summary
}
function formatProtectionLevel(level: string, language: 'de' | 'en'): string {
const levels: Record<string, Record<'de' | 'en', string>> = {
NORMAL: { de: 'Normal', en: 'Normal' },
HIGH: { de: 'Hoch', en: 'High' },
VERY_HIGH: { de: 'Sehr hoch', en: 'Very High' },
}
return levels[level]?.[language] || level
}
function formatType(type: string, language: 'de' | 'en'): string {
const types: Record<string, Record<'de' | 'en', string>> = {
TECHNICAL: { de: 'Technisch', en: 'Technical' },
ORGANIZATIONAL: { de: 'Organisatorisch', en: 'Organizational' },
}
return types[type]?.[language] || type
}
function formatImplementationStatus(status: string, language: 'de' | 'en'): string {
const statuses: Record<string, Record<'de' | 'en', string>> = {
NOT_IMPLEMENTED: { de: 'Nicht umgesetzt', en: 'Not Implemented' },
PARTIAL: { de: 'Teilweise', en: 'Partial' },
IMPLEMENTED: { de: 'Umgesetzt', en: 'Implemented' },
}
return statuses[status]?.[language] || status
}
function formatApplicability(applicability: string, language: 'de' | 'en'): string {
const apps: Record<string, Record<'de' | 'en', string>> = {
REQUIRED: { de: 'Erforderlich', en: 'Required' },
RECOMMENDED: { de: 'Empfohlen', en: 'Recommended' },
OPTIONAL: { de: 'Optional', en: 'Optional' },
NOT_APPLICABLE: { de: 'N/A', en: 'N/A' },
}
return apps[applicability]?.[language] || applicability
}
function getCIAMeaning(rating: number, language: 'de' | 'en'): string {
const meanings: Record<number, Record<'de' | 'en', string>> = {
1: { de: 'Sehr gering', en: 'Very Low' },
2: { de: 'Gering', en: 'Low' },
3: { de: 'Mittel', en: 'Medium' },
4: { de: 'Hoch', en: 'High' },
5: { de: 'Sehr hoch', en: 'Very High' },
}
return meanings[rating]?.[language] || String(rating)
}
// =============================================================================
// PDF BLOB GENERATION
// Note: For production, use jspdf or pdfmake library
// =============================================================================
/**
* Generate a PDF file as a Blob
* This is a placeholder - in production, use jspdf or similar library
*/
export async function generatePDFBlob(
state: TOMGeneratorState,
options: Partial<PDFExportOptions> = {}
): Promise<Blob> {
const content = generatePDFContent(state, options)
// Convert to simple text-based content for now
// In production, use jspdf library
const textContent = content
.map((section) => {
switch (section.type) {
case 'title':
return `\n\n${'='.repeat(60)}\n${section.content}\n${'='.repeat(60)}\n`
case 'heading':
return `\n\n${section.content}\n${'-'.repeat(40)}\n`
case 'subheading':
return `\n${section.content}\n`
case 'paragraph':
return `${section.content}\n`
case 'list':
return section.items?.map((item) => `${item}`).join('\n') + '\n'
case 'table':
if (section.table) {
const headerLine = section.table.headers.join(' | ')
const separator = '-'.repeat(headerLine.length)
const rows = section.table.rows.map((row) => row.join(' | ')).join('\n')
return `\n${headerLine}\n${separator}\n${rows}\n`
}
return ''
case 'pagebreak':
return '\n\n' + '='.repeat(60) + '\n\n'
default:
return ''
}
})
.join('')
return new Blob([textContent], { type: 'application/pdf' })
}
// =============================================================================
// FILENAME GENERATION
// =============================================================================
/**
* Generate a filename for the PDF export
*/
export function generatePDFFilename(
state: TOMGeneratorState,
language: 'de' | 'en' = 'de'
): string {
const companyName = state.companyProfile?.name?.replace(/[^a-zA-Z0-9]/g, '-') || 'unknown'
const date = new Date().toISOString().split('T')[0]
const prefix = language === 'de' ? 'TOMs' : 'TOMs'
return `${prefix}-${companyName}-${date}.pdf`
}
// Types are exported at their definition site above

View File

@@ -0,0 +1,544 @@
// =============================================================================
// TOM Generator ZIP Export
// Export complete TOM package as ZIP archive
// =============================================================================
import { TOMGeneratorState, DerivedTOM, EvidenceDocument } from '../types'
import { generateDOCXContent, DOCXExportOptions } from './docx'
import { generatePDFContent, PDFExportOptions } from './pdf'
import { getControlById, getAllControls, getLibraryMetadata } from '../controls/loader'
// =============================================================================
// TYPES
// =============================================================================
export interface ZIPExportOptions {
language: 'de' | 'en'
includeNotApplicable: boolean
includeEvidence: boolean
includeGapAnalysis: boolean
includeControlLibrary: boolean
includeRawData: boolean
formats: Array<'json' | 'docx' | 'pdf'>
}
const DEFAULT_OPTIONS: ZIPExportOptions = {
language: 'de',
includeNotApplicable: false,
includeEvidence: true,
includeGapAnalysis: true,
includeControlLibrary: true,
includeRawData: true,
formats: ['json', 'docx'],
}
// =============================================================================
// ZIP CONTENT STRUCTURE
// =============================================================================
export interface ZIPFileEntry {
path: string
content: string | Blob
mimeType: string
}
/**
* Generate all files for the ZIP archive
*/
export function generateZIPFiles(
state: TOMGeneratorState,
options: Partial<ZIPExportOptions> = {}
): ZIPFileEntry[] {
const opts = { ...DEFAULT_OPTIONS, ...options }
const files: ZIPFileEntry[] = []
// README
files.push({
path: 'README.md',
content: generateReadme(state, opts),
mimeType: 'text/markdown',
})
// State JSON
if (opts.includeRawData) {
files.push({
path: 'data/state.json',
content: JSON.stringify(state, null, 2),
mimeType: 'application/json',
})
}
// Profile data
files.push({
path: 'data/profiles/company-profile.json',
content: JSON.stringify(state.companyProfile, null, 2),
mimeType: 'application/json',
})
files.push({
path: 'data/profiles/data-profile.json',
content: JSON.stringify(state.dataProfile, null, 2),
mimeType: 'application/json',
})
files.push({
path: 'data/profiles/architecture-profile.json',
content: JSON.stringify(state.architectureProfile, null, 2),
mimeType: 'application/json',
})
files.push({
path: 'data/profiles/security-profile.json',
content: JSON.stringify(state.securityProfile, null, 2),
mimeType: 'application/json',
})
files.push({
path: 'data/profiles/risk-profile.json',
content: JSON.stringify(state.riskProfile, null, 2),
mimeType: 'application/json',
})
// Derived TOMs
files.push({
path: 'data/toms/derived-toms.json',
content: JSON.stringify(state.derivedTOMs, null, 2),
mimeType: 'application/json',
})
// TOMs by category
const tomsByCategory = groupTOMsByCategory(state.derivedTOMs)
for (const [category, toms] of tomsByCategory.entries()) {
files.push({
path: `data/toms/by-category/${category.toLowerCase()}.json`,
content: JSON.stringify(toms, null, 2),
mimeType: 'application/json',
})
}
// Required TOMs summary
const requiredTOMs = state.derivedTOMs.filter(
(tom) => tom.applicability === 'REQUIRED'
)
files.push({
path: 'data/toms/required-toms.json',
content: JSON.stringify(requiredTOMs, null, 2),
mimeType: 'application/json',
})
// Implementation status summary
const implementationSummary = generateImplementationSummary(state.derivedTOMs)
files.push({
path: 'data/toms/implementation-summary.json',
content: JSON.stringify(implementationSummary, null, 2),
mimeType: 'application/json',
})
// Evidence documents
if (opts.includeEvidence && state.documents.length > 0) {
files.push({
path: 'data/evidence/documents.json',
content: JSON.stringify(state.documents, null, 2),
mimeType: 'application/json',
})
// Evidence by control
const evidenceByControl = groupEvidenceByControl(state.documents)
files.push({
path: 'data/evidence/by-control.json',
content: JSON.stringify(Object.fromEntries(evidenceByControl), null, 2),
mimeType: 'application/json',
})
}
// Gap Analysis
if (opts.includeGapAnalysis && state.gapAnalysis) {
files.push({
path: 'data/gap-analysis/analysis.json',
content: JSON.stringify(state.gapAnalysis, null, 2),
mimeType: 'application/json',
})
// Missing controls details
if (state.gapAnalysis.missingControls.length > 0) {
files.push({
path: 'data/gap-analysis/missing-controls.json',
content: JSON.stringify(state.gapAnalysis.missingControls, null, 2),
mimeType: 'application/json',
})
}
// Recommendations
if (state.gapAnalysis.recommendations.length > 0) {
files.push({
path: 'data/gap-analysis/recommendations.md',
content: generateRecommendationsMarkdown(
state.gapAnalysis.recommendations,
opts.language
),
mimeType: 'text/markdown',
})
}
}
// Control Library
if (opts.includeControlLibrary) {
const controls = getAllControls()
const metadata = getLibraryMetadata()
files.push({
path: 'reference/control-library/metadata.json',
content: JSON.stringify(metadata, null, 2),
mimeType: 'application/json',
})
files.push({
path: 'reference/control-library/all-controls.json',
content: JSON.stringify(controls, null, 2),
mimeType: 'application/json',
})
// Controls by category
for (const category of new Set(controls.map((c) => c.category))) {
const categoryControls = controls.filter((c) => c.category === category)
files.push({
path: `reference/control-library/by-category/${category.toLowerCase()}.json`,
content: JSON.stringify(categoryControls, null, 2),
mimeType: 'application/json',
})
}
}
// Export history
if (state.exports.length > 0) {
files.push({
path: 'data/exports/history.json',
content: JSON.stringify(state.exports, null, 2),
mimeType: 'application/json',
})
}
// DOCX content structure (if requested)
if (opts.formats.includes('docx')) {
const docxOptions: Partial<DOCXExportOptions> = {
language: opts.language,
includeNotApplicable: opts.includeNotApplicable,
includeEvidence: opts.includeEvidence,
includeGapAnalysis: opts.includeGapAnalysis,
}
const docxContent = generateDOCXContent(state, docxOptions)
files.push({
path: 'documents/tom-document-structure.json',
content: JSON.stringify(docxContent, null, 2),
mimeType: 'application/json',
})
}
// PDF content structure (if requested)
if (opts.formats.includes('pdf')) {
const pdfOptions: Partial<PDFExportOptions> = {
language: opts.language,
includeNotApplicable: opts.includeNotApplicable,
includeEvidence: opts.includeEvidence,
includeGapAnalysis: opts.includeGapAnalysis,
}
const pdfContent = generatePDFContent(state, pdfOptions)
files.push({
path: 'documents/tom-document-structure-pdf.json',
content: JSON.stringify(pdfContent, null, 2),
mimeType: 'application/json',
})
}
// Markdown summary
files.push({
path: 'documents/tom-summary.md',
content: generateMarkdownSummary(state, opts),
mimeType: 'text/markdown',
})
// CSV export for spreadsheet import
files.push({
path: 'documents/toms.csv',
content: generateCSV(state.derivedTOMs, opts),
mimeType: 'text/csv',
})
return files
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
function generateReadme(
state: TOMGeneratorState,
opts: ZIPExportOptions
): string {
const date = new Date().toISOString().split('T')[0]
const lang = opts.language
return `# TOM Export Package
${lang === 'de' ? 'Exportiert am' : 'Exported on'}: ${date}
${lang === 'de' ? 'Unternehmen' : 'Company'}: ${state.companyProfile?.name || 'N/A'}
## ${lang === 'de' ? 'Inhalt' : 'Contents'}
### /data
- **profiles/** - ${lang === 'de' ? 'Profilinformationen (Unternehmen, Daten, Architektur, Sicherheit, Risiko)' : 'Profile information (company, data, architecture, security, risk)'}
- **toms/** - ${lang === 'de' ? 'Abgeleitete TOMs und Zusammenfassungen' : 'Derived TOMs and summaries'}
- **evidence/** - ${lang === 'de' ? 'Nachweisdokumente und Zuordnungen' : 'Evidence documents and mappings'}
- **gap-analysis/** - ${lang === 'de' ? 'Lückenanalyse und Empfehlungen' : 'Gap analysis and recommendations'}
### /reference
- **control-library/** - ${lang === 'de' ? 'Kontrollbibliothek mit allen 60+ Kontrollen' : 'Control library with all 60+ controls'}
### /documents
- **tom-summary.md** - ${lang === 'de' ? 'Zusammenfassung als Markdown' : 'Summary as Markdown'}
- **toms.csv** - ${lang === 'de' ? 'CSV für Tabellenimport' : 'CSV for spreadsheet import'}
## ${lang === 'de' ? 'Statistiken' : 'Statistics'}
- ${lang === 'de' ? 'Gesamtzahl TOMs' : 'Total TOMs'}: ${state.derivedTOMs.length}
- ${lang === 'de' ? 'Erforderlich' : 'Required'}: ${state.derivedTOMs.filter((t) => t.applicability === 'REQUIRED').length}
- ${lang === 'de' ? 'Umgesetzt' : 'Implemented'}: ${state.derivedTOMs.filter((t) => t.implementationStatus === 'IMPLEMENTED').length}
- ${lang === 'de' ? 'Schutzbedarf' : 'Protection Level'}: ${state.riskProfile?.protectionLevel || 'N/A'}
${state.gapAnalysis ? `- ${lang === 'de' ? 'Compliance Score' : 'Compliance Score'}: ${state.gapAnalysis.overallScore}%` : ''}
---
${lang === 'de' ? 'Generiert mit dem TOM Generator' : 'Generated with TOM Generator'}
`
}
function groupTOMsByCategory(
toms: DerivedTOM[]
): Map<string, DerivedTOM[]> {
const grouped = new Map<string, DerivedTOM[]>()
for (const tom of toms) {
const control = getControlById(tom.controlId)
if (!control) continue
const category = control.category
const existing: DerivedTOM[] = grouped.get(category) || []
existing.push(tom)
grouped.set(category, existing)
}
return grouped
}
function generateImplementationSummary(
toms: Array<{ implementationStatus: string; applicability: string }>
): Record<string, number> {
return {
total: toms.length,
required: toms.filter((t) => t.applicability === 'REQUIRED').length,
recommended: toms.filter((t) => t.applicability === 'RECOMMENDED').length,
optional: toms.filter((t) => t.applicability === 'OPTIONAL').length,
notApplicable: toms.filter((t) => t.applicability === 'NOT_APPLICABLE').length,
implemented: toms.filter((t) => t.implementationStatus === 'IMPLEMENTED').length,
partial: toms.filter((t) => t.implementationStatus === 'PARTIAL').length,
notImplemented: toms.filter((t) => t.implementationStatus === 'NOT_IMPLEMENTED').length,
}
}
function groupEvidenceByControl(
documents: Array<{ id: string; linkedControlIds: string[] }>
): Map<string, string[]> {
const grouped = new Map<string, string[]>()
for (const doc of documents) {
for (const controlId of doc.linkedControlIds) {
const existing = grouped.get(controlId) || []
existing.push(doc.id)
grouped.set(controlId, existing)
}
}
return grouped
}
function generateRecommendationsMarkdown(
recommendations: string[],
language: 'de' | 'en'
): string {
const title = language === 'de' ? 'Empfehlungen' : 'Recommendations'
return `# ${title}
${recommendations.map((rec, i) => `${i + 1}. ${rec}`).join('\n\n')}
---
${language === 'de' ? 'Generiert am' : 'Generated on'} ${new Date().toISOString().split('T')[0]}
`
}
function generateMarkdownSummary(
state: TOMGeneratorState,
opts: ZIPExportOptions
): string {
const lang = opts.language
const date = new Date().toLocaleDateString(lang === 'de' ? 'de-DE' : 'en-US')
let md = `# ${lang === 'de' ? 'Technische und Organisatorische Maßnahmen' : 'Technical and Organizational Measures'}
**${lang === 'de' ? 'Unternehmen' : 'Company'}:** ${state.companyProfile?.name || 'N/A'}
**${lang === 'de' ? 'Stand' : 'Date'}:** ${date}
**${lang === 'de' ? 'Schutzbedarf' : 'Protection Level'}:** ${state.riskProfile?.protectionLevel || 'N/A'}
## ${lang === 'de' ? 'Zusammenfassung' : 'Summary'}
| ${lang === 'de' ? 'Metrik' : 'Metric'} | ${lang === 'de' ? 'Wert' : 'Value'} |
|--------|-------|
| ${lang === 'de' ? 'Gesamtzahl TOMs' : 'Total TOMs'} | ${state.derivedTOMs.length} |
| ${lang === 'de' ? 'Erforderlich' : 'Required'} | ${state.derivedTOMs.filter((t) => t.applicability === 'REQUIRED').length} |
| ${lang === 'de' ? 'Umgesetzt' : 'Implemented'} | ${state.derivedTOMs.filter((t) => t.implementationStatus === 'IMPLEMENTED').length} |
| ${lang === 'de' ? 'Teilweise umgesetzt' : 'Partially Implemented'} | ${state.derivedTOMs.filter((t) => t.implementationStatus === 'PARTIAL').length} |
| ${lang === 'de' ? 'Nicht umgesetzt' : 'Not Implemented'} | ${state.derivedTOMs.filter((t) => t.implementationStatus === 'NOT_IMPLEMENTED').length} |
`
if (state.gapAnalysis) {
md += `
## ${lang === 'de' ? 'Compliance Score' : 'Compliance Score'}
**${state.gapAnalysis.overallScore}%**
`
}
// Add required TOMs table
const requiredTOMs = state.derivedTOMs.filter(
(t) => t.applicability === 'REQUIRED'
)
if (requiredTOMs.length > 0) {
md += `
## ${lang === 'de' ? 'Erforderliche Maßnahmen' : 'Required Measures'}
| ID | ${lang === 'de' ? 'Maßnahme' : 'Measure'} | Status |
|----|----------|--------|
${requiredTOMs.map((tom) => `| ${tom.controlId} | ${tom.name} | ${formatStatus(tom.implementationStatus, lang)} |`).join('\n')}
`
}
return md
}
function generateCSV(
toms: Array<{
controlId: string
name: string
description: string
applicability: string
implementationStatus: string
responsiblePerson: string | null
}>,
opts: ZIPExportOptions
): string {
const lang = opts.language
const headers = lang === 'de'
? ['ID', 'Name', 'Beschreibung', 'Anwendbarkeit', 'Status', 'Verantwortlich']
: ['ID', 'Name', 'Description', 'Applicability', 'Status', 'Responsible']
const rows = toms.map((tom) => [
tom.controlId,
escapeCSV(tom.name),
escapeCSV(tom.description),
tom.applicability,
tom.implementationStatus,
tom.responsiblePerson || '',
])
return [
headers.join(','),
...rows.map((row) => row.join(',')),
].join('\n')
}
function escapeCSV(value: string): string {
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
return `"${value.replace(/"/g, '""')}"`
}
return value
}
function formatStatus(status: string, lang: 'de' | 'en'): string {
const statuses: Record<string, Record<'de' | 'en', string>> = {
NOT_IMPLEMENTED: { de: 'Nicht umgesetzt', en: 'Not Implemented' },
PARTIAL: { de: 'Teilweise', en: 'Partial' },
IMPLEMENTED: { de: 'Umgesetzt', en: 'Implemented' },
}
return statuses[status]?.[lang] || status
}
// =============================================================================
// ZIP BLOB GENERATION
// Note: For production, use jszip library
// =============================================================================
/**
* Generate a ZIP file as a Blob
* This is a placeholder - in production, use jszip library
*/
export async function generateZIPBlob(
state: TOMGeneratorState,
options: Partial<ZIPExportOptions> = {}
): Promise<Blob> {
const files = generateZIPFiles(state, options)
// Create a simple JSON representation for now
// In production, use JSZip library
const manifest = {
generated: new Date().toISOString(),
files: files.map((f) => ({
path: f.path,
mimeType: f.mimeType,
size: typeof f.content === 'string' ? f.content.length : 0,
})),
}
const allContent = files
.filter((f) => typeof f.content === 'string')
.map((f) => `\n\n=== ${f.path} ===\n\n${f.content}`)
.join('\n')
const output = `TOM Export Package
Generated: ${manifest.generated}
Files:
${manifest.files.map((f) => ` - ${f.path} (${f.mimeType})`).join('\n')}
${allContent}`
return new Blob([output], { type: 'application/zip' })
}
// =============================================================================
// FILENAME GENERATION
// =============================================================================
/**
* Generate a filename for ZIP export
*/
export function generateZIPFilename(
state: TOMGeneratorState,
language: 'de' | 'en' = 'de'
): string {
const companyName = state.companyProfile?.name?.replace(/[^a-zA-Z0-9]/g, '-') || 'unknown'
const date = new Date().toISOString().split('T')[0]
const prefix = language === 'de' ? 'TOMs-Export' : 'TOMs-Export'
return `${prefix}-${companyName}-${date}.zip`
}
// =============================================================================
// EXPORT
// =============================================================================
// Types are exported at their definition site above

View File

@@ -0,0 +1,206 @@
// =============================================================================
// TOM Generator Module - Public API
// =============================================================================
// Types
export * from './types'
// Context and Hooks
export {
TOMGeneratorProvider,
useTOMGenerator,
TOMGeneratorContext,
} from './context'
export type {
TOMGeneratorAction,
TOMGeneratorContextValue,
} from './context'
// Rules Engine
export {
TOMRulesEngine,
getTOMRulesEngine,
evaluateControlsForContext,
deriveTOMsForContext,
performQuickGapAnalysis,
} from './rules-engine'
// Control Library
export {
getControlLibrary,
getAllControls,
getControlById,
getControlsByCategory,
getControlsByType,
getControlsByPriority,
getControlsByTag,
getAllTags,
getCategoryMetadata,
getAllCategories,
getLibraryMetadata,
searchControls,
getControlsByFramework,
getControlsCountByCategory,
} from './controls/loader'
export type { ControlLibrary } from './controls/loader'
// AI Integration
export {
AI_PROMPTS,
getDocumentAnalysisPrompt,
getTOMDescriptionPrompt,
getGapRecommendationsPrompt,
getDocumentTypeDetectionPrompt,
getClauseExtractionPrompt,
getComplianceAssessmentPrompt,
} from './ai/prompts'
export type {
DocumentAnalysisPromptContext,
TOMDescriptionPromptContext,
GapRecommendationsPromptContext,
} from './ai/prompts'
export {
TOMDocumentAnalyzer,
getDocumentAnalyzer,
analyzeEvidenceDocument,
detectEvidenceDocumentType,
getEvidenceGapsForAllControls,
} from './ai/document-analyzer'
export type {
AnalysisResult,
DocumentTypeDetectionResult,
} from './ai/document-analyzer'
// Export Functions
export {
generateDOCXContent,
generateDOCXBlob,
} from './export/docx'
export type {
DOCXExportOptions,
DocxElement,
DocxParagraph,
DocxTable,
DocxTableRow,
} from './export/docx'
export {
generatePDFContent,
generatePDFBlob,
} from './export/pdf'
export type {
PDFExportOptions,
PDFSection,
} from './export/pdf'
export {
generateZIPFiles,
generateZIPBlob,
} from './export/zip'
export type {
ZIPExportOptions,
ZIPFileEntry,
} from './export/zip'
// Demo Data
export {
generateDemoState,
generateEmptyState,
generatePartialState,
DEMO_COMPANY_PROFILES,
DEMO_DATA_PROFILES,
DEMO_ARCHITECTURE_PROFILES,
DEMO_SECURITY_PROFILES,
DEMO_RISK_PROFILES,
DEMO_EVIDENCE_DOCUMENTS,
} from './demo-data'
export type { DemoScenario } from './demo-data'
// =============================================================================
// CONVENIENCE EXPORTS
// =============================================================================
import { TOMRulesEngine } from './rules-engine'
import {
TOMGeneratorState,
RulesEngineEvaluationContext,
DerivedTOM,
GapAnalysisResult,
EvidenceDocument,
} from './types'
/**
* Create a new TOM Rules Engine instance
*/
export function createRulesEngine(): TOMRulesEngine {
return new TOMRulesEngine()
}
/**
* Derive TOMs for a given state
*/
export function deriveTOMsFromState(state: TOMGeneratorState): DerivedTOM[] {
const engine = new TOMRulesEngine()
return engine.deriveAllTOMs({
companyProfile: state.companyProfile,
dataProfile: state.dataProfile,
architectureProfile: state.architectureProfile,
securityProfile: state.securityProfile,
riskProfile: state.riskProfile,
})
}
/**
* Perform gap analysis on a state
*/
export function analyzeGapsFromState(
state: TOMGeneratorState
): GapAnalysisResult {
const engine = new TOMRulesEngine()
return engine.performGapAnalysis(state.derivedTOMs, state.documents)
}
/**
* Get completion statistics for a state
*/
export function getStateStatistics(state: TOMGeneratorState): {
totalControls: number
requiredControls: number
implementedControls: number
partialControls: number
notImplementedControls: number
complianceScore: number
stepsCompleted: number
totalSteps: number
documentsUploaded: number
} {
const totalControls = state.derivedTOMs.length
const requiredControls = state.derivedTOMs.filter(
(t) => t.applicability === 'REQUIRED'
).length
const implementedControls = state.derivedTOMs.filter(
(t) => t.implementationStatus === 'IMPLEMENTED'
).length
const partialControls = state.derivedTOMs.filter(
(t) => t.implementationStatus === 'PARTIAL'
).length
const notImplementedControls = state.derivedTOMs.filter(
(t) => t.implementationStatus === 'NOT_IMPLEMENTED'
).length
const stepsCompleted = state.steps.filter((s) => s.completed).length
const totalSteps = state.steps.length
return {
totalControls,
requiredControls,
implementedControls,
partialControls,
notImplementedControls,
complianceScore: state.gapAnalysis?.overallScore ?? 0,
stepsCompleted,
totalSteps,
documentsUploaded: state.documents.length,
}
}

View File

@@ -0,0 +1,560 @@
// =============================================================================
// TOM Rules Engine
// Evaluates control applicability based on company context
// =============================================================================
import {
ControlLibraryEntry,
ApplicabilityCondition,
ControlApplicability,
RulesEngineResult,
RulesEngineEvaluationContext,
DerivedTOM,
EvidenceDocument,
GapAnalysisResult,
MissingControl,
PartialControl,
MissingEvidence,
ConditionOperator,
} from './types'
import { getAllControls, getControlById } from './controls/loader'
// =============================================================================
// RULES ENGINE CLASS
// =============================================================================
export class TOMRulesEngine {
private controls: ControlLibraryEntry[]
constructor() {
this.controls = getAllControls()
}
/**
* Evaluate all controls against the current context
*/
evaluateControls(context: RulesEngineEvaluationContext): RulesEngineResult[] {
return this.controls.map((control) => this.evaluateControl(control, context))
}
/**
* Evaluate a single control against the context
*/
evaluateControl(
control: ControlLibraryEntry,
context: RulesEngineEvaluationContext
): RulesEngineResult {
// Sort conditions by priority (highest first)
const sortedConditions = [...control.applicabilityConditions].sort(
(a, b) => b.priority - a.priority
)
// Evaluate conditions in priority order
for (const condition of sortedConditions) {
const matches = this.evaluateCondition(condition, context)
if (matches) {
return {
controlId: control.id,
applicability: condition.result,
reason: this.formatConditionReason(condition, context),
matchedCondition: condition,
}
}
}
// No condition matched, use default applicability
return {
controlId: control.id,
applicability: control.defaultApplicability,
reason: 'Standard-Anwendbarkeit (keine spezifische Bedingung erfüllt)',
}
}
/**
* Evaluate a single condition
*/
private evaluateCondition(
condition: ApplicabilityCondition,
context: RulesEngineEvaluationContext
): boolean {
const value = this.getFieldValue(condition.field, context)
if (value === undefined || value === null) {
return false
}
return this.evaluateOperator(condition.operator, value, condition.value)
}
/**
* Get a nested field value from the context
*/
private getFieldValue(
fieldPath: string,
context: RulesEngineEvaluationContext
): unknown {
const parts = fieldPath.split('.')
let current: unknown = context
for (const part of parts) {
if (current === null || current === undefined) {
return undefined
}
if (typeof current === 'object') {
current = (current as Record<string, unknown>)[part]
} else {
return undefined
}
}
return current
}
/**
* Evaluate an operator with given values
*/
private evaluateOperator(
operator: ConditionOperator,
actualValue: unknown,
expectedValue: unknown
): boolean {
switch (operator) {
case 'EQUALS':
return actualValue === expectedValue
case 'NOT_EQUALS':
return actualValue !== expectedValue
case 'CONTAINS':
if (Array.isArray(actualValue)) {
return actualValue.includes(expectedValue)
}
if (typeof actualValue === 'string' && typeof expectedValue === 'string') {
return actualValue.includes(expectedValue)
}
return false
case 'GREATER_THAN':
if (typeof actualValue === 'number' && typeof expectedValue === 'number') {
return actualValue > expectedValue
}
return false
case 'IN':
if (Array.isArray(expectedValue)) {
return expectedValue.includes(actualValue)
}
return false
default:
return false
}
}
/**
* Format a human-readable reason for the condition match
*/
private formatConditionReason(
condition: ApplicabilityCondition,
context: RulesEngineEvaluationContext
): string {
const fieldValue = this.getFieldValue(condition.field, context)
const fieldLabel = this.getFieldLabel(condition.field)
switch (condition.operator) {
case 'EQUALS':
return `${fieldLabel} ist "${this.formatValue(fieldValue)}"`
case 'NOT_EQUALS':
return `${fieldLabel} ist nicht "${this.formatValue(condition.value)}"`
case 'CONTAINS':
return `${fieldLabel} enthält "${this.formatValue(condition.value)}"`
case 'GREATER_THAN':
return `${fieldLabel} ist größer als ${this.formatValue(condition.value)}`
case 'IN':
return `${fieldLabel} ("${this.formatValue(fieldValue)}") ist in [${Array.isArray(condition.value) ? condition.value.join(', ') : condition.value}]`
default:
return `Bedingung erfüllt: ${condition.field} ${condition.operator} ${this.formatValue(condition.value)}`
}
}
/**
* Get a human-readable label for a field path
*/
private getFieldLabel(fieldPath: string): string {
const labels: Record<string, string> = {
'companyProfile.role': 'Unternehmensrolle',
'companyProfile.size': 'Unternehmensgröße',
'dataProfile.hasSpecialCategories': 'Besondere Datenkategorien',
'dataProfile.processesMinors': 'Verarbeitung von Minderjährigen-Daten',
'dataProfile.dataVolume': 'Datenvolumen',
'dataProfile.thirdCountryTransfers': 'Drittlandübermittlungen',
'architectureProfile.hostingModel': 'Hosting-Modell',
'architectureProfile.hostingLocation': 'Hosting-Standort',
'architectureProfile.multiTenancy': 'Mandantentrennung',
'architectureProfile.hasSubprocessors': 'Unterauftragsverarbeiter',
'architectureProfile.encryptionAtRest': 'Verschlüsselung ruhender Daten',
'securityProfile.hasMFA': 'Multi-Faktor-Authentifizierung',
'securityProfile.hasSSO': 'Single Sign-On',
'securityProfile.hasPAM': 'Privileged Access Management',
'riskProfile.protectionLevel': 'Schutzbedarf',
'riskProfile.dsfaRequired': 'DSFA erforderlich',
'riskProfile.ciaAssessment.confidentiality': 'Vertraulichkeit',
'riskProfile.ciaAssessment.integrity': 'Integrität',
'riskProfile.ciaAssessment.availability': 'Verfügbarkeit',
}
return labels[fieldPath] || fieldPath
}
/**
* Format a value for display
*/
private formatValue(value: unknown): string {
if (value === true) return 'Ja'
if (value === false) return 'Nein'
if (value === null || value === undefined) return 'nicht gesetzt'
if (Array.isArray(value)) return value.join(', ')
return String(value)
}
/**
* Derive all TOMs based on the current context
*/
deriveAllTOMs(context: RulesEngineEvaluationContext): DerivedTOM[] {
const results = this.evaluateControls(context)
return results.map((result) => {
const control = getControlById(result.controlId)
if (!control) {
throw new Error(`Control not found: ${result.controlId}`)
}
return {
id: `derived-${result.controlId}`,
controlId: result.controlId,
name: control.name.de,
description: control.description.de,
applicability: result.applicability,
applicabilityReason: result.reason,
implementationStatus: 'NOT_IMPLEMENTED',
responsiblePerson: null,
responsibleDepartment: null,
implementationDate: null,
reviewDate: null,
linkedEvidence: [],
evidenceGaps: [...control.evidenceRequirements],
aiGeneratedDescription: null,
aiRecommendations: [],
}
})
}
/**
* Get only required and recommended TOMs
*/
getApplicableTOMs(context: RulesEngineEvaluationContext): DerivedTOM[] {
const allTOMs = this.deriveAllTOMs(context)
return allTOMs.filter(
(tom) =>
tom.applicability === 'REQUIRED' || tom.applicability === 'RECOMMENDED'
)
}
/**
* Get only required TOMs
*/
getRequiredTOMs(context: RulesEngineEvaluationContext): DerivedTOM[] {
const allTOMs = this.deriveAllTOMs(context)
return allTOMs.filter((tom) => tom.applicability === 'REQUIRED')
}
/**
* Perform gap analysis on derived TOMs and evidence
*/
performGapAnalysis(
derivedTOMs: DerivedTOM[],
documents: EvidenceDocument[]
): GapAnalysisResult {
const missingControls: MissingControl[] = []
const partialControls: PartialControl[] = []
const missingEvidence: MissingEvidence[] = []
const recommendations: string[] = []
let totalScore = 0
let totalWeight = 0
// Analyze each required/recommended TOM
const applicableTOMs = derivedTOMs.filter(
(tom) =>
tom.applicability === 'REQUIRED' || tom.applicability === 'RECOMMENDED'
)
for (const tom of applicableTOMs) {
const control = getControlById(tom.controlId)
if (!control) continue
const weight = tom.applicability === 'REQUIRED' ? 3 : 1
totalWeight += weight
// Check implementation status
if (tom.implementationStatus === 'NOT_IMPLEMENTED') {
missingControls.push({
controlId: tom.controlId,
reason: `${control.name.de} ist nicht implementiert`,
priority: control.priority,
})
// Score: 0 for not implemented
} else if (tom.implementationStatus === 'PARTIAL') {
partialControls.push({
controlId: tom.controlId,
missingAspects: tom.evidenceGaps,
})
// Score: 50% for partial
totalScore += weight * 0.5
} else {
// Fully implemented
totalScore += weight
}
// Check evidence
const linkedEvidenceIds = tom.linkedEvidence
const requiredEvidence = control.evidenceRequirements
const providedEvidence = documents.filter((doc) =>
linkedEvidenceIds.includes(doc.id)
)
if (providedEvidence.length < requiredEvidence.length) {
const missing = requiredEvidence.filter(
(req) =>
!providedEvidence.some(
(doc) =>
doc.documentType === 'POLICY' ||
doc.documentType === 'CERTIFICATE' ||
doc.originalName.toLowerCase().includes(req.toLowerCase())
)
)
if (missing.length > 0) {
missingEvidence.push({
controlId: tom.controlId,
requiredEvidence: missing,
})
}
}
}
// Calculate overall score as percentage
const overallScore =
totalWeight > 0 ? Math.round((totalScore / totalWeight) * 100) : 0
// Generate recommendations
if (missingControls.length > 0) {
const criticalMissing = missingControls.filter(
(mc) => mc.priority === 'CRITICAL'
)
if (criticalMissing.length > 0) {
recommendations.push(
`${criticalMissing.length} kritische Kontrollen sind nicht implementiert. Diese sollten priorisiert werden.`
)
}
}
if (partialControls.length > 0) {
recommendations.push(
`${partialControls.length} Kontrollen sind nur teilweise implementiert. Vervollständigen Sie die Implementierung.`
)
}
if (missingEvidence.length > 0) {
recommendations.push(
`Für ${missingEvidence.length} Kontrollen fehlen Nachweisdokumente. Laden Sie die entsprechenden Dokumente hoch.`
)
}
if (overallScore >= 80) {
recommendations.push(
'Ihr TOM-Compliance-Score ist gut. Führen Sie regelmäßige Überprüfungen durch.'
)
} else if (overallScore >= 50) {
recommendations.push(
'Ihr TOM-Compliance-Score erfordert Verbesserungen. Fokussieren Sie sich auf die kritischen Lücken.'
)
} else {
recommendations.push(
'Ihr TOM-Compliance-Score ist niedrig. Eine systematische Überarbeitung der Maßnahmen wird empfohlen.'
)
}
return {
overallScore,
missingControls,
partialControls,
missingEvidence,
recommendations,
generatedAt: new Date(),
}
}
/**
* Get controls by applicability level
*/
getControlsByApplicability(
context: RulesEngineEvaluationContext,
applicability: ControlApplicability
): ControlLibraryEntry[] {
const results = this.evaluateControls(context)
return results
.filter((r) => r.applicability === applicability)
.map((r) => getControlById(r.controlId))
.filter((c): c is ControlLibraryEntry => c !== undefined)
}
/**
* Get summary statistics for the evaluation
*/
getSummaryStatistics(context: RulesEngineEvaluationContext): {
total: number
required: number
recommended: number
optional: number
notApplicable: number
byCategory: Map<string, { required: number; recommended: number }>
} {
const results = this.evaluateControls(context)
const stats = {
total: results.length,
required: 0,
recommended: 0,
optional: 0,
notApplicable: 0,
byCategory: new Map<string, { required: number; recommended: number }>(),
}
for (const result of results) {
switch (result.applicability) {
case 'REQUIRED':
stats.required++
break
case 'RECOMMENDED':
stats.recommended++
break
case 'OPTIONAL':
stats.optional++
break
case 'NOT_APPLICABLE':
stats.notApplicable++
break
}
// Count by category
const control = getControlById(result.controlId)
if (control) {
const category = control.category
const existing = stats.byCategory.get(category) || {
required: 0,
recommended: 0,
}
if (result.applicability === 'REQUIRED') {
existing.required++
} else if (result.applicability === 'RECOMMENDED') {
existing.recommended++
}
stats.byCategory.set(category, existing)
}
}
return stats
}
/**
* Check if a specific control is applicable
*/
isControlApplicable(
controlId: string,
context: RulesEngineEvaluationContext
): boolean {
const control = getControlById(controlId)
if (!control) return false
const result = this.evaluateControl(control, context)
return (
result.applicability === 'REQUIRED' ||
result.applicability === 'RECOMMENDED'
)
}
/**
* Get all controls that match a specific tag
*/
getControlsByTagWithApplicability(
tag: string,
context: RulesEngineEvaluationContext
): Array<{ control: ControlLibraryEntry; result: RulesEngineResult }> {
return this.controls
.filter((control) => control.tags.includes(tag))
.map((control) => ({
control,
result: this.evaluateControl(control, context),
}))
}
/**
* Reload controls (useful if the control library is updated)
*/
reloadControls(): void {
this.controls = getAllControls()
}
}
// =============================================================================
// SINGLETON INSTANCE
// =============================================================================
let rulesEngineInstance: TOMRulesEngine | null = null
export function getTOMRulesEngine(): TOMRulesEngine {
if (!rulesEngineInstance) {
rulesEngineInstance = new TOMRulesEngine()
}
return rulesEngineInstance
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
/**
* Quick evaluation of controls for a context
*/
export function evaluateControlsForContext(
context: RulesEngineEvaluationContext
): RulesEngineResult[] {
return getTOMRulesEngine().evaluateControls(context)
}
/**
* Quick derivation of TOMs for a context
*/
export function deriveTOMsForContext(
context: RulesEngineEvaluationContext
): DerivedTOM[] {
return getTOMRulesEngine().deriveAllTOMs(context)
}
/**
* Quick gap analysis
*/
export function performQuickGapAnalysis(
derivedTOMs: DerivedTOM[],
documents: EvidenceDocument[]
): GapAnalysisResult {
return getTOMRulesEngine().performGapAnalysis(derivedTOMs, documents)
}

View File

@@ -0,0 +1,901 @@
// =============================================================================
// TOM Generator Module - TypeScript Types
// DSGVO Art. 32 Technical and Organizational Measures
// =============================================================================
// =============================================================================
// ENUMS & LITERAL TYPES
// =============================================================================
export type TOMGeneratorStepId =
| 'scope-roles'
| 'data-categories'
| 'architecture-hosting'
| 'security-profile'
| 'risk-protection'
| 'review-export'
export type CompanyRole = 'CONTROLLER' | 'PROCESSOR' | 'JOINT_CONTROLLER'
export type DataCategory =
| 'IDENTIFICATION'
| 'CONTACT'
| 'FINANCIAL'
| 'PROFESSIONAL'
| 'LOCATION'
| 'BEHAVIORAL'
| 'BIOMETRIC'
| 'HEALTH'
| 'GENETIC'
| 'POLITICAL'
| 'RELIGIOUS'
| 'SEXUAL_ORIENTATION'
| 'CRIMINAL'
export type DataSubject =
| 'EMPLOYEES'
| 'CUSTOMERS'
| 'PROSPECTS'
| 'SUPPLIERS'
| 'MINORS'
| 'PATIENTS'
| 'STUDENTS'
| 'GENERAL_PUBLIC'
export type HostingLocation =
| 'DE'
| 'EU'
| 'EEA'
| 'THIRD_COUNTRY_ADEQUATE'
| 'THIRD_COUNTRY'
export type HostingModel = 'ON_PREMISE' | 'PRIVATE_CLOUD' | 'PUBLIC_CLOUD' | 'HYBRID'
export type MultiTenancy = 'SINGLE_TENANT' | 'MULTI_TENANT' | 'DEDICATED'
export type ControlApplicability =
| 'REQUIRED'
| 'RECOMMENDED'
| 'OPTIONAL'
| 'NOT_APPLICABLE'
export type DocumentType =
| 'AVV'
| 'DPA'
| 'SLA'
| 'NDA'
| 'POLICY'
| 'CERTIFICATE'
| 'AUDIT_REPORT'
| 'OTHER'
export type ProtectionLevel = 'NORMAL' | 'HIGH' | 'VERY_HIGH'
export type CIARating = 1 | 2 | 3 | 4 | 5
export type ControlCategory =
| 'ACCESS_CONTROL'
| 'ADMISSION_CONTROL'
| 'ACCESS_AUTHORIZATION'
| 'TRANSFER_CONTROL'
| 'INPUT_CONTROL'
| 'ORDER_CONTROL'
| 'AVAILABILITY'
| 'SEPARATION'
| 'ENCRYPTION'
| 'PSEUDONYMIZATION'
| 'RESILIENCE'
| 'RECOVERY'
| 'REVIEW'
export type CompanySize = 'MICRO' | 'SMALL' | 'MEDIUM' | 'LARGE' | 'ENTERPRISE'
export type DataVolume = 'LOW' | 'MEDIUM' | 'HIGH' | 'VERY_HIGH'
export type AuthMethodType =
| 'PASSWORD'
| 'MFA'
| 'SSO'
| 'CERTIFICATE'
| 'BIOMETRIC'
export type BackupFrequency = 'HOURLY' | 'DAILY' | 'WEEKLY' | 'MONTHLY'
export type ReviewFrequency = 'MONTHLY' | 'QUARTERLY' | 'SEMI_ANNUAL' | 'ANNUAL'
export type ControlPriority = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
export type ControlComplexity = 'LOW' | 'MEDIUM' | 'HIGH'
export type ImplementationStatus = 'NOT_IMPLEMENTED' | 'PARTIAL' | 'IMPLEMENTED'
export type EvidenceStatus = 'PENDING' | 'ANALYZED' | 'VERIFIED' | 'REJECTED'
export type ConditionOperator =
| 'EQUALS'
| 'NOT_EQUALS'
| 'CONTAINS'
| 'GREATER_THAN'
| 'IN'
// =============================================================================
// PROFILE INTERFACES (Wizard Steps 1-5)
// =============================================================================
export interface CompanyProfile {
id: string
name: string
industry: string
size: CompanySize
role: CompanyRole
products: string[]
dpoPerson: string | null
dpoEmail: string | null
itSecurityContact: string | null
}
export interface DataProfile {
categories: DataCategory[]
subjects: DataSubject[]
hasSpecialCategories: boolean
processesMinors: boolean
dataVolume: DataVolume
thirdCountryTransfers: boolean
thirdCountryList: string[]
}
export interface CloudProvider {
name: string
location: HostingLocation
certifications: string[]
}
export interface ArchitectureProfile {
hostingModel: HostingModel
hostingLocation: HostingLocation
providers: CloudProvider[]
multiTenancy: MultiTenancy
hasSubprocessors: boolean
subprocessorCount: number
encryptionAtRest: boolean
encryptionInTransit: boolean
}
export interface AuthMethod {
type: AuthMethodType
provider: string | null
}
export interface SecurityProfile {
authMethods: AuthMethod[]
hasMFA: boolean
hasSSO: boolean
hasIAM: boolean
hasPAM: boolean
hasEncryptionAtRest: boolean
hasEncryptionInTransit: boolean
hasLogging: boolean
logRetentionDays: number
hasBackup: boolean
backupFrequency: BackupFrequency
backupRetentionDays: number
hasDRPlan: boolean
rtoHours: number | null
rpoHours: number | null
hasVulnerabilityManagement: boolean
hasPenetrationTests: boolean
hasSecurityTraining: boolean
}
export interface CIAAssessment {
confidentiality: CIARating
integrity: CIARating
availability: CIARating
justification: string
}
export interface RiskProfile {
ciaAssessment: CIAAssessment
protectionLevel: ProtectionLevel
specialRisks: string[]
regulatoryRequirements: string[]
hasHighRiskProcessing: boolean
dsfaRequired: boolean
}
// =============================================================================
// EVIDENCE DOCUMENT
// =============================================================================
export interface ExtractedClause {
id: string
text: string
type: string
relatedControlId: string | null
}
export interface AIDocumentAnalysis {
summary: string
extractedClauses: ExtractedClause[]
applicableControls: string[]
gaps: string[]
confidence: number
analyzedAt: Date
}
export interface EvidenceDocument {
id: string
filename: string
originalName: string
mimeType: string
size: number
uploadedAt: Date
uploadedBy: string
documentType: DocumentType
detectedType: DocumentType | null
hash: string
validFrom: Date | null
validUntil: Date | null
linkedControlIds: string[]
aiAnalysis: AIDocumentAnalysis | null
status: EvidenceStatus
}
// =============================================================================
// CONTROL LIBRARY
// =============================================================================
export interface LocalizedString {
de: string
en: string
}
export interface FrameworkMapping {
framework: string
reference: string
}
export interface ApplicabilityCondition {
field: string
operator: ConditionOperator
value: unknown
result: ControlApplicability
priority: number
}
export interface ControlLibraryEntry {
id: string
code: string
category: ControlCategory
type: 'TECHNICAL' | 'ORGANIZATIONAL'
name: LocalizedString
description: LocalizedString
mappings: FrameworkMapping[]
applicabilityConditions: ApplicabilityCondition[]
defaultApplicability: ControlApplicability
evidenceRequirements: string[]
reviewFrequency: ReviewFrequency
priority: ControlPriority
complexity: ControlComplexity
tags: string[]
}
// =============================================================================
// DERIVED TOM
// =============================================================================
export interface DerivedTOM {
id: string
controlId: string
name: string
description: string
applicability: ControlApplicability
applicabilityReason: string
implementationStatus: ImplementationStatus
responsiblePerson: string | null
responsibleDepartment: string | null
implementationDate: Date | null
reviewDate: Date | null
linkedEvidence: string[]
evidenceGaps: string[]
aiGeneratedDescription: string | null
aiRecommendations: string[]
}
// =============================================================================
// GAP ANALYSIS
// =============================================================================
export interface MissingControl {
controlId: string
reason: string
priority: string
}
export interface PartialControl {
controlId: string
missingAspects: string[]
}
export interface MissingEvidence {
controlId: string
requiredEvidence: string[]
}
export interface GapAnalysisResult {
overallScore: number
missingControls: MissingControl[]
partialControls: PartialControl[]
missingEvidence: MissingEvidence[]
recommendations: string[]
generatedAt: Date
}
// =============================================================================
// WIZARD STEP
// =============================================================================
export interface WizardStep {
id: TOMGeneratorStepId
completed: boolean
data: unknown
validatedAt: Date | null
}
// =============================================================================
// EXPORT RECORD
// =============================================================================
export interface ExportRecord {
id: string
format: 'DOCX' | 'PDF' | 'JSON' | 'ZIP'
generatedAt: Date
filename: string
}
// =============================================================================
// TOM GENERATOR STATE
// =============================================================================
export interface TOMGeneratorState {
id: string
tenantId: string
companyProfile: CompanyProfile | null
dataProfile: DataProfile | null
architectureProfile: ArchitectureProfile | null
securityProfile: SecurityProfile | null
riskProfile: RiskProfile | null
currentStep: TOMGeneratorStepId
steps: WizardStep[]
documents: EvidenceDocument[]
derivedTOMs: DerivedTOM[]
gapAnalysis: GapAnalysisResult | null
exports: ExportRecord[]
createdAt: Date
updatedAt: Date
}
// =============================================================================
// RULES ENGINE TYPES
// =============================================================================
export interface RulesEngineResult {
controlId: string
applicability: ControlApplicability
reason: string
matchedCondition?: ApplicabilityCondition
}
export interface RulesEngineEvaluationContext {
companyProfile: CompanyProfile | null
dataProfile: DataProfile | null
architectureProfile: ArchitectureProfile | null
securityProfile: SecurityProfile | null
riskProfile: RiskProfile | null
}
// =============================================================================
// API TYPES
// =============================================================================
export interface TOMGeneratorStateRequest {
tenantId: string
}
export interface TOMGeneratorStateResponse {
success: boolean
state: TOMGeneratorState | null
error?: string
}
export interface ControlsEvaluationRequest {
tenantId: string
context: RulesEngineEvaluationContext
}
export interface ControlsEvaluationResponse {
success: boolean
results: RulesEngineResult[]
evaluatedAt: string
}
export interface EvidenceUploadRequest {
tenantId: string
documentType: DocumentType
validFrom?: string
validUntil?: string
}
export interface EvidenceUploadResponse {
success: boolean
document: EvidenceDocument | null
error?: string
}
export interface EvidenceAnalyzeRequest {
documentId: string
tenantId: string
}
export interface EvidenceAnalyzeResponse {
success: boolean
analysis: AIDocumentAnalysis | null
error?: string
}
export interface ExportRequest {
tenantId: string
format: 'DOCX' | 'PDF' | 'JSON' | 'ZIP'
language: 'de' | 'en'
}
export interface ExportResponse {
success: boolean
exportId: string
filename: string
downloadUrl?: string
error?: string
}
export interface GapAnalysisRequest {
tenantId: string
}
export interface GapAnalysisResponse {
success: boolean
result: GapAnalysisResult | null
error?: string
}
// =============================================================================
// STEP CONFIGURATION
// =============================================================================
export interface StepConfig {
id: TOMGeneratorStepId
title: LocalizedString
description: LocalizedString
checkpointId: string
path: string
/** Alias for path (for convenience) */
url: string
/** German title for display (for convenience) */
name: string
}
export const TOM_GENERATOR_STEPS: StepConfig[] = [
{
id: 'scope-roles',
title: { de: 'Scope & Rollen', en: 'Scope & Roles' },
description: {
de: 'Unternehmensname, Branche, Größe und Rolle definieren',
en: 'Define company name, industry, size and role',
},
checkpointId: 'CP-TOM-SCOPE',
path: '/sdk/tom-generator/scope',
url: '/sdk/tom-generator/scope',
name: 'Scope & Rollen',
},
{
id: 'data-categories',
title: { de: 'Datenkategorien', en: 'Data Categories' },
description: {
de: 'Datenkategorien und betroffene Personen erfassen',
en: 'Capture data categories and data subjects',
},
checkpointId: 'CP-TOM-DATA',
path: '/sdk/tom-generator/data',
url: '/sdk/tom-generator/data',
name: 'Datenkategorien',
},
{
id: 'architecture-hosting',
title: { de: 'Architektur & Hosting', en: 'Architecture & Hosting' },
description: {
de: 'Hosting-Modell, Standort und Provider definieren',
en: 'Define hosting model, location and providers',
},
checkpointId: 'CP-TOM-ARCH',
path: '/sdk/tom-generator/architecture',
url: '/sdk/tom-generator/architecture',
name: 'Architektur & Hosting',
},
{
id: 'security-profile',
title: { de: 'Security-Profil', en: 'Security Profile' },
description: {
de: 'Authentifizierung, Verschlüsselung und Backup konfigurieren',
en: 'Configure authentication, encryption and backup',
},
checkpointId: 'CP-TOM-SEC',
path: '/sdk/tom-generator/security',
url: '/sdk/tom-generator/security',
name: 'Security-Profil',
},
{
id: 'risk-protection',
title: { de: 'Risiko & Schutzbedarf', en: 'Risk & Protection Level' },
description: {
de: 'CIA-Bewertung und Schutzbedarf ermitteln',
en: 'Determine CIA assessment and protection level',
},
checkpointId: 'CP-TOM-RISK',
path: '/sdk/tom-generator/risk',
url: '/sdk/tom-generator/risk',
name: 'Risiko & Schutzbedarf',
},
{
id: 'review-export',
title: { de: 'Review & Export', en: 'Review & Export' },
description: {
de: 'Zusammenfassung prüfen und TOMs exportieren',
en: 'Review summary and export TOMs',
},
checkpointId: 'CP-TOM-REVIEW',
path: '/sdk/tom-generator/review',
url: '/sdk/tom-generator/review',
name: 'Review & Export',
},
]
// =============================================================================
// CATEGORY METADATA
// =============================================================================
export interface CategoryMetadata {
id: ControlCategory
name: LocalizedString
gdprReference: string
icon?: string
}
export const CONTROL_CATEGORIES: CategoryMetadata[] = [
{
id: 'ACCESS_CONTROL',
name: { de: 'Zutrittskontrolle', en: 'Physical Access Control' },
gdprReference: 'Art. 32 Abs. 1 lit. b',
},
{
id: 'ADMISSION_CONTROL',
name: { de: 'Zugangskontrolle', en: 'System Access Control' },
gdprReference: 'Art. 32 Abs. 1 lit. b',
},
{
id: 'ACCESS_AUTHORIZATION',
name: { de: 'Zugriffskontrolle', en: 'Access Authorization' },
gdprReference: 'Art. 32 Abs. 1 lit. b',
},
{
id: 'TRANSFER_CONTROL',
name: { de: 'Weitergabekontrolle', en: 'Transfer Control' },
gdprReference: 'Art. 32 Abs. 1 lit. b',
},
{
id: 'INPUT_CONTROL',
name: { de: 'Eingabekontrolle', en: 'Input Control' },
gdprReference: 'Art. 32 Abs. 1 lit. b',
},
{
id: 'ORDER_CONTROL',
name: { de: 'Auftragskontrolle', en: 'Order Control' },
gdprReference: 'Art. 28',
},
{
id: 'AVAILABILITY',
name: { de: 'Verfügbarkeit', en: 'Availability' },
gdprReference: 'Art. 32 Abs. 1 lit. b, c',
},
{
id: 'SEPARATION',
name: { de: 'Trennbarkeit', en: 'Separation' },
gdprReference: 'Art. 32 Abs. 1 lit. b',
},
{
id: 'ENCRYPTION',
name: { de: 'Verschlüsselung', en: 'Encryption' },
gdprReference: 'Art. 32 Abs. 1 lit. a',
},
{
id: 'PSEUDONYMIZATION',
name: { de: 'Pseudonymisierung', en: 'Pseudonymization' },
gdprReference: 'Art. 32 Abs. 1 lit. a',
},
{
id: 'RESILIENCE',
name: { de: 'Belastbarkeit', en: 'Resilience' },
gdprReference: 'Art. 32 Abs. 1 lit. b',
},
{
id: 'RECOVERY',
name: { de: 'Wiederherstellbarkeit', en: 'Recovery' },
gdprReference: 'Art. 32 Abs. 1 lit. c',
},
{
id: 'REVIEW',
name: { de: 'Überprüfung & Bewertung', en: 'Review & Assessment' },
gdprReference: 'Art. 32 Abs. 1 lit. d',
},
]
// =============================================================================
// DATA CATEGORY METADATA
// =============================================================================
export interface DataCategoryMetadata {
id: DataCategory
name: LocalizedString
isSpecialCategory: boolean
gdprReference?: string
}
export const DATA_CATEGORIES_METADATA: DataCategoryMetadata[] = [
{
id: 'IDENTIFICATION',
name: { de: 'Identifikationsdaten', en: 'Identification Data' },
isSpecialCategory: false,
},
{
id: 'CONTACT',
name: { de: 'Kontaktdaten', en: 'Contact Data' },
isSpecialCategory: false,
},
{
id: 'FINANCIAL',
name: { de: 'Finanzdaten', en: 'Financial Data' },
isSpecialCategory: false,
},
{
id: 'PROFESSIONAL',
name: { de: 'Berufliche Daten', en: 'Professional Data' },
isSpecialCategory: false,
},
{
id: 'LOCATION',
name: { de: 'Standortdaten', en: 'Location Data' },
isSpecialCategory: false,
},
{
id: 'BEHAVIORAL',
name: { de: 'Verhaltensdaten', en: 'Behavioral Data' },
isSpecialCategory: false,
},
{
id: 'BIOMETRIC',
name: { de: 'Biometrische Daten', en: 'Biometric Data' },
isSpecialCategory: true,
gdprReference: 'Art. 9 Abs. 1',
},
{
id: 'HEALTH',
name: { de: 'Gesundheitsdaten', en: 'Health Data' },
isSpecialCategory: true,
gdprReference: 'Art. 9 Abs. 1',
},
{
id: 'GENETIC',
name: { de: 'Genetische Daten', en: 'Genetic Data' },
isSpecialCategory: true,
gdprReference: 'Art. 9 Abs. 1',
},
{
id: 'POLITICAL',
name: { de: 'Politische Meinungen', en: 'Political Opinions' },
isSpecialCategory: true,
gdprReference: 'Art. 9 Abs. 1',
},
{
id: 'RELIGIOUS',
name: { de: 'Religiöse Überzeugungen', en: 'Religious Beliefs' },
isSpecialCategory: true,
gdprReference: 'Art. 9 Abs. 1',
},
{
id: 'SEXUAL_ORIENTATION',
name: { de: 'Sexuelle Orientierung', en: 'Sexual Orientation' },
isSpecialCategory: true,
gdprReference: 'Art. 9 Abs. 1',
},
{
id: 'CRIMINAL',
name: { de: 'Strafrechtliche Daten', en: 'Criminal Data' },
isSpecialCategory: true,
gdprReference: 'Art. 10',
},
]
// =============================================================================
// DATA SUBJECT METADATA
// =============================================================================
export interface DataSubjectMetadata {
id: DataSubject
name: LocalizedString
isVulnerable: boolean
}
export const DATA_SUBJECTS_METADATA: DataSubjectMetadata[] = [
{
id: 'EMPLOYEES',
name: { de: 'Mitarbeiter', en: 'Employees' },
isVulnerable: false,
},
{
id: 'CUSTOMERS',
name: { de: 'Kunden', en: 'Customers' },
isVulnerable: false,
},
{
id: 'PROSPECTS',
name: { de: 'Interessenten', en: 'Prospects' },
isVulnerable: false,
},
{
id: 'SUPPLIERS',
name: { de: 'Lieferanten', en: 'Suppliers' },
isVulnerable: false,
},
{
id: 'MINORS',
name: { de: 'Minderjährige', en: 'Minors' },
isVulnerable: true,
},
{
id: 'PATIENTS',
name: { de: 'Patienten', en: 'Patients' },
isVulnerable: true,
},
{
id: 'STUDENTS',
name: { de: 'Schüler/Studenten', en: 'Students' },
isVulnerable: false,
},
{
id: 'GENERAL_PUBLIC',
name: { de: 'Allgemeine Öffentlichkeit', en: 'General Public' },
isVulnerable: false,
},
]
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
export function getStepByIndex(index: number): StepConfig | undefined {
return TOM_GENERATOR_STEPS[index]
}
export function getStepById(id: TOMGeneratorStepId): StepConfig | undefined {
return TOM_GENERATOR_STEPS.find((step) => step.id === id)
}
export function getStepIndex(id: TOMGeneratorStepId): number {
return TOM_GENERATOR_STEPS.findIndex((step) => step.id === id)
}
export function getNextStep(
currentId: TOMGeneratorStepId
): StepConfig | undefined {
const currentIndex = getStepIndex(currentId)
return TOM_GENERATOR_STEPS[currentIndex + 1]
}
export function getPreviousStep(
currentId: TOMGeneratorStepId
): StepConfig | undefined {
const currentIndex = getStepIndex(currentId)
return currentIndex > 0 ? TOM_GENERATOR_STEPS[currentIndex - 1] : undefined
}
export function isSpecialCategory(category: DataCategory): boolean {
const meta = DATA_CATEGORIES_METADATA.find((c) => c.id === category)
return meta?.isSpecialCategory ?? false
}
export function hasSpecialCategories(categories: DataCategory[]): boolean {
return categories.some(isSpecialCategory)
}
export function isVulnerableSubject(subject: DataSubject): boolean {
const meta = DATA_SUBJECTS_METADATA.find((s) => s.id === subject)
return meta?.isVulnerable ?? false
}
export function hasVulnerableSubjects(subjects: DataSubject[]): boolean {
return subjects.some(isVulnerableSubject)
}
export function calculateProtectionLevel(
ciaAssessment: CIAAssessment
): ProtectionLevel {
const maxRating = Math.max(
ciaAssessment.confidentiality,
ciaAssessment.integrity,
ciaAssessment.availability
)
if (maxRating >= 4) return 'VERY_HIGH'
if (maxRating >= 3) return 'HIGH'
return 'NORMAL'
}
export function isDSFARequired(
dataProfile: DataProfile | null,
riskProfile: RiskProfile | null
): boolean {
if (!dataProfile) return false
// DSFA required if:
// 1. Special categories are processed
if (dataProfile.hasSpecialCategories) return true
// 2. Minors data is processed
if (dataProfile.processesMinors) return true
// 3. Large scale processing
if (dataProfile.dataVolume === 'VERY_HIGH') return true
// 4. High risk processing indicated
if (riskProfile?.hasHighRiskProcessing) return true
// 5. Very high protection level
if (riskProfile?.protectionLevel === 'VERY_HIGH') return true
return false
}
// =============================================================================
// INITIAL STATE FACTORY
// =============================================================================
export function createInitialTOMGeneratorState(
tenantId: string
): TOMGeneratorState {
const now = new Date()
return {
id: crypto.randomUUID(),
tenantId,
companyProfile: null,
dataProfile: null,
architectureProfile: null,
securityProfile: null,
riskProfile: null,
currentStep: 'scope-roles',
steps: TOM_GENERATOR_STEPS.map((step) => ({
id: step.id,
completed: false,
data: null,
validatedAt: null,
})),
documents: [],
derivedTOMs: [],
gapAnalysis: null,
exports: [],
createdAt: now,
updatedAt: now,
}
}
/**
* Alias for createInitialTOMGeneratorState (for API compatibility)
*/
export const createEmptyTOMGeneratorState = createInitialTOMGeneratorState

1785
admin-v2/lib/sdk/types.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
/**
* Catalog exports
*
* Pre-defined templates, categories, and reference data
*/
export * from './processing-activities'
export * from './vendor-templates'
export * from './legal-basis'

View File

@@ -0,0 +1,562 @@
/**
* Legal Basis Catalog
*
* Comprehensive information about GDPR legal bases (Art. 6, 9, 10)
*/
import { LegalBasisType, LocalizedText, PersonalDataCategory } from '../types'
export interface LegalBasisInfo {
type: LegalBasisType
article: string
name: LocalizedText
shortName: LocalizedText
description: LocalizedText
requirements: LocalizedText[]
suitableFor: string[]
notSuitableFor: string[]
documentationNeeded: LocalizedText[]
isSpecialCategory: boolean
notes?: LocalizedText
}
export interface RetentionPeriodInfo {
id: string
name: LocalizedText
legalBasis: string
duration: {
value: number
unit: 'DAYS' | 'MONTHS' | 'YEARS'
}
description: LocalizedText
applicableTo: string[]
}
// ==========================================
// LEGAL BASIS INFORMATION (Art. 6 DSGVO)
// ==========================================
export const LEGAL_BASIS_INFO: LegalBasisInfo[] = [
// Art. 6 Abs. 1 DSGVO - Standard legal bases
{
type: 'CONSENT',
article: 'Art. 6 Abs. 1 lit. a DSGVO',
name: { de: 'Einwilligung', en: 'Consent' },
shortName: { de: 'Einwilligung', en: 'Consent' },
description: {
de: 'Die betroffene Person hat ihre Einwilligung zu der Verarbeitung der sie betreffenden personenbezogenen Daten für einen oder mehrere bestimmte Zwecke gegeben.',
en: 'The data subject has given consent to the processing of his or her personal data for one or more specific purposes.',
},
requirements: [
{ de: 'Freiwillig erteilt', en: 'Freely given' },
{ de: 'Für bestimmten Zweck', en: 'For a specific purpose' },
{ de: 'Informiert', en: 'Informed' },
{ de: 'Unmissverständlich', en: 'Unambiguous' },
{ de: 'Jederzeit widerrufbar', en: 'Revocable at any time' },
{ de: 'Nachweis muss möglich sein', en: 'Must be demonstrable' },
],
suitableFor: ['Newsletter', 'Marketing', 'Cookies', 'Tracking', 'Fotos/Videos'],
notSuitableFor: ['Vertragsdurchführung', 'Gesetzliche Pflichten', 'Arbeitsverhältnis'],
documentationNeeded: [
{ de: 'Einwilligungstext', en: 'Consent text' },
{ de: 'Zeitpunkt der Einwilligung', en: 'Time of consent' },
{ de: 'Art der Erteilung (Opt-in)', en: 'Method of consent (opt-in)' },
{ de: 'Widerrufsbelehrung', en: 'Information about withdrawal' },
],
isSpecialCategory: false,
},
{
type: 'CONTRACT',
article: 'Art. 6 Abs. 1 lit. b DSGVO',
name: { de: 'Vertragserfüllung', en: 'Contract Performance' },
shortName: { de: 'Vertrag', en: 'Contract' },
description: {
de: 'Die Verarbeitung ist für die Erfüllung eines Vertrags, dessen Vertragspartei die betroffene Person ist, oder zur Durchführung vorvertraglicher Maßnahmen erforderlich.',
en: 'Processing is necessary for the performance of a contract to which the data subject is party or for pre-contractual measures.',
},
requirements: [
{ de: 'Vertrag besteht oder wird angebahnt', en: 'Contract exists or is being initiated' },
{ de: 'Verarbeitung ist für Erfüllung erforderlich', en: 'Processing is necessary for performance' },
{ de: 'Betroffene Person ist Vertragspartei', en: 'Data subject is a party to the contract' },
],
suitableFor: ['Kundendaten', 'Bestellabwicklung', 'Lieferung', 'Rechnungsstellung', 'Kundenservice'],
notSuitableFor: ['Marketing', 'Profiling', 'Weitergabe an Dritte ohne Vertragsbezug'],
documentationNeeded: [
{ de: 'Vertrag oder AGB', en: 'Contract or T&C' },
{ de: 'Zusammenhang zur Verarbeitung', en: 'Connection to processing' },
],
isSpecialCategory: false,
},
{
type: 'LEGAL_OBLIGATION',
article: 'Art. 6 Abs. 1 lit. c DSGVO',
name: { de: 'Rechtliche Verpflichtung', en: 'Legal Obligation' },
shortName: { de: 'Gesetz', en: 'Legal' },
description: {
de: 'Die Verarbeitung ist zur Erfüllung einer rechtlichen Verpflichtung erforderlich, der der Verantwortliche unterliegt.',
en: 'Processing is necessary for compliance with a legal obligation to which the controller is subject.',
},
requirements: [
{ de: 'Rechtliche Verpflichtung im EU/nationalen Recht', en: 'Legal obligation in EU/national law' },
{ de: 'Verarbeitung ist zur Erfüllung erforderlich', en: 'Processing is necessary for compliance' },
{ de: 'Konkrete Rechtsgrundlage benennen', en: 'Cite specific legal basis' },
],
suitableFor: ['Steuerliche Aufbewahrung', 'Sozialversicherung', 'AML/KYC', 'Meldepflichten'],
notSuitableFor: ['Freiwillige Maßnahmen', 'Marketing'],
documentationNeeded: [
{ de: 'Konkrete Rechtsvorschrift', en: 'Specific legal provision' },
{ de: 'HGB, AO, SGB, etc.', en: 'Commercial code, tax code, etc.' },
],
isSpecialCategory: false,
},
{
type: 'VITAL_INTEREST',
article: 'Art. 6 Abs. 1 lit. d DSGVO',
name: { de: 'Lebenswichtige Interessen', en: 'Vital Interests' },
shortName: { de: 'Vital', en: 'Vital' },
description: {
de: 'Die Verarbeitung ist erforderlich, um lebenswichtige Interessen der betroffenen Person oder einer anderen natürlichen Person zu schützen.',
en: 'Processing is necessary to protect the vital interests of the data subject or of another natural person.',
},
requirements: [
{ de: 'Gefahr für Leben oder Gesundheit', en: 'Danger to life or health' },
{ de: 'Keine andere Rechtsgrundlage möglich', en: 'No other legal basis possible' },
{ de: 'Subsidiär zu anderen Rechtsgrundlagen', en: 'Subsidiary to other legal bases' },
],
suitableFor: ['Notfall', 'Medizinische Notversorgung', 'Katastrophenschutz'],
notSuitableFor: ['Regelmäßige Verarbeitung', 'Vorsorgemaßnahmen'],
documentationNeeded: [
{ de: 'Dokumentation des Notfalls', en: 'Documentation of emergency' },
{ de: 'Begründung der Erforderlichkeit', en: 'Justification of necessity' },
],
isSpecialCategory: false,
notes: {
de: 'Sollte nur in Ausnahmefällen verwendet werden, wenn keine andere Rechtsgrundlage greift.',
en: 'Should only be used in exceptional cases when no other legal basis applies.',
},
},
{
type: 'PUBLIC_TASK',
article: 'Art. 6 Abs. 1 lit. e DSGVO',
name: { de: 'Öffentliche Aufgabe', en: 'Public Task' },
shortName: { de: 'Öffentlich', en: 'Public' },
description: {
de: 'Die Verarbeitung ist für die Wahrnehmung einer Aufgabe erforderlich, die im öffentlichen Interesse liegt oder in Ausübung öffentlicher Gewalt erfolgt.',
en: 'Processing is necessary for the performance of a task carried out in the public interest or in the exercise of official authority.',
},
requirements: [
{ de: 'Öffentliches Interesse oder hoheitliche Aufgabe', en: 'Public interest or official authority' },
{ de: 'Rechtsgrundlage im EU/nationalen Recht', en: 'Legal basis in EU/national law' },
],
suitableFor: ['Behörden', 'Öffentlich-rechtliche Einrichtungen', 'Bildungseinrichtungen'],
notSuitableFor: ['Private Unternehmen (in der Regel)'],
documentationNeeded: [
{ de: 'Rechtsgrundlage für öffentliche Aufgabe', en: 'Legal basis for public task' },
{ de: 'Zusammenhang zur Aufgabe', en: 'Connection to task' },
],
isSpecialCategory: false,
},
{
type: 'LEGITIMATE_INTEREST',
article: 'Art. 6 Abs. 1 lit. f DSGVO',
name: { de: 'Berechtigtes Interesse', en: 'Legitimate Interest' },
shortName: { de: 'Ber. Interesse', en: 'Leg. Interest' },
description: {
de: 'Die Verarbeitung ist zur Wahrung der berechtigten Interessen des Verantwortlichen oder eines Dritten erforderlich, sofern nicht die Interessen oder Grundrechte der betroffenen Person überwiegen.',
en: 'Processing is necessary for the legitimate interests pursued by the controller or a third party, except where such interests are overridden by the interests or rights of the data subject.',
},
requirements: [
{ de: 'Berechtigtes Interesse identifizieren', en: 'Identify legitimate interest' },
{ de: 'Erforderlichkeit prüfen', en: 'Check necessity' },
{ de: 'Interessenabwägung durchführen', en: 'Conduct balancing test' },
{ de: 'Dokumentieren', en: 'Document' },
],
suitableFor: ['B2B-Marketing', 'IT-Sicherheit', 'Betrugsprävention', 'Konzerninterner Datenaustausch'],
notSuitableFor: ['Behörden', 'Verarbeitung sensibler Daten', 'Wenn Einwilligung verweigert wurde'],
documentationNeeded: [
{ de: 'Interessenabwägung (LIA)', en: 'Legitimate Interest Assessment (LIA)' },
{ de: 'Konkrete Interessen', en: 'Specific interests' },
{ de: 'Abwägung der Betroffenenrechte', en: 'Balancing of data subject rights' },
],
isSpecialCategory: false,
notes: {
de: 'Nicht anwendbar für Behörden bei Aufgabenerfüllung. Interessenabwägung (LIA) erforderlich.',
en: 'Not applicable for public authorities performing their tasks. Legitimate Interest Assessment (LIA) required.',
},
},
// Art. 9 Abs. 2 DSGVO - Special categories
{
type: 'ART9_CONSENT',
article: 'Art. 9 Abs. 2 lit. a DSGVO',
name: { de: 'Ausdrückliche Einwilligung', en: 'Explicit Consent' },
shortName: { de: 'Ausd. Einwilligung', en: 'Explicit Consent' },
description: {
de: 'Die betroffene Person hat in die Verarbeitung der besonderen Kategorien personenbezogener Daten ausdrücklich eingewilligt.',
en: 'The data subject has given explicit consent to the processing of special categories of personal data.',
},
requirements: [
{ de: 'Alle Anforderungen der normalen Einwilligung', en: 'All requirements of normal consent' },
{ de: 'Zusätzlich: Ausdrücklich', en: 'Additionally: Explicit' },
{ de: 'Besonderer Hinweis auf sensible Daten', en: 'Special notice about sensitive data' },
],
suitableFor: ['Gesundheitsdaten mit Einwilligung', 'Religiöse Daten mit Einwilligung'],
notSuitableFor: ['Arbeitsverhältnis (in der Regel)', 'Wenn nationales Recht es verbietet'],
documentationNeeded: [
{ de: 'Einwilligungstext mit Hinweis auf sensible Daten', en: 'Consent text with reference to sensitive data' },
{ de: 'Nachweis der ausdrücklichen Erteilung', en: 'Proof of explicit consent' },
],
isSpecialCategory: true,
},
{
type: 'ART9_EMPLOYMENT',
article: 'Art. 9 Abs. 2 lit. b DSGVO',
name: { de: 'Arbeitsrecht', en: 'Employment Law' },
shortName: { de: 'Arbeitsrecht', en: 'Employment' },
description: {
de: 'Die Verarbeitung ist erforderlich für arbeitsrechtliche Zwecke auf Grundlage von nationalen Rechtsvorschriften.',
en: 'Processing is necessary for employment law purposes based on national law provisions.',
},
requirements: [
{ de: 'Arbeitsrechtliche Grundlage (z.B. § 26 BDSG)', en: 'Employment law basis (e.g., § 26 BDSG)' },
{ de: 'Erforderlichkeit für Beschäftigung', en: 'Necessity for employment' },
{ de: 'Angemessene Garantien', en: 'Appropriate safeguards' },
],
suitableFor: ['Gesundheitsdaten im Arbeitsverhältnis', 'Schwerbehinderung', 'Gewerkschaftszugehörigkeit'],
notSuitableFor: ['Verarbeitung über das Erforderliche hinaus'],
documentationNeeded: [
{ de: 'Rechtsgrundlage (§ 26 BDSG)', en: 'Legal basis (§ 26 BDSG)' },
{ de: 'Erforderlichkeit dokumentieren', en: 'Document necessity' },
],
isSpecialCategory: true,
},
{
type: 'ART9_VITAL_INTEREST',
article: 'Art. 9 Abs. 2 lit. c DSGVO',
name: { de: 'Lebenswichtige Interessen (Art. 9)', en: 'Vital Interests (Art. 9)' },
shortName: { de: 'Vital (Art. 9)', en: 'Vital (Art. 9)' },
description: {
de: 'Die Verarbeitung ist zum Schutz lebenswichtiger Interessen erforderlich und die betroffene Person ist nicht einwilligungsfähig.',
en: 'Processing is necessary to protect vital interests and the data subject is incapable of giving consent.',
},
requirements: [
{ de: 'Schutz lebenswichtiger Interessen', en: 'Protection of vital interests' },
{ de: 'Betroffene Person nicht einwilligungsfähig', en: 'Data subject incapable of consent' },
],
suitableFor: ['Medizinische Notfälle', 'Bewusstlose Personen'],
notSuitableFor: ['Regelmäßige Verarbeitung'],
documentationNeeded: [
{ de: 'Dokumentation des Notfalls', en: 'Documentation of emergency' },
{ de: 'Nachweis der fehlenden Einwilligungsfähigkeit', en: 'Proof of incapacity to consent' },
],
isSpecialCategory: true,
},
{
type: 'ART9_HEALTH',
article: 'Art. 9 Abs. 2 lit. h DSGVO',
name: { de: 'Gesundheitsversorgung', en: 'Health Care' },
shortName: { de: 'Gesundheit', en: 'Health' },
description: {
de: 'Die Verarbeitung ist für Zwecke der Gesundheitsvorsorge oder Arbeitsmedizin erforderlich, auf Grundlage von EU- oder nationalem Recht.',
en: 'Processing is necessary for health care purposes or occupational medicine based on EU or national law.',
},
requirements: [
{ de: 'Gesundheitsvorsorge, Arbeitsmedizin', en: 'Health care, occupational medicine' },
{ de: 'Rechtsgrundlage im EU/nationalen Recht', en: 'Legal basis in EU/national law' },
{ de: 'Verarbeitung durch Fachpersonal', en: 'Processing by health professionals' },
{ de: 'Berufsgeheimnis beachten', en: 'Professional secrecy' },
],
suitableFor: ['Medizinische Behandlung', 'Betriebsärztliche Untersuchungen', 'Gesundheitsmanagement'],
notSuitableFor: ['Verarbeitung ohne medizinischen Kontext'],
documentationNeeded: [
{ de: 'Rechtsgrundlage', en: 'Legal basis' },
{ de: 'Fachliche Zuständigkeit', en: 'Professional competence' },
],
isSpecialCategory: true,
},
{
type: 'ART9_PUBLIC_HEALTH',
article: 'Art. 9 Abs. 2 lit. i DSGVO',
name: { de: 'Öffentliche Gesundheit', en: 'Public Health' },
shortName: { de: 'Öff. Gesundheit', en: 'Public Health' },
description: {
de: 'Die Verarbeitung ist aus Gründen des öffentlichen Interesses im Bereich der öffentlichen Gesundheit erforderlich.',
en: 'Processing is necessary for reasons of public interest in the area of public health.',
},
requirements: [
{ de: 'Öffentliches Interesse an öffentlicher Gesundheit', en: 'Public interest in public health' },
{ de: 'Rechtsgrundlage im EU/nationalen Recht', en: 'Legal basis in EU/national law' },
{ de: 'Angemessene Garantien', en: 'Appropriate safeguards' },
],
suitableFor: ['Pandemiebekämpfung', 'Seuchenprävention', 'Qualitätssicherung im Gesundheitswesen'],
notSuitableFor: ['Private Interessen'],
documentationNeeded: [
{ de: 'Rechtsgrundlage', en: 'Legal basis' },
{ de: 'Nachweis öffentliches Interesse', en: 'Proof of public interest' },
],
isSpecialCategory: true,
},
{
type: 'ART9_LEGAL_CLAIMS',
article: 'Art. 9 Abs. 2 lit. f DSGVO',
name: { de: 'Rechtsansprüche', en: 'Legal Claims' },
shortName: { de: 'Rechtsansprüche', en: 'Legal Claims' },
description: {
de: 'Die Verarbeitung ist zur Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen erforderlich.',
en: 'Processing is necessary for the establishment, exercise or defence of legal claims.',
},
requirements: [
{ de: 'Rechtsansprüche bestehen oder drohen', en: 'Legal claims exist or are anticipated' },
{ de: 'Verarbeitung ist erforderlich', en: 'Processing is necessary' },
],
suitableFor: ['Rechtsstreitigkeiten', 'Compliance-Untersuchungen', 'Interne Ermittlungen'],
notSuitableFor: ['Präventive Maßnahmen ohne konkreten Anlass'],
documentationNeeded: [
{ de: 'Dokumentation des Rechtsstreits/Anspruchs', en: 'Documentation of legal dispute/claim' },
{ de: 'Erforderlichkeit der Verarbeitung', en: 'Necessity of processing' },
],
isSpecialCategory: true,
},
]
// ==========================================
// RETENTION PERIODS
// ==========================================
export const STANDARD_RETENTION_PERIODS: RetentionPeriodInfo[] = [
// Handelsrechtliche Aufbewahrung
{
id: 'hgb-257',
name: { de: 'Handelsbücher und Buchungsbelege', en: 'Commercial Books and Vouchers' },
legalBasis: '§ 257 HGB',
duration: { value: 10, unit: 'YEARS' },
description: {
de: 'Handelsbücher, Inventare, Eröffnungsbilanzen, Jahresabschlüsse, Lageberichte, Konzernabschlüsse, Buchungsbelege',
en: 'Commercial books, inventories, opening balance sheets, annual financial statements, management reports, consolidated financial statements, accounting vouchers',
},
applicableTo: ['Buchhaltung', 'Jahresabschlüsse', 'Rechnungen', 'Verträge'],
},
{
id: 'hgb-257-6',
name: { de: 'Handels- und Geschäftsbriefe', en: 'Commercial and Business Correspondence' },
legalBasis: '§ 257 Abs. 1 Nr. 2, 3 HGB',
duration: { value: 6, unit: 'YEARS' },
description: {
de: 'Empfangene Handels- und Geschäftsbriefe, Wiedergaben der abgesandten Handels- und Geschäftsbriefe',
en: 'Received commercial and business correspondence, copies of sent correspondence',
},
applicableTo: ['Geschäftskorrespondenz', 'Angebote', 'Auftragsbestätigungen'],
},
// Steuerrechtliche Aufbewahrung
{
id: 'ao-147',
name: { de: 'Steuerrechtliche Unterlagen', en: 'Tax Documents' },
legalBasis: '§ 147 AO',
duration: { value: 10, unit: 'YEARS' },
description: {
de: 'Bücher und Aufzeichnungen, Inventare, Jahresabschlüsse, Buchungsbelege, steuerrelevante Unterlagen',
en: 'Books and records, inventories, annual financial statements, accounting vouchers, tax-relevant documents',
},
applicableTo: ['Steuererklärungen', 'Buchhaltung', 'Belege'],
},
// Arbeitsrechtliche Aufbewahrung
{
id: 'arbeitsrecht-personal',
name: { de: 'Personalunterlagen', en: 'Personnel Records' },
legalBasis: 'Verschiedene (AGG, ArbZG, etc.)',
duration: { value: 3, unit: 'YEARS' },
description: {
de: 'Personalakte nach Beendigung des Arbeitsverhältnisses (Regelverjährung)',
en: 'Personnel file after termination of employment (standard limitation period)',
},
applicableTo: ['Personalakten', 'Arbeitsverträge', 'Zeugnisse'],
},
{
id: 'arbzg',
name: { de: 'Arbeitszeitaufzeichnungen', en: 'Working Time Records' },
legalBasis: '§ 16 Abs. 2 ArbZG',
duration: { value: 2, unit: 'YEARS' },
description: {
de: 'Aufzeichnungen über Arbeitszeiten, die über 8 Stunden hinausgehen',
en: 'Records of working hours exceeding 8 hours',
},
applicableTo: ['Zeiterfassung', 'Überstunden'],
},
{
id: 'lohnsteuer',
name: { de: 'Lohnunterlagen', en: 'Payroll Documents' },
legalBasis: '§ 41 EStG, § 28f SGB IV',
duration: { value: 6, unit: 'YEARS' },
description: {
de: 'Lohnkonten und Unterlagen für den Lohnsteuerabzug',
en: 'Payroll accounts and documents for wage tax deduction',
},
applicableTo: ['Lohnabrechnungen', 'Lohnsteuerbescheinigungen'],
},
{
id: 'sozialversicherung',
name: { de: 'Sozialversicherungsunterlagen', en: 'Social Security Documents' },
legalBasis: '§ 28f SGB IV',
duration: { value: 5, unit: 'YEARS' },
description: {
de: 'Unterlagen zum Gesamtsozialversicherungsbeitrag',
en: 'Documents for total social security contributions',
},
applicableTo: ['Sozialversicherungsmeldungen', 'Beitragsnachweise'],
},
// Bewerberdaten
{
id: 'bewerbung',
name: { de: 'Bewerbungsunterlagen', en: 'Application Documents' },
legalBasis: '§ 15 Abs. 4 AGG',
duration: { value: 6, unit: 'MONTHS' },
description: {
de: 'Bewerbungsunterlagen nach Absage (AGG-Frist)',
en: 'Application documents after rejection (AGG deadline)',
},
applicableTo: ['Bewerbungen', 'Lebensläufe', 'Zeugnisse von Bewerbern'],
},
// Datenschutzrechtliche Fristen
{
id: 'einwilligung',
name: { de: 'Einwilligungen', en: 'Consents' },
legalBasis: 'Art. 7 Abs. 1 DSGVO',
duration: { value: 3, unit: 'YEARS' },
description: {
de: 'Dokumentation der Einwilligung (Regelverjährung)',
en: 'Documentation of consent (standard limitation period)',
},
applicableTo: ['Einwilligungsnachweise', 'Opt-in-Dokumentation'],
},
{
id: 'videoüberwachung',
name: { de: 'Videoüberwachung', en: 'Video Surveillance' },
legalBasis: 'Verhältnismäßigkeit',
duration: { value: 72, unit: 'DAYS' },
description: {
de: 'Videoaufnahmen (max. 72 Stunden, sofern kein Vorfall)',
en: 'Video recordings (max. 72 hours, unless incident occurred)',
},
applicableTo: ['CCTV-Aufnahmen', 'Überwachungsvideos'],
},
// Löschung nach Vertrag
{
id: 'avv-loeschung',
name: { de: 'AVV-Daten nach Vertragsende', en: 'DPA Data after Contract End' },
legalBasis: 'Art. 28 Abs. 3 lit. g DSGVO',
duration: { value: 30, unit: 'DAYS' },
description: {
de: 'Löschung oder Rückgabe aller personenbezogenen Daten nach Vertragsende',
en: 'Deletion or return of all personal data after contract end',
},
applicableTo: ['Auftragsverarbeitung', 'Dienstleister-Daten'],
},
]
// ==========================================
// HELPER FUNCTIONS
// ==========================================
/**
* Get legal basis info by type
*/
export function getLegalBasisInfo(type: LegalBasisType): LegalBasisInfo | undefined {
return LEGAL_BASIS_INFO.find((lb) => lb.type === type)
}
/**
* Get legal bases for standard data (non-special categories)
*/
export function getStandardLegalBases(): LegalBasisInfo[] {
return LEGAL_BASIS_INFO.filter((lb) => !lb.isSpecialCategory)
}
/**
* Get legal bases for special category data (Art. 9)
*/
export function getSpecialCategoryLegalBases(): LegalBasisInfo[] {
return LEGAL_BASIS_INFO.filter((lb) => lb.isSpecialCategory)
}
/**
* Get appropriate legal bases for data categories
*/
export function getAppropriateLegalBases(
dataCategories: PersonalDataCategory[]
): LegalBasisInfo[] {
const hasSpecialCategory = dataCategories.some((cat) =>
[
'HEALTH_DATA', 'GENETIC_DATA', 'BIOMETRIC_DATA', 'RACIAL_ETHNIC',
'POLITICAL_OPINIONS', 'RELIGIOUS_BELIEFS', 'TRADE_UNION', 'SEX_LIFE',
].includes(cat)
)
if (hasSpecialCategory) {
// Return Art. 9 bases plus compatible Art. 6 bases
return [
...getSpecialCategoryLegalBases(),
...getStandardLegalBases().filter((lb) =>
['LEGAL_OBLIGATION', 'VITAL_INTEREST', 'PUBLIC_TASK'].includes(lb.type)
),
]
}
return getStandardLegalBases()
}
/**
* Get retention period by ID
*/
export function getRetentionPeriod(id: string): RetentionPeriodInfo | undefined {
return STANDARD_RETENTION_PERIODS.find((rp) => rp.id === id)
}
/**
* Get retention periods applicable to a category
*/
export function getRetentionPeriodsForCategory(category: string): RetentionPeriodInfo[] {
return STANDARD_RETENTION_PERIODS.filter((rp) =>
rp.applicableTo.some((a) => a.toLowerCase().includes(category.toLowerCase()))
)
}
/**
* Get longest applicable retention period
*/
export function getLongestRetentionPeriod(categories: string[]): RetentionPeriodInfo | undefined {
const applicable = categories.flatMap((cat) => getRetentionPeriodsForCategory(cat))
if (applicable.length === 0) return undefined
return applicable.reduce((longest, current) => {
const longestMonths = toMonths(longest.duration)
const currentMonths = toMonths(current.duration)
return currentMonths > longestMonths ? current : longest
})
}
function toMonths(duration: { value: number; unit: 'DAYS' | 'MONTHS' | 'YEARS' }): number {
switch (duration.unit) {
case 'DAYS':
return duration.value / 30
case 'MONTHS':
return duration.value
case 'YEARS':
return duration.value * 12
}
}
/**
* Format retention period for display
*/
export function formatRetentionPeriod(
duration: { value: number; unit: 'DAYS' | 'MONTHS' | 'YEARS' },
locale: 'de' | 'en' = 'de'
): string {
const units = {
de: { DAYS: 'Tage', MONTHS: 'Monate', YEARS: 'Jahre' },
en: { DAYS: 'days', MONTHS: 'months', YEARS: 'years' },
}
return `${duration.value} ${units[locale][duration.unit]}`
}

View File

@@ -0,0 +1,813 @@
/**
* Standard Processing Activities Catalog
*
* 28 predefined processing activities templates following Art. 30 DSGVO
*/
import {
ProcessingActivityFormData,
DataSubjectCategory,
PersonalDataCategory,
LegalBasisType,
ProtectionLevel,
LocalizedText,
} from '../types'
export interface ProcessingActivityTemplate {
id: string
category: ProcessingActivityCategory
name: LocalizedText
description: LocalizedText
purposes: LocalizedText[]
dataSubjectCategories: DataSubjectCategory[]
personalDataCategories: PersonalDataCategory[]
suggestedLegalBasis: LegalBasisType[]
suggestedRetentionYears: number
suggestedProtectionLevel: ProtectionLevel
dpiaLikely: boolean
commonSystems: string[]
commonVendorCategories: string[]
}
export type ProcessingActivityCategory =
| 'HR' // Human Resources
| 'SALES' // Vertrieb
| 'MARKETING' // Marketing
| 'FINANCE' // Finanzen
| 'IT' // IT & Sicherheit
| 'CUSTOMER_SERVICE' // Kundenservice
| 'WEBSITE' // Website & Apps
| 'GENERAL' // Allgemein
export const PROCESSING_ACTIVITY_CATEGORY_META: Record<ProcessingActivityCategory, LocalizedText> = {
HR: { de: 'Personal', en: 'Human Resources' },
SALES: { de: 'Vertrieb', en: 'Sales' },
MARKETING: { de: 'Marketing', en: 'Marketing' },
FINANCE: { de: 'Finanzen', en: 'Finance' },
IT: { de: 'IT & Sicherheit', en: 'IT & Security' },
CUSTOMER_SERVICE: { de: 'Kundenservice', en: 'Customer Service' },
WEBSITE: { de: 'Website & Apps', en: 'Website & Apps' },
GENERAL: { de: 'Allgemein', en: 'General' },
}
export const PROCESSING_ACTIVITY_TEMPLATES: ProcessingActivityTemplate[] = [
// ==========================================
// HR - Human Resources
// ==========================================
{
id: 'tpl-hr-recruitment',
category: 'HR',
name: {
de: 'Bewerbermanagement',
en: 'Recruitment Management',
},
description: {
de: 'Verarbeitung von Bewerberdaten im Rahmen des Recruiting-Prozesses',
en: 'Processing of applicant data as part of the recruitment process',
},
purposes: [
{ de: 'Durchführung des Bewerbungsverfahrens', en: 'Conducting the application process' },
{ de: 'Prüfung der Eignung', en: 'Assessing suitability' },
{ de: 'Aufbau eines Talentpools (bei Einwilligung)', en: 'Building a talent pool (with consent)' },
],
dataSubjectCategories: ['APPLICANTS'],
personalDataCategories: [
'NAME', 'CONTACT', 'ADDRESS', 'DOB', 'EDUCATION_DATA',
'EMPLOYMENT_DATA', 'PHOTO_VIDEO',
],
suggestedLegalBasis: ['CONTRACT', 'CONSENT'],
suggestedRetentionYears: 0.5, // 6 Monate nach Absage
suggestedProtectionLevel: 'MEDIUM',
dpiaLikely: false,
commonSystems: ['E-Recruiting', 'Personio', 'Workday'],
commonVendorCategories: ['HR_SOFTWARE', 'CLOUD_INFRASTRUCTURE'],
},
{
id: 'tpl-hr-personnel',
category: 'HR',
name: {
de: 'Personalverwaltung',
en: 'Personnel Administration',
},
description: {
de: 'Führung der Personalakte und Verwaltung des Beschäftigungsverhältnisses',
en: 'Maintaining personnel files and managing employment relationships',
},
purposes: [
{ de: 'Führung der Personalakte', en: 'Maintaining personnel files' },
{ de: 'Durchführung des Arbeitsverhältnisses', en: 'Executing the employment relationship' },
{ de: 'Erfüllung gesetzlicher Pflichten', en: 'Fulfilling legal obligations' },
],
dataSubjectCategories: ['EMPLOYEES'],
personalDataCategories: [
'NAME', 'CONTACT', 'ADDRESS', 'DOB', 'ID_NUMBER',
'SOCIAL_SECURITY', 'TAX_ID', 'BANK_ACCOUNT', 'EMPLOYMENT_DATA',
'SALARY_DATA', 'EDUCATION_DATA', 'PHOTO_VIDEO',
],
suggestedLegalBasis: ['CONTRACT', 'LEGAL_OBLIGATION'],
suggestedRetentionYears: 10, // Nach Beendigung
suggestedProtectionLevel: 'HIGH',
dpiaLikely: false,
commonSystems: ['SAP HCM', 'Personio', 'DATEV'],
commonVendorCategories: ['HR_SOFTWARE', 'ERP'],
},
{
id: 'tpl-hr-payroll',
category: 'HR',
name: {
de: 'Lohn- und Gehaltsabrechnung',
en: 'Payroll Processing',
},
description: {
de: 'Berechnung und Auszahlung von Gehältern, Abführung von Steuern und Sozialabgaben',
en: 'Calculation and payment of salaries, tax and social security contributions',
},
purposes: [
{ de: 'Gehaltsberechnung und -auszahlung', en: 'Salary calculation and payment' },
{ de: 'Abführung von Lohnsteuer und Sozialabgaben', en: 'Payment of payroll taxes and social contributions' },
{ de: 'Erstellung von Lohnabrechnungen', en: 'Creating payslips' },
],
dataSubjectCategories: ['EMPLOYEES'],
personalDataCategories: [
'NAME', 'ADDRESS', 'DOB', 'SOCIAL_SECURITY', 'TAX_ID',
'BANK_ACCOUNT', 'SALARY_DATA', 'EMPLOYMENT_DATA',
],
suggestedLegalBasis: ['CONTRACT', 'LEGAL_OBLIGATION'],
suggestedRetentionYears: 10, // Handels- und Steuerrecht
suggestedProtectionLevel: 'HIGH',
dpiaLikely: false,
commonSystems: ['DATEV', 'SAP', 'Lexware'],
commonVendorCategories: ['ACCOUNTING', 'HR_SOFTWARE'],
},
{
id: 'tpl-hr-time-tracking',
category: 'HR',
name: {
de: 'Arbeitszeiterfassung',
en: 'Time Tracking',
},
description: {
de: 'Erfassung der Arbeitszeiten zur Einhaltung des Arbeitszeitgesetzes',
en: 'Recording working hours for compliance with working time regulations',
},
purposes: [
{ de: 'Erfassung der Arbeitszeiten', en: 'Recording working hours' },
{ de: 'Einhaltung des Arbeitszeitgesetzes', en: 'Compliance with working time regulations' },
{ de: 'Grundlage für Gehaltsabrechnung', en: 'Basis for payroll' },
],
dataSubjectCategories: ['EMPLOYEES'],
personalDataCategories: ['NAME', 'EMPLOYMENT_DATA', 'USAGE_DATA'],
suggestedLegalBasis: ['LEGAL_OBLIGATION', 'CONTRACT'],
suggestedRetentionYears: 2,
suggestedProtectionLevel: 'LOW',
dpiaLikely: false,
commonSystems: ['ATOSS', 'Clockodo', 'Toggl'],
commonVendorCategories: ['HR_SOFTWARE'],
},
{
id: 'tpl-hr-health-management',
category: 'HR',
name: {
de: 'Betriebliches Gesundheitsmanagement',
en: 'Occupational Health Management',
},
description: {
de: 'Verwaltung von Arbeitsunfähigkeitsbescheinigungen und betriebsärztlichen Untersuchungen',
en: 'Management of sick notes and occupational health examinations',
},
purposes: [
{ de: 'Verwaltung von Krankmeldungen', en: 'Managing sick leave' },
{ de: 'Organisation betriebsärztlicher Untersuchungen', en: 'Organizing occupational health examinations' },
{ de: 'Betriebliches Eingliederungsmanagement', en: 'Occupational reintegration management' },
],
dataSubjectCategories: ['EMPLOYEES'],
personalDataCategories: ['NAME', 'EMPLOYMENT_DATA', 'HEALTH_DATA'],
suggestedLegalBasis: ['ART9_EMPLOYMENT', 'LEGAL_OBLIGATION'],
suggestedRetentionYears: 3,
suggestedProtectionLevel: 'HIGH',
dpiaLikely: true,
commonSystems: ['HR-Software', 'BEM-System'],
commonVendorCategories: ['HR_SOFTWARE', 'CONSULTING'],
},
// ==========================================
// SALES - Vertrieb
// ==========================================
{
id: 'tpl-sales-crm',
category: 'SALES',
name: {
de: 'Kundenbeziehungsmanagement (CRM)',
en: 'Customer Relationship Management (CRM)',
},
description: {
de: 'Verwaltung von Kundenbeziehungen, Kontakthistorie und Verkaufschancen',
en: 'Managing customer relationships, contact history, and sales opportunities',
},
purposes: [
{ de: 'Pflege von Kundenbeziehungen', en: 'Maintaining customer relationships' },
{ de: 'Dokumentation von Kundenkontakten', en: 'Documenting customer contacts' },
{ de: 'Vertriebssteuerung', en: 'Sales management' },
],
dataSubjectCategories: ['CUSTOMERS', 'PROSPECTIVE_CUSTOMERS', 'BUSINESS_PARTNERS'],
personalDataCategories: [
'NAME', 'CONTACT', 'ADDRESS', 'CONTRACT_DATA', 'COMMUNICATION_DATA',
],
suggestedLegalBasis: ['CONTRACT', 'LEGITIMATE_INTEREST'],
suggestedRetentionYears: 3, // Nach letztem Kontakt
suggestedProtectionLevel: 'MEDIUM',
dpiaLikely: false,
commonSystems: ['Salesforce', 'HubSpot', 'Pipedrive', 'Microsoft Dynamics'],
commonVendorCategories: ['CRM'],
},
{
id: 'tpl-sales-contract-management',
category: 'SALES',
name: {
de: 'Vertragsmanagement',
en: 'Contract Management',
},
description: {
de: 'Verwaltung von Kundenverträgen, Angeboten und Aufträgen',
en: 'Managing customer contracts, quotes, and orders',
},
purposes: [
{ de: 'Erstellung und Verwaltung von Verträgen', en: 'Creating and managing contracts' },
{ de: 'Angebotsverfolgung', en: 'Quote tracking' },
{ de: 'Auftragsabwicklung', en: 'Order processing' },
],
dataSubjectCategories: ['CUSTOMERS', 'BUSINESS_PARTNERS'],
personalDataCategories: [
'NAME', 'CONTACT', 'ADDRESS', 'CONTRACT_DATA', 'PAYMENT_DATA',
],
suggestedLegalBasis: ['CONTRACT', 'LEGAL_OBLIGATION'],
suggestedRetentionYears: 10, // Handelsrechtlich
suggestedProtectionLevel: 'MEDIUM',
dpiaLikely: false,
commonSystems: ['ERP', 'CRM', 'Vertragsverwaltung'],
commonVendorCategories: ['ERP', 'CRM'],
},
// ==========================================
// MARKETING
// ==========================================
{
id: 'tpl-marketing-newsletter',
category: 'MARKETING',
name: {
de: 'Newsletter-Versand',
en: 'Newsletter Distribution',
},
description: {
de: 'Versand von E-Mail-Newslettern und Marketing-Kommunikation',
en: 'Sending email newsletters and marketing communications',
},
purposes: [
{ de: 'Versand von Newsletter und Marketing-E-Mails', en: 'Sending newsletters and marketing emails' },
{ de: 'Messung von Öffnungs- und Klickraten', en: 'Measuring open and click rates' },
],
dataSubjectCategories: ['NEWSLETTER_SUBSCRIBERS', 'CUSTOMERS'],
personalDataCategories: ['NAME', 'CONTACT', 'USAGE_DATA'],
suggestedLegalBasis: ['CONSENT'],
suggestedRetentionYears: 0, // Bis Widerruf
suggestedProtectionLevel: 'LOW',
dpiaLikely: false,
commonSystems: ['Mailchimp', 'CleverReach', 'Sendinblue'],
commonVendorCategories: ['EMAIL', 'MARKETING'],
},
{
id: 'tpl-marketing-advertising',
category: 'MARKETING',
name: {
de: 'Online-Werbung',
en: 'Online Advertising',
},
description: {
de: 'Schaltung und Auswertung von Online-Werbeanzeigen',
en: 'Running and analyzing online advertisements',
},
purposes: [
{ de: 'Schaltung von Online-Werbung', en: 'Running online advertisements' },
{ de: 'Conversion-Tracking', en: 'Conversion tracking' },
{ de: 'Retargeting', en: 'Retargeting' },
],
dataSubjectCategories: ['WEBSITE_USERS'],
personalDataCategories: ['IP_ADDRESS', 'DEVICE_ID', 'USAGE_DATA'],
suggestedLegalBasis: ['CONSENT'],
suggestedRetentionYears: 1,
suggestedProtectionLevel: 'MEDIUM',
dpiaLikely: true,
commonSystems: ['Google Ads', 'Meta Ads', 'LinkedIn Ads'],
commonVendorCategories: ['MARKETING', 'ANALYTICS'],
},
{
id: 'tpl-marketing-events',
category: 'MARKETING',
name: {
de: 'Veranstaltungsmanagement',
en: 'Event Management',
},
description: {
de: 'Organisation und Durchführung von Veranstaltungen, Messen und Webinaren',
en: 'Organizing and conducting events, trade shows, and webinars',
},
purposes: [
{ de: 'Teilnehmerregistrierung', en: 'Participant registration' },
{ de: 'Veranstaltungsdurchführung', en: 'Event execution' },
{ de: 'Nachbereitung und Follow-up', en: 'Follow-up activities' },
],
dataSubjectCategories: ['CUSTOMERS', 'PROSPECTIVE_CUSTOMERS', 'BUSINESS_PARTNERS'],
personalDataCategories: ['NAME', 'CONTACT', 'ADDRESS', 'PHOTO_VIDEO'],
suggestedLegalBasis: ['CONTRACT', 'CONSENT'],
suggestedRetentionYears: 2,
suggestedProtectionLevel: 'LOW',
dpiaLikely: false,
commonSystems: ['Eventbrite', 'GoToWebinar', 'Zoom'],
commonVendorCategories: ['MARKETING', 'COMMUNICATION'],
},
// ==========================================
// FINANCE
// ==========================================
{
id: 'tpl-finance-accounting',
category: 'FINANCE',
name: {
de: 'Finanzbuchhaltung',
en: 'Financial Accounting',
},
description: {
de: 'Führung der Finanzbuchhaltung, Rechnungsstellung und Zahlungsabwicklung',
en: 'Financial accounting, invoicing, and payment processing',
},
purposes: [
{ de: 'Buchführung und Rechnungswesen', en: 'Bookkeeping and accounting' },
{ de: 'Rechnungsstellung', en: 'Invoicing' },
{ de: 'Zahlungsabwicklung', en: 'Payment processing' },
],
dataSubjectCategories: ['CUSTOMERS', 'SUPPLIERS', 'BUSINESS_PARTNERS'],
personalDataCategories: [
'NAME', 'ADDRESS', 'BANK_ACCOUNT', 'PAYMENT_DATA', 'CONTRACT_DATA', 'TAX_ID',
],
suggestedLegalBasis: ['CONTRACT', 'LEGAL_OBLIGATION'],
suggestedRetentionYears: 10, // HGB/AO
suggestedProtectionLevel: 'HIGH',
dpiaLikely: false,
commonSystems: ['DATEV', 'SAP', 'Lexware', 'Xero'],
commonVendorCategories: ['ACCOUNTING', 'ERP'],
},
{
id: 'tpl-finance-debt-collection',
category: 'FINANCE',
name: {
de: 'Forderungsmanagement',
en: 'Debt Collection',
},
description: {
de: 'Verwaltung offener Forderungen und Mahnwesen',
en: 'Managing outstanding receivables and dunning',
},
purposes: [
{ de: 'Überwachung offener Forderungen', en: 'Monitoring outstanding receivables' },
{ de: 'Mahnwesen', en: 'Dunning process' },
{ de: 'Inkasso bei Bedarf', en: 'Debt collection if necessary' },
],
dataSubjectCategories: ['CUSTOMERS'],
personalDataCategories: ['NAME', 'ADDRESS', 'CONTACT', 'PAYMENT_DATA', 'CONTRACT_DATA'],
suggestedLegalBasis: ['CONTRACT', 'LEGITIMATE_INTEREST'],
suggestedRetentionYears: 10,
suggestedProtectionLevel: 'MEDIUM',
dpiaLikely: false,
commonSystems: ['ERP', 'Inkasso-Software'],
commonVendorCategories: ['ACCOUNTING', 'LEGAL'],
},
// ==========================================
// IT & SICHERHEIT
// ==========================================
{
id: 'tpl-it-user-management',
category: 'IT',
name: {
de: 'IT-Benutzerverwaltung',
en: 'IT User Management',
},
description: {
de: 'Verwaltung von Benutzerkonten, Zugriffsrechten und Authentifizierung',
en: 'Managing user accounts, access rights, and authentication',
},
purposes: [
{ de: 'Verwaltung von Benutzerkonten', en: 'Managing user accounts' },
{ de: 'Zugriffssteuerung', en: 'Access control' },
{ de: 'Single Sign-On', en: 'Single Sign-On' },
],
dataSubjectCategories: ['EMPLOYEES'],
personalDataCategories: ['NAME', 'CONTACT', 'LOGIN_DATA', 'USAGE_DATA'],
suggestedLegalBasis: ['CONTRACT', 'LEGITIMATE_INTEREST'],
suggestedRetentionYears: 1, // Nach Kontoschließung
suggestedProtectionLevel: 'HIGH',
dpiaLikely: false,
commonSystems: ['Active Directory', 'Okta', 'Azure AD'],
commonVendorCategories: ['SECURITY', 'CLOUD_INFRASTRUCTURE'],
},
{
id: 'tpl-it-logging',
category: 'IT',
name: {
de: 'IT-Protokollierung',
en: 'IT Logging',
},
description: {
de: 'Protokollierung von IT-Aktivitäten zur Sicherheit und Fehleranalyse',
en: 'Logging IT activities for security and error analysis',
},
purposes: [
{ de: 'Sicherheitsüberwachung', en: 'Security monitoring' },
{ de: 'Fehleranalyse', en: 'Error analysis' },
{ de: 'Nachvollziehbarkeit', en: 'Traceability' },
],
dataSubjectCategories: ['EMPLOYEES', 'CUSTOMERS', 'WEBSITE_USERS'],
personalDataCategories: ['IP_ADDRESS', 'DEVICE_ID', 'USAGE_DATA', 'LOGIN_DATA'],
suggestedLegalBasis: ['LEGITIMATE_INTEREST'],
suggestedRetentionYears: 1,
suggestedProtectionLevel: 'MEDIUM',
dpiaLikely: false,
commonSystems: ['Splunk', 'ELK Stack', 'Datadog'],
commonVendorCategories: ['SECURITY', 'ANALYTICS'],
},
{
id: 'tpl-it-video-surveillance',
category: 'IT',
name: {
de: 'Videoüberwachung',
en: 'Video Surveillance',
},
description: {
de: 'Videoüberwachung von Geschäftsräumen zum Schutz vor Diebstahl und Vandalismus',
en: 'Video surveillance of business premises for theft and vandalism prevention',
},
purposes: [
{ de: 'Schutz vor Diebstahl und Vandalismus', en: 'Protection against theft and vandalism' },
{ de: 'Zugangskontrolle', en: 'Access control' },
{ de: 'Beweissicherung', en: 'Evidence preservation' },
],
dataSubjectCategories: ['EMPLOYEES', 'VISITORS', 'CUSTOMERS'],
personalDataCategories: ['PHOTO_VIDEO', 'BIOMETRIC_DATA'],
suggestedLegalBasis: ['LEGITIMATE_INTEREST'],
suggestedRetentionYears: 0.1, // 72 Stunden
suggestedProtectionLevel: 'HIGH',
dpiaLikely: true,
commonSystems: ['CCTV-System'],
commonVendorCategories: ['SECURITY'],
},
{
id: 'tpl-it-backup',
category: 'IT',
name: {
de: 'Datensicherung (Backup)',
en: 'Data Backup',
},
description: {
de: 'Regelmäßige Sicherung von Unternehmensdaten',
en: 'Regular backup of company data',
},
purposes: [
{ de: 'Datensicherung', en: 'Data backup' },
{ de: 'Disaster Recovery', en: 'Disaster Recovery' },
{ de: 'Geschäftskontinuität', en: 'Business continuity' },
],
dataSubjectCategories: ['EMPLOYEES', 'CUSTOMERS', 'SUPPLIERS'],
personalDataCategories: ['NAME', 'CONTACT', 'CONTRACT_DATA', 'COMMUNICATION_DATA'],
suggestedLegalBasis: ['LEGITIMATE_INTEREST', 'LEGAL_OBLIGATION'],
suggestedRetentionYears: 1, // Je nach Backup-Konzept
suggestedProtectionLevel: 'HIGH',
dpiaLikely: false,
commonSystems: ['Veeam', 'AWS Backup', 'Azure Backup'],
commonVendorCategories: ['BACKUP', 'CLOUD_INFRASTRUCTURE'],
},
// ==========================================
// CUSTOMER SERVICE
// ==========================================
{
id: 'tpl-cs-support',
category: 'CUSTOMER_SERVICE',
name: {
de: 'Kundenbetreuung und Support',
en: 'Customer Support',
},
description: {
de: 'Bearbeitung von Kundenanfragen, Beschwerden und Support-Tickets',
en: 'Handling customer inquiries, complaints, and support tickets',
},
purposes: [
{ de: 'Bearbeitung von Kundenanfragen', en: 'Handling customer inquiries' },
{ de: 'Beschwerdemanagement', en: 'Complaint management' },
{ de: 'Technischer Support', en: 'Technical support' },
],
dataSubjectCategories: ['CUSTOMERS'],
personalDataCategories: ['NAME', 'CONTACT', 'CONTRACT_DATA', 'COMMUNICATION_DATA'],
suggestedLegalBasis: ['CONTRACT', 'LEGITIMATE_INTEREST'],
suggestedRetentionYears: 3,
suggestedProtectionLevel: 'MEDIUM',
dpiaLikely: false,
commonSystems: ['Zendesk', 'Freshdesk', 'Intercom'],
commonVendorCategories: ['SUPPORT', 'CRM'],
},
{
id: 'tpl-cs-satisfaction',
category: 'CUSTOMER_SERVICE',
name: {
de: 'Kundenzufriedenheitsbefragungen',
en: 'Customer Satisfaction Surveys',
},
description: {
de: 'Durchführung von Umfragen zur Messung der Kundenzufriedenheit',
en: 'Conducting surveys to measure customer satisfaction',
},
purposes: [
{ de: 'Messung der Kundenzufriedenheit', en: 'Measuring customer satisfaction' },
{ de: 'Qualitätsverbesserung', en: 'Quality improvement' },
],
dataSubjectCategories: ['CUSTOMERS'],
personalDataCategories: ['NAME', 'CONTACT', 'USAGE_DATA'],
suggestedLegalBasis: ['CONSENT', 'LEGITIMATE_INTEREST'],
suggestedRetentionYears: 2,
suggestedProtectionLevel: 'LOW',
dpiaLikely: false,
commonSystems: ['SurveyMonkey', 'Typeform', 'NPS-Tools'],
commonVendorCategories: ['ANALYTICS', 'MARKETING'],
},
// ==========================================
// WEBSITE & APPS
// ==========================================
{
id: 'tpl-web-analytics',
category: 'WEBSITE',
name: {
de: 'Web-Analyse',
en: 'Web Analytics',
},
description: {
de: 'Analyse des Nutzerverhaltens auf der Website zur Optimierung',
en: 'Analyzing user behavior on the website for optimization',
},
purposes: [
{ de: 'Analyse des Nutzerverhaltens', en: 'Analyzing user behavior' },
{ de: 'Website-Optimierung', en: 'Website optimization' },
{ de: 'Conversion-Tracking', en: 'Conversion tracking' },
],
dataSubjectCategories: ['WEBSITE_USERS'],
personalDataCategories: ['IP_ADDRESS', 'DEVICE_ID', 'USAGE_DATA', 'LOCATION_DATA'],
suggestedLegalBasis: ['CONSENT'],
suggestedRetentionYears: 2,
suggestedProtectionLevel: 'MEDIUM',
dpiaLikely: false,
commonSystems: ['Google Analytics', 'Matomo', 'Plausible'],
commonVendorCategories: ['ANALYTICS'],
},
{
id: 'tpl-web-contact-form',
category: 'WEBSITE',
name: {
de: 'Kontaktformular',
en: 'Contact Form',
},
description: {
de: 'Verarbeitung von Anfragen über das Website-Kontaktformular',
en: 'Processing inquiries submitted via the website contact form',
},
purposes: [
{ de: 'Bearbeitung von Kontaktanfragen', en: 'Processing contact inquiries' },
{ de: 'Kommunikation mit Interessenten', en: 'Communication with prospects' },
],
dataSubjectCategories: ['PROSPECTIVE_CUSTOMERS', 'WEBSITE_USERS'],
personalDataCategories: ['NAME', 'CONTACT', 'COMMUNICATION_DATA'],
suggestedLegalBasis: ['CONTRACT', 'LEGITIMATE_INTEREST'],
suggestedRetentionYears: 1,
suggestedProtectionLevel: 'LOW',
dpiaLikely: false,
commonSystems: ['CRM', 'E-Mail-System'],
commonVendorCategories: ['CRM', 'EMAIL'],
},
{
id: 'tpl-web-user-accounts',
category: 'WEBSITE',
name: {
de: 'Benutzerkonten / Kundenportal',
en: 'User Accounts / Customer Portal',
},
description: {
de: 'Verwaltung von Benutzerkonten im Kundenportal oder Online-Shop',
en: 'Managing user accounts in customer portal or online shop',
},
purposes: [
{ de: 'Bereitstellung des Kundenportals', en: 'Providing customer portal' },
{ de: 'Benutzerverwaltung', en: 'User management' },
{ de: 'Personalisierung', en: 'Personalization' },
],
dataSubjectCategories: ['CUSTOMERS', 'APP_USERS'],
personalDataCategories: ['NAME', 'CONTACT', 'LOGIN_DATA', 'USAGE_DATA', 'CONTRACT_DATA'],
suggestedLegalBasis: ['CONTRACT'],
suggestedRetentionYears: 1, // Nach Kontoschließung
suggestedProtectionLevel: 'MEDIUM',
dpiaLikely: false,
commonSystems: ['E-Commerce', 'CRM', 'Auth0'],
commonVendorCategories: ['HOSTING', 'CRM', 'SECURITY'],
},
{
id: 'tpl-web-cookies',
category: 'WEBSITE',
name: {
de: 'Cookie-Verwaltung',
en: 'Cookie Management',
},
description: {
de: 'Verwaltung von Cookies und Einholung von Cookie-Einwilligungen',
en: 'Managing cookies and obtaining cookie consents',
},
purposes: [
{ de: 'Speicherung von Cookie-Präferenzen', en: 'Storing cookie preferences' },
{ de: 'Einwilligungsmanagement', en: 'Consent management' },
],
dataSubjectCategories: ['WEBSITE_USERS'],
personalDataCategories: ['IP_ADDRESS', 'DEVICE_ID', 'USAGE_DATA'],
suggestedLegalBasis: ['CONSENT', 'LEGITIMATE_INTEREST'],
suggestedRetentionYears: 1,
suggestedProtectionLevel: 'LOW',
dpiaLikely: false,
commonSystems: ['Cookiebot', 'Usercentrics', 'OneTrust'],
commonVendorCategories: ['ANALYTICS', 'SECURITY'],
},
// ==========================================
// GENERAL
// ==========================================
{
id: 'tpl-gen-communication',
category: 'GENERAL',
name: {
de: 'Geschäftliche Kommunikation',
en: 'Business Communication',
},
description: {
de: 'E-Mail-Kommunikation, Telefonie und Messaging im Geschäftsverkehr',
en: 'Email communication, telephony, and messaging in business operations',
},
purposes: [
{ de: 'Geschäftliche Kommunikation', en: 'Business communication' },
{ de: 'Dokumentation von Korrespondenz', en: 'Documentation of correspondence' },
],
dataSubjectCategories: ['CUSTOMERS', 'SUPPLIERS', 'BUSINESS_PARTNERS', 'EMPLOYEES'],
personalDataCategories: ['NAME', 'CONTACT', 'COMMUNICATION_DATA'],
suggestedLegalBasis: ['CONTRACT', 'LEGITIMATE_INTEREST'],
suggestedRetentionYears: 6, // Handelsrechtlich relevant
suggestedProtectionLevel: 'MEDIUM',
dpiaLikely: false,
commonSystems: ['Microsoft 365', 'Google Workspace', 'Slack'],
commonVendorCategories: ['EMAIL', 'COMMUNICATION', 'CLOUD_INFRASTRUCTURE'],
},
{
id: 'tpl-gen-visitor',
category: 'GENERAL',
name: {
de: 'Besucherverwaltung',
en: 'Visitor Management',
},
description: {
de: 'Erfassung und Verwaltung von Besuchern in Geschäftsräumen',
en: 'Recording and managing visitors in business premises',
},
purposes: [
{ de: 'Zutrittskontrolle', en: 'Access control' },
{ de: 'Sicherheit', en: 'Security' },
{ de: 'Nachvollziehbarkeit', en: 'Traceability' },
],
dataSubjectCategories: ['VISITORS'],
personalDataCategories: ['NAME', 'CONTACT', 'PHOTO_VIDEO'],
suggestedLegalBasis: ['LEGITIMATE_INTEREST'],
suggestedRetentionYears: 0.1, // 1 Monat
suggestedProtectionLevel: 'LOW',
dpiaLikely: false,
commonSystems: ['Besuchermanagement-System'],
commonVendorCategories: ['SECURITY'],
},
{
id: 'tpl-gen-supplier',
category: 'GENERAL',
name: {
de: 'Lieferantenverwaltung',
en: 'Supplier Management',
},
description: {
de: 'Verwaltung von Lieferantenbeziehungen und Beschaffung',
en: 'Managing supplier relationships and procurement',
},
purposes: [
{ de: 'Lieferantenverwaltung', en: 'Supplier management' },
{ de: 'Beschaffung', en: 'Procurement' },
{ de: 'Qualitätsmanagement', en: 'Quality management' },
],
dataSubjectCategories: ['SUPPLIERS', 'BUSINESS_PARTNERS'],
personalDataCategories: ['NAME', 'CONTACT', 'ADDRESS', 'CONTRACT_DATA', 'BANK_ACCOUNT'],
suggestedLegalBasis: ['CONTRACT', 'LEGITIMATE_INTEREST'],
suggestedRetentionYears: 10,
suggestedProtectionLevel: 'MEDIUM',
dpiaLikely: false,
commonSystems: ['ERP', 'Lieferantenportal'],
commonVendorCategories: ['ERP'],
},
{
id: 'tpl-gen-whistleblower',
category: 'GENERAL',
name: {
de: 'Hinweisgebersystem',
en: 'Whistleblower System',
},
description: {
de: 'Entgegennahme und Bearbeitung von Hinweisen gemäß Hinweisgeberschutzgesetz',
en: 'Receiving and processing reports according to whistleblower protection law',
},
purposes: [
{ de: 'Entgegennahme von Hinweisen', en: 'Receiving reports' },
{ de: 'Untersuchung von Verstößen', en: 'Investigating violations' },
{ de: 'Schutz von Hinweisgebern', en: 'Protecting whistleblowers' },
],
dataSubjectCategories: ['EMPLOYEES', 'BUSINESS_PARTNERS'],
personalDataCategories: ['NAME', 'CONTACT', 'COMMUNICATION_DATA'],
suggestedLegalBasis: ['LEGAL_OBLIGATION'],
suggestedRetentionYears: 3,
suggestedProtectionLevel: 'HIGH',
dpiaLikely: true,
commonSystems: ['Hinweisgeberportal'],
commonVendorCategories: ['SECURITY', 'LEGAL'],
},
]
/**
* Get templates by category
*/
export function getTemplatesByCategory(
category: ProcessingActivityCategory
): ProcessingActivityTemplate[] {
return PROCESSING_ACTIVITY_TEMPLATES.filter((t) => t.category === category)
}
/**
* Get template by ID
*/
export function getTemplateById(id: string): ProcessingActivityTemplate | undefined {
return PROCESSING_ACTIVITY_TEMPLATES.find((t) => t.id === id)
}
/**
* Get all categories with their templates
*/
export function getGroupedTemplates(): Map<ProcessingActivityCategory, ProcessingActivityTemplate[]> {
const grouped = new Map<ProcessingActivityCategory, ProcessingActivityTemplate[]>()
for (const template of PROCESSING_ACTIVITY_TEMPLATES) {
const existing = grouped.get(template.category) || []
grouped.set(template.category, [...existing, template])
}
return grouped
}
/**
* Create form data from template
*/
export function createFormDataFromTemplate(
template: ProcessingActivityTemplate,
organizationDefaults?: {
responsible?: ProcessingActivityFormData['responsible']
dpoContact?: ProcessingActivityFormData['dpoContact']
}
): Partial<ProcessingActivityFormData> {
return {
vvtId: '', // Will be generated
name: template.name,
purposes: template.purposes,
dataSubjectCategories: template.dataSubjectCategories,
personalDataCategories: template.personalDataCategories,
legalBasis: template.suggestedLegalBasis.map((type) => ({ type })),
protectionLevel: template.suggestedProtectionLevel,
dpiaRequired: template.dpiaLikely,
retentionPeriod: {
duration: template.suggestedRetentionYears,
durationUnit: 'YEARS',
description: { de: '', en: '' },
},
recipientCategories: [],
thirdCountryTransfers: [],
technicalMeasures: [],
dataSources: [],
systems: [],
dataFlows: [],
subProcessors: [],
owner: '',
responsible: organizationDefaults?.responsible,
dpoContact: organizationDefaults?.dpoContact,
}
}

View File

@@ -0,0 +1,564 @@
/**
* Vendor Templates and Categories
*
* Pre-defined vendor templates and risk profiles
*/
import {
VendorFormData,
VendorRole,
ServiceCategory,
DataAccessLevel,
TransferMechanismType,
DocumentType,
ReviewFrequency,
LocalizedText,
PersonalDataCategory,
} from '../types'
export interface VendorTemplate {
id: string
name: LocalizedText
description: LocalizedText
serviceCategory: ServiceCategory
suggestedRole: VendorRole
suggestedDataAccess: DataAccessLevel
suggestedTransferMechanisms: TransferMechanismType[]
suggestedContractTypes: DocumentType[]
typicalDataCategories: PersonalDataCategory[]
typicalCertifications: string[]
inherentRiskFactors: RiskFactorWeight[]
commonProviders: string[]
}
export interface RiskFactorWeight {
factor: string
weight: number // 0-1
description: LocalizedText
}
export interface CountryRiskProfile {
code: string // ISO 3166-1 alpha-2
name: LocalizedText
isEU: boolean
isEEA: boolean
hasAdequacyDecision: boolean
adequacyDecisionDate?: string
riskLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'VERY_HIGH'
notes?: LocalizedText
}
// ==========================================
// VENDOR TEMPLATES
// ==========================================
export const VENDOR_TEMPLATES: VendorTemplate[] = [
// Cloud & Infrastructure
{
id: 'tpl-vendor-cloud-iaas',
name: { de: 'Cloud IaaS-Anbieter', en: 'Cloud IaaS Provider' },
description: {
de: 'Infrastructure-as-a-Service Anbieter (AWS, Azure, GCP)',
en: 'Infrastructure-as-a-Service provider (AWS, Azure, GCP)',
},
serviceCategory: 'CLOUD_INFRASTRUCTURE',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'CONTENT',
suggestedTransferMechanisms: ['SCC_PROCESSOR', 'BCR'],
suggestedContractTypes: ['AVV', 'MSA', 'SLA', 'TOM_ANNEX'],
typicalDataCategories: ['NAME', 'CONTACT', 'USAGE_DATA', 'IP_ADDRESS'],
typicalCertifications: ['ISO 27001', 'SOC 2', 'C5'],
inherentRiskFactors: [
{ factor: 'data_volume', weight: 0.9, description: { de: 'Hohes Datenvolumen', en: 'High data volume' } },
{ factor: 'criticality', weight: 0.9, description: { de: 'Geschäftskritisch', en: 'Business critical' } },
{ factor: 'sub_processors', weight: 0.7, description: { de: 'Viele Unterauftragnehmer', en: 'Many sub-processors' } },
],
commonProviders: ['AWS', 'Microsoft Azure', 'Google Cloud Platform', 'Hetzner', 'OVH'],
},
{
id: 'tpl-vendor-hosting',
name: { de: 'Webhosting-Anbieter', en: 'Web Hosting Provider' },
description: {
de: 'Hosting von Websites und Webanwendungen',
en: 'Hosting of websites and web applications',
},
serviceCategory: 'HOSTING',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'ADMINISTRATIVE',
suggestedTransferMechanisms: ['ADEQUACY_DECISION', 'SCC_PROCESSOR'],
suggestedContractTypes: ['AVV', 'MSA', 'SLA'],
typicalDataCategories: ['IP_ADDRESS', 'USAGE_DATA', 'LOGIN_DATA'],
typicalCertifications: ['ISO 27001'],
inherentRiskFactors: [
{ factor: 'data_volume', weight: 0.6, description: { de: 'Mittleres Datenvolumen', en: 'Medium data volume' } },
{ factor: 'criticality', weight: 0.7, description: { de: 'Wichtig für Betrieb', en: 'Important for operations' } },
],
commonProviders: ['Hetzner', 'All-Inkl', 'IONOS', 'Strato', 'DigitalOcean'],
},
{
id: 'tpl-vendor-cdn',
name: { de: 'CDN-Anbieter', en: 'CDN Provider' },
description: {
de: 'Content Delivery Network für schnelle Inhaltsauslieferung',
en: 'Content Delivery Network for fast content delivery',
},
serviceCategory: 'CDN',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'POTENTIAL',
suggestedTransferMechanisms: ['SCC_PROCESSOR'],
suggestedContractTypes: ['AVV', 'MSA'],
typicalDataCategories: ['IP_ADDRESS', 'USAGE_DATA'],
typicalCertifications: ['ISO 27001', 'SOC 2'],
inherentRiskFactors: [
{ factor: 'data_transit', weight: 0.5, description: { de: 'Daten im Transit', en: 'Data in transit' } },
{ factor: 'global_presence', weight: 0.6, description: { de: 'Globale Präsenz', en: 'Global presence' } },
],
commonProviders: ['Cloudflare', 'Fastly', 'Akamai', 'AWS CloudFront'],
},
// Business Software
{
id: 'tpl-vendor-crm',
name: { de: 'CRM-System', en: 'CRM System' },
description: {
de: 'Customer Relationship Management System',
en: 'Customer Relationship Management System',
},
serviceCategory: 'CRM',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'CONTENT',
suggestedTransferMechanisms: ['SCC_PROCESSOR', 'BCR'],
suggestedContractTypes: ['AVV', 'MSA', 'SLA', 'TOM_ANNEX'],
typicalDataCategories: ['NAME', 'CONTACT', 'ADDRESS', 'COMMUNICATION_DATA', 'CONTRACT_DATA'],
typicalCertifications: ['ISO 27001', 'SOC 2'],
inherentRiskFactors: [
{ factor: 'customer_data', weight: 0.8, description: { de: 'Kundendaten', en: 'Customer data' } },
{ factor: 'data_volume', weight: 0.7, description: { de: 'Hohes Datenvolumen', en: 'High data volume' } },
],
commonProviders: ['Salesforce', 'HubSpot', 'Pipedrive', 'Microsoft Dynamics', 'Zoho CRM'],
},
{
id: 'tpl-vendor-erp',
name: { de: 'ERP-System', en: 'ERP System' },
description: {
de: 'Enterprise Resource Planning System',
en: 'Enterprise Resource Planning System',
},
serviceCategory: 'ERP',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'CONTENT',
suggestedTransferMechanisms: ['ADEQUACY_DECISION', 'SCC_PROCESSOR'],
suggestedContractTypes: ['AVV', 'MSA', 'SLA', 'TOM_ANNEX'],
typicalDataCategories: [
'NAME', 'CONTACT', 'ADDRESS', 'BANK_ACCOUNT', 'CONTRACT_DATA',
'EMPLOYMENT_DATA', 'SALARY_DATA',
],
typicalCertifications: ['ISO 27001', 'SOC 2'],
inherentRiskFactors: [
{ factor: 'data_volume', weight: 0.9, description: { de: 'Sehr hohes Datenvolumen', en: 'Very high data volume' } },
{ factor: 'criticality', weight: 0.95, description: { de: 'Geschäftskritisch', en: 'Business critical' } },
{ factor: 'sensitive_data', weight: 0.8, description: { de: 'Sensible Daten', en: 'Sensitive data' } },
],
commonProviders: ['SAP', 'Oracle', 'Microsoft Dynamics', 'Sage', 'Odoo'],
},
{
id: 'tpl-vendor-hr',
name: { de: 'HR-Software', en: 'HR Software' },
description: {
de: 'Personalverwaltung und HR-Management',
en: 'Personnel administration and HR management',
},
serviceCategory: 'HR_SOFTWARE',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'CONTENT',
suggestedTransferMechanisms: ['ADEQUACY_DECISION', 'SCC_PROCESSOR'],
suggestedContractTypes: ['AVV', 'MSA', 'TOM_ANNEX'],
typicalDataCategories: [
'NAME', 'CONTACT', 'ADDRESS', 'DOB', 'SOCIAL_SECURITY', 'TAX_ID',
'BANK_ACCOUNT', 'EMPLOYMENT_DATA', 'SALARY_DATA', 'HEALTH_DATA',
],
typicalCertifications: ['ISO 27001'],
inherentRiskFactors: [
{ factor: 'employee_data', weight: 0.9, description: { de: 'Mitarbeiterdaten', en: 'Employee data' } },
{ factor: 'sensitive_data', weight: 0.85, description: { de: 'Sensible Daten', en: 'Sensitive data' } },
{ factor: 'special_categories', weight: 0.7, description: { de: 'Besondere Kategorien möglich', en: 'Special categories possible' } },
],
commonProviders: ['Personio', 'Workday', 'SAP SuccessFactors', 'HRworks', 'Factorial'],
},
{
id: 'tpl-vendor-accounting',
name: { de: 'Buchhaltungssoftware', en: 'Accounting Software' },
description: {
de: 'Finanzbuchhaltung und Rechnungswesen',
en: 'Financial accounting and bookkeeping',
},
serviceCategory: 'ACCOUNTING',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'CONTENT',
suggestedTransferMechanisms: ['ADEQUACY_DECISION'],
suggestedContractTypes: ['AVV', 'MSA'],
typicalDataCategories: ['NAME', 'ADDRESS', 'BANK_ACCOUNT', 'PAYMENT_DATA', 'TAX_ID'],
typicalCertifications: ['ISO 27001', 'IDW PS 951'],
inherentRiskFactors: [
{ factor: 'financial_data', weight: 0.85, description: { de: 'Finanzdaten', en: 'Financial data' } },
{ factor: 'legal_retention', weight: 0.7, description: { de: 'Aufbewahrungspflichten', en: 'Retention requirements' } },
],
commonProviders: ['DATEV', 'Lexware', 'SevDesk', 'Xero', 'Sage'],
},
// Communication & Collaboration
{
id: 'tpl-vendor-email',
name: { de: 'E-Mail-Dienst', en: 'Email Service' },
description: {
de: 'E-Mail-Hosting und -Kommunikation',
en: 'Email hosting and communication',
},
serviceCategory: 'EMAIL',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'CONTENT',
suggestedTransferMechanisms: ['SCC_PROCESSOR', 'BCR'],
suggestedContractTypes: ['AVV', 'MSA', 'SLA'],
typicalDataCategories: ['NAME', 'CONTACT', 'COMMUNICATION_DATA'],
typicalCertifications: ['ISO 27001', 'SOC 2'],
inherentRiskFactors: [
{ factor: 'communication_data', weight: 0.8, description: { de: 'Kommunikationsdaten', en: 'Communication data' } },
{ factor: 'criticality', weight: 0.8, description: { de: 'Geschäftskritisch', en: 'Business critical' } },
],
commonProviders: ['Microsoft 365', 'Google Workspace', 'Zoho Mail', 'ProtonMail'],
},
{
id: 'tpl-vendor-communication',
name: { de: 'Kollaborations-Tool', en: 'Collaboration Tool' },
description: {
de: 'Team-Kommunikation und Zusammenarbeit',
en: 'Team communication and collaboration',
},
serviceCategory: 'COMMUNICATION',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'CONTENT',
suggestedTransferMechanisms: ['SCC_PROCESSOR', 'BCR'],
suggestedContractTypes: ['AVV', 'MSA'],
typicalDataCategories: ['NAME', 'CONTACT', 'COMMUNICATION_DATA', 'PHOTO_VIDEO'],
typicalCertifications: ['ISO 27001', 'SOC 2'],
inherentRiskFactors: [
{ factor: 'communication_data', weight: 0.7, description: { de: 'Kommunikationsdaten', en: 'Communication data' } },
{ factor: 'file_sharing', weight: 0.6, description: { de: 'Dateifreigabe', en: 'File sharing' } },
],
commonProviders: ['Slack', 'Microsoft Teams', 'Zoom', 'Google Meet', 'Webex'],
},
// Marketing & Analytics
{
id: 'tpl-vendor-analytics',
name: { de: 'Analytics-Tool', en: 'Analytics Tool' },
description: {
de: 'Web-Analyse und Nutzerverhalten',
en: 'Web analytics and user behavior',
},
serviceCategory: 'ANALYTICS',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'CONTENT',
suggestedTransferMechanisms: ['SCC_PROCESSOR'],
suggestedContractTypes: ['AVV', 'MSA'],
typicalDataCategories: ['IP_ADDRESS', 'DEVICE_ID', 'USAGE_DATA', 'LOCATION_DATA'],
typicalCertifications: ['ISO 27001'],
inherentRiskFactors: [
{ factor: 'tracking', weight: 0.7, description: { de: 'Tracking', en: 'Tracking' } },
{ factor: 'profiling', weight: 0.6, description: { de: 'Profiling möglich', en: 'Profiling possible' } },
],
commonProviders: ['Google Analytics', 'Matomo', 'Plausible', 'Mixpanel', 'Amplitude'],
},
{
id: 'tpl-vendor-marketing-automation',
name: { de: 'Marketing-Automatisierung', en: 'Marketing Automation' },
description: {
de: 'E-Mail-Marketing und Automatisierung',
en: 'Email marketing and automation',
},
serviceCategory: 'MARKETING',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'CONTENT',
suggestedTransferMechanisms: ['SCC_PROCESSOR'],
suggestedContractTypes: ['AVV', 'MSA'],
typicalDataCategories: ['NAME', 'CONTACT', 'USAGE_DATA'],
typicalCertifications: ['ISO 27001'],
inherentRiskFactors: [
{ factor: 'marketing_data', weight: 0.6, description: { de: 'Marketing-Daten', en: 'Marketing data' } },
{ factor: 'consent_management', weight: 0.7, description: { de: 'Einwilligungsmanagement', en: 'Consent management' } },
],
commonProviders: ['Mailchimp', 'HubSpot', 'Sendinblue', 'CleverReach', 'ActiveCampaign'],
},
// Support & Service
{
id: 'tpl-vendor-support',
name: { de: 'Support-/Ticketsystem', en: 'Support/Ticket System' },
description: {
de: 'Kundenservice und Ticket-Management',
en: 'Customer service and ticket management',
},
serviceCategory: 'SUPPORT',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'CONTENT',
suggestedTransferMechanisms: ['SCC_PROCESSOR'],
suggestedContractTypes: ['AVV', 'MSA'],
typicalDataCategories: ['NAME', 'CONTACT', 'COMMUNICATION_DATA', 'CONTRACT_DATA'],
typicalCertifications: ['ISO 27001', 'SOC 2'],
inherentRiskFactors: [
{ factor: 'customer_data', weight: 0.7, description: { de: 'Kundendaten', en: 'Customer data' } },
{ factor: 'communication', weight: 0.6, description: { de: 'Kommunikationsinhalte', en: 'Communication content' } },
],
commonProviders: ['Zendesk', 'Freshdesk', 'Intercom', 'HelpScout', 'Jira Service Management'],
},
// Payment & Finance
{
id: 'tpl-vendor-payment',
name: { de: 'Zahlungsdienstleister', en: 'Payment Service Provider' },
description: {
de: 'Zahlungsabwicklung und Payment Gateway',
en: 'Payment processing and payment gateway',
},
serviceCategory: 'PAYMENT',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'CONTENT',
suggestedTransferMechanisms: ['SCC_PROCESSOR', 'BCR'],
suggestedContractTypes: ['AVV', 'MSA', 'SLA'],
typicalDataCategories: ['NAME', 'ADDRESS', 'BANK_ACCOUNT', 'PAYMENT_DATA'],
typicalCertifications: ['PCI DSS', 'ISO 27001'],
inherentRiskFactors: [
{ factor: 'financial_data', weight: 0.9, description: { de: 'Finanzdaten', en: 'Financial data' } },
{ factor: 'pci_scope', weight: 0.8, description: { de: 'PCI-Scope', en: 'PCI scope' } },
],
commonProviders: ['Stripe', 'PayPal', 'Adyen', 'Mollie', 'Klarna'],
},
// Security
{
id: 'tpl-vendor-security',
name: { de: 'Sicherheitsdienstleister', en: 'Security Service Provider' },
description: {
de: 'IT-Sicherheit, Penetrationstests, SIEM',
en: 'IT security, penetration testing, SIEM',
},
serviceCategory: 'SECURITY',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'ADMINISTRATIVE',
suggestedTransferMechanisms: ['ADEQUACY_DECISION', 'SCC_PROCESSOR'],
suggestedContractTypes: ['AVV', 'MSA', 'NDA'],
typicalDataCategories: ['IP_ADDRESS', 'USAGE_DATA', 'LOGIN_DATA'],
typicalCertifications: ['ISO 27001', 'SOC 2'],
inherentRiskFactors: [
{ factor: 'system_access', weight: 0.8, description: { de: 'Systemzugriff', en: 'System access' } },
{ factor: 'security_data', weight: 0.7, description: { de: 'Sicherheitsdaten', en: 'Security data' } },
],
commonProviders: ['CrowdStrike', 'Splunk', 'Palo Alto Networks', 'Tenable'],
},
// Backup & Storage
{
id: 'tpl-vendor-backup',
name: { de: 'Backup-Anbieter', en: 'Backup Provider' },
description: {
de: 'Datensicherung und Disaster Recovery',
en: 'Data backup and disaster recovery',
},
serviceCategory: 'BACKUP',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'CONTENT',
suggestedTransferMechanisms: ['ADEQUACY_DECISION', 'SCC_PROCESSOR'],
suggestedContractTypes: ['AVV', 'MSA', 'SLA'],
typicalDataCategories: ['NAME', 'CONTACT', 'CONTRACT_DATA'],
typicalCertifications: ['ISO 27001', 'SOC 2'],
inherentRiskFactors: [
{ factor: 'full_backup', weight: 0.9, description: { de: 'Vollständige Kopie', en: 'Full copy' } },
{ factor: 'retention', weight: 0.7, description: { de: 'Lange Aufbewahrung', en: 'Long retention' } },
],
commonProviders: ['Veeam', 'Acronis', 'Commvault', 'AWS Backup'],
},
// Consulting
{
id: 'tpl-vendor-consulting',
name: { de: 'Beratungsunternehmen', en: 'Consulting Company' },
description: {
de: 'IT-Beratung, Projektunterstützung',
en: 'IT consulting, project support',
},
serviceCategory: 'CONSULTING',
suggestedRole: 'PROCESSOR',
suggestedDataAccess: 'POTENTIAL',
suggestedTransferMechanisms: ['ADEQUACY_DECISION'],
suggestedContractTypes: ['AVV', 'MSA', 'NDA'],
typicalDataCategories: ['NAME', 'CONTACT', 'CONTRACT_DATA'],
typicalCertifications: ['ISO 27001'],
inherentRiskFactors: [
{ factor: 'project_access', weight: 0.5, description: { de: 'Projektzugriff', en: 'Project access' } },
{ factor: 'temporary', weight: 0.4, description: { de: 'Temporär', en: 'Temporary' } },
],
commonProviders: ['Accenture', 'McKinsey', 'Deloitte', 'PwC', 'KPMG'],
},
]
// ==========================================
// COUNTRY RISK PROFILES
// ==========================================
export const COUNTRY_RISK_PROFILES: CountryRiskProfile[] = [
// EU Countries (Low Risk)
{ code: 'DE', name: { de: 'Deutschland', en: 'Germany' }, isEU: true, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'AT', name: { de: 'Österreich', en: 'Austria' }, isEU: true, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'FR', name: { de: 'Frankreich', en: 'France' }, isEU: true, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'NL', name: { de: 'Niederlande', en: 'Netherlands' }, isEU: true, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'BE', name: { de: 'Belgien', en: 'Belgium' }, isEU: true, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'IT', name: { de: 'Italien', en: 'Italy' }, isEU: true, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'ES', name: { de: 'Spanien', en: 'Spain' }, isEU: true, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'PT', name: { de: 'Portugal', en: 'Portugal' }, isEU: true, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'PL', name: { de: 'Polen', en: 'Poland' }, isEU: true, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'CZ', name: { de: 'Tschechien', en: 'Czech Republic' }, isEU: true, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'SE', name: { de: 'Schweden', en: 'Sweden' }, isEU: true, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'DK', name: { de: 'Dänemark', en: 'Denmark' }, isEU: true, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'FI', name: { de: 'Finnland', en: 'Finland' }, isEU: true, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'IE', name: { de: 'Irland', en: 'Ireland' }, isEU: true, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'LU', name: { de: 'Luxemburg', en: 'Luxembourg' }, isEU: true, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
// EEA Countries
{ code: 'NO', name: { de: 'Norwegen', en: 'Norway' }, isEU: false, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'IS', name: { de: 'Island', en: 'Iceland' }, isEU: false, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'LI', name: { de: 'Liechtenstein', en: 'Liechtenstein' }, isEU: false, isEEA: true, hasAdequacyDecision: true, riskLevel: 'LOW' },
// Adequacy Decision Countries
{ code: 'CH', name: { de: 'Schweiz', en: 'Switzerland' }, isEU: false, isEEA: false, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'GB', name: { de: 'Vereinigtes Königreich', en: 'United Kingdom' }, isEU: false, isEEA: false, hasAdequacyDecision: true, adequacyDecisionDate: '2021-06-28', riskLevel: 'LOW' },
{ code: 'JP', name: { de: 'Japan', en: 'Japan' }, isEU: false, isEEA: false, hasAdequacyDecision: true, adequacyDecisionDate: '2019-01-23', riskLevel: 'LOW' },
{ code: 'KR', name: { de: 'Südkorea', en: 'South Korea' }, isEU: false, isEEA: false, hasAdequacyDecision: true, adequacyDecisionDate: '2022-12-17', riskLevel: 'LOW' },
{ code: 'IL', name: { de: 'Israel', en: 'Israel' }, isEU: false, isEEA: false, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'NZ', name: { de: 'Neuseeland', en: 'New Zealand' }, isEU: false, isEEA: false, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'CA', name: { de: 'Kanada', en: 'Canada' }, isEU: false, isEEA: false, hasAdequacyDecision: true, riskLevel: 'LOW', notes: { de: 'Nur PIPEDA-Bereich', en: 'PIPEDA scope only' } },
{ code: 'AR', name: { de: 'Argentinien', en: 'Argentina' }, isEU: false, isEEA: false, hasAdequacyDecision: true, riskLevel: 'LOW' },
{ code: 'UY', name: { de: 'Uruguay', en: 'Uruguay' }, isEU: false, isEEA: false, hasAdequacyDecision: true, riskLevel: 'LOW' },
// US (Special - DPF)
{ code: 'US', name: { de: 'USA', en: 'United States' }, isEU: false, isEEA: false, hasAdequacyDecision: true, adequacyDecisionDate: '2023-07-10', riskLevel: 'MEDIUM', notes: { de: 'EU-US Data Privacy Framework erforderlich', en: 'EU-US Data Privacy Framework required' } },
// Third Countries without Adequacy (High Risk)
{ code: 'CN', name: { de: 'China', en: 'China' }, isEU: false, isEEA: false, hasAdequacyDecision: false, riskLevel: 'VERY_HIGH', notes: { de: 'Staatlicher Datenzugriff möglich', en: 'Government data access possible' } },
{ code: 'RU', name: { de: 'Russland', en: 'Russia' }, isEU: false, isEEA: false, hasAdequacyDecision: false, riskLevel: 'VERY_HIGH', notes: { de: 'Sanktionen beachten', en: 'Consider sanctions' } },
{ code: 'IN', name: { de: 'Indien', en: 'India' }, isEU: false, isEEA: false, hasAdequacyDecision: false, riskLevel: 'HIGH' },
{ code: 'BR', name: { de: 'Brasilien', en: 'Brazil' }, isEU: false, isEEA: false, hasAdequacyDecision: false, riskLevel: 'MEDIUM', notes: { de: 'LGPD vorhanden', en: 'LGPD in place' } },
{ code: 'AU', name: { de: 'Australien', en: 'Australia' }, isEU: false, isEEA: false, hasAdequacyDecision: false, riskLevel: 'MEDIUM' },
{ code: 'SG', name: { de: 'Singapur', en: 'Singapore' }, isEU: false, isEEA: false, hasAdequacyDecision: false, riskLevel: 'MEDIUM', notes: { de: 'PDPA vorhanden', en: 'PDPA in place' } },
{ code: 'HK', name: { de: 'Hongkong', en: 'Hong Kong' }, isEU: false, isEEA: false, hasAdequacyDecision: false, riskLevel: 'HIGH' },
{ code: 'AE', name: { de: 'VAE', en: 'UAE' }, isEU: false, isEEA: false, hasAdequacyDecision: false, riskLevel: 'HIGH' },
{ code: 'ZA', name: { de: 'Südafrika', en: 'South Africa' }, isEU: false, isEEA: false, hasAdequacyDecision: false, riskLevel: 'MEDIUM', notes: { de: 'POPIA vorhanden', en: 'POPIA in place' } },
]
// ==========================================
// HELPER FUNCTIONS
// ==========================================
/**
* Get vendor template by ID
*/
export function getVendorTemplateById(id: string): VendorTemplate | undefined {
return VENDOR_TEMPLATES.find((t) => t.id === id)
}
/**
* Get vendor templates by category
*/
export function getVendorTemplatesByCategory(category: ServiceCategory): VendorTemplate[] {
return VENDOR_TEMPLATES.filter((t) => t.serviceCategory === category)
}
/**
* Get country risk profile
*/
export function getCountryRiskProfile(countryCode: string): CountryRiskProfile | undefined {
return COUNTRY_RISK_PROFILES.find((c) => c.code === countryCode.toUpperCase())
}
/**
* Check if country requires transfer mechanism
*/
export function requiresTransferMechanism(countryCode: string): boolean {
const profile = getCountryRiskProfile(countryCode)
if (!profile) return true // Unknown country = requires mechanism
return !profile.isEU && !profile.isEEA && !profile.hasAdequacyDecision
}
/**
* Get suggested transfer mechanisms for country
*/
export function getSuggestedTransferMechanisms(countryCode: string): TransferMechanismType[] {
const profile = getCountryRiskProfile(countryCode)
if (!profile) {
return ['SCC_PROCESSOR']
}
if (profile.isEU || profile.isEEA) {
return [] // No mechanism needed
}
if (profile.hasAdequacyDecision) {
return ['ADEQUACY_DECISION']
}
// Third country without adequacy
return ['SCC_PROCESSOR', 'BCR']
}
/**
* Calculate inherent risk score for vendor template
*/
export function calculateTemplateRiskScore(template: VendorTemplate): number {
const baseScore = template.inherentRiskFactors.reduce(
(sum, factor) => sum + factor.weight * 100,
0
)
return Math.min(100, baseScore / template.inherentRiskFactors.length)
}
/**
* Create form data from vendor template
*/
export function createVendorFormDataFromTemplate(
template: VendorTemplate
): Partial<VendorFormData> {
return {
serviceCategory: template.serviceCategory,
role: template.suggestedRole,
dataAccessLevel: template.suggestedDataAccess,
transferMechanisms: template.suggestedTransferMechanisms,
contractTypes: template.suggestedContractTypes,
certifications: template.typicalCertifications.map((type) => ({
type,
issuedDate: undefined,
expirationDate: undefined,
})),
reviewFrequency: 'ANNUAL',
}
}
/**
* Get all EU/EEA countries
*/
export function getEUEEACountries(): CountryRiskProfile[] {
return COUNTRY_RISK_PROFILES.filter((c) => c.isEU || c.isEEA)
}
/**
* Get all countries with adequacy decision
*/
export function getAdequateCountries(): CountryRiskProfile[] {
return COUNTRY_RISK_PROFILES.filter((c) => c.hasAdequacyDecision)
}
/**
* Get all high-risk countries
*/
export function getHighRiskCountries(): CountryRiskProfile[] {
return COUNTRY_RISK_PROFILES.filter((c) => c.riskLevel === 'HIGH' || c.riskLevel === 'VERY_HIGH')
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,459 @@
/**
* Contract Analyzer
*
* LLM-based contract review for GDPR compliance
*/
import {
Finding,
Citation,
FindingType,
FindingCategory,
FindingSeverity,
DocumentType,
LocalizedText,
} from '../types'
import { AVV_CHECKLIST, INCIDENT_CHECKLIST, TRANSFER_CHECKLIST } from './checklists'
// ==========================================
// TYPES
// ==========================================
export interface ContractAnalysisRequest {
contractId: string
vendorId: string
tenantId: string
documentText: string
documentType?: DocumentType
language?: 'de' | 'en'
analysisScope?: AnalysisScope[]
}
export interface ContractAnalysisResponse {
documentType: DocumentType
language: 'de' | 'en'
parties: ContractPartyInfo[]
findings: Finding[]
complianceScore: number
topRisks: LocalizedText[]
requiredActions: LocalizedText[]
metadata: ExtractedMetadata
}
export interface ContractPartyInfo {
role: 'CONTROLLER' | 'PROCESSOR' | 'PARTY'
name: string
address?: string
}
export interface ExtractedMetadata {
effectiveDate?: string
expirationDate?: string
autoRenewal?: boolean
terminationNoticePeriod?: number
governingLaw?: string
jurisdiction?: string
}
export type AnalysisScope =
| 'AVV_COMPLIANCE'
| 'SUBPROCESSOR'
| 'INCIDENT_RESPONSE'
| 'AUDIT_RIGHTS'
| 'DELETION'
| 'TOM'
| 'TRANSFER'
| 'LIABILITY'
| 'SLA'
// ==========================================
// SYSTEM PROMPTS
// ==========================================
export const CONTRACT_REVIEW_SYSTEM_PROMPT = `Du bist ein Datenschutz-Rechtsexperte, der Verträge auf DSGVO-Konformität prüft.
WICHTIG:
1. Jede Feststellung MUSS mit einer Textstelle belegt werden (Citation)
2. Gib niemals Rechtsberatung - nur Compliance-Hinweise
3. Markiere unklare Stellen als UNKNOWN, nicht als GAP
4. Sei konservativ: im Zweifel RISK statt OK
PRÜFUNGSSCHEMA Art. 28 DSGVO AVV:
${AVV_CHECKLIST.map((item) => `- ${item.id}: ${item.requirement.de} (${item.article})`).join('\n')}
INCIDENT RESPONSE:
${INCIDENT_CHECKLIST.map((item) => `- ${item.id}: ${item.requirement.de} (${item.article})`).join('\n')}
DRITTLANDTRANSFER:
${TRANSFER_CHECKLIST.map((item) => `- ${item.id}: ${item.requirement.de} (${item.article})`).join('\n')}
AUSGABEFORMAT (JSON):
{
"document_type": "AVV|MSA|SLA|SCC|NDA|TOM_ANNEX|OTHER|UNKNOWN",
"language": "de|en",
"parties": [
{
"role": "CONTROLLER|PROCESSOR|PARTY",
"name": "...",
"address": "..."
}
],
"findings": [
{
"category": "AVV_CONTENT|SUBPROCESSOR|INCIDENT|AUDIT_RIGHTS|DELETION|TOM|TRANSFER|LIABILITY|SLA|DATA_SUBJECT_RIGHTS|CONFIDENTIALITY|INSTRUCTION|GENERAL",
"type": "OK|GAP|RISK|UNKNOWN",
"severity": "LOW|MEDIUM|HIGH|CRITICAL",
"title_de": "...",
"title_en": "...",
"description_de": "...",
"description_en": "...",
"recommendation_de": "...",
"recommendation_en": "...",
"citations": [
{
"page": 3,
"quoted_text": "Der Auftragnehmer...",
"start_char": 1234,
"end_char": 1456
}
],
"affected_requirement": "Art. 28 Abs. 3 lit. a DSGVO"
}
],
"compliance_score": 72,
"top_risks": [
{"de": "...", "en": "..."}
],
"required_actions": [
{"de": "...", "en": "..."}
],
"metadata": {
"effective_date": "2024-01-01",
"expiration_date": "2025-12-31",
"auto_renewal": true,
"termination_notice_period": 90,
"governing_law": "Germany",
"jurisdiction": "Frankfurt am Main"
}
}`
export const CONTRACT_CLASSIFICATION_PROMPT = `Analysiere den folgenden Vertragstext und klassifiziere ihn:
1. Dokumenttyp (AVV, MSA, SLA, SCC, NDA, TOM_ANNEX, OTHER)
2. Sprache (de, en)
3. Vertragsparteien mit Rollen
Antworte im JSON-Format:
{
"document_type": "...",
"language": "...",
"parties": [...]
}`
export const METADATA_EXTRACTION_PROMPT = `Extrahiere die folgenden Metadaten aus dem Vertrag:
1. Inkrafttreten / Effective Date
2. Laufzeit / Ablaufdatum
3. Automatische Verlängerung
4. Kündigungsfrist
5. Anwendbares Recht
6. Gerichtsstand
Antworte im JSON-Format.`
// ==========================================
// ANALYSIS FUNCTIONS
// ==========================================
/**
* Analyze a contract for GDPR compliance
*/
export async function analyzeContract(
request: ContractAnalysisRequest
): Promise<ContractAnalysisResponse> {
// This function would typically call an LLM API
// For now, we provide the structure that would be used
const apiEndpoint = '/api/sdk/v1/vendor-compliance/contracts/analyze'
const response = await fetch(apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
contract_id: request.contractId,
vendor_id: request.vendorId,
tenant_id: request.tenantId,
document_text: request.documentText,
document_type: request.documentType,
language: request.language || 'de',
analysis_scope: request.analysisScope || [
'AVV_COMPLIANCE',
'SUBPROCESSOR',
'INCIDENT_RESPONSE',
'AUDIT_RIGHTS',
'DELETION',
'TOM',
'TRANSFER',
],
}),
})
if (!response.ok) {
throw new Error('Contract analysis failed')
}
const result = await response.json()
return transformAnalysisResponse(result, request)
}
/**
* Transform LLM response to typed response
*/
function transformAnalysisResponse(
llmResponse: Record<string, unknown>,
request: ContractAnalysisRequest
): ContractAnalysisResponse {
const findings: Finding[] = (llmResponse.findings as Array<Record<string, unknown>> || []).map((f, idx) => ({
id: `finding-${request.contractId}-${idx}`,
tenantId: request.tenantId,
contractId: request.contractId,
vendorId: request.vendorId,
type: (f.type as FindingType) || 'UNKNOWN',
category: (f.category as FindingCategory) || 'GENERAL',
severity: (f.severity as FindingSeverity) || 'MEDIUM',
title: {
de: (f.title_de as string) || '',
en: (f.title_en as string) || '',
},
description: {
de: (f.description_de as string) || '',
en: (f.description_en as string) || '',
},
recommendation: f.recommendation_de ? {
de: f.recommendation_de as string,
en: (f.recommendation_en as string) || '',
} : undefined,
citations: ((f.citations as Array<Record<string, unknown>>) || []).map((c) => ({
documentId: request.contractId,
page: (c.page as number) || 1,
startChar: (c.start_char as number) || 0,
endChar: (c.end_char as number) || 0,
quotedText: (c.quoted_text as string) || '',
quoteHash: generateQuoteHash((c.quoted_text as string) || ''),
})),
affectedRequirement: f.affected_requirement as string | undefined,
triggeredControls: [],
status: 'OPEN',
createdAt: new Date(),
updatedAt: new Date(),
}))
const metadata = llmResponse.metadata as Record<string, unknown> || {}
return {
documentType: (llmResponse.document_type as DocumentType) || 'OTHER',
language: (llmResponse.language as 'de' | 'en') || 'de',
parties: ((llmResponse.parties as Array<Record<string, unknown>>) || []).map((p) => ({
role: (p.role as 'CONTROLLER' | 'PROCESSOR' | 'PARTY') || 'PARTY',
name: (p.name as string) || '',
address: p.address as string | undefined,
})),
findings,
complianceScore: (llmResponse.compliance_score as number) || 0,
topRisks: ((llmResponse.top_risks as Array<Record<string, string>>) || []).map((r) => ({
de: r.de || '',
en: r.en || '',
})),
requiredActions: ((llmResponse.required_actions as Array<Record<string, string>>) || []).map((a) => ({
de: a.de || '',
en: a.en || '',
})),
metadata: {
effectiveDate: metadata.effective_date as string | undefined,
expirationDate: metadata.expiration_date as string | undefined,
autoRenewal: metadata.auto_renewal as boolean | undefined,
terminationNoticePeriod: metadata.termination_notice_period as number | undefined,
governingLaw: metadata.governing_law as string | undefined,
jurisdiction: metadata.jurisdiction as string | undefined,
},
}
}
/**
* Generate a hash for quote verification
*/
function generateQuoteHash(text: string): string {
// Simple hash for demo - in production use crypto.subtle.digest
let hash = 0
for (let i = 0; i < text.length; i++) {
const char = text.charCodeAt(i)
hash = ((hash << 5) - hash) + char
hash = hash & hash
}
return Math.abs(hash).toString(16).padStart(16, '0')
}
// ==========================================
// CITATION UTILITIES
// ==========================================
/**
* Verify citation integrity
*/
export function verifyCitation(
citation: Citation,
documentText: string
): boolean {
const extractedText = documentText.substring(citation.startChar, citation.endChar)
const expectedHash = generateQuoteHash(extractedText)
return citation.quoteHash === expectedHash
}
/**
* Find citation context in document
*/
export function getCitationContext(
citation: Citation,
documentText: string,
contextChars: number = 100
): {
before: string
quoted: string
after: string
} {
const start = Math.max(0, citation.startChar - contextChars)
const end = Math.min(documentText.length, citation.endChar + contextChars)
return {
before: documentText.substring(start, citation.startChar),
quoted: documentText.substring(citation.startChar, citation.endChar),
after: documentText.substring(citation.endChar, end),
}
}
/**
* Highlight citations in text
*/
export function highlightCitations(
documentText: string,
citations: Citation[]
): string {
// Sort citations by start position (reverse to avoid offset issues)
const sortedCitations = [...citations].sort((a, b) => b.startChar - a.startChar)
let result = documentText
for (const citation of sortedCitations) {
const before = result.substring(0, citation.startChar)
const quoted = result.substring(citation.startChar, citation.endChar)
const after = result.substring(citation.endChar)
result = `${before}<mark data-citation-id="${citation.documentId}">${quoted}</mark>${after}`
}
return result
}
// ==========================================
// COMPLIANCE SCORE CALCULATION
// ==========================================
export interface ComplianceScoreBreakdown {
totalScore: number
categoryScores: Record<FindingCategory, number>
severityCounts: Record<FindingSeverity, number>
findingCounts: {
total: number
gaps: number
risks: number
ok: number
unknown: number
}
}
/**
* Calculate detailed compliance score
*/
export function calculateComplianceScore(findings: Finding[]): ComplianceScoreBreakdown {
const severityWeights: Record<FindingSeverity, number> = {
CRITICAL: 25,
HIGH: 15,
MEDIUM: 8,
LOW: 3,
}
const categoryWeights: Partial<Record<FindingCategory, number>> = {
AVV_CONTENT: 1.5,
SUBPROCESSOR: 1.3,
INCIDENT: 1.3,
DELETION: 1.2,
AUDIT_RIGHTS: 1.1,
TOM: 1.2,
TRANSFER: 1.4,
}
let totalDeductions = 0
const maxPossibleDeductions = 100
const categoryScores: Partial<Record<FindingCategory, number>> = {}
const severityCounts: Record<FindingSeverity, number> = {
LOW: 0,
MEDIUM: 0,
HIGH: 0,
CRITICAL: 0,
}
let gaps = 0
let risks = 0
let ok = 0
let unknown = 0
for (const finding of findings) {
severityCounts[finding.severity]++
switch (finding.type) {
case 'GAP':
gaps++
totalDeductions += severityWeights[finding.severity] * (categoryWeights[finding.category] || 1)
break
case 'RISK':
risks++
totalDeductions += severityWeights[finding.severity] * 0.7 * (categoryWeights[finding.category] || 1)
break
case 'OK':
ok++
break
case 'UNKNOWN':
unknown++
totalDeductions += severityWeights[finding.severity] * 0.3 * (categoryWeights[finding.category] || 1)
break
}
}
// Calculate category scores
const categories = new Set(findings.map((f) => f.category))
for (const category of categories) {
const categoryFindings = findings.filter((f) => f.category === category)
const categoryOk = categoryFindings.filter((f) => f.type === 'OK').length
const categoryTotal = categoryFindings.length
categoryScores[category] = categoryTotal > 0 ? Math.round((categoryOk / categoryTotal) * 100) : 100
}
const totalScore = Math.max(0, Math.round(100 - (totalDeductions / maxPossibleDeductions) * 100))
return {
totalScore,
categoryScores: categoryScores as Record<FindingCategory, number>,
severityCounts,
findingCounts: {
total: findings.length,
gaps,
risks,
ok,
unknown,
},
}
}

View File

@@ -0,0 +1,508 @@
/**
* Contract Review Checklists
*
* DSGVO Art. 28 compliance checklists for contract reviews
*/
import { LocalizedText, FindingCategory } from '../types'
export interface ChecklistItem {
id: string
category: FindingCategory
requirement: LocalizedText
article: string
description: LocalizedText
checkPoints: LocalizedText[]
isRequired: boolean
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
}
export interface ChecklistGroup {
id: string
name: LocalizedText
description: LocalizedText
items: ChecklistItem[]
}
// ==========================================
// ART. 28 DSGVO CHECKLIST
// ==========================================
export const AVV_CHECKLIST: ChecklistItem[] = [
// Art. 28 Abs. 3 lit. a - Weisungsgebundenheit
{
id: 'art28_3_a',
category: 'INSTRUCTION',
requirement: {
de: 'Weisungsgebundenheit',
en: 'Instruction binding',
},
article: 'Art. 28 Abs. 3 lit. a DSGVO',
description: {
de: 'Der Auftragsverarbeiter verarbeitet personenbezogene Daten nur auf dokumentierte Weisung des Verantwortlichen.',
en: 'The processor processes personal data only on documented instructions from the controller.',
},
checkPoints: [
{ de: 'Weisungsgebundenheit explizit vereinbart', en: 'Instruction binding explicitly agreed' },
{ de: 'Dokumentierte Weisungen vorgesehen', en: 'Documented instructions provided for' },
{ de: 'Hinweispflicht bei rechtswidrigen Weisungen', en: 'Obligation to notify of unlawful instructions' },
{ de: 'Keine eigenständige Verarbeitung erlaubt', en: 'No independent processing allowed' },
],
isRequired: true,
severity: 'CRITICAL',
},
// Art. 28 Abs. 3 lit. b - Vertraulichkeit
{
id: 'art28_3_b',
category: 'CONFIDENTIALITY',
requirement: {
de: 'Vertraulichkeitsverpflichtung',
en: 'Confidentiality obligation',
},
article: 'Art. 28 Abs. 3 lit. b DSGVO',
description: {
de: 'Der Auftragsverarbeiter gewährleistet, dass sich die zur Verarbeitung befugten Personen zur Vertraulichkeit verpflichtet haben.',
en: 'The processor ensures that persons authorised to process personal data have committed themselves to confidentiality.',
},
checkPoints: [
{ de: 'Vertraulichkeitsverpflichtung der Mitarbeiter', en: 'Confidentiality obligation of employees' },
{ de: 'Gesetzliche Verschwiegenheitspflicht oder', en: 'Statutory confidentiality obligation or' },
{ de: 'Vertragliche Verpflichtung', en: 'Contractual obligation' },
],
isRequired: true,
severity: 'HIGH',
},
// Art. 28 Abs. 3 lit. c - Technische und organisatorische Maßnahmen
{
id: 'art28_3_c',
category: 'TOM',
requirement: {
de: 'Technische und organisatorische Maßnahmen',
en: 'Technical and organisational measures',
},
article: 'Art. 28 Abs. 3 lit. c DSGVO',
description: {
de: 'Der Auftragsverarbeiter trifft alle gemäß Art. 32 erforderlichen Maßnahmen.',
en: 'The processor takes all measures required pursuant to Art. 32.',
},
checkPoints: [
{ de: 'TOM-Anlage vorhanden', en: 'TOM annex present' },
{ de: 'TOM konkret und aktuell', en: 'TOM specific and current' },
{ de: 'Bezug zu Art. 32 DSGVO', en: 'Reference to Art. 32 GDPR' },
{ de: 'Aktualisierungspflicht vereinbart', en: 'Update obligation agreed' },
],
isRequired: true,
severity: 'CRITICAL',
},
// Art. 28 Abs. 3 lit. d - Unterauftragsverarbeitung
{
id: 'art28_3_d',
category: 'SUBPROCESSOR',
requirement: {
de: 'Unterauftragsverarbeitung',
en: 'Sub-processing',
},
article: 'Art. 28 Abs. 3 lit. d DSGVO',
description: {
de: 'Der Auftragsverarbeiter nimmt keinen weiteren Auftragsverarbeiter ohne vorherige Genehmigung in Anspruch.',
en: 'The processor does not engage another processor without prior authorisation.',
},
checkPoints: [
{ de: 'Genehmigungserfordernis (allgemein oder spezifisch)', en: 'Authorisation requirement (general or specific)' },
{ de: 'Bei allgemeiner Genehmigung: Informationspflicht', en: 'With general authorisation: notification obligation' },
{ de: 'Einspruchsrecht des Verantwortlichen', en: 'Right of objection for controller' },
{ de: 'Liste aktueller Unterauftragnehmer', en: 'List of current sub-processors' },
{ de: 'Weitergabe der Pflichten an Unterauftragnehmer', en: 'Transfer of obligations to sub-processors' },
],
isRequired: true,
severity: 'CRITICAL',
},
// Art. 28 Abs. 3 lit. e - Unterstützung bei Betroffenenrechten
{
id: 'art28_3_e',
category: 'DATA_SUBJECT_RIGHTS',
requirement: {
de: 'Unterstützung bei Betroffenenrechten',
en: 'Assistance with data subject rights',
},
article: 'Art. 28 Abs. 3 lit. e DSGVO',
description: {
de: 'Der Auftragsverarbeiter unterstützt den Verantwortlichen bei der Erfüllung der Betroffenenrechte.',
en: 'The processor assists the controller in fulfilling data subject rights obligations.',
},
checkPoints: [
{ de: 'Unterstützungspflicht vereinbart', en: 'Assistance obligation agreed' },
{ de: 'Verfahren zur Weiterleitung von Anfragen', en: 'Procedure for forwarding requests' },
{ de: 'Fristen für Unterstützung', en: 'Deadlines for assistance' },
{ de: 'Kostenregelung', en: 'Cost arrangement' },
],
isRequired: true,
severity: 'HIGH',
},
// Art. 28 Abs. 3 lit. f - Unterstützung bei DSFA
{
id: 'art28_3_f',
category: 'GENERAL',
requirement: {
de: 'Unterstützung bei DSFA und Konsultation',
en: 'Assistance with DPIA and consultation',
},
article: 'Art. 28 Abs. 3 lit. f DSGVO',
description: {
de: 'Der Auftragsverarbeiter unterstützt den Verantwortlichen bei der Einhaltung der Pflichten gemäß Art. 32-36.',
en: 'The processor assists the controller in ensuring compliance with obligations pursuant to Art. 32-36.',
},
checkPoints: [
{ de: 'Unterstützung bei DSFA', en: 'Assistance with DPIA' },
{ de: 'Unterstützung bei vorheriger Konsultation', en: 'Assistance with prior consultation' },
{ de: 'Bereitstellung notwendiger Informationen', en: 'Provision of necessary information' },
],
isRequired: true,
severity: 'MEDIUM',
},
// Art. 28 Abs. 3 lit. g - Löschung/Rückgabe
{
id: 'art28_3_g',
category: 'DELETION',
requirement: {
de: 'Löschung/Rückgabe nach Vertragsende',
en: 'Deletion/return after contract end',
},
article: 'Art. 28 Abs. 3 lit. g DSGVO',
description: {
de: 'Nach Abschluss der Verarbeitung werden alle personenbezogenen Daten gelöscht oder zurückgegeben.',
en: 'After the end of processing, all personal data is deleted or returned.',
},
checkPoints: [
{ de: 'Löschung oder Rückgabe nach Wahl des Verantwortlichen', en: 'Deletion or return at controller choice' },
{ de: 'Frist für Löschung/Rückgabe (max. 30 Tage empfohlen)', en: 'Deadline for deletion/return (max. 30 days recommended)' },
{ de: 'Löschung auch bei Unterauftragnehmern', en: 'Deletion also at sub-processors' },
{ de: 'Löschbestätigung/Nachweis', en: 'Deletion confirmation/proof' },
{ de: 'Ausnahme nur bei gesetzlicher Aufbewahrungspflicht', en: 'Exception only for legal retention obligation' },
],
isRequired: true,
severity: 'CRITICAL',
},
// Art. 28 Abs. 3 lit. h - Audit/Inspektion
{
id: 'art28_3_h',
category: 'AUDIT_RIGHTS',
requirement: {
de: 'Audit- und Inspektionsrechte',
en: 'Audit and inspection rights',
},
article: 'Art. 28 Abs. 3 lit. h DSGVO',
description: {
de: 'Der Auftragsverarbeiter ermöglicht und unterstützt Überprüfungen durch den Verantwortlichen.',
en: 'The processor enables and contributes to audits and inspections by the controller.',
},
checkPoints: [
{ de: 'Auditrecht ausdrücklich vereinbart', en: 'Audit right explicitly agreed' },
{ de: 'Vor-Ort-Inspektionen möglich', en: 'On-site inspections possible' },
{ de: 'Angemessene Vorlaufzeit (max. 30 Tage)', en: 'Reasonable notice period (max. 30 days)' },
{ de: 'Keine unangemessenen Einschränkungen', en: 'No unreasonable restrictions' },
{ de: 'Bereitstellung aller relevanten Informationen', en: 'Provision of all relevant information' },
{ de: 'Akzeptanz unabhängiger Prüfer', en: 'Acceptance of independent auditors' },
],
isRequired: true,
severity: 'HIGH',
},
]
// ==========================================
// INCIDENT RESPONSE CHECKLIST
// ==========================================
export const INCIDENT_CHECKLIST: ChecklistItem[] = [
{
id: 'incident_notification',
category: 'INCIDENT',
requirement: {
de: 'Meldung von Datenschutzverletzungen',
en: 'Notification of data breaches',
},
article: 'Art. 33 Abs. 2 DSGVO',
description: {
de: 'Der Auftragsverarbeiter meldet dem Verantwortlichen unverzüglich jede Datenschutzverletzung.',
en: 'The processor notifies the controller without undue delay of any personal data breach.',
},
checkPoints: [
{ de: 'Meldepflicht vereinbart', en: 'Notification obligation agreed' },
{ de: 'Frist: Unverzüglich (max. 24-48h empfohlen)', en: 'Deadline: Without undue delay (max. 24-48h recommended)' },
{ de: 'Mindestinhalt der Meldung definiert', en: 'Minimum content of notification defined' },
{ de: 'Kontaktstelle für Meldungen', en: 'Contact point for notifications' },
{ de: 'Unterstützung bei Dokumentation', en: 'Assistance with documentation' },
],
isRequired: true,
severity: 'CRITICAL',
},
{
id: 'incident_content',
category: 'INCIDENT',
requirement: {
de: 'Inhalt der Incident-Meldung',
en: 'Content of incident notification',
},
article: 'Art. 33 Abs. 3 DSGVO',
description: {
de: 'Die Meldung muss bestimmte Mindestinformationen enthalten.',
en: 'The notification must contain certain minimum information.',
},
checkPoints: [
{ de: 'Art der Verletzung', en: 'Nature of the breach' },
{ de: 'Betroffene Datenkategorien', en: 'Affected data categories' },
{ de: 'Ungefähre Anzahl betroffener Personen', en: 'Approximate number of affected persons' },
{ de: 'Wahrscheinliche Folgen', en: 'Likely consequences' },
{ de: 'Ergriffene Maßnahmen', en: 'Measures taken' },
],
isRequired: true,
severity: 'HIGH',
},
]
// ==========================================
// THIRD COUNTRY TRANSFER CHECKLIST
// ==========================================
export const TRANSFER_CHECKLIST: ChecklistItem[] = [
{
id: 'transfer_basis',
category: 'TRANSFER',
requirement: {
de: 'Rechtsgrundlage für Drittlandtransfer',
en: 'Legal basis for third country transfer',
},
article: 'Art. 44-49 DSGVO',
description: {
de: 'Drittlandtransfers nur auf Basis geeigneter Garantien.',
en: 'Third country transfers only on the basis of appropriate safeguards.',
},
checkPoints: [
{ de: 'Angemessenheitsbeschluss oder', en: 'Adequacy decision or' },
{ de: 'Standardvertragsklauseln (SCC) oder', en: 'Standard contractual clauses (SCC) or' },
{ de: 'Binding Corporate Rules (BCR) oder', en: 'Binding Corporate Rules (BCR) or' },
{ de: 'Sonstige Ausnahme Art. 49', en: 'Other derogation Art. 49' },
],
isRequired: true,
severity: 'CRITICAL',
},
{
id: 'transfer_scc',
category: 'TRANSFER',
requirement: {
de: 'Standardvertragsklauseln',
en: 'Standard Contractual Clauses',
},
article: 'Art. 46 Abs. 2 lit. c DSGVO',
description: {
de: 'Bei SCC: Verwendung der aktuellen EU-Kommission-Klauseln.',
en: 'With SCC: Use of current EU Commission clauses.',
},
checkPoints: [
{ de: 'SCC 2021 verwendet', en: 'SCC 2021 used' },
{ de: 'Korrektes Modul gewählt', en: 'Correct module selected' },
{ de: 'Anhänge vollständig ausgefüllt', en: 'Annexes completely filled out' },
{ de: 'TIA durchgeführt (bei Risiko)', en: 'TIA conducted (if risk)' },
{ de: 'Zusätzliche Maßnahmen dokumentiert', en: 'Additional measures documented' },
],
isRequired: false,
severity: 'HIGH',
},
{
id: 'transfer_tia',
category: 'TRANSFER',
requirement: {
de: 'Transfer Impact Assessment',
en: 'Transfer Impact Assessment',
},
article: 'Schrems II, EDSA 01/2020',
description: {
de: 'Bewertung der Risiken im Drittland.',
en: 'Assessment of risks in the third country.',
},
checkPoints: [
{ de: 'Rechtslage im Drittland analysiert', en: 'Legal situation in third country analyzed' },
{ de: 'Zugriff durch Behörden bewertet', en: 'Access by authorities assessed' },
{ de: 'Zusätzliche technische Maßnahmen', en: 'Additional technical measures' },
{ de: 'Dokumentation der Bewertung', en: 'Documentation of assessment' },
],
isRequired: true,
severity: 'HIGH',
},
]
// ==========================================
// SLA & LIABILITY CHECKLIST
// ==========================================
export const SLA_LIABILITY_CHECKLIST: ChecklistItem[] = [
{
id: 'sla_availability',
category: 'SLA',
requirement: {
de: 'Verfügbarkeit',
en: 'Availability',
},
article: 'Vertragliche Vereinbarung',
description: {
de: 'Service Level Agreement für Verfügbarkeit des Dienstes.',
en: 'Service Level Agreement for service availability.',
},
checkPoints: [
{ de: 'Verfügbarkeit definiert (z.B. 99,9%)', en: 'Availability defined (e.g., 99.9%)' },
{ de: 'Messzeitraum festgelegt', en: 'Measurement period defined' },
{ de: 'Ausnahmen klar definiert', en: 'Exceptions clearly defined' },
{ de: 'Konsequenzen bei Nichteinhaltung', en: 'Consequences of non-compliance' },
],
isRequired: false,
severity: 'MEDIUM',
},
{
id: 'liability_cap',
category: 'LIABILITY',
requirement: {
de: 'Haftungsbegrenzung',
en: 'Liability cap',
},
article: 'Vertragliche Vereinbarung',
description: {
de: 'Prüfung von Haftungsbegrenzungen und deren Auswirkungen.',
en: 'Review of liability caps and their implications.',
},
checkPoints: [
{ de: 'Haftungshöchstgrenze prüfen', en: 'Check liability cap' },
{ de: 'Ausschluss von Vorsatz/grober Fahrlässigkeit', en: 'Exclusion of intent/gross negligence' },
{ de: 'Freistellungsklauseln (Indemnity)', en: 'Indemnification clauses' },
{ de: 'Versicherungsnachweis', en: 'Insurance proof' },
],
isRequired: false,
severity: 'MEDIUM',
},
]
// ==========================================
// GROUPED CHECKLISTS
// ==========================================
export const CHECKLIST_GROUPS: ChecklistGroup[] = [
{
id: 'avv',
name: { de: 'Art. 28 DSGVO - AVV Pflichtinhalte', en: 'Art. 28 GDPR - DPA Mandatory Content' },
description: {
de: 'Prüfung der Pflichtinhalte eines Auftragsverarbeitungsvertrags',
en: 'Review of mandatory content of a Data Processing Agreement',
},
items: AVV_CHECKLIST,
},
{
id: 'incident',
name: { de: 'Incident Response', en: 'Incident Response' },
description: {
de: 'Prüfung der Regelungen zu Datenschutzverletzungen',
en: 'Review of data breach provisions',
},
items: INCIDENT_CHECKLIST,
},
{
id: 'transfer',
name: { de: 'Drittlandtransfer', en: 'Third Country Transfer' },
description: {
de: 'Prüfung der Regelungen zu Drittlandtransfers',
en: 'Review of third country transfer provisions',
},
items: TRANSFER_CHECKLIST,
},
{
id: 'sla_liability',
name: { de: 'SLA & Haftung', en: 'SLA & Liability' },
description: {
de: 'Prüfung von Service Levels und Haftungsregelungen',
en: 'Review of service levels and liability provisions',
},
items: SLA_LIABILITY_CHECKLIST,
},
]
// ==========================================
// HELPER FUNCTIONS
// ==========================================
/**
* Get all required checklist items
*/
export function getRequiredChecklistItems(): ChecklistItem[] {
return [
...AVV_CHECKLIST,
...INCIDENT_CHECKLIST,
...TRANSFER_CHECKLIST,
].filter((item) => item.isRequired)
}
/**
* Get checklist items by category
*/
export function getChecklistItemsByCategory(category: FindingCategory): ChecklistItem[] {
return [
...AVV_CHECKLIST,
...INCIDENT_CHECKLIST,
...TRANSFER_CHECKLIST,
...SLA_LIABILITY_CHECKLIST,
].filter((item) => item.category === category)
}
/**
* Get checklist item by ID
*/
export function getChecklistItemById(id: string): ChecklistItem | undefined {
return [
...AVV_CHECKLIST,
...INCIDENT_CHECKLIST,
...TRANSFER_CHECKLIST,
...SLA_LIABILITY_CHECKLIST,
].find((item) => item.id === id)
}
/**
* Calculate compliance score based on checklist results
*/
export function calculateChecklistComplianceScore(
results: Map<string, 'PASS' | 'PARTIAL' | 'FAIL' | 'NOT_CHECKED'>
): number {
const allItems = [
...AVV_CHECKLIST,
...INCIDENT_CHECKLIST,
...TRANSFER_CHECKLIST,
]
let totalWeight = 0
let earnedScore = 0
for (const item of allItems) {
const weight = item.severity === 'CRITICAL' ? 3 : item.severity === 'HIGH' ? 2 : 1
const result = results.get(item.id) || 'NOT_CHECKED'
totalWeight += weight
switch (result) {
case 'PASS':
earnedScore += weight
break
case 'PARTIAL':
earnedScore += weight * 0.5
break
case 'FAIL':
case 'NOT_CHECKED':
// No score
break
}
}
return totalWeight > 0 ? Math.round((earnedScore / totalWeight) * 100) : 0
}

View File

@@ -0,0 +1,573 @@
/**
* Finding Types and Templates
*
* Pre-defined finding templates for contract reviews
*/
import {
FindingType,
FindingCategory,
FindingSeverity,
LocalizedText,
} from '../types'
export interface FindingTemplate {
id: string
type: FindingType
category: FindingCategory
severity: FindingSeverity
title: LocalizedText
description: LocalizedText
recommendation: LocalizedText
affectedRequirement: string
triggerControls: string[]
}
// ==========================================
// FINDING SEVERITY DEFINITIONS
// ==========================================
export const SEVERITY_DEFINITIONS: Record<FindingSeverity, {
label: LocalizedText
description: LocalizedText
responseTime: LocalizedText
color: string
}> = {
LOW: {
label: { de: 'Niedrig', en: 'Low' },
description: {
de: 'Geringfügige Abweichung ohne wesentliche Auswirkungen',
en: 'Minor deviation without significant impact',
},
responseTime: {
de: 'Bei nächster Vertragserneuerung',
en: 'At next contract renewal',
},
color: 'blue',
},
MEDIUM: {
label: { de: 'Mittel', en: 'Medium' },
description: {
de: 'Abweichung mit potenziellen Auswirkungen auf Compliance',
en: 'Deviation with potential impact on compliance',
},
responseTime: {
de: 'Innerhalb von 90 Tagen',
en: 'Within 90 days',
},
color: 'yellow',
},
HIGH: {
label: { de: 'Hoch', en: 'High' },
description: {
de: 'Erhebliche Abweichung mit Auswirkungen auf Datenschutz-Compliance',
en: 'Significant deviation with impact on data protection compliance',
},
responseTime: {
de: 'Innerhalb von 30 Tagen',
en: 'Within 30 days',
},
color: 'orange',
},
CRITICAL: {
label: { de: 'Kritisch', en: 'Critical' },
description: {
de: 'Schwerwiegende Abweichung - unmittelbarer Handlungsbedarf',
en: 'Serious deviation - immediate action required',
},
responseTime: {
de: 'Sofort / vor Vertragsabschluss',
en: 'Immediately / before contract signing',
},
color: 'red',
},
}
// ==========================================
// FINDING TYPE DEFINITIONS
// ==========================================
export const FINDING_TYPE_DEFINITIONS: Record<FindingType, {
label: LocalizedText
description: LocalizedText
icon: string
}> = {
OK: {
label: { de: 'Erfüllt', en: 'Fulfilled' },
description: {
de: 'Anforderung ist vollständig erfüllt',
en: 'Requirement is fully met',
},
icon: 'check-circle',
},
GAP: {
label: { de: 'Lücke', en: 'Gap' },
description: {
de: 'Anforderung fehlt oder ist unvollständig',
en: 'Requirement is missing or incomplete',
},
icon: 'alert-circle',
},
RISK: {
label: { de: 'Risiko', en: 'Risk' },
description: {
de: 'Potenzielles Risiko identifiziert',
en: 'Potential risk identified',
},
icon: 'alert-triangle',
},
UNKNOWN: {
label: { de: 'Unklar', en: 'Unknown' },
description: {
de: 'Nicht eindeutig bestimmbar',
en: 'Cannot be clearly determined',
},
icon: 'help-circle',
},
}
// ==========================================
// FINDING TEMPLATES
// ==========================================
export const FINDING_TEMPLATES: FindingTemplate[] = [
// AVV_CONTENT - Weisungsgebundenheit
{
id: 'tpl-avv-instruction-missing',
type: 'GAP',
category: 'AVV_CONTENT',
severity: 'CRITICAL',
title: {
de: 'Weisungsgebundenheit fehlt',
en: 'Instruction binding missing',
},
description: {
de: 'Der Vertrag enthält keine Regelung zur Weisungsgebundenheit des Auftragsverarbeiters.',
en: 'The contract does not contain a provision on the processor\'s instruction binding.',
},
recommendation: {
de: 'Ergänzen Sie eine Klausel, die den Auftragsverarbeiter verpflichtet, personenbezogene Daten nur auf dokumentierte Weisung des Verantwortlichen zu verarbeiten.',
en: 'Add a clause obligating the processor to process personal data only on documented instructions from the controller.',
},
affectedRequirement: 'Art. 28 Abs. 3 lit. a DSGVO',
triggerControls: ['VND-CON-01'],
},
{
id: 'tpl-avv-instruction-weak',
type: 'RISK',
category: 'AVV_CONTENT',
severity: 'MEDIUM',
title: {
de: 'Weisungsgebundenheit unvollständig',
en: 'Instruction binding incomplete',
},
description: {
de: 'Die Regelung zur Weisungsgebundenheit ist vorhanden, aber es fehlt die Hinweispflicht bei rechtswidrigen Weisungen.',
en: 'The instruction binding provision exists, but the obligation to notify of unlawful instructions is missing.',
},
recommendation: {
de: 'Ergänzen Sie eine Pflicht des Auftragsverarbeiters, den Verantwortlichen unverzüglich zu informieren, wenn eine Weisung nach seiner Auffassung gegen Datenschutzrecht verstößt.',
en: 'Add an obligation for the processor to immediately inform the controller if an instruction, in their opinion, violates data protection law.',
},
affectedRequirement: 'Art. 28 Abs. 3 lit. a DSGVO',
triggerControls: ['VND-CON-01'],
},
// AVV_CONTENT - TOM
{
id: 'tpl-avv-tom-missing',
type: 'GAP',
category: 'TOM',
severity: 'CRITICAL',
title: {
de: 'TOM-Anlage fehlt',
en: 'TOM annex missing',
},
description: {
de: 'Der Vertrag enthält keine technischen und organisatorischen Maßnahmen (TOM) als Anlage.',
en: 'The contract does not contain technical and organizational measures (TOM) as an annex.',
},
recommendation: {
de: 'Fordern Sie eine detaillierte TOM-Anlage an, die die Maßnahmen gemäß Art. 32 DSGVO beschreibt.',
en: 'Request a detailed TOM annex describing the measures according to Art. 32 GDPR.',
},
affectedRequirement: 'Art. 28 Abs. 3 lit. c DSGVO',
triggerControls: ['VND-TOM-01'],
},
{
id: 'tpl-avv-tom-generic',
type: 'RISK',
category: 'TOM',
severity: 'MEDIUM',
title: {
de: 'TOM zu unspezifisch',
en: 'TOM too generic',
},
description: {
de: 'Die TOM-Anlage enthält nur allgemeine Aussagen ohne konkrete Maßnahmen.',
en: 'The TOM annex contains only general statements without specific measures.',
},
recommendation: {
de: 'Fordern Sie eine konkretere Beschreibung der Maßnahmen mit Bezug zum spezifischen Verarbeitungskontext an.',
en: 'Request a more specific description of measures with reference to the specific processing context.',
},
affectedRequirement: 'Art. 28 Abs. 3 lit. c DSGVO',
triggerControls: ['VND-TOM-01'],
},
// SUBPROCESSOR
{
id: 'tpl-subprocessor-no-approval',
type: 'GAP',
category: 'SUBPROCESSOR',
severity: 'CRITICAL',
title: {
de: 'Keine Genehmigungspflicht für Unterauftragnehmer',
en: 'No approval requirement for sub-processors',
},
description: {
de: 'Der Vertrag regelt nicht, ob und wie der Einsatz von Unterauftragnehmern zu genehmigen ist.',
en: 'The contract does not regulate whether and how the use of sub-processors must be approved.',
},
recommendation: {
de: 'Ergänzen Sie eine Klausel, die entweder eine spezifische oder allgemeine Genehmigung für Unterauftragnehmer vorsieht, einschließlich Informations- und Einspruchsrechten.',
en: 'Add a clause providing either specific or general authorization for sub-processors, including information and objection rights.',
},
affectedRequirement: 'Art. 28 Abs. 3 lit. d DSGVO',
triggerControls: ['VND-SUB-01'],
},
{
id: 'tpl-subprocessor-no-list',
type: 'RISK',
category: 'SUBPROCESSOR',
severity: 'HIGH',
title: {
de: 'Keine Liste der Unterauftragnehmer',
en: 'No list of sub-processors',
},
description: {
de: 'Es liegt keine aktuelle Liste der eingesetzten Unterauftragnehmer vor.',
en: 'There is no current list of sub-processors used.',
},
recommendation: {
de: 'Fordern Sie eine vollständige Liste aller Unterauftragnehmer mit Name, Sitz und Verarbeitungszweck an.',
en: 'Request a complete list of all sub-processors with name, location, and processing purpose.',
},
affectedRequirement: 'Art. 28 Abs. 3 lit. d DSGVO',
triggerControls: ['VND-SUB-02'],
},
// INCIDENT
{
id: 'tpl-incident-no-notification',
type: 'GAP',
category: 'INCIDENT',
severity: 'CRITICAL',
title: {
de: 'Keine Meldepflicht bei Datenpannen',
en: 'No notification obligation for data breaches',
},
description: {
de: 'Der Vertrag enthält keine Regelung zur Meldung von Datenschutzverletzungen.',
en: 'The contract does not contain a provision for reporting data breaches.',
},
recommendation: {
de: 'Ergänzen Sie eine Klausel, die den Auftragsverarbeiter verpflichtet, Datenschutzverletzungen unverzüglich (innerhalb von 24-48h) zu melden.',
en: 'Add a clause obligating the processor to report data breaches without undue delay (within 24-48h).',
},
affectedRequirement: 'Art. 33 Abs. 2 DSGVO',
triggerControls: ['VND-INC-01'],
},
{
id: 'tpl-incident-long-deadline',
type: 'RISK',
category: 'INCIDENT',
severity: 'HIGH',
title: {
de: 'Zu lange Meldefrist',
en: 'Notification deadline too long',
},
description: {
de: 'Die vereinbarte Meldefrist für Datenschutzverletzungen ist zu lang (>72h), um die eigene Meldepflicht einhalten zu können.',
en: 'The agreed notification deadline for data breaches is too long (>72h) to meet own notification obligations.',
},
recommendation: {
de: 'Verkürzen Sie die Meldefrist auf maximal 24-48 Stunden, um ausreichend Zeit für die eigene Meldung an die Aufsichtsbehörde zu haben.',
en: 'Reduce the notification deadline to a maximum of 24-48 hours to have sufficient time for own notification to the supervisory authority.',
},
affectedRequirement: 'Art. 33 DSGVO',
triggerControls: ['VND-INC-01'],
},
// AUDIT_RIGHTS
{
id: 'tpl-audit-no-right',
type: 'GAP',
category: 'AUDIT_RIGHTS',
severity: 'HIGH',
title: {
de: 'Kein Auditrecht vereinbart',
en: 'No audit right agreed',
},
description: {
de: 'Der Vertrag enthält kein Recht des Verantwortlichen auf Prüfungen und Inspektionen.',
en: 'The contract does not contain a right of the controller to audits and inspections.',
},
recommendation: {
de: 'Ergänzen Sie ein ausdrückliches Recht auf Vor-Ort-Inspektionen und die Einsicht in relevante Unterlagen.',
en: 'Add an explicit right to on-site inspections and access to relevant documents.',
},
affectedRequirement: 'Art. 28 Abs. 3 lit. h DSGVO',
triggerControls: ['VND-AUD-01'],
},
{
id: 'tpl-audit-restricted',
type: 'RISK',
category: 'AUDIT_RIGHTS',
severity: 'MEDIUM',
title: {
de: 'Auditrecht eingeschränkt',
en: 'Audit right restricted',
},
description: {
de: 'Das Auditrecht ist durch unangemessene Einschränkungen (z.B. sehr lange Vorlaufzeit, Ausschluss von Vor-Ort-Inspektionen) begrenzt.',
en: 'The audit right is limited by unreasonable restrictions (e.g., very long notice period, exclusion of on-site inspections).',
},
recommendation: {
de: 'Verhandeln Sie angemessene Bedingungen für Audits (max. 30 Tage Vorlaufzeit, Möglichkeit zur Vor-Ort-Inspektion).',
en: 'Negotiate reasonable audit conditions (max. 30 days notice, possibility for on-site inspection).',
},
affectedRequirement: 'Art. 28 Abs. 3 lit. h DSGVO',
triggerControls: ['VND-AUD-01'],
},
// DELETION
{
id: 'tpl-deletion-no-clause',
type: 'GAP',
category: 'DELETION',
severity: 'CRITICAL',
title: {
de: 'Keine Lösch-/Rückgaberegelung',
en: 'No deletion/return clause',
},
description: {
de: 'Der Vertrag regelt nicht, was mit den Daten nach Vertragsende geschieht.',
en: 'The contract does not regulate what happens to the data after contract termination.',
},
recommendation: {
de: 'Ergänzen Sie eine Klausel zur Löschung oder Rückgabe aller personenbezogenen Daten nach Vertragsende (max. 30 Tage).',
en: 'Add a clause for deletion or return of all personal data after contract end (max. 30 days).',
},
affectedRequirement: 'Art. 28 Abs. 3 lit. g DSGVO',
triggerControls: ['VND-DEL-01'],
},
{
id: 'tpl-deletion-no-confirmation',
type: 'RISK',
category: 'DELETION',
severity: 'MEDIUM',
title: {
de: 'Keine Löschbestätigung vorgesehen',
en: 'No deletion confirmation provided',
},
description: {
de: 'Der Vertrag sieht keine Bestätigung der Löschung durch den Auftragsverarbeiter vor.',
en: 'The contract does not provide for confirmation of deletion by the processor.',
},
recommendation: {
de: 'Ergänzen Sie eine Pflicht zur schriftlichen Bestätigung der vollständigen Löschung.',
en: 'Add an obligation for written confirmation of complete deletion.',
},
affectedRequirement: 'Art. 28 Abs. 3 lit. g DSGVO',
triggerControls: ['VND-DEL-01'],
},
// TRANSFER
{
id: 'tpl-transfer-no-basis',
type: 'GAP',
category: 'TRANSFER',
severity: 'CRITICAL',
title: {
de: 'Drittlandtransfer ohne Rechtsgrundlage',
en: 'Third country transfer without legal basis',
},
description: {
de: 'Der Vertrag erlaubt oder impliziert Transfers in Drittländer ohne geeignete Garantien.',
en: 'The contract allows or implies transfers to third countries without appropriate safeguards.',
},
recommendation: {
de: 'Vereinbaren Sie geeignete Garantien (SCC, BCR) oder beschränken Sie die Verarbeitung auf EU/EWR.',
en: 'Agree on appropriate safeguards (SCC, BCR) or restrict processing to EU/EEA.',
},
affectedRequirement: 'Art. 44-49 DSGVO',
triggerControls: ['VND-TRF-01'],
},
{
id: 'tpl-transfer-old-scc',
type: 'RISK',
category: 'TRANSFER',
severity: 'HIGH',
title: {
de: 'Veraltete Standardvertragsklauseln',
en: 'Outdated Standard Contractual Clauses',
},
description: {
de: 'Der Vertrag verwendet die alten SCC (vor 2021), die nicht mehr gültig sind.',
en: 'The contract uses old SCC (pre-2021) that are no longer valid.',
},
recommendation: {
de: 'Aktualisieren Sie auf die SCC 2021 (Durchführungsbeschluss (EU) 2021/914).',
en: 'Update to SCC 2021 (Implementing Decision (EU) 2021/914).',
},
affectedRequirement: 'Art. 46 Abs. 2 lit. c DSGVO',
triggerControls: ['VND-TRF-02'],
},
// LIABILITY
{
id: 'tpl-liability-excessive-cap',
type: 'RISK',
category: 'LIABILITY',
severity: 'MEDIUM',
title: {
de: 'Unangemessene Haftungsbegrenzung',
en: 'Inappropriate liability cap',
},
description: {
de: 'Die Haftungsbegrenzung ist sehr niedrig und könnte bei Datenschutzverletzungen problematisch sein.',
en: 'The liability cap is very low and could be problematic in case of data protection violations.',
},
recommendation: {
de: 'Prüfen Sie, ob die Haftungsbegrenzung angemessen ist. Erwägen Sie eine Ausnahme für Datenschutzverletzungen oder eine höhere Obergrenze.',
en: 'Check if the liability cap is appropriate. Consider an exception for data protection violations or a higher limit.',
},
affectedRequirement: 'Vertragliche Vereinbarung',
triggerControls: [],
},
// DATA_SUBJECT_RIGHTS
{
id: 'tpl-dsr-no-support',
type: 'GAP',
category: 'DATA_SUBJECT_RIGHTS',
severity: 'HIGH',
title: {
de: 'Keine Unterstützung bei Betroffenenrechten',
en: 'No support for data subject rights',
},
description: {
de: 'Der Vertrag enthält keine Regelung zur Unterstützung bei der Erfüllung von Betroffenenrechten.',
en: 'The contract does not contain a provision for support in fulfilling data subject rights.',
},
recommendation: {
de: 'Ergänzen Sie eine Klausel zur Unterstützung bei Auskunft, Berichtigung, Löschung und anderen Betroffenenrechten.',
en: 'Add a clause for support with access, rectification, deletion, and other data subject rights.',
},
affectedRequirement: 'Art. 28 Abs. 3 lit. e DSGVO',
triggerControls: ['VND-DSR-01'],
},
// CONFIDENTIALITY
{
id: 'tpl-confidentiality-missing',
type: 'GAP',
category: 'CONFIDENTIALITY',
severity: 'HIGH',
title: {
de: 'Keine Vertraulichkeitsverpflichtung',
en: 'No confidentiality obligation',
},
description: {
de: 'Der Vertrag enthält keine Verpflichtung zur Vertraulichkeit der Mitarbeiter.',
en: 'The contract does not contain an obligation for employee confidentiality.',
},
recommendation: {
de: 'Ergänzen Sie eine Klausel, die die Verpflichtung der Mitarbeiter zur Vertraulichkeit sicherstellt.',
en: 'Add a clause ensuring the obligation of employees to confidentiality.',
},
affectedRequirement: 'Art. 28 Abs. 3 lit. b DSGVO',
triggerControls: ['VND-CON-02'],
},
]
// ==========================================
// HELPER FUNCTIONS
// ==========================================
/**
* Get finding template by ID
*/
export function getFindingTemplateById(id: string): FindingTemplate | undefined {
return FINDING_TEMPLATES.find((t) => t.id === id)
}
/**
* Get finding templates by category
*/
export function getFindingTemplatesByCategory(category: FindingCategory): FindingTemplate[] {
return FINDING_TEMPLATES.filter((t) => t.category === category)
}
/**
* Get finding templates by type
*/
export function getFindingTemplatesByType(type: FindingType): FindingTemplate[] {
return FINDING_TEMPLATES.filter((t) => t.type === type)
}
/**
* Get finding templates by severity
*/
export function getFindingTemplatesBySeverity(severity: FindingSeverity): FindingTemplate[] {
return FINDING_TEMPLATES.filter((t) => t.severity === severity)
}
/**
* Get severity color class
*/
export function getSeverityColorClass(severity: FindingSeverity): string {
return SEVERITY_DEFINITIONS[severity].color
}
/**
* Sort findings by severity (critical first)
*/
export function sortFindingsBySeverity<T extends { severity: FindingSeverity }>(
findings: T[]
): T[] {
const order: Record<FindingSeverity, number> = {
CRITICAL: 0,
HIGH: 1,
MEDIUM: 2,
LOW: 3,
}
return [...findings].sort((a, b) => order[a.severity] - order[b.severity])
}
/**
* Count findings by severity
*/
export function countFindingsBySeverity<T extends { severity: FindingSeverity }>(
findings: T[]
): Record<FindingSeverity, number> {
return findings.reduce(
(acc, f) => {
acc[f.severity] = (acc[f.severity] || 0) + 1
return acc
},
{ LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0 } as Record<FindingSeverity, number>
)
}
/**
* Get overall severity from list of findings
*/
export function getOverallSeverity(findings: { severity: FindingSeverity }[]): FindingSeverity {
if (findings.some((f) => f.severity === 'CRITICAL')) return 'CRITICAL'
if (findings.some((f) => f.severity === 'HIGH')) return 'HIGH'
if (findings.some((f) => f.severity === 'MEDIUM')) return 'MEDIUM'
return 'LOW'
}

View File

@@ -0,0 +1,7 @@
/**
* Contract Review exports
*/
export * from './analyzer'
export * from './checklists'
export * from './findings'

View File

@@ -0,0 +1,72 @@
/**
* Export Utilities
*
* Functions for generating compliance reports and exports:
* - VVT Export (Art. 30 DSGVO)
* - RoPA Export (Art. 30(2) DSGVO)
* - Vendor Audit Pack
* - Management Summary
*/
// ==========================================
// VVT EXPORT
// ==========================================
export {
// Types
type VVTExportOptions,
type VVTExportResult,
type VVTRow,
// Functions
transformToVVTRows,
generateVVTJson,
generateVVTCsv,
getLocalizedText,
formatDataSubjects,
formatPersonalData,
formatLegalBasis,
formatRecipients,
formatTransfers,
formatRetention,
hasSpecialCategoryData,
hasThirdCountryTransfers,
generateComplianceSummary,
} from './vvt-export'
// ==========================================
// VENDOR AUDIT PACK
// ==========================================
export {
// Types
type VendorAuditPackOptions,
type VendorAuditSection,
type VendorAuditPackResult,
// Functions
generateVendorOverview,
generateContactsSection,
generateLocationsSection,
generateTransferSection,
generateCertificationsSection,
generateContractsSection,
generateFindingsSection,
generateControlStatusSection,
generateRiskSection,
generateReviewScheduleSection,
generateVendorAuditPack,
generateVendorAuditJson,
} from './vendor-audit-pack'
// ==========================================
// ROPA EXPORT
// ==========================================
export {
// Types
type RoPAExportOptions,
type RoPARow,
type RoPAExportResult,
// Functions
transformToRoPARows,
generateRoPAJson,
generateRoPACsv,
generateProcessorSummary,
validateRoPACompleteness,
} from './ropa-export'

View File

@@ -0,0 +1,356 @@
/**
* RoPA (Records of Processing Activities) Export Utilities
*
* Functions for generating Art. 30(2) DSGVO compliant
* processor-perspective records.
*/
import type {
ProcessingActivity,
Vendor,
Organization,
LocalizedText,
ThirdCountryTransfer,
} from '../types'
// ==========================================
// TYPES
// ==========================================
export interface RoPAExportOptions {
activities: ProcessingActivity[]
vendors: Vendor[]
organization: Organization
language: 'de' | 'en'
format: 'PDF' | 'DOCX' | 'XLSX'
includeSubProcessors?: boolean
includeTransfers?: boolean
}
export interface RoPARow {
// Art. 30(2)(a) - Controller info
controllerName: string
controllerAddress: string
controllerDPO: string
// Art. 30(2)(b) - Processing categories
processingCategories: string[]
// Art. 30(2)(c) - Third country transfers
thirdCountryTransfers: string[]
transferMechanisms: string[]
// Art. 30(2)(d) - Technical measures (general description)
technicalMeasures: string[]
// Additional info
subProcessors: string[]
status: string
}
export interface RoPAExportResult {
success: boolean
filename: string
mimeType: string
content: string
metadata: {
controllerCount: number
processingCount: number
generatedAt: Date
language: string
processorName: string
}
}
// ==========================================
// HELPER FUNCTIONS
// ==========================================
function getLocalizedText(text: LocalizedText | undefined, lang: 'de' | 'en'): string {
if (!text) return ''
return text[lang] || text.de || ''
}
function formatTransfers(transfers: ThirdCountryTransfer[], lang: 'de' | 'en'): string[] {
const mechanismLabels: Record<string, LocalizedText> = {
ADEQUACY_DECISION: { de: 'Angemessenheitsbeschluss', en: 'Adequacy Decision' },
SCC_CONTROLLER: { de: 'SCC (C2C)', en: 'SCC (C2C)' },
SCC_PROCESSOR: { de: 'SCC (C2P)', en: 'SCC (C2P)' },
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' },
CERTIFICATION: { de: 'Zertifizierung', en: 'Certification' },
}
return transfers.map((t) => {
const mechanism = mechanismLabels[t.transferMechanism]?.[lang] || t.transferMechanism
return `${t.country}: ${t.recipient} (${mechanism})`
})
}
function formatAddress(address: { street?: string; city?: string; postalCode?: string; country?: string }): string {
if (!address) return '-'
const parts = [address.street, address.postalCode, address.city, address.country].filter(Boolean)
return parts.join(', ') || '-'
}
// ==========================================
// EXPORT FUNCTIONS
// ==========================================
/**
* Transform activities to RoPA rows from processor perspective
* Groups by controller (responsible party)
*/
export function transformToRoPARows(
activities: ProcessingActivity[],
vendors: Vendor[],
lang: 'de' | 'en'
): RoPARow[] {
// Group activities by controller
const byController = new Map<string, ProcessingActivity[]>()
for (const activity of activities) {
const controllerName = activity.responsible.organizationName
if (!byController.has(controllerName)) {
byController.set(controllerName, [])
}
byController.get(controllerName)!.push(activity)
}
// Transform to RoPA rows
const rows: RoPARow[] = []
for (const [controllerName, controllerActivities] of byController) {
const firstActivity = controllerActivities[0]
// Collect all processing categories
const processingCategories = controllerActivities.map((a) =>
getLocalizedText(a.name, lang)
)
// Collect all third country transfers
const allTransfers = controllerActivities.flatMap((a) => a.thirdCountryTransfers)
const uniqueTransfers = formatTransfers(
allTransfers.filter(
(t, i, arr) => arr.findIndex((x) => x.country === t.country && x.recipient === t.recipient) === i
),
lang
)
// Collect unique transfer mechanisms
const uniqueMechanisms = [...new Set(allTransfers.map((t) => t.transferMechanism))]
// Collect all TOM references
const allTOM = [...new Set(controllerActivities.flatMap((a) => a.technicalMeasures))]
// Collect sub-processors from vendors
const subProcessorIds = [...new Set(controllerActivities.flatMap((a) => a.subProcessors))]
const subProcessorNames = subProcessorIds
.map((id) => vendors.find((v) => v.id === id)?.name)
.filter((name): name is string => !!name)
// DPO contact
const dpoContact = firstActivity.dpoContact
? `${firstActivity.dpoContact.name} (${firstActivity.dpoContact.email})`
: firstActivity.responsible.contact
? `${firstActivity.responsible.contact.name} (${firstActivity.responsible.contact.email})`
: '-'
rows.push({
controllerName,
controllerAddress: formatAddress(firstActivity.responsible.address),
controllerDPO: dpoContact,
processingCategories,
thirdCountryTransfers: uniqueTransfers,
transferMechanisms: uniqueMechanisms,
technicalMeasures: allTOM,
subProcessors: subProcessorNames,
status: controllerActivities.every((a) => a.status === 'APPROVED') ? 'APPROVED' : 'PENDING',
})
}
return rows
}
/**
* Generate RoPA as JSON
*/
export function generateRoPAJson(options: RoPAExportOptions): RoPAExportResult {
const rows = transformToRoPARows(options.activities, options.vendors, options.language)
const exportData = {
metadata: {
processor: {
name: options.organization.name,
address: formatAddress(options.organization.address),
dpo: options.organization.dpoContact
? `${options.organization.dpoContact.name} (${options.organization.dpoContact.email})`
: '-',
},
generatedAt: new Date().toISOString(),
language: options.language,
gdprArticle: 'Art. 30(2) DSGVO',
version: '1.0',
},
records: rows.map((row, index) => ({
recordNumber: index + 1,
...row,
})),
summary: {
controllerCount: rows.length,
totalProcessingCategories: rows.reduce((sum, r) => sum + r.processingCategories.length, 0),
withThirdCountryTransfers: rows.filter((r) => r.thirdCountryTransfers.length > 0).length,
uniqueSubProcessors: [...new Set(rows.flatMap((r) => r.subProcessors))].length,
},
}
const content = JSON.stringify(exportData, null, 2)
return {
success: true,
filename: `RoPA_${options.organization.name.replace(/\s+/g, '_')}_${new Date().toISOString().slice(0, 10)}.json`,
mimeType: 'application/json',
content,
metadata: {
controllerCount: rows.length,
processingCount: options.activities.length,
generatedAt: new Date(),
language: options.language,
processorName: options.organization.name,
},
}
}
/**
* Generate RoPA as CSV
*/
export function generateRoPACsv(options: RoPAExportOptions): string {
const rows = transformToRoPARows(options.activities, options.vendors, options.language)
const lang = options.language
const headers =
lang === 'de'
? [
'Nr.',
'Verantwortlicher',
'Anschrift',
'DSB',
'Verarbeitungskategorien',
'Drittlandtransfers',
'Transfermechanismen',
'TOM',
'Unterauftragnehmer',
'Status',
]
: [
'No.',
'Controller',
'Address',
'DPO',
'Processing Categories',
'Third Country Transfers',
'Transfer Mechanisms',
'Technical Measures',
'Sub-Processors',
'Status',
]
const csvRows = rows.map((row, index) => [
(index + 1).toString(),
row.controllerName,
row.controllerAddress,
row.controllerDPO,
row.processingCategories.join('; '),
row.thirdCountryTransfers.join('; '),
row.transferMechanisms.join('; '),
row.technicalMeasures.join('; '),
row.subProcessors.join('; '),
row.status,
])
const escape = (val: string) => `"${val.replace(/"/g, '""')}"`
return [
headers.map(escape).join(','),
...csvRows.map((row) => row.map(escape).join(',')),
].join('\n')
}
/**
* Generate processor summary for RoPA
*/
export function generateProcessorSummary(
activities: ProcessingActivity[],
vendors: Vendor[],
lang: 'de' | 'en'
): {
totalControllers: number
totalCategories: number
withTransfers: number
subProcessorCount: number
pendingApproval: number
} {
const rows = transformToRoPARows(activities, vendors, lang)
return {
totalControllers: rows.length,
totalCategories: rows.reduce((sum, r) => sum + r.processingCategories.length, 0),
withTransfers: rows.filter((r) => r.thirdCountryTransfers.length > 0).length,
subProcessorCount: [...new Set(rows.flatMap((r) => r.subProcessors))].length,
pendingApproval: rows.filter((r) => r.status !== 'APPROVED').length,
}
}
/**
* Validate RoPA completeness
*/
export function validateRoPACompleteness(
rows: RoPARow[],
lang: 'de' | 'en'
): { isComplete: boolean; issues: string[] } {
const issues: string[] = []
for (const row of rows) {
// Art. 30(2)(a) - Controller info
if (!row.controllerName) {
issues.push(
lang === 'de'
? 'Name des Verantwortlichen fehlt'
: 'Controller name missing'
)
}
// Art. 30(2)(b) - Processing categories
if (row.processingCategories.length === 0) {
issues.push(
lang === 'de'
? `${row.controllerName}: Keine Verarbeitungskategorien angegeben`
: `${row.controllerName}: No processing categories specified`
)
}
// Art. 30(2)(c) - Transfers without mechanism
if (row.thirdCountryTransfers.length > 0 && row.transferMechanisms.length === 0) {
issues.push(
lang === 'de'
? `${row.controllerName}: Drittlandtransfer ohne Rechtsgrundlage`
: `${row.controllerName}: Third country transfer without legal basis`
)
}
// Art. 30(2)(d) - TOM
if (row.technicalMeasures.length === 0) {
issues.push(
lang === 'de'
? `${row.controllerName}: Keine TOM angegeben`
: `${row.controllerName}: No technical measures specified`
)
}
}
return {
isComplete: issues.length === 0,
issues,
}
}

View File

@@ -0,0 +1,489 @@
/**
* Vendor Audit Pack Export Utilities
*
* Functions for generating comprehensive vendor audit documentation.
*/
import type {
Vendor,
ContractDocument,
Finding,
ControlInstance,
RiskAssessment,
LocalizedText,
} from '../types'
// ==========================================
// TYPES
// ==========================================
export interface VendorAuditPackOptions {
vendor: Vendor
contracts: ContractDocument[]
findings: Finding[]
controlInstances: ControlInstance[]
riskAssessment?: RiskAssessment
language: 'de' | 'en'
format: 'PDF' | 'DOCX'
includeContracts?: boolean
includeFindings?: boolean
includeControlStatus?: boolean
includeRiskAssessment?: boolean
}
export interface VendorAuditSection {
title: string
content: string | Record<string, unknown>
level: 1 | 2 | 3
}
export interface VendorAuditPackResult {
success: boolean
filename: string
sections: VendorAuditSection[]
metadata: {
vendorName: string
generatedAt: Date
language: string
contractCount: number
findingCount: number
openFindingCount: number
riskLevel: string
}
}
// ==========================================
// CONSTANTS
// ==========================================
const VENDOR_ROLE_LABELS: Record<string, LocalizedText> = {
PROCESSOR: { de: 'Auftragsverarbeiter', en: 'Processor' },
JOINT_CONTROLLER: { de: 'Gemeinsam Verantwortlicher', en: 'Joint Controller' },
CONTROLLER: { de: 'Verantwortlicher', en: 'Controller' },
SUB_PROCESSOR: { de: 'Unterauftragnehmer', en: 'Sub-Processor' },
THIRD_PARTY: { de: 'Dritter', en: 'Third Party' },
}
const SERVICE_CATEGORY_LABELS: Record<string, LocalizedText> = {
HOSTING: { de: 'Hosting', en: 'Hosting' },
CLOUD_INFRASTRUCTURE: { de: 'Cloud-Infrastruktur', en: 'Cloud Infrastructure' },
ANALYTICS: { de: 'Analytics', en: 'Analytics' },
CRM: { de: 'CRM-System', en: 'CRM System' },
ERP: { de: 'ERP-System', en: 'ERP System' },
HR_SOFTWARE: { de: 'HR-Software', en: 'HR Software' },
PAYMENT: { de: 'Zahlungsabwicklung', en: 'Payment Processing' },
EMAIL: { de: 'E-Mail-Dienst', en: 'Email Service' },
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: 'Recht', en: 'Legal' },
ACCOUNTING: { de: 'Buchhaltung', en: 'Accounting' },
COMMUNICATION: { de: 'Kommunikation', en: 'Communication' },
STORAGE: { de: 'Speicher', en: 'Storage' },
OTHER: { de: 'Sonstiges', en: 'Other' },
}
const DATA_ACCESS_LABELS: Record<string, LocalizedText> = {
NONE: { de: 'Kein Zugriff', en: 'No Access' },
POTENTIAL: { de: 'Potentieller Zugriff', en: 'Potential Access' },
ADMINISTRATIVE: { de: 'Administrativer Zugriff', en: 'Administrative Access' },
CONTENT: { de: 'Inhaltlicher Zugriff', en: 'Content Access' },
}
// ==========================================
// HELPER FUNCTIONS
// ==========================================
function getLabel(labels: Record<string, LocalizedText>, key: string, lang: 'de' | 'en'): string {
return labels[key]?.[lang] || key
}
function formatDate(date: Date | string | undefined, lang: 'de' | 'en'): string {
if (!date) return lang === 'de' ? 'Nicht angegeben' : 'Not specified'
const d = new Date(date)
return d.toLocaleDateString(lang === 'de' ? 'de-DE' : 'en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})
}
function getRiskLevelLabel(score: number, lang: 'de' | 'en'): string {
if (score >= 70) return lang === 'de' ? 'KRITISCH' : 'CRITICAL'
if (score >= 50) return lang === 'de' ? 'HOCH' : 'HIGH'
if (score >= 30) return lang === 'de' ? 'MITTEL' : 'MEDIUM'
return lang === 'de' ? 'NIEDRIG' : 'LOW'
}
// ==========================================
// SECTION GENERATORS
// ==========================================
/**
* Generate vendor overview section
*/
export function generateVendorOverview(
vendor: Vendor,
lang: 'de' | 'en'
): VendorAuditSection {
const title = lang === 'de' ? 'Vendor-Übersicht' : 'Vendor Overview'
const content = {
name: vendor.name,
legalForm: vendor.legalForm || '-',
country: vendor.country,
address: vendor.address
? `${vendor.address.street}, ${vendor.address.postalCode} ${vendor.address.city}`
: '-',
website: vendor.website || '-',
role: getLabel(VENDOR_ROLE_LABELS, vendor.role, lang),
serviceCategory: getLabel(SERVICE_CATEGORY_LABELS, vendor.serviceCategory, lang),
serviceDescription: vendor.serviceDescription,
dataAccessLevel: getLabel(DATA_ACCESS_LABELS, vendor.dataAccessLevel, lang),
status: vendor.status,
createdAt: formatDate(vendor.createdAt, lang),
}
return { title, content, level: 1 }
}
/**
* Generate contacts section
*/
export function generateContactsSection(
vendor: Vendor,
lang: 'de' | 'en'
): VendorAuditSection {
const title = lang === 'de' ? 'Kontaktdaten' : 'Contact Information'
const content = {
primaryContact: {
name: vendor.primaryContact.name,
email: vendor.primaryContact.email,
phone: vendor.primaryContact.phone || '-',
department: vendor.primaryContact.department || '-',
role: vendor.primaryContact.role || '-',
},
dpoContact: vendor.dpoContact
? {
name: vendor.dpoContact.name,
email: vendor.dpoContact.email,
phone: vendor.dpoContact.phone || '-',
}
: null,
}
return { title, content, level: 2 }
}
/**
* Generate processing locations section
*/
export function generateLocationsSection(
vendor: Vendor,
lang: 'de' | 'en'
): VendorAuditSection {
const title = lang === 'de' ? 'Verarbeitungsstandorte' : 'Processing Locations'
const locations = vendor.processingLocations.map((loc) => ({
country: loc.country,
region: loc.region || '-',
city: loc.city || '-',
dataCenter: loc.dataCenter || '-',
isEU: loc.isEU,
isAdequate: loc.isAdequate,
}))
return { title, content: { locations }, level: 2 }
}
/**
* Generate transfer mechanisms section
*/
export function generateTransferSection(
vendor: Vendor,
lang: 'de' | 'en'
): VendorAuditSection {
const title = lang === 'de' ? 'Drittlandtransfers' : 'Third Country Transfers'
const mechanismLabels: Record<string, LocalizedText> = {
ADEQUACY_DECISION: { de: 'Angemessenheitsbeschluss', en: 'Adequacy Decision' },
SCC_CONTROLLER: { de: 'SCC (C2C)', en: 'SCC (C2C)' },
SCC_PROCESSOR: { de: 'SCC (C2P)', en: 'SCC (C2P)' },
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' },
CERTIFICATION: { de: 'Zertifizierung', en: 'Certification' },
CODE_OF_CONDUCT: { de: 'Verhaltensregeln', en: 'Code of Conduct' },
}
const mechanisms = vendor.transferMechanisms.map((tm) => ({
type: tm,
label: mechanismLabels[tm]?.[lang] || tm,
}))
return { title, content: { mechanisms }, level: 2 }
}
/**
* Generate certifications section
*/
export function generateCertificationsSection(
vendor: Vendor,
lang: 'de' | 'en'
): VendorAuditSection {
const title = lang === 'de' ? 'Zertifizierungen' : 'Certifications'
const certifications = vendor.certifications.map((cert) => ({
type: cert.type,
issuer: cert.issuer || '-',
issuedDate: cert.issuedDate ? formatDate(cert.issuedDate, lang) : '-',
expirationDate: cert.expirationDate ? formatDate(cert.expirationDate, lang) : '-',
scope: cert.scope || '-',
certificateNumber: cert.certificateNumber || '-',
}))
return { title, content: { certifications }, level: 2 }
}
/**
* Generate contracts section
*/
export function generateContractsSection(
contracts: ContractDocument[],
lang: 'de' | 'en'
): VendorAuditSection {
const title = lang === 'de' ? 'Verträge' : 'Contracts'
const contractList = contracts.map((contract) => ({
type: contract.documentType,
fileName: contract.originalName,
version: contract.version,
effectiveDate: contract.effectiveDate ? formatDate(contract.effectiveDate, lang) : '-',
expirationDate: contract.expirationDate ? formatDate(contract.expirationDate, lang) : '-',
status: contract.status,
reviewStatus: contract.reviewStatus,
complianceScore: contract.complianceScore !== undefined ? `${contract.complianceScore}%` : '-',
}))
return { title, content: { contracts: contractList }, level: 1 }
}
/**
* Generate findings section
*/
export function generateFindingsSection(
findings: Finding[],
lang: 'de' | 'en'
): VendorAuditSection {
const title = lang === 'de' ? 'Findings' : 'Findings'
const summary = {
total: findings.length,
open: findings.filter((f) => f.status === 'OPEN').length,
inProgress: findings.filter((f) => f.status === 'IN_PROGRESS').length,
resolved: findings.filter((f) => f.status === 'RESOLVED').length,
critical: findings.filter((f) => f.severity === 'CRITICAL').length,
high: findings.filter((f) => f.severity === 'HIGH').length,
medium: findings.filter((f) => f.severity === 'MEDIUM').length,
low: findings.filter((f) => f.severity === 'LOW').length,
}
const findingList = findings.map((finding) => ({
id: finding.id.slice(0, 8),
type: finding.type,
category: finding.category,
severity: finding.severity,
title: finding.title[lang] || finding.title.de,
description: finding.description[lang] || finding.description.de,
recommendation: finding.recommendation
? finding.recommendation[lang] || finding.recommendation.de
: '-',
status: finding.status,
affectedRequirement: finding.affectedRequirement || '-',
createdAt: formatDate(finding.createdAt, lang),
resolvedAt: finding.resolvedAt ? formatDate(finding.resolvedAt, lang) : '-',
}))
return { title, content: { summary, findings: findingList }, level: 1 }
}
/**
* Generate control status section
*/
export function generateControlStatusSection(
controlInstances: ControlInstance[],
lang: 'de' | 'en'
): VendorAuditSection {
const title = lang === 'de' ? 'Control-Status' : 'Control Status'
const summary = {
total: controlInstances.length,
pass: controlInstances.filter((c) => c.status === 'PASS').length,
partial: controlInstances.filter((c) => c.status === 'PARTIAL').length,
fail: controlInstances.filter((c) => c.status === 'FAIL').length,
notApplicable: controlInstances.filter((c) => c.status === 'NOT_APPLICABLE').length,
planned: controlInstances.filter((c) => c.status === 'PLANNED').length,
}
const passRate =
summary.total > 0
? Math.round((summary.pass / (summary.total - summary.notApplicable)) * 100)
: 0
const controlList = controlInstances.map((ci) => ({
controlId: ci.controlId,
status: ci.status,
lastAssessedAt: formatDate(ci.lastAssessedAt, lang),
nextAssessmentDate: formatDate(ci.nextAssessmentDate, lang),
notes: ci.notes || '-',
}))
return {
title,
content: { summary, passRate: `${passRate}%`, controls: controlList },
level: 1,
}
}
/**
* Generate risk assessment section
*/
export function generateRiskSection(
vendor: Vendor,
riskAssessment: RiskAssessment | undefined,
lang: 'de' | 'en'
): VendorAuditSection {
const title = lang === 'de' ? 'Risikobewertung' : 'Risk Assessment'
const content = {
inherentRiskScore: vendor.inherentRiskScore,
inherentRiskLevel: getRiskLevelLabel(vendor.inherentRiskScore, lang),
residualRiskScore: vendor.residualRiskScore,
residualRiskLevel: getRiskLevelLabel(vendor.residualRiskScore, lang),
manualAdjustment: vendor.manualRiskAdjustment || 0,
justification: vendor.riskJustification || '-',
assessment: riskAssessment
? {
assessedBy: riskAssessment.assessedBy,
assessedAt: formatDate(riskAssessment.assessedAt, lang),
approvedBy: riskAssessment.approvedBy || '-',
approvedAt: riskAssessment.approvedAt
? formatDate(riskAssessment.approvedAt, lang)
: '-',
nextAssessmentDate: formatDate(riskAssessment.nextAssessmentDate, lang),
riskFactors: riskAssessment.riskFactors.map((rf) => ({
name: rf.name[lang] || rf.name.de,
category: rf.category,
value: rf.value,
weight: rf.weight,
rationale: rf.rationale || '-',
})),
}
: null,
}
return { title, content, level: 1 }
}
/**
* Generate review schedule section
*/
export function generateReviewScheduleSection(
vendor: Vendor,
lang: 'de' | 'en'
): VendorAuditSection {
const title = lang === 'de' ? 'Review-Zeitplan' : 'Review Schedule'
const frequencyLabels: Record<string, LocalizedText> = {
QUARTERLY: { de: 'Vierteljährlich', en: 'Quarterly' },
SEMI_ANNUAL: { de: 'Halbjährlich', en: 'Semi-Annual' },
ANNUAL: { de: 'Jährlich', en: 'Annual' },
BIENNIAL: { de: 'Alle 2 Jahre', en: 'Biennial' },
}
const content = {
reviewFrequency: getLabel(frequencyLabels, vendor.reviewFrequency, lang),
lastReviewDate: vendor.lastReviewDate ? formatDate(vendor.lastReviewDate, lang) : '-',
nextReviewDate: vendor.nextReviewDate ? formatDate(vendor.nextReviewDate, lang) : '-',
isOverdue:
vendor.nextReviewDate && new Date(vendor.nextReviewDate) < new Date()
? lang === 'de'
? 'Ja'
: 'Yes'
: lang === 'de'
? 'Nein'
: 'No',
}
return { title, content, level: 2 }
}
// ==========================================
// MAIN EXPORT FUNCTION
// ==========================================
/**
* Generate complete vendor audit pack
*/
export function generateVendorAuditPack(options: VendorAuditPackOptions): VendorAuditPackResult {
const { vendor, contracts, findings, controlInstances, riskAssessment, language } = options
const sections: VendorAuditSection[] = []
// Always include vendor overview
sections.push(generateVendorOverview(vendor, language))
sections.push(generateContactsSection(vendor, language))
sections.push(generateLocationsSection(vendor, language))
sections.push(generateTransferSection(vendor, language))
sections.push(generateCertificationsSection(vendor, language))
sections.push(generateReviewScheduleSection(vendor, language))
// Contracts (optional)
if (options.includeContracts !== false && contracts.length > 0) {
sections.push(generateContractsSection(contracts, language))
}
// Findings (optional)
if (options.includeFindings !== false && findings.length > 0) {
sections.push(generateFindingsSection(findings, language))
}
// Control status (optional)
if (options.includeControlStatus !== false && controlInstances.length > 0) {
sections.push(generateControlStatusSection(controlInstances, language))
}
// Risk assessment (optional)
if (options.includeRiskAssessment !== false) {
sections.push(generateRiskSection(vendor, riskAssessment, language))
}
// Calculate metadata
const openFindings = findings.filter((f) => f.status === 'OPEN').length
return {
success: true,
filename: `Vendor_Audit_${vendor.name.replace(/\s+/g, '_')}_${new Date().toISOString().slice(0, 10)}.${options.format.toLowerCase()}`,
sections,
metadata: {
vendorName: vendor.name,
generatedAt: new Date(),
language,
contractCount: contracts.length,
findingCount: findings.length,
openFindingCount: openFindings,
riskLevel: getRiskLevelLabel(vendor.inherentRiskScore, language),
},
}
}
/**
* Generate vendor audit pack as JSON
*/
export function generateVendorAuditJson(options: VendorAuditPackOptions): string {
const result = generateVendorAuditPack(options)
return JSON.stringify(result, null, 2)
}

View File

@@ -0,0 +1,444 @@
/**
* VVT Export Utilities
*
* Functions for generating Art. 30 DSGVO compliant
* Verarbeitungsverzeichnis (VVT) exports.
*/
import type {
ProcessingActivity,
Organization,
LocalizedText,
LegalBasis,
DataSubjectCategory,
PersonalDataCategory,
RecipientCategory,
ThirdCountryTransfer,
RetentionPeriod,
} from '../types'
// ==========================================
// TYPES
// ==========================================
export interface VVTExportOptions {
activities: ProcessingActivity[]
organization: Organization
language: 'de' | 'en'
format: 'PDF' | 'DOCX' | 'XLSX'
includeAnnexes?: boolean
includeRiskAssessment?: boolean
watermark?: string
}
export interface VVTExportResult {
success: boolean
filename: string
mimeType: string
content: Uint8Array | string
metadata: {
activityCount: number
generatedAt: Date
language: string
organization: string
}
}
export interface VVTRow {
vvtId: string
name: string
responsible: string
purposes: string[]
legalBasis: string[]
dataSubjects: string[]
personalData: string[]
recipients: string[]
thirdCountryTransfers: string[]
retentionPeriod: string
technicalMeasures: string[]
dpiaRequired: string
status: string
}
// ==========================================
// CONSTANTS
// ==========================================
const DATA_SUBJECT_LABELS: Record<DataSubjectCategory, LocalizedText> = {
EMPLOYEES: { de: 'Beschäftigte', en: 'Employees' },
APPLICANTS: { de: 'Bewerber', en: 'Job 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' },
}
const PERSONAL_DATA_LABELS: Record<PersonalDataCategory, LocalizedText> = {
NAME: { de: 'Name', en: 'Name' },
CONTACT: { de: 'Kontaktdaten', en: 'Contact Data' },
ADDRESS: { de: 'Adressdaten', en: 'Address' },
DOB: { de: 'Geburtsdatum', en: 'Date of Birth' },
ID_NUMBER: { de: 'Ausweisnummern', en: 'ID Numbers' },
SOCIAL_SECURITY: { de: 'Sozialversicherungsnummer', en: 'Social Security Number' },
TAX_ID: { de: 'Steuer-ID', en: 'Tax ID' },
BANK_ACCOUNT: { de: 'Bankverbindung', en: 'Bank Account' },
PAYMENT_DATA: { de: 'Zahlungsdaten', en: 'Payment Data' },
EMPLOYMENT_DATA: { de: 'Beschäftigungsdaten', en: 'Employment Data' },
SALARY_DATA: { de: 'Gehaltsdaten', en: 'Salary Data' },
EDUCATION_DATA: { de: 'Bildungsdaten', en: 'Education Data' },
PHOTO_VIDEO: { de: 'Fotos/Videos', en: 'Photos/Videos' },
IP_ADDRESS: { de: 'IP-Adressen', en: 'IP Addresses' },
DEVICE_ID: { de: 'Geräte-Kennungen', en: 'Device IDs' },
LOCATION_DATA: { de: 'Standortdaten', en: 'Location Data' },
USAGE_DATA: { de: 'Nutzungsdaten', en: 'Usage Data' },
COMMUNICATION_DATA: { de: 'Kommunikationsdaten', en: 'Communication Data' },
CONTRACT_DATA: { de: 'Vertragsdaten', en: 'Contract Data' },
LOGIN_DATA: { de: 'Login-Daten', en: 'Login Data' },
HEALTH_DATA: { de: 'Gesundheitsdaten (Art. 9)', en: 'Health Data (Art. 9)' },
GENETIC_DATA: { de: 'Genetische Daten (Art. 9)', en: 'Genetic Data (Art. 9)' },
BIOMETRIC_DATA: { de: 'Biometrische Daten (Art. 9)', en: 'Biometric Data (Art. 9)' },
RACIAL_ETHNIC: { de: 'Rassische/Ethnische Herkunft (Art. 9)', en: 'Racial/Ethnic Origin (Art. 9)' },
POLITICAL_OPINIONS: { de: 'Politische Meinungen (Art. 9)', en: 'Political Opinions (Art. 9)' },
RELIGIOUS_BELIEFS: { de: 'Religiöse Überzeugungen (Art. 9)', en: 'Religious Beliefs (Art. 9)' },
TRADE_UNION: { de: 'Gewerkschaftszugehörigkeit (Art. 9)', en: 'Trade Union Membership (Art. 9)' },
SEX_LIFE: { de: 'Sexualleben/Orientierung (Art. 9)', en: 'Sex Life/Orientation (Art. 9)' },
CRIMINAL_DATA: { de: 'Strafrechtliche Daten (Art. 10)', en: 'Criminal Data (Art. 10)' },
OTHER: { de: 'Sonstige', en: 'Other' },
}
const LEGAL_BASIS_LABELS: Record<string, LocalizedText> = {
CONSENT: { de: 'Einwilligung (Art. 6 Abs. 1 lit. a)', en: 'Consent (Art. 6(1)(a))' },
CONTRACT: { de: 'Vertragserfüllung (Art. 6 Abs. 1 lit. b)', en: 'Contract (Art. 6(1)(b))' },
LEGAL_OBLIGATION: { de: 'Rechtliche Verpflichtung (Art. 6 Abs. 1 lit. c)', en: 'Legal Obligation (Art. 6(1)(c))' },
VITAL_INTEREST: { de: 'Lebenswichtige Interessen (Art. 6 Abs. 1 lit. d)', en: 'Vital Interests (Art. 6(1)(d))' },
PUBLIC_TASK: { de: 'Öffentliche Aufgabe (Art. 6 Abs. 1 lit. e)', en: 'Public Task (Art. 6(1)(e))' },
LEGITIMATE_INTEREST: { de: 'Berechtigtes Interesse (Art. 6 Abs. 1 lit. f)', en: 'Legitimate Interest (Art. 6(1)(f))' },
}
// ==========================================
// HELPER FUNCTIONS
// ==========================================
export function getLocalizedText(text: LocalizedText | undefined, lang: 'de' | 'en'): string {
if (!text) return ''
return text[lang] || text.de || ''
}
export function formatDataSubjects(categories: DataSubjectCategory[], lang: 'de' | 'en'): string[] {
return categories.map((cat) => DATA_SUBJECT_LABELS[cat]?.[lang] || cat)
}
export function formatPersonalData(categories: PersonalDataCategory[], lang: 'de' | 'en'): string[] {
return categories.map((cat) => PERSONAL_DATA_LABELS[cat]?.[lang] || cat)
}
export function formatLegalBasis(bases: LegalBasis[], lang: 'de' | 'en'): string[] {
return bases.map((basis) => {
const label = LEGAL_BASIS_LABELS[basis.type]?.[lang] || basis.type
return basis.description ? `${label}: ${basis.description}` : label
})
}
export function formatRecipients(recipients: RecipientCategory[], lang: 'de' | 'en'): string[] {
return recipients.map((r) => {
const suffix = r.isThirdCountry && r.country ? ` (${r.country})` : ''
return r.name + suffix
})
}
export function formatTransfers(transfers: ThirdCountryTransfer[], lang: 'de' | 'en'): string[] {
const labels: Record<string, LocalizedText> = {
ADEQUACY_DECISION: { de: 'Angemessenheitsbeschluss', en: 'Adequacy Decision' },
SCC_CONTROLLER: { de: 'SCC (C2C)', en: 'SCC (C2C)' },
SCC_PROCESSOR: { de: 'SCC (C2P)', en: 'SCC (C2P)' },
BCR: { de: 'BCR', en: 'BCR' },
}
return transfers.map((t) => {
const mechanism = labels[t.transferMechanism]?.[lang] || t.transferMechanism
return `${t.country}: ${t.recipient} (${mechanism})`
})
}
export function formatRetention(retention: RetentionPeriod | undefined, lang: 'de' | 'en'): string {
if (!retention) return lang === 'de' ? 'Nicht festgelegt' : 'Not specified'
// If description is available, use it
if (retention.description) {
const desc = retention.description[lang] || retention.description.de
if (desc) return desc
}
// Otherwise build from duration
if (!retention.duration) {
return lang === 'de' ? 'Nicht festgelegt' : 'Not specified'
}
const periodLabels: Record<string, LocalizedText> = {
DAYS: { de: 'Tage', en: 'days' },
MONTHS: { de: 'Monate', en: 'months' },
YEARS: { de: 'Jahre', en: 'years' },
}
const unit = periodLabels[retention.durationUnit || 'YEARS'][lang]
let result = `${retention.duration} ${unit}`
if (retention.legalBasis) {
result += ` (${retention.legalBasis})`
}
return result
}
// ==========================================
// EXPORT FUNCTIONS
// ==========================================
/**
* Transform processing activities to VVT rows for export
*/
export function transformToVVTRows(
activities: ProcessingActivity[],
lang: 'de' | 'en'
): VVTRow[] {
return activities.map((activity) => ({
vvtId: activity.vvtId,
name: getLocalizedText(activity.name, lang),
responsible: activity.responsible.organizationName,
purposes: activity.purposes.map((p) => getLocalizedText(p, lang)),
legalBasis: formatLegalBasis(activity.legalBasis, lang),
dataSubjects: formatDataSubjects(activity.dataSubjectCategories, lang),
personalData: formatPersonalData(activity.personalDataCategories, lang),
recipients: formatRecipients(activity.recipientCategories, lang),
thirdCountryTransfers: formatTransfers(activity.thirdCountryTransfers, lang),
retentionPeriod: formatRetention(activity.retentionPeriod, lang),
technicalMeasures: activity.technicalMeasures,
dpiaRequired: activity.dpiaRequired
? lang === 'de'
? 'Ja'
: 'Yes'
: lang === 'de'
? 'Nein'
: 'No',
status: activity.status,
}))
}
/**
* Generate VVT as JSON (for further processing)
*/
export function generateVVTJson(options: VVTExportOptions): VVTExportResult {
const rows = transformToVVTRows(options.activities, options.language)
const exportData = {
metadata: {
organization: options.organization.name,
generatedAt: new Date().toISOString(),
language: options.language,
activityCount: rows.length,
version: '1.0',
gdprArticle: 'Art. 30 DSGVO',
},
responsible: {
name: options.organization.name,
legalForm: options.organization.legalForm,
address: options.organization.address,
dpo: options.organization.dpoContact,
},
activities: rows,
}
const content = JSON.stringify(exportData, null, 2)
return {
success: true,
filename: `VVT_${options.organization.name.replace(/\s+/g, '_')}_${new Date().toISOString().slice(0, 10)}.json`,
mimeType: 'application/json',
content,
metadata: {
activityCount: rows.length,
generatedAt: new Date(),
language: options.language,
organization: options.organization.name,
},
}
}
/**
* Generate VVT CSV content (for Excel compatibility)
*/
export function generateVVTCsv(options: VVTExportOptions): string {
const rows = transformToVVTRows(options.activities, options.language)
const lang = options.language
const headers = lang === 'de'
? [
'VVT-Nr.',
'Bezeichnung',
'Verantwortlicher',
'Zwecke',
'Rechtsgrundlage',
'Betroffene',
'Datenkategorien',
'Empfänger',
'Drittlandtransfers',
'Löschfristen',
'TOM',
'DSFA erforderlich',
'Status',
]
: [
'VVT ID',
'Name',
'Responsible',
'Purposes',
'Legal Basis',
'Data Subjects',
'Data Categories',
'Recipients',
'Third Country Transfers',
'Retention Period',
'Technical Measures',
'DPIA Required',
'Status',
]
const csvRows = rows.map((row) => [
row.vvtId,
row.name,
row.responsible,
row.purposes.join('; '),
row.legalBasis.join('; '),
row.dataSubjects.join('; '),
row.personalData.join('; '),
row.recipients.join('; '),
row.thirdCountryTransfers.join('; '),
row.retentionPeriod,
row.technicalMeasures.join('; '),
row.dpiaRequired,
row.status,
])
const escape = (val: string) => `"${val.replace(/"/g, '""')}"`
return [
headers.map(escape).join(','),
...csvRows.map((row) => row.map(escape).join(',')),
].join('\n')
}
/**
* Check if activities have special category data (Art. 9)
*/
export function hasSpecialCategoryData(activities: ProcessingActivity[]): boolean {
const specialCategories: PersonalDataCategory[] = [
'HEALTH_DATA',
'GENETIC_DATA',
'BIOMETRIC_DATA',
'RACIAL_ETHNIC',
'POLITICAL_OPINIONS',
'RELIGIOUS_BELIEFS',
'TRADE_UNION',
'SEX_LIFE',
]
return activities.some((activity) =>
activity.personalDataCategories.some((cat) => specialCategories.includes(cat))
)
}
/**
* Check if activities have third country transfers
*/
export function hasThirdCountryTransfers(activities: ProcessingActivity[]): boolean {
return activities.some((activity) => activity.thirdCountryTransfers.length > 0)
}
/**
* Generate compliance summary for VVT
*/
export function generateComplianceSummary(
activities: ProcessingActivity[],
lang: 'de' | 'en'
): {
totalActivities: number
byStatus: Record<string, number>
withSpecialCategories: number
withThirdCountryTransfers: number
dpiaRequired: number
issues: string[]
} {
const byStatus: Record<string, number> = {}
let withSpecialCategories = 0
let withThirdCountryTransfers = 0
let dpiaRequired = 0
const issues: string[] = []
for (const activity of activities) {
// Count by status
byStatus[activity.status] = (byStatus[activity.status] || 0) + 1
// Check for special categories
if (
activity.personalDataCategories.some((cat) =>
[
'HEALTH_DATA',
'GENETIC_DATA',
'BIOMETRIC_DATA',
'RACIAL_ETHNIC',
'POLITICAL_OPINIONS',
'RELIGIOUS_BELIEFS',
'TRADE_UNION',
'SEX_LIFE',
].includes(cat)
)
) {
withSpecialCategories++
}
// Check for third country transfers
if (activity.thirdCountryTransfers.length > 0) {
withThirdCountryTransfers++
}
// Check DPIA
if (activity.dpiaRequired) {
dpiaRequired++
}
// Check for issues
if (activity.legalBasis.length === 0) {
issues.push(
lang === 'de'
? `${activity.vvtId}: Keine Rechtsgrundlage angegeben`
: `${activity.vvtId}: No legal basis specified`
)
}
if (!activity.retentionPeriod) {
issues.push(
lang === 'de'
? `${activity.vvtId}: Keine Löschfrist angegeben`
: `${activity.vvtId}: No retention period specified`
)
}
}
return {
totalActivities: activities.length,
byStatus,
withSpecialCategories,
withThirdCountryTransfers,
dpiaRequired,
issues,
}
}

View File

@@ -0,0 +1,196 @@
/**
* Vendor & Contract Compliance Module (VVT/RoPA)
*
* Public exports for:
* - VVT (Verarbeitungsverzeichnis) - Art. 30 DSGVO Controller-Perspektive
* - RoPA (Records of Processing Activities) - Processor-Perspektive
* - Vendor Register - Lieferanten-/Auftragsverarbeiter-Verwaltung
* - Contract Reviewer - LLM-gestuetzte Vertragspruefung mit Citations
* - Risk & Controls - Risikobewertung und Massnahmenmanagement
* - Audit Reports - Automatisierte Berichtsgenerierung
*/
// ==========================================
// TYPES
// ==========================================
export * from './types'
// ==========================================
// CONTEXT & HOOKS
// ==========================================
export {
VendorComplianceProvider,
useVendorCompliance,
useVendor,
useProcessingActivity,
useVendorContracts,
useVendorFindings,
useContractFindings,
useControlInstancesForEntity,
} from './context'
// ==========================================
// CATALOGS
// ==========================================
export {
// Processing Activity Templates
PROCESSING_ACTIVITY_TEMPLATES,
PROCESSING_ACTIVITY_CATEGORY_META,
getTemplatesByCategory,
getTemplateById,
getGroupedTemplates,
createFormDataFromTemplate,
type ProcessingActivityTemplate,
type ProcessingActivityCategory,
} from './catalog/processing-activities'
export {
// Vendor Templates
VENDOR_TEMPLATES,
COUNTRY_RISK_PROFILES,
getVendorTemplateById,
getVendorTemplatesByCategory,
getCountryRiskProfile,
requiresTransferMechanism,
getSuggestedTransferMechanisms,
calculateTemplateRiskScore,
createVendorFormDataFromTemplate,
getEUEEACountries,
getAdequateCountries,
getHighRiskCountries,
type VendorTemplate,
type CountryRiskProfile,
type RiskFactorWeight,
} from './catalog/vendor-templates'
export {
// Legal Basis
LEGAL_BASIS_INFO,
STANDARD_RETENTION_PERIODS,
getLegalBasisInfo,
getStandardLegalBases,
getSpecialCategoryLegalBases,
getAppropriateLegalBases,
getRetentionPeriod,
getRetentionPeriodsForCategory,
getLongestRetentionPeriod,
formatRetentionPeriod,
type LegalBasisInfo,
type RetentionPeriodInfo,
} from './catalog/legal-basis'
// ==========================================
// CONTRACT REVIEW
// ==========================================
export {
// Analyzer
analyzeContract,
verifyCitation,
getCitationContext,
highlightCitations,
calculateComplianceScore as calculateContractComplianceScore,
CONTRACT_REVIEW_SYSTEM_PROMPT,
CONTRACT_CLASSIFICATION_PROMPT,
METADATA_EXTRACTION_PROMPT,
type ContractAnalysisRequest,
type ContractAnalysisResponse,
type ContractPartyInfo,
type ExtractedMetadata,
type AnalysisScope,
type ComplianceScoreBreakdown,
} from './contract-review/analyzer'
export {
// Checklists
AVV_CHECKLIST,
INCIDENT_CHECKLIST,
TRANSFER_CHECKLIST,
SLA_LIABILITY_CHECKLIST,
CHECKLIST_GROUPS,
getRequiredChecklistItems,
getChecklistItemsByCategory,
getChecklistItemById,
calculateChecklistComplianceScore as calculateChecklistScore,
type ChecklistItem,
type ChecklistGroup,
} from './contract-review/checklists'
export {
// Findings
FINDING_TEMPLATES,
SEVERITY_DEFINITIONS,
FINDING_TYPE_DEFINITIONS,
getFindingTemplateById,
getFindingTemplatesByCategory,
getFindingTemplatesByType,
getFindingTemplatesBySeverity,
getSeverityColorClass,
sortFindingsBySeverity,
countFindingsBySeverity,
getOverallSeverity,
type FindingTemplate,
} from './contract-review/findings'
// ==========================================
// RISK & CONTROLS
// ==========================================
export {
// Risk Calculator
RISK_FACTOR_DEFINITIONS,
calculateVendorInherentRisk,
calculateProcessingActivityInherentRisk,
calculateResidualRisk,
generateRiskMatrix,
getRiskLevelColor,
calculateRiskTrend,
type RiskFactorDefinition,
type RiskContext,
type RiskMatrixCell,
type RiskTrend,
} from './risk/calculator'
export {
// Controls Library
CONTROLS_LIBRARY,
getAllControls,
getControlsByDomain,
getControlById,
getRequiredControls,
getControlsByFrequency,
getVendorControls,
getProcessingActivityControls,
getControlsGroupedByDomain,
getControlDomainMeta,
calculateControlCoverage,
} from './risk/controls-library'
// ==========================================
// EXPORT UTILITIES
// ==========================================
export {
// VVT Export
type VVTExportOptions,
type VVTExportResult,
type VVTRow,
transformToVVTRows,
generateVVTJson,
generateVVTCsv,
hasSpecialCategoryData,
hasThirdCountryTransfers,
generateComplianceSummary,
// Vendor Audit Pack
type VendorAuditPackOptions,
type VendorAuditSection,
type VendorAuditPackResult,
generateVendorAuditPack,
generateVendorAuditJson,
// RoPA Export
type RoPAExportOptions,
type RoPARow,
type RoPAExportResult,
transformToRoPARows,
generateRoPAJson,
generateRoPACsv,
generateProcessorSummary,
validateRoPACompleteness,
} from './export'

View File

@@ -0,0 +1,488 @@
/**
* Risk Score Calculator
*
* Calculate inherent and residual risk scores for vendors and processing activities
*/
import {
Vendor,
ProcessingActivity,
RiskScore,
RiskLevel,
RiskFactor,
ControlInstance,
DataAccessLevel,
VendorRole,
ServiceCategory,
PersonalDataCategory,
getRiskLevelFromScore,
isSpecialCategory,
hasAdequacyDecision,
LocalizedText,
} from '../types'
// ==========================================
// RISK FACTOR DEFINITIONS
// ==========================================
export interface RiskFactorDefinition {
id: string
name: LocalizedText
category: 'DATA' | 'ACCESS' | 'LOCATION' | 'VENDOR' | 'PROCESSING'
description: LocalizedText
weight: number // 0-1
evaluator: (context: RiskContext) => number // Returns 1-5
}
export interface RiskContext {
vendor?: Vendor
processingActivity?: ProcessingActivity
controlInstances?: ControlInstance[]
}
export const RISK_FACTOR_DEFINITIONS: RiskFactorDefinition[] = [
// DATA FACTORS
{
id: 'data_volume',
name: { de: 'Datenvolumen', en: 'Data Volume' },
category: 'DATA',
description: { de: 'Menge der verarbeiteten Daten', en: 'Volume of data processed' },
weight: 0.15,
evaluator: (ctx) => {
// Based on vendor service category or processing activity scope
if (ctx.vendor) {
const highVolume: ServiceCategory[] = ['CLOUD_INFRASTRUCTURE', 'ERP', 'CRM', 'HR_SOFTWARE']
if (highVolume.includes(ctx.vendor.serviceCategory)) return 5
const medVolume: ServiceCategory[] = ['HOSTING', 'EMAIL', 'ANALYTICS', 'BACKUP']
if (medVolume.includes(ctx.vendor.serviceCategory)) return 3
return 2
}
return 3 // Default medium
},
},
{
id: 'data_sensitivity',
name: { de: 'Datensensibilität', en: 'Data Sensitivity' },
category: 'DATA',
description: { de: 'Sensibilität der verarbeiteten Daten', en: 'Sensitivity of data processed' },
weight: 0.2,
evaluator: (ctx) => {
if (ctx.processingActivity) {
const hasSpecial = ctx.processingActivity.personalDataCategories.some(isSpecialCategory)
if (hasSpecial) return 5
const hasFinancial = ctx.processingActivity.personalDataCategories.some(
(c) => ['BANK_ACCOUNT', 'PAYMENT_DATA', 'SALARY_DATA', 'TAX_ID'].includes(c)
)
if (hasFinancial) return 4
const hasIdentifiers = ctx.processingActivity.personalDataCategories.some(
(c) => ['ID_NUMBER', 'SOCIAL_SECURITY'].includes(c)
)
if (hasIdentifiers) return 4
return 2
}
return 3
},
},
{
id: 'special_categories',
name: { de: 'Besondere Kategorien', en: 'Special Categories' },
category: 'DATA',
description: { de: 'Verarbeitung besonderer Datenkategorien (Art. 9)', en: 'Processing of special data categories (Art. 9)' },
weight: 0.15,
evaluator: (ctx) => {
if (ctx.processingActivity) {
const specialCount = ctx.processingActivity.personalDataCategories.filter(isSpecialCategory).length
if (specialCount >= 3) return 5
if (specialCount >= 1) return 4
return 1
}
return 1
},
},
// ACCESS FACTORS
{
id: 'data_access_level',
name: { de: 'Datenzugriffsebene', en: 'Data Access Level' },
category: 'ACCESS',
description: { de: 'Art des Zugriffs auf personenbezogene Daten', en: 'Type of access to personal data' },
weight: 0.15,
evaluator: (ctx) => {
if (ctx.vendor) {
const accessLevelScores: Record<DataAccessLevel, number> = {
NONE: 1,
POTENTIAL: 2,
ADMINISTRATIVE: 4,
CONTENT: 5,
}
return accessLevelScores[ctx.vendor.dataAccessLevel]
}
return 3
},
},
{
id: 'vendor_role',
name: { de: 'Vendor-Rolle', en: 'Vendor Role' },
category: 'ACCESS',
description: { de: 'Rolle des Vendors im Datenschutzkontext', en: 'Role of vendor in data protection context' },
weight: 0.1,
evaluator: (ctx) => {
if (ctx.vendor) {
const roleScores: Record<VendorRole, number> = {
THIRD_PARTY: 1,
CONTROLLER: 3,
JOINT_CONTROLLER: 4,
PROCESSOR: 4,
SUB_PROCESSOR: 5,
}
return roleScores[ctx.vendor.role]
}
return 3
},
},
// LOCATION FACTORS
{
id: 'third_country_transfer',
name: { de: 'Drittlandtransfer', en: 'Third Country Transfer' },
category: 'LOCATION',
description: { de: 'Datenübermittlung in Drittländer', en: 'Data transfer to third countries' },
weight: 0.15,
evaluator: (ctx) => {
if (ctx.vendor) {
const locations = ctx.vendor.processingLocations
const hasNonAdequate = locations.some((l) => !l.isEU && !l.isAdequate)
if (hasNonAdequate) return 5
const hasAdequate = locations.some((l) => !l.isEU && l.isAdequate)
if (hasAdequate) return 3
return 1 // EU only
}
if (ctx.processingActivity && ctx.processingActivity.thirdCountryTransfers.length > 0) {
const hasNonAdequate = ctx.processingActivity.thirdCountryTransfers.some(
(t) => !hasAdequacyDecision(t.country)
)
if (hasNonAdequate) return 5
return 3
}
return 1
},
},
// VENDOR FACTORS
{
id: 'certification_status',
name: { de: 'Zertifizierungsstatus', en: 'Certification Status' },
category: 'VENDOR',
description: { de: 'Vorhandensein relevanter Zertifizierungen', en: 'Presence of relevant certifications' },
weight: 0.1,
evaluator: (ctx) => {
if (ctx.vendor) {
const certs = ctx.vendor.certifications
const relevantCerts = ['ISO 27001', 'SOC 2', 'SOC2', 'TISAX', 'C5', 'PCI DSS']
const hasCert = certs.some((c) => relevantCerts.some((rc) => c.type.includes(rc)))
const hasExpired = certs.some((c) => c.expirationDate && new Date(c.expirationDate) < new Date())
if (hasCert && !hasExpired) return 1
if (hasCert && hasExpired) return 3
return 4
}
return 3
},
},
// PROCESSING FACTORS
{
id: 'processing_scope',
name: { de: 'Verarbeitungsumfang', en: 'Processing Scope' },
category: 'PROCESSING',
description: { de: 'Umfang und Art der Verarbeitung', en: 'Scope and nature of processing' },
weight: 0.1,
evaluator: (ctx) => {
if (ctx.processingActivity) {
const subjectCount = ctx.processingActivity.dataSubjectCategories.length
const categoryCount = ctx.processingActivity.personalDataCategories.length
const totalScope = subjectCount + categoryCount
if (totalScope > 15) return 5
if (totalScope > 10) return 4
if (totalScope > 5) return 3
return 2
}
return 3
},
},
]
// ==========================================
// RISK CALCULATION FUNCTIONS
// ==========================================
/**
* Calculate inherent risk score for a vendor
*/
export function calculateVendorInherentRisk(vendor: Vendor): {
score: RiskScore
factors: RiskFactor[]
} {
const context: RiskContext = { vendor }
return calculateInherentRisk(context)
}
/**
* Calculate inherent risk score for a processing activity
*/
export function calculateProcessingActivityInherentRisk(activity: ProcessingActivity): {
score: RiskScore
factors: RiskFactor[]
} {
const context: RiskContext = { processingActivity: activity }
return calculateInherentRisk(context)
}
/**
* Generic inherent risk calculation
*/
function calculateInherentRisk(context: RiskContext): {
score: RiskScore
factors: RiskFactor[]
} {
const factors: RiskFactor[] = []
let totalWeight = 0
let weightedSum = 0
for (const definition of RISK_FACTOR_DEFINITIONS) {
const value = definition.evaluator(context)
const factor: RiskFactor = {
id: definition.id,
name: definition.name,
category: definition.category,
weight: definition.weight,
value,
}
factors.push(factor)
totalWeight += definition.weight
weightedSum += value * definition.weight
}
// Calculate average (1-5 scale)
const averageScore = weightedSum / totalWeight
// Map to likelihood and impact
const likelihood = Math.round(averageScore) as 1 | 2 | 3 | 4 | 5
const impact = calculateImpact(context)
const score = likelihood * impact
const level = getRiskLevelFromScore(score)
return {
score: {
likelihood,
impact,
score,
level,
rationale: generateRationale(factors, likelihood, impact),
},
factors,
}
}
/**
* Calculate impact based on context
*/
function calculateImpact(context: RiskContext): 1 | 2 | 3 | 4 | 5 {
let impactScore = 3 // Default medium
if (context.vendor) {
// Higher impact for critical services
const criticalServices: ServiceCategory[] = ['CLOUD_INFRASTRUCTURE', 'ERP', 'PAYMENT', 'SECURITY']
if (criticalServices.includes(context.vendor.serviceCategory)) {
impactScore += 1
}
// Higher impact for content access
if (context.vendor.dataAccessLevel === 'CONTENT') {
impactScore += 1
}
}
if (context.processingActivity) {
// Higher impact for special categories
if (context.processingActivity.personalDataCategories.some(isSpecialCategory)) {
impactScore += 1
}
// Higher impact for high protection level
if (context.processingActivity.protectionLevel === 'HIGH') {
impactScore += 1
}
}
return Math.min(5, Math.max(1, impactScore)) as 1 | 2 | 3 | 4 | 5
}
/**
* Generate rationale text
*/
function generateRationale(
factors: RiskFactor[],
likelihood: number,
impact: number
): string {
const highFactors = factors.filter((f) => f.value >= 4).map((f) => f.name.de)
const lowFactors = factors.filter((f) => f.value <= 2).map((f) => f.name.de)
let rationale = `Bewertung: Eintrittswahrscheinlichkeit ${likelihood}/5, Auswirkung ${impact}/5.`
if (highFactors.length > 0) {
rationale += ` Erhöhte Risikofaktoren: ${highFactors.join(', ')}.`
}
if (lowFactors.length > 0) {
rationale += ` Risikomindernde Faktoren: ${lowFactors.join(', ')}.`
}
return rationale
}
// ==========================================
// RESIDUAL RISK CALCULATION
// ==========================================
/**
* Calculate residual risk based on control effectiveness
*/
export function calculateResidualRisk(
inherentRisk: RiskScore,
controlInstances: ControlInstance[]
): RiskScore {
// Calculate control effectiveness (0-1)
const effectiveness = calculateControlEffectiveness(controlInstances)
// Reduce likelihood based on control effectiveness
const reducedLikelihood = Math.max(1, Math.round(inherentRisk.likelihood * (1 - effectiveness * 0.6)))
// Impact reduction is smaller (controls primarily reduce likelihood)
const reducedImpact = Math.max(1, Math.round(inherentRisk.impact * (1 - effectiveness * 0.3)))
const residualScore = reducedLikelihood * reducedImpact
const level = getRiskLevelFromScore(residualScore)
return {
likelihood: reducedLikelihood as 1 | 2 | 3 | 4 | 5,
impact: reducedImpact as 1 | 2 | 3 | 4 | 5,
score: residualScore,
level,
rationale: `Restrisiko nach Berücksichtigung von ${controlInstances.length} Kontrollen (Effektivität: ${Math.round(effectiveness * 100)}%).`,
}
}
/**
* Calculate overall control effectiveness
*/
function calculateControlEffectiveness(controlInstances: ControlInstance[]): number {
if (controlInstances.length === 0) return 0
const statusScores: Record<string, number> = {
PASS: 1,
PARTIAL: 0.5,
FAIL: 0,
NOT_APPLICABLE: 0,
PLANNED: 0.2,
}
const totalScore = controlInstances.reduce(
(sum, ci) => sum + (statusScores[ci.status] || 0),
0
)
return totalScore / controlInstances.length
}
// ==========================================
// RISK MATRIX
// ==========================================
export interface RiskMatrixCell {
likelihood: number
impact: number
level: RiskLevel
score: number
}
/**
* Generate full risk matrix
*/
export function generateRiskMatrix(): RiskMatrixCell[][] {
const matrix: RiskMatrixCell[][] = []
for (let likelihood = 1; likelihood <= 5; likelihood++) {
const row: RiskMatrixCell[] = []
for (let impact = 1; impact <= 5; impact++) {
const score = likelihood * impact
row.push({
likelihood,
impact,
score,
level: getRiskLevelFromScore(score),
})
}
matrix.push(row)
}
return matrix
}
/**
* Get risk matrix colors
*/
export function getRiskLevelColor(level: RiskLevel): {
bg: string
text: string
border: string
} {
switch (level) {
case 'LOW':
return { bg: 'bg-green-100', text: 'text-green-800', border: 'border-green-300' }
case 'MEDIUM':
return { bg: 'bg-yellow-100', text: 'text-yellow-800', border: 'border-yellow-300' }
case 'HIGH':
return { bg: 'bg-orange-100', text: 'text-orange-800', border: 'border-orange-300' }
case 'CRITICAL':
return { bg: 'bg-red-100', text: 'text-red-800', border: 'border-red-300' }
}
}
// ==========================================
// RISK TREND ANALYSIS
// ==========================================
export interface RiskTrend {
currentScore: number
previousScore: number
change: number
trend: 'IMPROVING' | 'STABLE' | 'DETERIORATING'
}
/**
* Calculate risk trend
*/
export function calculateRiskTrend(
currentScore: number,
previousScore: number
): RiskTrend {
const change = currentScore - previousScore
let trend: 'IMPROVING' | 'STABLE' | 'DETERIORATING'
if (Math.abs(change) <= 2) {
trend = 'STABLE'
} else if (change < 0) {
trend = 'IMPROVING'
} else {
trend = 'DETERIORATING'
}
return {
currentScore,
previousScore,
change,
trend,
}
}

View File

@@ -0,0 +1,943 @@
/**
* Controls Library
*
* Standard controls for vendor and processing activity compliance
*/
import { Control, ControlDomain, ReviewFrequency, LocalizedText } from '../types'
// ==========================================
// CONTROL DEFINITIONS
// ==========================================
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',
},
// ==========================================
// 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,
}
}

View File

@@ -0,0 +1,6 @@
/**
* Risk & Controls exports
*/
export * from './calculator'
export * from './controls-library'

File diff suppressed because it is too large Load Diff