refactor: Remove all SDK/compliance pages and API routes from admin-lehrer

SDK/compliance content belongs exclusively in admin-compliance (port 3007).
Removed:
- All (sdk)/ pages (document-crawler, dsb-portal, industry-templates, multi-tenant, sso)
- All api/sdk/ proxy routes
- All developers/sdk/ documentation pages
- Unused lib/sdk/ modules (kept: catalog-manager + its deps for dashboard)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Boenisch
2026-02-14 09:24:36 +01:00
parent 27f1899428
commit 6a53f8d79c
131 changed files with 0 additions and 47136 deletions

View File

@@ -1,324 +0,0 @@
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

@@ -1,250 +0,0 @@
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)
})
})

File diff suppressed because it is too large Load Diff

View File

@@ -1,722 +0,0 @@
import type { ScopeProfilingAnswer, ComplianceDepthLevel, ScopeDocumentType } from './compliance-scope-types'
export interface GoldenTest {
id: string
name: string
description: string
answers: ScopeProfilingAnswer[]
expectedLevel: ComplianceDepthLevel | null // null for prefill tests
expectedMinDocuments?: ScopeDocumentType[]
expectedHardTriggerIds?: string[]
expectedDsfaRequired?: boolean
tags: string[]
}
export const GOLDEN_TESTS: GoldenTest[] = [
// GT-01: 2-Person Freelancer, nur B2B, DE-Hosting → L1
{
id: 'GT-01',
name: '2-Person Freelancer B2B',
description: 'Kleinstes Setup ohne besondere Risiken',
answers: [
{ questionId: 'org_employee_count', value: '2' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'de' },
{ questionId: 'org_industry', value: 'consulting' },
{ questionId: 'data_health', value: false },
{ questionId: 'data_genetic', value: false },
{ questionId: 'data_biometric', value: false },
{ questionId: 'data_racial_ethnic', value: false },
{ questionId: 'data_political_opinion', value: false },
{ questionId: 'data_religious', value: false },
{ questionId: 'data_union_membership', value: false },
{ questionId: 'data_sexual_orientation', value: false },
{ questionId: 'data_criminal', value: false },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
{ questionId: 'process_has_dsfa', value: true },
{ questionId: 'process_has_incident_plan', value: true },
{ questionId: 'data_volume', value: '<1000' },
{ questionId: 'org_customer_count', value: '<100' },
],
expectedLevel: 'L1',
expectedMinDocuments: ['VVT', 'TOM', 'COOKIE_BANNER'],
expectedHardTriggerIds: [],
expectedDsfaRequired: false,
tags: ['baseline', 'freelancer', 'b2b'],
},
// GT-02: Solo IT-Berater → L1
{
id: 'GT-02',
name: 'Solo IT-Berater',
description: 'Einzelperson, minimale Datenverarbeitung',
answers: [
{ questionId: 'org_employee_count', value: '1' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'de' },
{ questionId: 'org_industry', value: 'it_services' },
{ questionId: 'data_health', value: false },
{ questionId: 'data_genetic', value: false },
{ questionId: 'data_biometric', value: false },
{ questionId: 'data_volume', value: '<1000' },
{ questionId: 'org_customer_count', value: '<50' },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
],
expectedLevel: 'L1',
expectedHardTriggerIds: [],
tags: ['baseline', 'solo', 'minimal'],
},
// GT-03: 5-Person Agentur, Website, kein Tracking → L1
{
id: 'GT-03',
name: '5-Person Agentur ohne Tracking',
description: 'Kleine Agentur, einfache Website ohne Analytics',
answers: [
{ questionId: 'org_employee_count', value: '5' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'marketing' },
{ questionId: 'tech_has_website', value: true },
{ questionId: 'tech_has_tracking', value: false },
{ questionId: 'data_volume', value: '1000-10000' },
{ questionId: 'org_customer_count', value: '100-1000' },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
],
expectedLevel: 'L1',
expectedMinDocuments: ['VVT', 'TOM', 'COOKIE_BANNER'],
tags: ['baseline', 'agency', 'simple'],
},
// GT-04: 30-Person SaaS B2B, EU-Cloud → L2 (scale trigger)
{
id: 'GT-04',
name: '30-Person SaaS B2B',
description: 'Scale-Trigger durch Mitarbeiterzahl',
answers: [
{ questionId: 'org_employee_count', value: '30' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'software' },
{ questionId: 'tech_has_cloud', value: true },
{ questionId: 'data_volume', value: '10000-100000' },
{ questionId: 'org_customer_count', value: '1000-10000' },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
{ questionId: 'process_has_dsfa', value: false },
],
expectedLevel: 'L2',
expectedMinDocuments: ['VVT', 'TOM', 'AVV', 'COOKIE_BANNER'],
tags: ['scale', 'saas', 'growth'],
},
// GT-05: 50-Person Handel B2C, Webshop → L2 (B2C+Webshop)
{
id: 'GT-05',
name: '50-Person E-Commerce B2C',
description: 'B2C mit Webshop erhöht Anforderungen',
answers: [
{ questionId: 'org_employee_count', value: '50' },
{ questionId: 'org_business_model', value: 'b2c' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'retail' },
{ questionId: 'tech_has_webshop', value: true },
{ questionId: 'data_volume', value: '100000-1000000' },
{ questionId: 'org_customer_count', value: '10000-100000' },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
],
expectedLevel: 'L2',
expectedHardTriggerIds: ['HT-H01'],
expectedMinDocuments: ['VVT', 'TOM', 'AVV', 'COOKIE_BANNER', 'EINWILLIGUNG'],
tags: ['b2c', 'webshop', 'retail'],
},
// GT-06: 80-Person Dienstleister, Cloud → L2 (scale)
{
id: 'GT-06',
name: '80-Person Dienstleister',
description: 'Größerer Betrieb mit Cloud-Services',
answers: [
{ questionId: 'org_employee_count', value: '80' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'professional_services' },
{ questionId: 'tech_has_cloud', value: true },
{ questionId: 'data_volume', value: '100000-1000000' },
{ questionId: 'org_customer_count', value: '1000-10000' },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
],
expectedLevel: 'L2',
expectedMinDocuments: ['VVT', 'TOM', 'AVV'],
tags: ['scale', 'services'],
},
// GT-07: 20-Person Startup mit GA4 Tracking → L2 (tracking)
{
id: 'GT-07',
name: 'Startup mit Google Analytics',
description: 'Tracking-Tools erhöhen Compliance-Anforderungen',
answers: [
{ questionId: 'org_employee_count', value: '20' },
{ questionId: 'org_business_model', value: 'b2c' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'technology' },
{ questionId: 'tech_has_website', value: true },
{ questionId: 'tech_has_tracking', value: true },
{ questionId: 'tech_tracking_tools', value: 'google_analytics' },
{ questionId: 'data_volume', value: '10000-100000' },
{ questionId: 'process_has_vvt', value: true },
],
expectedLevel: 'L2',
expectedMinDocuments: ['VVT', 'TOM', 'COOKIE_BANNER', 'EINWILLIGUNG'],
tags: ['tracking', 'analytics', 'startup'],
},
// GT-08: Kita-App (Minderjaehrige) → L3 (HT-B01)
{
id: 'GT-08',
name: 'Kita-App für Eltern',
description: 'Datenverarbeitung von Minderjährigen unter 16',
answers: [
{ questionId: 'org_employee_count', value: '15' },
{ questionId: 'org_business_model', value: 'b2c' },
{ questionId: 'tech_hosting_location', value: 'de' },
{ questionId: 'org_industry', value: 'education' },
{ questionId: 'data_subjects_minors', value: true },
{ questionId: 'data_subjects_minors_age', value: '<16' },
{ questionId: 'data_volume', value: '1000-10000' },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
],
expectedLevel: 'L3',
expectedHardTriggerIds: ['HT-B01'],
expectedDsfaRequired: true,
expectedMinDocuments: ['VVT', 'TOM', 'DSFA', 'EINWILLIGUNG', 'AVV'],
tags: ['hard-trigger', 'minors', 'education'],
},
// GT-09: Krankenhaus-Software → L3 (HT-A01)
{
id: 'GT-09',
name: 'Krankenhaus-Verwaltungssoftware',
description: 'Gesundheitsdaten Art. 9 DSGVO',
answers: [
{ questionId: 'org_employee_count', value: '200' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'de' },
{ questionId: 'org_industry', value: 'healthcare' },
{ questionId: 'data_health', value: true },
{ questionId: 'data_volume', value: '>1000000' },
{ questionId: 'org_customer_count', value: '10-50' },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
],
expectedLevel: 'L3',
expectedHardTriggerIds: ['HT-A01'],
expectedDsfaRequired: true,
expectedMinDocuments: ['VVT', 'TOM', 'DSFA', 'AVV'],
tags: ['hard-trigger', 'health', 'art9'],
},
// GT-10: HR-Scoring-Plattform → L3 (HT-C01)
{
id: 'GT-10',
name: 'HR-Scoring für Bewerbungen',
description: 'Automatisierte Entscheidungen im HR-Bereich',
answers: [
{ questionId: 'org_employee_count', value: '40' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'hr_tech' },
{ questionId: 'tech_has_adm', value: true },
{ questionId: 'tech_adm_type', value: 'profiling' },
{ questionId: 'tech_adm_impact', value: 'employment' },
{ questionId: 'data_volume', value: '100000-1000000' },
{ questionId: 'process_has_vvt', value: true },
],
expectedLevel: 'L3',
expectedHardTriggerIds: ['HT-C01'],
expectedDsfaRequired: true,
expectedMinDocuments: ['VVT', 'TOM', 'DSFA', 'AVV'],
tags: ['hard-trigger', 'adm', 'profiling'],
},
// GT-11: Fintech Kreditscoring → L3 (HT-H05 + C01)
{
id: 'GT-11',
name: 'Fintech Kreditscoring',
description: 'Finanzsektor mit automatisierten Entscheidungen',
answers: [
{ questionId: 'org_employee_count', value: '120' },
{ questionId: 'org_business_model', value: 'b2c' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'finance' },
{ questionId: 'tech_has_adm', value: true },
{ questionId: 'tech_adm_type', value: 'scoring' },
{ questionId: 'tech_adm_impact', value: 'credit' },
{ questionId: 'data_volume', value: '>1000000' },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
],
expectedLevel: 'L3',
expectedHardTriggerIds: ['HT-H05', 'HT-C01'],
expectedDsfaRequired: true,
expectedMinDocuments: ['VVT', 'TOM', 'DSFA', 'AVV'],
tags: ['hard-trigger', 'finance', 'scoring'],
},
// GT-12: Bildungsplattform Minderjaehrige → L3 (HT-B01)
{
id: 'GT-12',
name: 'Online-Lernplattform für Schüler',
description: 'Bildungssektor mit minderjährigen Nutzern',
answers: [
{ questionId: 'org_employee_count', value: '35' },
{ questionId: 'org_business_model', value: 'b2c' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'education' },
{ questionId: 'data_subjects_minors', value: true },
{ questionId: 'data_subjects_minors_age', value: '<16' },
{ questionId: 'tech_has_tracking', value: true },
{ questionId: 'data_volume', value: '100000-1000000' },
{ questionId: 'process_has_vvt', value: true },
],
expectedLevel: 'L3',
expectedHardTriggerIds: ['HT-B01'],
expectedDsfaRequired: true,
tags: ['hard-trigger', 'education', 'minors'],
},
// GT-13: Datenbroker → L3 (HT-H02)
{
id: 'GT-13',
name: 'Datenbroker / Adresshandel',
description: 'Geschäftsmodell basiert auf Datenhandel',
answers: [
{ questionId: 'org_employee_count', value: '25' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'data_broker' },
{ questionId: 'data_is_core_business', value: true },
{ questionId: 'data_volume', value: '>1000000' },
{ questionId: 'org_customer_count', value: '100-1000' },
{ questionId: 'process_has_vvt', value: true },
],
expectedLevel: 'L3',
expectedHardTriggerIds: ['HT-H02'],
expectedDsfaRequired: true,
tags: ['hard-trigger', 'data-broker'],
},
// GT-14: Video + ADM → L3 (HT-D05)
{
id: 'GT-14',
name: 'Videoüberwachung mit Gesichtserkennung',
description: 'Biometrische Daten mit automatisierter Verarbeitung',
answers: [
{ questionId: 'org_employee_count', value: '60' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'de' },
{ questionId: 'org_industry', value: 'security' },
{ questionId: 'data_biometric', value: true },
{ questionId: 'tech_has_video_surveillance', value: true },
{ questionId: 'tech_has_adm', value: true },
{ questionId: 'data_volume', value: '100000-1000000' },
{ questionId: 'process_has_vvt', value: true },
],
expectedLevel: 'L3',
expectedHardTriggerIds: ['HT-D05'],
expectedDsfaRequired: true,
tags: ['hard-trigger', 'biometric', 'video'],
},
// GT-15: 500-MA Konzern ohne Zert → L3 (HT-G04)
{
id: 'GT-15',
name: 'Großunternehmen ohne Zertifizierung',
description: 'Scale-Trigger durch Unternehmensgröße',
answers: [
{ questionId: 'org_employee_count', value: '500' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'manufacturing' },
{ questionId: 'data_volume', value: '>1000000' },
{ questionId: 'org_customer_count', value: '>100000' },
{ questionId: 'cert_has_iso27001', value: false },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
],
expectedLevel: 'L3',
expectedHardTriggerIds: ['HT-G04'],
expectedDsfaRequired: true,
tags: ['hard-trigger', 'scale', 'enterprise'],
},
// GT-16: ISO 27001 Anbieter → L4 (HT-F01)
{
id: 'GT-16',
name: 'ISO 27001 zertifizierter Cloud-Provider',
description: 'Zertifizierung erfordert höchste Compliance',
answers: [
{ questionId: 'org_employee_count', value: '150' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'cloud_services' },
{ questionId: 'cert_has_iso27001', value: true },
{ questionId: 'data_volume', value: '>1000000' },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
{ questionId: 'process_has_dsfa', value: true },
],
expectedLevel: 'L4',
expectedHardTriggerIds: ['HT-F01'],
expectedMinDocuments: ['VVT', 'TOM', 'DSFA', 'AVV', 'CERT_ISO27001'],
tags: ['hard-trigger', 'certification', 'iso'],
},
// GT-17: TISAX Automobilzulieferer → L4 (HT-F04)
{
id: 'GT-17',
name: 'TISAX-zertifizierter Automobilzulieferer',
description: 'Automotive-Branche mit TISAX-Anforderungen',
answers: [
{ questionId: 'org_employee_count', value: '300' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'de' },
{ questionId: 'org_industry', value: 'automotive' },
{ questionId: 'cert_has_tisax', value: true },
{ questionId: 'data_volume', value: '>1000000' },
{ questionId: 'org_customer_count', value: '10-50' },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
],
expectedLevel: 'L4',
expectedHardTriggerIds: ['HT-F04'],
tags: ['hard-trigger', 'certification', 'tisax'],
},
// GT-18: ISO 27701 Cloud-Provider → L4 (HT-F02)
{
id: 'GT-18',
name: 'ISO 27701 Privacy-zertifiziert',
description: 'Privacy-spezifische Zertifizierung',
answers: [
{ questionId: 'org_employee_count', value: '200' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'cloud_services' },
{ questionId: 'cert_has_iso27701', value: true },
{ questionId: 'data_volume', value: '>1000000' },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
{ questionId: 'process_has_dsfa', value: true },
],
expectedLevel: 'L4',
expectedHardTriggerIds: ['HT-F02'],
tags: ['hard-trigger', 'certification', 'privacy'],
},
// GT-19: Grosskonzern + Art.9 + >1M DS → L4 (HT-G05)
{
id: 'GT-19',
name: 'Konzern mit sensiblen Massendaten',
description: 'Kombination aus Scale und Art. 9 Daten',
answers: [
{ questionId: 'org_employee_count', value: '2000' },
{ questionId: 'org_business_model', value: 'b2c' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'insurance' },
{ questionId: 'data_health', value: true },
{ questionId: 'data_volume', value: '>1000000' },
{ questionId: 'org_customer_count', value: '>100000' },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
],
expectedLevel: 'L4',
expectedHardTriggerIds: ['HT-G05'],
expectedDsfaRequired: true,
tags: ['hard-trigger', 'scale', 'art9'],
},
// GT-20: Nur B2C Webshop → L2 (HT-H01)
{
id: 'GT-20',
name: 'Reiner B2C Webshop',
description: 'B2C-Trigger ohne weitere Risiken',
answers: [
{ questionId: 'org_employee_count', value: '12' },
{ questionId: 'org_business_model', value: 'b2c' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'retail' },
{ questionId: 'tech_has_webshop', value: true },
{ questionId: 'data_volume', value: '10000-100000' },
{ questionId: 'org_customer_count', value: '1000-10000' },
{ questionId: 'process_has_vvt', value: true },
],
expectedLevel: 'L2',
expectedHardTriggerIds: ['HT-H01'],
tags: ['b2c', 'webshop'],
},
// GT-21: Keine Daten, keine MA → L1
{
id: 'GT-21',
name: 'Minimale Datenverarbeitung',
description: 'Absolute Baseline ohne Risiken',
answers: [
{ questionId: 'org_employee_count', value: '1' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'de' },
{ questionId: 'org_industry', value: 'consulting' },
{ questionId: 'data_volume', value: '<1000' },
{ questionId: 'org_customer_count', value: '<50' },
{ questionId: 'tech_has_website', value: false },
{ questionId: 'process_has_vvt', value: true },
],
expectedLevel: 'L1',
expectedHardTriggerIds: [],
tags: ['baseline', 'minimal'],
},
// GT-22: Alle Art.9 Kategorien → L3 (HT-A09)
{
id: 'GT-22',
name: 'Alle Art. 9 Kategorien',
description: 'Multiple sensible Datenkategorien',
answers: [
{ questionId: 'org_employee_count', value: '50' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'research' },
{ questionId: 'data_health', value: true },
{ questionId: 'data_genetic', value: true },
{ questionId: 'data_biometric', value: true },
{ questionId: 'data_racial_ethnic', value: true },
{ questionId: 'data_political_opinion', value: true },
{ questionId: 'data_religious', value: true },
{ questionId: 'data_union_membership', value: true },
{ questionId: 'data_sexual_orientation', value: true },
{ questionId: 'data_criminal', value: true },
{ questionId: 'data_volume', value: '100000-1000000' },
{ questionId: 'process_has_vvt', value: true },
],
expectedLevel: 'L3',
expectedHardTriggerIds: ['HT-A09'],
expectedDsfaRequired: true,
tags: ['hard-trigger', 'art9', 'multiple-categories'],
},
// GT-23: Drittland + Art.9 → L3 (HT-E04)
{
id: 'GT-23',
name: 'Drittlandtransfer mit Art. 9 Daten',
description: 'Kombination aus Drittland und sensiblen Daten',
answers: [
{ questionId: 'org_employee_count', value: '45' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'us' },
{ questionId: 'org_industry', value: 'healthcare' },
{ questionId: 'data_health', value: true },
{ questionId: 'tech_has_third_country_transfer', value: true },
{ questionId: 'data_volume', value: '100000-1000000' },
{ questionId: 'process_has_vvt', value: true },
],
expectedLevel: 'L3',
expectedHardTriggerIds: ['HT-E04'],
expectedDsfaRequired: true,
tags: ['hard-trigger', 'third-country', 'art9'],
},
// GT-24: Minderjaehrige + Art.9 → L4 (HT-B02)
{
id: 'GT-24',
name: 'Minderjährige mit Gesundheitsdaten',
description: 'Kombination aus vulnerabler Gruppe und Art. 9',
answers: [
{ questionId: 'org_employee_count', value: '30' },
{ questionId: 'org_business_model', value: 'b2c' },
{ questionId: 'tech_hosting_location', value: 'de' },
{ questionId: 'org_industry', value: 'healthcare' },
{ questionId: 'data_subjects_minors', value: true },
{ questionId: 'data_subjects_minors_age', value: '<16' },
{ questionId: 'data_health', value: true },
{ questionId: 'data_volume', value: '10000-100000' },
{ questionId: 'process_has_vvt', value: true },
],
expectedLevel: 'L4',
expectedHardTriggerIds: ['HT-B02'],
expectedDsfaRequired: true,
tags: ['hard-trigger', 'minors', 'health', 'combined-risk'],
},
// GT-25: KI autonome Entscheidungen → L3 (HT-C02)
{
id: 'GT-25',
name: 'KI mit autonomen Entscheidungen',
description: 'AI Act relevante autonome Systeme',
answers: [
{ questionId: 'org_employee_count', value: '70' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'ai_services' },
{ questionId: 'tech_has_adm', value: true },
{ questionId: 'tech_adm_type', value: 'autonomous_decision' },
{ questionId: 'tech_has_ai', value: true },
{ questionId: 'data_volume', value: '100000-1000000' },
{ questionId: 'process_has_vvt', value: true },
],
expectedLevel: 'L3',
expectedHardTriggerIds: ['HT-C02'],
expectedDsfaRequired: true,
tags: ['hard-trigger', 'ai', 'adm'],
},
// GT-26: Multiple Zertifizierungen → L4 (HT-F01-05)
{
id: 'GT-26',
name: 'Multiple Zertifizierungen',
description: 'Mehrere Zertifizierungen kombiniert',
answers: [
{ questionId: 'org_employee_count', value: '250' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'cloud_services' },
{ questionId: 'cert_has_iso27001', value: true },
{ questionId: 'cert_has_iso27701', value: true },
{ questionId: 'cert_has_soc2', value: true },
{ questionId: 'data_volume', value: '>1000000' },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
{ questionId: 'process_has_dsfa', value: true },
],
expectedLevel: 'L4',
expectedHardTriggerIds: ['HT-F01', 'HT-F02', 'HT-F03'],
tags: ['hard-trigger', 'certification', 'multiple'],
},
// GT-27: Oeffentlicher Sektor + Gesundheit → L3 (HT-H07 + A01)
{
id: 'GT-27',
name: 'Öffentlicher Sektor mit Gesundheitsdaten',
description: 'Behörde mit Art. 9 Datenverarbeitung',
answers: [
{ questionId: 'org_employee_count', value: '120' },
{ questionId: 'org_business_model', value: 'b2g' },
{ questionId: 'tech_hosting_location', value: 'de' },
{ questionId: 'org_industry', value: 'public_sector' },
{ questionId: 'org_is_public_sector', value: true },
{ questionId: 'data_health', value: true },
{ questionId: 'data_volume', value: '>1000000' },
{ questionId: 'process_has_vvt', value: true },
],
expectedLevel: 'L3',
expectedHardTriggerIds: ['HT-H07', 'HT-A01'],
expectedDsfaRequired: true,
tags: ['hard-trigger', 'public-sector', 'health'],
},
// GT-28: Bildung + KI + Minderjaehrige → L4 (HT-B03)
{
id: 'GT-28',
name: 'EdTech mit KI für Minderjährige',
description: 'Triple-Risiko: Bildung, KI, vulnerable Gruppe',
answers: [
{ questionId: 'org_employee_count', value: '55' },
{ questionId: 'org_business_model', value: 'b2c' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'education' },
{ questionId: 'data_subjects_minors', value: true },
{ questionId: 'data_subjects_minors_age', value: '<16' },
{ questionId: 'tech_has_ai', value: true },
{ questionId: 'tech_has_adm', value: true },
{ questionId: 'data_volume', value: '100000-1000000' },
{ questionId: 'process_has_vvt', value: true },
],
expectedLevel: 'L4',
expectedHardTriggerIds: ['HT-B03'],
expectedDsfaRequired: true,
tags: ['hard-trigger', 'education', 'ai', 'minors', 'triple-risk'],
},
// GT-29: Freelancer mit 1 Art.9 → L3 (hard trigger override despite low score)
{
id: 'GT-29',
name: 'Freelancer mit Gesundheitsdaten',
description: 'Hard Trigger überschreibt niedrige Score-Bewertung',
answers: [
{ questionId: 'org_employee_count', value: '1' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'de' },
{ questionId: 'org_industry', value: 'healthcare' },
{ questionId: 'data_health', value: true },
{ questionId: 'data_volume', value: '<1000' },
{ questionId: 'org_customer_count', value: '<50' },
{ questionId: 'process_has_vvt', value: true },
],
expectedLevel: 'L3',
expectedHardTriggerIds: ['HT-A01'],
expectedDsfaRequired: true,
tags: ['hard-trigger', 'override', 'art9', 'freelancer'],
},
// GT-30: Enterprise, alle Prozesse vorhanden → L3 (good process maturity)
{
id: 'GT-30',
name: 'Enterprise mit reifer Prozesslandschaft',
description: 'Große Organisation mit allen Compliance-Prozessen',
answers: [
{ questionId: 'org_employee_count', value: '450' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'tech_hosting_location', value: 'eu' },
{ questionId: 'org_industry', value: 'manufacturing' },
{ questionId: 'data_volume', value: '>1000000' },
{ questionId: 'org_customer_count', value: '10000-100000' },
{ questionId: 'process_has_vvt', value: true },
{ questionId: 'process_has_tom', value: true },
{ questionId: 'process_has_dsfa', value: true },
{ questionId: 'process_has_incident_plan', value: true },
{ questionId: 'process_has_dsb', value: true },
{ questionId: 'process_has_training', value: true },
],
expectedLevel: 'L3',
expectedHardTriggerIds: ['HT-G04'],
tags: ['enterprise', 'mature', 'all-processes'],
},
// GT-31: SMB, nur 1 Block beantwortet → L1 (graceful degradation)
{
id: 'GT-31',
name: 'Unvollständige Profilerstellung',
description: 'Test für graceful degradation bei unvollständigen Antworten',
answers: [
{ questionId: 'org_employee_count', value: '8' },
{ questionId: 'org_business_model', value: 'b2b' },
{ questionId: 'org_industry', value: 'consulting' },
// Nur Block 1 (Organization) beantwortet, Rest fehlt
],
expectedLevel: 'L1',
expectedHardTriggerIds: [],
tags: ['incomplete', 'degradation', 'edge-case'],
},
// GT-32: CompanyProfile Prefill Konsistenz → null (prefill test, no expected level)
{
id: 'GT-32',
name: 'CompanyProfile Prefill Test',
description: 'Prüft ob CompanyProfile-Daten korrekt in ScopeProfile übernommen werden',
answers: [
{ questionId: 'org_employee_count', value: '25' },
{ questionId: 'org_business_model', value: 'b2c' },
{ questionId: 'org_industry', value: 'retail' },
{ questionId: 'tech_hosting_location', value: 'eu' },
// Diese Werte sollten mit CompanyProfile-Prefill übereinstimmen
],
expectedLevel: null,
tags: ['prefill', 'integration', 'consistency'],
},
]

View File

@@ -1,821 +0,0 @@
import type {
ScopeQuestionBlock,
ScopeQuestionBlockId,
ScopeProfilingQuestion,
ScopeProfilingAnswer,
ComplianceScopeState,
} from './compliance-scope-types'
import type { CompanyProfile } from './types'
/**
* Block 1: Organisation & Reife
*/
const BLOCK_1_ORGANISATION: ScopeQuestionBlock = {
id: 'organisation',
title: 'Organisation & Reife',
description: 'Grundlegende Informationen zu Ihrer Organisation und Compliance-Zielen',
order: 1,
questions: [
{
id: 'org_employee_count',
type: 'number',
question: 'Wie viele Mitarbeiter hat Ihre Organisation?',
helpText: 'Geben Sie die Gesamtzahl aller Beschäftigten an (inkl. Teilzeit, Minijobs)',
required: true,
scoreWeights: { risk: 5, complexity: 8, assurance: 6 },
mapsToCompanyProfile: 'employeeCount',
},
{
id: 'org_customer_count',
type: 'single',
question: 'Wie viele Kunden/Nutzer betreuen Sie?',
helpText: 'Schätzen Sie die Anzahl aktiver Kunden oder Nutzer',
required: true,
options: [
{ value: '<100', label: 'Weniger als 100' },
{ value: '100-1000', label: '100 bis 1.000' },
{ value: '1000-10000', label: '1.000 bis 10.000' },
{ value: '10000-100000', label: '10.000 bis 100.000' },
{ value: '100000+', label: 'Mehr als 100.000' },
],
scoreWeights: { risk: 6, complexity: 7, assurance: 6 },
},
{
id: 'org_annual_revenue',
type: 'single',
question: 'Wie hoch ist Ihr jährlicher Umsatz?',
helpText: 'Wählen Sie die zutreffende Umsatzklasse',
required: true,
options: [
{ value: '<2Mio', label: 'Unter 2 Mio. EUR' },
{ value: '2-10Mio', label: '2 bis 10 Mio. EUR' },
{ value: '10-50Mio', label: '10 bis 50 Mio. EUR' },
{ value: '>50Mio', label: 'Über 50 Mio. EUR' },
],
scoreWeights: { risk: 4, complexity: 6, assurance: 7 },
mapsToCompanyProfile: 'annualRevenue',
},
{
id: 'org_cert_target',
type: 'multi',
question: 'Welche Zertifizierungen streben Sie an oder besitzen Sie bereits?',
helpText: 'Mehrfachauswahl möglich. Zertifizierungen erhöhen den Assurance-Bedarf',
required: false,
options: [
{ value: 'ISO27001', label: 'ISO 27001 (Informationssicherheit)' },
{ value: 'ISO27701', label: 'ISO 27701 (Datenschutz-Erweiterung)' },
{ value: 'TISAX', label: 'TISAX (Automotive)' },
{ value: 'SOC2', label: 'SOC 2 (US-Standard)' },
{ value: 'BSI-Grundschutz', label: 'BSI IT-Grundschutz' },
{ value: 'Keine', label: 'Keine Zertifizierung geplant' },
],
scoreWeights: { risk: 3, complexity: 5, assurance: 10 },
},
{
id: 'org_industry',
type: 'single',
question: 'In welcher Branche sind Sie tätig?',
helpText: 'Ihre Branche beeinflusst Risikobewertung und regulatorische Anforderungen',
required: true,
options: [
{ value: 'it_software', label: 'IT & Software' },
{ value: 'healthcare', label: 'Gesundheitswesen' },
{ value: 'education', label: 'Bildung & Forschung' },
{ value: 'finance', label: 'Finanzdienstleistungen' },
{ value: 'retail', label: 'Einzelhandel & E-Commerce' },
{ value: 'manufacturing', label: 'Produktion & Fertigung' },
{ value: 'consulting', label: 'Beratung & Dienstleistungen' },
{ value: 'public', label: 'Öffentliche Verwaltung' },
{ value: 'other', label: 'Sonstige' },
],
scoreWeights: { risk: 7, complexity: 5, assurance: 6 },
mapsToCompanyProfile: 'industry',
mapsToVVTQuestion: 'org_industry',
mapsToLFQuestion: 'org-branche',
},
{
id: 'org_business_model',
type: 'single',
question: 'Was ist Ihr primäres Geschäftsmodell?',
helpText: 'B2C-Modelle haben höhere Datenschutzanforderungen',
required: true,
options: [
{ value: 'b2b', label: 'B2B (Business-to-Business)' },
{ value: 'b2c', label: 'B2C (Business-to-Consumer)' },
{ value: 'both', label: 'B2B und B2C gemischt' },
{ value: 'b2g', label: 'B2G (Business-to-Government)' },
],
scoreWeights: { risk: 6, complexity: 5, assurance: 5 },
mapsToCompanyProfile: 'businessModel',
mapsToVVTQuestion: 'org_b2b_b2c',
mapsToLFQuestion: 'org-geschaeftsmodell',
},
{
id: 'org_has_dsb',
type: 'boolean',
question: 'Haben Sie einen Datenschutzbeauftragten bestellt?',
helpText: 'Ein DSB ist bei mehr als 20 Personen mit regelmäßiger Datenverarbeitung Pflicht',
required: true,
scoreWeights: { risk: 5, complexity: 3, assurance: 6 },
},
],
}
/**
* Block 2: Daten & Betroffene
*/
const BLOCK_2_DATA: ScopeQuestionBlock = {
id: 'data',
title: 'Daten & Betroffene',
description: 'Art und Umfang der verarbeiteten personenbezogenen Daten',
order: 2,
questions: [
{
id: 'data_minors',
type: 'boolean',
question: 'Verarbeiten Sie Daten von Minderjährigen?',
helpText: 'Besondere Schutzpflichten für unter 16-Jährige (bzw. 13-Jährige bei Online-Diensten)',
required: true,
scoreWeights: { risk: 10, complexity: 5, assurance: 7 },
mapsToVVTQuestion: 'data_minors',
},
{
id: 'data_art9',
type: 'multi',
question: 'Verarbeiten Sie besondere Kategorien personenbezogener Daten (Art. 9 DSGVO)?',
helpText: 'Diese Daten unterliegen erhöhten Schutzanforderungen',
required: true,
options: [
{ value: 'gesundheit', label: 'Gesundheitsdaten' },
{ value: 'biometrie', label: 'Biometrische Daten (z.B. Fingerabdruck, Gesichtserkennung)' },
{ value: 'genetik', label: 'Genetische Daten' },
{ value: 'politisch', label: 'Politische Meinungen' },
{ value: 'religion', label: 'Religiöse/weltanschauliche Überzeugungen' },
{ value: 'gewerkschaft', label: 'Gewerkschaftszugehörigkeit' },
{ value: 'sexualleben', label: 'Sexualleben/sexuelle Orientierung' },
{ value: 'strafrechtlich', label: 'Strafrechtliche Verurteilungen/Straftaten' },
{ value: 'ethnisch', label: 'Ethnische Herkunft' },
],
scoreWeights: { risk: 10, complexity: 8, assurance: 9 },
mapsToVVTQuestion: 'data_health',
},
{
id: 'data_hr',
type: 'boolean',
question: 'Verarbeiten Sie Personaldaten (HR)?',
helpText: 'Bewerberdaten, Gehälter, Leistungsbeurteilungen etc.',
required: true,
scoreWeights: { risk: 6, complexity: 4, assurance: 5 },
mapsToVVTQuestion: 'dept_hr',
mapsToLFQuestion: 'data-hr',
},
{
id: 'data_communication',
type: 'boolean',
question: 'Verarbeiten Sie Kommunikationsdaten (E-Mail, Chat, Telefonie)?',
helpText: 'Inhalte oder Metadaten von Kommunikationsvorgängen',
required: true,
scoreWeights: { risk: 7, complexity: 5, assurance: 6 },
},
{
id: 'data_financial',
type: 'boolean',
question: 'Verarbeiten Sie Finanzdaten (Konten, Zahlungen)?',
helpText: 'Bankdaten, Kreditkartendaten, Buchhaltungsdaten',
required: true,
scoreWeights: { risk: 8, complexity: 6, assurance: 7 },
mapsToVVTQuestion: 'dept_finance',
mapsToLFQuestion: 'data-buchhaltung',
},
{
id: 'data_volume',
type: 'single',
question: 'Wie viele Personendatensätze verarbeiten Sie insgesamt?',
helpText: 'Schätzen Sie die Gesamtzahl betroffener Personen',
required: true,
options: [
{ value: '<1000', label: 'Unter 1.000' },
{ value: '1000-10000', label: '1.000 bis 10.000' },
{ value: '10000-100000', label: '10.000 bis 100.000' },
{ value: '100000-1000000', label: '100.000 bis 1 Mio.' },
{ value: '>1000000', label: 'Über 1 Mio.' },
],
scoreWeights: { risk: 7, complexity: 6, assurance: 6 },
},
],
}
/**
* Block 3: Verarbeitung & Zweck
*/
const BLOCK_3_PROCESSING: ScopeQuestionBlock = {
id: 'processing',
title: 'Verarbeitung & Zweck',
description: 'Wie und wofür werden personenbezogene Daten verarbeitet?',
order: 3,
questions: [
{
id: 'proc_tracking',
type: 'boolean',
question: 'Setzen Sie Tracking oder Profiling ein?',
helpText: 'Web-Analytics, Werbe-Tracking, Nutzungsprofile etc.',
required: true,
scoreWeights: { risk: 7, complexity: 6, assurance: 6 },
},
{
id: 'proc_adm_scoring',
type: 'boolean',
question: 'Treffen Sie automatisierte Entscheidungen (Art. 22 DSGVO)?',
helpText: 'Scoring, Bonitätsprüfung, automatische Ablehnung ohne menschliche Beteiligung',
required: true,
scoreWeights: { risk: 9, complexity: 8, assurance: 8 },
},
{
id: 'proc_ai_usage',
type: 'multi',
question: 'Setzen Sie KI-Systeme ein?',
helpText: 'KI-Einsatz kann zusätzliche Anforderungen (EU AI Act) auslösen',
required: true,
options: [
{ value: 'keine', label: 'Keine KI im Einsatz' },
{ value: 'chatbot', label: 'Chatbots/Virtuelle Assistenten' },
{ value: 'scoring', label: 'Scoring/Risikobewertung' },
{ value: 'profiling', label: 'Profiling/Verhaltensvorhersage' },
{ value: 'generativ', label: 'Generative KI (Text, Bild, Code)' },
{ value: 'autonom', label: 'Autonome Systeme/Entscheidungen' },
],
scoreWeights: { risk: 8, complexity: 9, assurance: 7 },
},
{
id: 'proc_data_combination',
type: 'boolean',
question: 'Führen Sie Daten aus verschiedenen Quellen zusammen?',
helpText: 'Data Matching, Anreicherung aus externen Quellen',
required: true,
scoreWeights: { risk: 7, complexity: 7, assurance: 6 },
},
{
id: 'proc_employee_monitoring',
type: 'boolean',
question: 'Überwachen Sie Mitarbeiter (Zeiterfassung, Standort, IT-Nutzung)?',
helpText: 'Beschäftigtendatenschutz nach § 26 BDSG',
required: true,
scoreWeights: { risk: 8, complexity: 6, assurance: 7 },
},
{
id: 'proc_video_surveillance',
type: 'boolean',
question: 'Setzen Sie Videoüberwachung ein?',
helpText: 'Kameras in Büros, Produktionsstätten, Verkaufsräumen etc.',
required: true,
scoreWeights: { risk: 8, complexity: 5, assurance: 7 },
mapsToVVTQuestion: 'special_video_surveillance',
mapsToLFQuestion: 'data-video',
},
],
}
/**
* Block 4: Technik/Hosting/Transfers
*/
const BLOCK_4_TECH: ScopeQuestionBlock = {
id: 'tech',
title: 'Technik, Hosting & Transfers',
description: 'Technische Infrastruktur und Datenübermittlung',
order: 4,
questions: [
{
id: 'tech_hosting_location',
type: 'single',
question: 'Wo werden Ihre Daten primär gehostet?',
helpText: 'Standort bestimmt anwendbares Datenschutzrecht',
required: true,
options: [
{ value: 'de', label: 'Deutschland' },
{ value: 'eu', label: 'EU (ohne Deutschland)' },
{ value: 'ewr', label: 'EWR (z.B. Norwegen, Island)' },
{ value: 'us_adequacy', label: 'USA (mit Angemessenheitsbeschluss/DPF)' },
{ value: 'drittland', label: 'Drittland ohne Angemessenheitsbeschluss' },
],
scoreWeights: { risk: 7, complexity: 6, assurance: 7 },
},
{
id: 'tech_subprocessors',
type: 'boolean',
question: 'Nutzen Sie Auftragsverarbeiter (externe Dienstleister)?',
helpText: 'Cloud-Anbieter, Hosting, E-Mail-Service, CRM etc. erfordert AVV nach Art. 28 DSGVO',
required: true,
scoreWeights: { risk: 6, complexity: 7, assurance: 7 },
},
{
id: 'tech_third_country',
type: 'boolean',
question: 'Übermitteln Sie Daten in Drittländer?',
helpText: 'Transfer außerhalb EU/EWR erfordert Schutzmaßnahmen (SCC, BCR etc.)',
required: true,
scoreWeights: { risk: 9, complexity: 8, assurance: 8 },
mapsToVVTQuestion: 'transfer_cloud_us',
},
{
id: 'tech_encryption_rest',
type: 'boolean',
question: 'Sind Daten im Ruhezustand verschlüsselt (at rest)?',
helpText: 'Datenbank-, Dateisystem- oder Volume-Verschlüsselung',
required: true,
scoreWeights: { risk: -5, complexity: 3, assurance: 7 },
},
{
id: 'tech_encryption_transit',
type: 'boolean',
question: 'Sind Daten bei Übertragung verschlüsselt (in transit)?',
helpText: 'TLS/SSL für alle Verbindungen',
required: true,
scoreWeights: { risk: -5, complexity: 2, assurance: 7 },
},
{
id: 'tech_cloud_providers',
type: 'multi',
question: 'Welche Cloud-Anbieter nutzen Sie?',
helpText: 'Mehrfachauswahl möglich',
required: false,
options: [
{ value: 'aws', label: 'Amazon Web Services (AWS)' },
{ value: 'azure', label: 'Microsoft Azure' },
{ value: 'gcp', label: 'Google Cloud Platform (GCP)' },
{ value: 'hetzner', label: 'Hetzner' },
{ value: 'ionos', label: 'IONOS' },
{ value: 'ovh', label: 'OVH' },
{ value: 'andere', label: 'Andere Anbieter' },
{ value: 'keine', label: 'Keine Cloud-Nutzung (On-Premise)' },
],
scoreWeights: { risk: 5, complexity: 6, assurance: 6 },
},
],
}
/**
* Block 5: Rechte & Prozesse
*/
const BLOCK_5_PROCESSES: ScopeQuestionBlock = {
id: 'processes',
title: 'Rechte & Prozesse',
description: 'Etablierte Datenschutz- und Sicherheitsprozesse',
order: 5,
questions: [
{
id: 'proc_dsar_process',
type: 'boolean',
question: 'Haben Sie einen Prozess für Betroffenenrechte (DSAR)?',
helpText: 'Auskunft, Löschung, Berichtigung, Widerspruch etc. Art. 15-22 DSGVO',
required: true,
scoreWeights: { risk: 6, complexity: 5, assurance: 8 },
},
{
id: 'proc_deletion_concept',
type: 'boolean',
question: 'Haben Sie ein Löschkonzept?',
helpText: 'Definierte Löschfristen und automatisierte Löschroutinen',
required: true,
scoreWeights: { risk: 7, complexity: 6, assurance: 8 },
},
{
id: 'proc_incident_response',
type: 'boolean',
question: 'Haben Sie einen Notfallplan für Datenschutzvorfälle?',
helpText: 'Incident Response Plan, 72h-Meldepflicht an Aufsichtsbehörde (Art. 33 DSGVO)',
required: true,
scoreWeights: { risk: 8, complexity: 6, assurance: 9 },
},
{
id: 'proc_regular_audits',
type: 'boolean',
question: 'Führen Sie regelmäßige Datenschutz-Audits durch?',
helpText: 'Interne oder externe Prüfungen mindestens jährlich',
required: true,
scoreWeights: { risk: 5, complexity: 4, assurance: 9 },
},
{
id: 'proc_training',
type: 'boolean',
question: 'Schulen Sie Ihre Mitarbeiter im Datenschutz?',
helpText: 'Awareness-Trainings, Onboarding, jährliche Auffrischung',
required: true,
scoreWeights: { risk: 6, complexity: 3, assurance: 7 },
},
],
}
/**
* Block 6: Produktkontext
*/
const BLOCK_6_PRODUCT: ScopeQuestionBlock = {
id: 'product',
title: 'Produktkontext',
description: 'Spezifische Merkmale Ihrer Produkte und Services',
order: 6,
questions: [
{
id: 'prod_type',
type: 'multi',
question: 'Welche Art von Produkten/Services bieten Sie an?',
helpText: 'Mehrfachauswahl möglich',
required: true,
options: [
{ value: 'webapp', label: 'Web-Anwendung' },
{ value: 'mobile', label: 'Mobile App (iOS/Android)' },
{ value: 'saas', label: 'SaaS-Plattform' },
{ value: 'onpremise', label: 'On-Premise Software' },
{ value: 'api', label: 'API/Schnittstellen' },
{ value: 'iot', label: 'IoT/Hardware' },
{ value: 'beratung', label: 'Beratungsleistungen' },
{ value: 'handel', label: 'Handel/Vertrieb' },
],
scoreWeights: { risk: 5, complexity: 6, assurance: 5 },
},
{
id: 'prod_cookies_consent',
type: 'boolean',
question: 'Benötigen Sie Cookie-Consent (Tracking-Cookies)?',
helpText: 'Nicht-essenzielle Cookies erfordern opt-in Einwilligung',
required: true,
scoreWeights: { risk: 5, complexity: 4, assurance: 6 },
},
{
id: 'prod_webshop',
type: 'boolean',
question: 'Betreiben Sie einen Online-Shop?',
helpText: 'E-Commerce mit Zahlungsabwicklung, Bestellverwaltung',
required: true,
scoreWeights: { risk: 7, complexity: 6, assurance: 6 },
},
{
id: 'prod_api_external',
type: 'boolean',
question: 'Bieten Sie externe APIs an (Daten-Weitergabe an Dritte)?',
helpText: 'Programmierschnittstellen für Partner, Entwickler etc.',
required: true,
scoreWeights: { risk: 7, complexity: 7, assurance: 7 },
},
{
id: 'prod_data_broker',
type: 'boolean',
question: 'Handeln Sie mit Daten (Data Brokerage, Adresshandel)?',
helpText: 'Verkauf oder Vermittlung personenbezogener Daten',
required: true,
scoreWeights: { risk: 10, complexity: 8, assurance: 9 },
},
],
}
/**
* All question blocks in order
*/
export const SCOPE_QUESTION_BLOCKS: ScopeQuestionBlock[] = [
BLOCK_1_ORGANISATION,
BLOCK_2_DATA,
BLOCK_3_PROCESSING,
BLOCK_4_TECH,
BLOCK_5_PROCESSES,
BLOCK_6_PRODUCT,
]
/**
* Prefill scope answers from CompanyProfile
*/
export function prefillFromCompanyProfile(
profile: CompanyProfile
): ScopeProfilingAnswer[] {
const answers: ScopeProfilingAnswer[] = []
// employeeCount
if (profile.employeeCount != null) {
answers.push({
questionId: 'org_employee_count',
value: profile.employeeCount,
})
}
// annualRevenue
if (profile.annualRevenue) {
answers.push({
questionId: 'org_annual_revenue',
value: profile.annualRevenue,
})
}
// industry
if (profile.industry) {
answers.push({
questionId: 'org_industry',
value: profile.industry,
})
}
// businessModel
if (profile.businessModel) {
answers.push({
questionId: 'org_business_model',
value: profile.businessModel,
})
}
// dpoName -> org_has_dsb
if (profile.dpoName && profile.dpoName.trim() !== '') {
answers.push({
questionId: 'org_has_dsb',
value: true,
})
}
// usesAI -> proc_ai_usage
if (profile.usesAI === true) {
// We don't know which specific AI type, so just mark as "generativ" as a default
answers.push({
questionId: 'proc_ai_usage',
value: ['generativ'],
})
} else if (profile.usesAI === false) {
answers.push({
questionId: 'proc_ai_usage',
value: ['keine'],
})
}
// offerings -> prod_type mapping
if (profile.offerings && profile.offerings.length > 0) {
const prodTypes: string[] = []
const offeringsLower = profile.offerings.map((o) => o.toLowerCase())
if (offeringsLower.some((o) => o.includes('webapp') || o.includes('web'))) {
prodTypes.push('webapp')
}
if (
offeringsLower.some((o) => o.includes('mobile') || o.includes('app'))
) {
prodTypes.push('mobile')
}
if (offeringsLower.some((o) => o.includes('saas') || o.includes('cloud'))) {
prodTypes.push('saas')
}
if (
offeringsLower.some(
(o) => o.includes('onpremise') || o.includes('on-premise')
)
) {
prodTypes.push('onpremise')
}
if (offeringsLower.some((o) => o.includes('api'))) {
prodTypes.push('api')
}
if (offeringsLower.some((o) => o.includes('iot') || o.includes('hardware'))) {
prodTypes.push('iot')
}
if (
offeringsLower.some(
(o) => o.includes('beratung') || o.includes('consulting')
)
) {
prodTypes.push('beratung')
}
if (
offeringsLower.some(
(o) => o.includes('handel') || o.includes('shop') || o.includes('commerce')
)
) {
prodTypes.push('handel')
}
if (prodTypes.length > 0) {
answers.push({
questionId: 'prod_type',
value: prodTypes,
})
}
}
return answers
}
/**
* Prefill scope answers from VVT profiling answers
*/
export function prefillFromVVTAnswers(
vvtAnswers: Record<string, unknown>
): ScopeProfilingAnswer[] {
const answers: ScopeProfilingAnswer[] = []
// Build reverse mapping: VVT question -> Scope question
const reverseMap: Record<string, string> = {}
for (const block of SCOPE_QUESTION_BLOCKS) {
for (const q of block.questions) {
if (q.mapsToVVTQuestion) {
reverseMap[q.mapsToVVTQuestion] = q.id
}
}
}
// Map VVT answers to scope answers
for (const [vvtQuestionId, vvtValue] of Object.entries(vvtAnswers)) {
const scopeQuestionId = reverseMap[vvtQuestionId]
if (scopeQuestionId) {
answers.push({
questionId: scopeQuestionId,
value: vvtValue,
})
}
}
return answers
}
/**
* Prefill scope answers from Loeschfristen profiling answers
*/
export function prefillFromLoeschfristenAnswers(
lfAnswers: Array<{ questionId: string; value: unknown }>
): ScopeProfilingAnswer[] {
const answers: ScopeProfilingAnswer[] = []
// Build reverse mapping: LF question -> Scope question
const reverseMap: Record<string, string> = {}
for (const block of SCOPE_QUESTION_BLOCKS) {
for (const q of block.questions) {
if (q.mapsToLFQuestion) {
reverseMap[q.mapsToLFQuestion] = q.id
}
}
}
// Map LF answers to scope answers
for (const lfAnswer of lfAnswers) {
const scopeQuestionId = reverseMap[lfAnswer.questionId]
if (scopeQuestionId) {
answers.push({
questionId: scopeQuestionId,
value: lfAnswer.value,
})
}
}
return answers
}
/**
* Export scope answers in VVT format
*/
export function exportToVVTAnswers(
scopeAnswers: ScopeProfilingAnswer[]
): Record<string, unknown> {
const vvtAnswers: Record<string, unknown> = {}
for (const answer of scopeAnswers) {
// Find the question
let question: ScopeProfilingQuestion | undefined
for (const block of SCOPE_QUESTION_BLOCKS) {
question = block.questions.find((q) => q.id === answer.questionId)
if (question) break
}
if (question?.mapsToVVTQuestion) {
vvtAnswers[question.mapsToVVTQuestion] = answer.value
}
}
return vvtAnswers
}
/**
* Export scope answers in Loeschfristen format
*/
export function exportToLoeschfristenAnswers(
scopeAnswers: ScopeProfilingAnswer[]
): Array<{ questionId: string; value: unknown }> {
const lfAnswers: Array<{ questionId: string; value: unknown }> = []
for (const answer of scopeAnswers) {
// Find the question
let question: ScopeProfilingQuestion | undefined
for (const block of SCOPE_QUESTION_BLOCKS) {
question = block.questions.find((q) => q.id === answer.questionId)
if (question) break
}
if (question?.mapsToLFQuestion) {
lfAnswers.push({
questionId: question.mapsToLFQuestion,
value: answer.value,
})
}
}
return lfAnswers
}
/**
* Export scope answers for TOM generator
*/
export function exportToTOMProfile(
scopeAnswers: ScopeProfilingAnswer[]
): Record<string, unknown> {
const tomProfile: Record<string, unknown> = {}
// Get answer values
const getVal = (qId: string) => getAnswerValue(scopeAnswers, qId)
// Map relevant scope answers to TOM profile fields
tomProfile.industry = getVal('org_industry')
tomProfile.employeeCount = getVal('org_employee_count')
tomProfile.hasDataMinors = getVal('data_minors')
tomProfile.hasSpecialCategories = Array.isArray(getVal('data_art9'))
? (getVal('data_art9') as string[]).length > 0
: false
tomProfile.hasAutomatedDecisions = getVal('proc_adm_scoring')
tomProfile.usesAI = Array.isArray(getVal('proc_ai_usage'))
? !(getVal('proc_ai_usage') as string[]).includes('keine')
: false
tomProfile.hasThirdCountryTransfer = getVal('tech_third_country')
tomProfile.hasEncryptionRest = getVal('tech_encryption_rest')
tomProfile.hasEncryptionTransit = getVal('tech_encryption_transit')
tomProfile.hasIncidentResponse = getVal('proc_incident_response')
tomProfile.hasDeletionConcept = getVal('proc_deletion_concept')
tomProfile.hasRegularAudits = getVal('proc_regular_audits')
tomProfile.hasTraining = getVal('proc_training')
return tomProfile
}
/**
* Check if a block is complete (all required questions answered)
*/
export function isBlockComplete(
answers: ScopeProfilingAnswer[],
blockId: ScopeQuestionBlockId
): boolean {
const block = SCOPE_QUESTION_BLOCKS.find((b) => b.id === blockId)
if (!block) return false
const requiredQuestions = block.questions.filter((q) => q.required)
const answeredQuestionIds = new Set(answers.map((a) => a.questionId))
return requiredQuestions.every((q) => answeredQuestionIds.has(q.id))
}
/**
* Get progress for a specific block (0-100)
*/
export function getBlockProgress(
answers: ScopeProfilingAnswer[],
blockId: ScopeQuestionBlockId
): number {
const block = SCOPE_QUESTION_BLOCKS.find((b) => b.id === blockId)
if (!block) return 0
const requiredQuestions = block.questions.filter((q) => q.required)
if (requiredQuestions.length === 0) return 100
const answeredQuestionIds = new Set(answers.map((a) => a.questionId))
const answeredCount = requiredQuestions.filter((q) =>
answeredQuestionIds.has(q.id)
).length
return Math.round((answeredCount / requiredQuestions.length) * 100)
}
/**
* Get total progress across all blocks (0-100)
*/
export function getTotalProgress(answers: ScopeProfilingAnswer[]): number {
let totalRequired = 0
let totalAnswered = 0
const answeredQuestionIds = new Set(answers.map((a) => a.questionId))
for (const block of SCOPE_QUESTION_BLOCKS) {
const requiredQuestions = block.questions.filter((q) => q.required)
totalRequired += requiredQuestions.length
totalAnswered += requiredQuestions.filter((q) =>
answeredQuestionIds.has(q.id)
).length
}
if (totalRequired === 0) return 100
return Math.round((totalAnswered / totalRequired) * 100)
}
/**
* Get answer value for a specific question
*/
export function getAnswerValue(
answers: ScopeProfilingAnswer[],
questionId: string
): unknown {
const answer = answers.find((a) => a.questionId === questionId)
return answer?.value
}
/**
* Get all questions as a flat array
*/
export function getAllQuestions(): ScopeProfilingQuestion[] {
return SCOPE_QUESTION_BLOCKS.flatMap((block) => block.questions)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,210 +0,0 @@
/**
* 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

@@ -1,224 +0,0 @@
/**
* 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

@@ -1,556 +0,0 @@
/**
* 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

@@ -1,268 +0,0 @@
/**
* 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

@@ -1,296 +0,0 @@
/**
* 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

@@ -1,85 +0,0 @@
/**
* 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

@@ -1,316 +0,0 @@
/**
* 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

@@ -1,548 +0,0 @@
/**
* 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

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

View File

@@ -1,224 +0,0 @@
import { ConstraintEnforcer } from '../constraint-enforcer'
import type { ScopeDecision } from '../../compliance-scope-types'
describe('ConstraintEnforcer', () => {
const enforcer = new ConstraintEnforcer()
// Helper: minimal valid ScopeDecision
function makeDecision(overrides: Partial<ScopeDecision> = {}): ScopeDecision {
return {
id: 'test-decision',
determinedLevel: 'L2',
scores: { risk_score: 50, complexity_score: 50, assurance_need: 50, composite_score: 50 },
triggeredHardTriggers: [],
requiredDocuments: [
{ documentType: 'vvt', label: 'VVT', required: true, depth: 'Standard', detailItems: [], estimatedEffort: '2h', triggeredBy: [] },
{ documentType: 'tom', label: 'TOM', required: true, depth: 'Standard', detailItems: [], estimatedEffort: '3h', triggeredBy: [] },
{ documentType: 'lf', label: 'LF', required: true, depth: 'Basis', detailItems: [], estimatedEffort: '1h', triggeredBy: [] },
],
riskFlags: [],
gaps: [],
nextActions: [],
reasoning: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
...overrides,
} as ScopeDecision
}
describe('check - no decision', () => {
it('should allow basic documents (vvt, tom, dsi) without decision', () => {
const result = enforcer.check('vvt', null)
expect(result.allowed).toBe(true)
expect(result.adjustments.length).toBeGreaterThan(0)
expect(result.checkedRules).toContain('RULE-NO-DECISION')
})
it('should allow tom without decision', () => {
const result = enforcer.check('tom', null)
expect(result.allowed).toBe(true)
})
it('should allow dsi without decision', () => {
const result = enforcer.check('dsi', null)
expect(result.allowed).toBe(true)
})
it('should block non-basic documents without decision', () => {
const result = enforcer.check('dsfa', null)
expect(result.allowed).toBe(false)
expect(result.violations.length).toBeGreaterThan(0)
})
it('should block av_vertrag without decision', () => {
const result = enforcer.check('av_vertrag', null)
expect(result.allowed).toBe(false)
})
})
describe('check - RULE-DOC-REQUIRED', () => {
it('should allow required documents', () => {
const decision = makeDecision()
const result = enforcer.check('vvt', decision)
expect(result.allowed).toBe(true)
})
it('should warn but allow optional documents', () => {
const decision = makeDecision({
requiredDocuments: [
{ documentType: 'vvt', label: 'VVT', required: true, depth: 'Standard', detailItems: [], estimatedEffort: '2h', triggeredBy: [] },
],
})
const result = enforcer.check('dsfa', decision)
expect(result.allowed).toBe(true) // Only warns, does not block
expect(result.adjustments.some(a => a.includes('nicht als Pflicht'))).toBe(true)
})
})
describe('check - RULE-DEPTH-MATCH', () => {
it('should block when requested depth exceeds determined level', () => {
const decision = makeDecision({ determinedLevel: 'L2' })
const result = enforcer.check('vvt', decision, 'L4')
expect(result.allowed).toBe(false)
expect(result.violations.some(v => v.includes('ueberschreitet'))).toBe(true)
})
it('should allow when requested depth matches level', () => {
const decision = makeDecision({ determinedLevel: 'L2' })
const result = enforcer.check('vvt', decision, 'L2')
expect(result.allowed).toBe(true)
})
it('should adjust when requested depth is below level', () => {
const decision = makeDecision({ determinedLevel: 'L3' })
const result = enforcer.check('vvt', decision, 'L1')
expect(result.allowed).toBe(true)
expect(result.adjustments.some(a => a.includes('angehoben'))).toBe(true)
})
it('should allow without requested depth level', () => {
const decision = makeDecision({ determinedLevel: 'L3' })
const result = enforcer.check('vvt', decision)
expect(result.allowed).toBe(true)
})
})
describe('check - RULE-DSFA-ENFORCEMENT', () => {
it('should note when DSFA is not required but requested', () => {
const decision = makeDecision({ determinedLevel: 'L2' })
const result = enforcer.check('dsfa', decision)
expect(result.allowed).toBe(true)
expect(result.adjustments.some(a => a.includes('nicht verpflichtend'))).toBe(true)
})
it('should allow DSFA when hard triggers require it', () => {
const decision = makeDecision({
determinedLevel: 'L3',
triggeredHardTriggers: [{
rule: {
id: 'HT-ART9',
label: 'Art. 9 Daten',
description: '',
conditionField: '',
conditionOperator: 'EQUALS' as const,
conditionValue: null,
minimumLevel: 'L3',
mandatoryDocuments: ['dsfa'],
dsfaRequired: true,
legalReference: 'Art. 35 DSGVO',
},
matchedValue: null,
explanation: 'Art. 9 Daten verarbeitet',
}],
})
const result = enforcer.check('dsfa', decision)
expect(result.allowed).toBe(true)
})
it('should warn about DSFA when drafting non-DSFA but DSFA is required', () => {
const decision = makeDecision({
determinedLevel: 'L3',
triggeredHardTriggers: [{
rule: {
id: 'HT-ART9',
label: 'Art. 9 Daten',
description: '',
conditionField: '',
conditionOperator: 'EQUALS' as const,
conditionValue: null,
minimumLevel: 'L3',
mandatoryDocuments: ['dsfa'],
dsfaRequired: true,
legalReference: 'Art. 35 DSGVO',
},
matchedValue: null,
explanation: '',
}],
requiredDocuments: [
{ documentType: 'dsfa', label: 'DSFA', required: true, depth: 'Vollstaendig', detailItems: [], estimatedEffort: '8h', triggeredBy: [] },
{ documentType: 'vvt', label: 'VVT', required: true, depth: 'Standard', detailItems: [], estimatedEffort: '2h', triggeredBy: [] },
],
})
const result = enforcer.check('vvt', decision)
expect(result.allowed).toBe(true)
expect(result.adjustments.some(a => a.includes('DSFA') && a.includes('verpflichtend'))).toBe(true)
})
})
describe('check - RULE-RISK-FLAGS', () => {
it('should note critical risk flags', () => {
const decision = makeDecision({
riskFlags: [
{ id: 'rf-1', severity: 'CRITICAL', title: 'Offene Art. 9 Verarbeitung', description: '', recommendation: 'DSFA durchfuehren' },
{ id: 'rf-2', severity: 'HIGH', title: 'Fehlende Verschluesselung', description: '', recommendation: 'TOM erstellen' },
{ id: 'rf-3', severity: 'LOW', title: 'Dokumentation unvollstaendig', description: '', recommendation: '' },
],
})
const result = enforcer.check('vvt', decision)
expect(result.allowed).toBe(true)
expect(result.adjustments.some(a => a.includes('2 kritische/hohe Risiko-Flags'))).toBe(true)
})
it('should not flag when no risk flags present', () => {
const decision = makeDecision({ riskFlags: [] })
const result = enforcer.check('vvt', decision)
expect(result.adjustments.every(a => !a.includes('Risiko-Flags'))).toBe(true)
})
})
describe('check - checkedRules tracking', () => {
it('should track all checked rules', () => {
const decision = makeDecision()
const result = enforcer.check('vvt', decision)
expect(result.checkedRules).toContain('RULE-DOC-REQUIRED')
expect(result.checkedRules).toContain('RULE-DEPTH-MATCH')
expect(result.checkedRules).toContain('RULE-DSFA-ENFORCEMENT')
expect(result.checkedRules).toContain('RULE-RISK-FLAGS')
expect(result.checkedRules).toContain('RULE-HARD-TRIGGER-CONSISTENCY')
})
})
describe('checkFromContext', () => {
it('should reconstruct decision from DraftContext and check', () => {
const context = {
decisions: {
level: 'L2' as const,
scores: { risk_score: 50, complexity_score: 50, assurance_need: 50, composite_score: 50 },
hardTriggers: [],
requiredDocuments: [
{ documentType: 'vvt' as const, depth: 'Standard', detailItems: [] },
],
},
companyProfile: { name: 'Test GmbH', industry: 'IT', employeeCount: 50, businessModel: 'SaaS', isPublicSector: false },
constraints: {
depthRequirements: { required: true, depth: 'Standard', detailItems: [], estimatedEffort: '2h' },
riskFlags: [],
boundaries: [],
},
}
const result = enforcer.checkFromContext('vvt', context)
expect(result.allowed).toBe(true)
expect(result.checkedRules.length).toBeGreaterThan(0)
})
})
})

View File

@@ -1,153 +0,0 @@
import { IntentClassifier } from '../intent-classifier'
describe('IntentClassifier', () => {
const classifier = new IntentClassifier()
describe('classify - Draft mode', () => {
it.each([
['Erstelle ein VVT fuer unseren Hauptprozess', 'draft'],
['Generiere eine TOM-Dokumentation', 'draft'],
['Schreibe eine Datenschutzerklaerung', 'draft'],
['Verfasse einen Entwurf fuer das Loeschkonzept', 'draft'],
['Create a DSFA document', 'draft'],
['Draft a privacy policy for us', 'draft'],
['Neues VVT anlegen', 'draft'],
])('"%s" should classify as %s', (input, expectedMode) => {
const result = classifier.classify(input)
expect(result.mode).toBe(expectedMode)
expect(result.confidence).toBeGreaterThan(0.7)
})
})
describe('classify - Validate mode', () => {
it.each([
['Pruefe die Konsistenz meiner Dokumente', 'validate'],
['Ist mein VVT korrekt?', 'validate'],
['Validiere die TOM gegen das VVT', 'validate'],
['Check die Vollstaendigkeit', 'validate'],
['Stimmt das mit der DSFA ueberein?', 'validate'],
['Cross-Check VVT und TOM', 'validate'],
])('"%s" should classify as %s', (input, expectedMode) => {
const result = classifier.classify(input)
expect(result.mode).toBe(expectedMode)
expect(result.confidence).toBeGreaterThan(0.7)
})
})
describe('classify - Ask mode', () => {
it.each([
['Was fehlt noch in meinem Profil?', 'ask'],
['Zeige mir die Luecken', 'ask'],
['Welche Dokumente fehlen noch?', 'ask'],
['Was ist der naechste Schritt?', 'ask'],
['Welche Informationen brauche ich noch?', 'ask'],
])('"%s" should classify as %s', (input, expectedMode) => {
const result = classifier.classify(input)
expect(result.mode).toBe(expectedMode)
expect(result.confidence).toBeGreaterThan(0.6)
})
})
describe('classify - Explain mode (fallback)', () => {
it.each([
['Was ist DSGVO?', 'explain'],
['Erklaere mir Art. 30', 'explain'],
['Hallo', 'explain'],
['Danke fuer die Hilfe', 'explain'],
])('"%s" should classify as %s (fallback)', (input, expectedMode) => {
const result = classifier.classify(input)
expect(result.mode).toBe(expectedMode)
})
})
describe('classify - confidence thresholds', () => {
it('should have high confidence for clear draft intents', () => {
const result = classifier.classify('Erstelle ein neues VVT')
expect(result.confidence).toBeGreaterThanOrEqual(0.85)
})
it('should have lower confidence for ambiguous inputs', () => {
const result = classifier.classify('Hallo')
expect(result.confidence).toBeLessThan(0.6)
})
it('should boost confidence with document type detection', () => {
const withDoc = classifier.classify('Erstelle VVT')
const withoutDoc = classifier.classify('Erstelle etwas')
expect(withDoc.confidence).toBeGreaterThanOrEqual(withoutDoc.confidence)
})
it('should boost confidence with multiple pattern matches', () => {
const single = classifier.classify('Erstelle Dokument')
const multi = classifier.classify('Erstelle und generiere ein neues Dokument')
expect(multi.confidence).toBeGreaterThanOrEqual(single.confidence)
})
})
describe('detectDocumentType', () => {
it.each([
['VVT erstellen', 'vvt'],
['Verarbeitungsverzeichnis', 'vvt'],
['Art. 30 Dokumentation', 'vvt'],
['TOM definieren', 'tom'],
['technisch organisatorische Massnahmen', 'tom'],
['Art. 32 Massnahmen', 'tom'],
['DSFA durchfuehren', 'dsfa'],
['Datenschutz-Folgenabschaetzung', 'dsfa'],
['Art. 35 Pruefung', 'dsfa'],
['DPIA erstellen', 'dsfa'],
['Datenschutzerklaerung', 'dsi'],
['Privacy Policy', 'dsi'],
['Art. 13 Information', 'dsi'],
['Loeschfristen definieren', 'lf'],
['Loeschkonzept erstellen', 'lf'],
['Retention Policy', 'lf'],
['Auftragsverarbeitung', 'av_vertrag'],
['AVV erstellen', 'av_vertrag'],
['Art. 28 Vertrag', 'av_vertrag'],
['Einwilligung einholen', 'einwilligung'],
['Consent Management', 'einwilligung'],
['Cookie Banner', 'einwilligung'],
])('"%s" should detect document type %s', (input, expectedType) => {
const result = classifier.detectDocumentType(input)
expect(result).toBe(expectedType)
})
it('should return undefined for unrecognized types', () => {
expect(classifier.detectDocumentType('Hallo Welt')).toBeUndefined()
expect(classifier.detectDocumentType('Was kostet das?')).toBeUndefined()
})
})
describe('classify - Umlaut handling', () => {
it('should handle German umlauts correctly', () => {
// With actual umlauts (ä, ö, ü)
const result1 = classifier.classify('Prüfe die Vollständigkeit')
expect(result1.mode).toBe('validate')
// With ae/oe/ue substitution
const result2 = classifier.classify('Pruefe die Vollstaendigkeit')
expect(result2.mode).toBe('validate')
})
it('should handle ß correctly', () => {
const result = classifier.classify('Schließe Lücken')
// Should still detect via normalized patterns
expect(result).toBeDefined()
})
})
describe('classify - combined mode + document type', () => {
it('should detect both mode and document type', () => {
const result = classifier.classify('Erstelle ein VVT fuer unsere Firma')
expect(result.mode).toBe('draft')
expect(result.detectedDocumentType).toBe('vvt')
})
it('should detect validate + document type', () => {
const result = classifier.classify('Pruefe mein TOM auf Konsistenz')
expect(result.mode).toBe('validate')
expect(result.detectedDocumentType).toBe('tom')
})
})
})

View File

@@ -1,311 +0,0 @@
import { StateProjector } from '../state-projector'
import type { SDKState } from '../../types'
describe('StateProjector', () => {
const projector = new StateProjector()
// Helper: minimal SDKState
function makeState(overrides: Partial<SDKState> = {}): SDKState {
return {
version: '1.0.0',
lastModified: new Date(),
tenantId: 'test',
userId: 'user1',
subscription: 'PROFESSIONAL',
customerType: null,
companyProfile: null,
complianceScope: null,
currentPhase: 1,
currentStep: 'company-profile',
completedSteps: [],
checkpoints: {},
importedDocuments: [],
gapAnalysis: null,
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: [],
preferences: {
language: 'de',
theme: 'light',
compactMode: false,
showHints: true,
autoSave: true,
autoValidate: true,
allowParallelWork: true,
},
...overrides,
} as SDKState
}
function makeDecisionState(level: string = 'L2'): SDKState {
return makeState({
companyProfile: {
companyName: 'Test GmbH',
industry: 'IT-Dienstleistung',
employeeCount: 50,
businessModel: 'SaaS',
isPublicSector: false,
} as any,
complianceScope: {
decision: {
id: 'dec-1',
determinedLevel: level,
scores: { risk_score: 60, complexity_score: 50, assurance_need: 55, composite_score: 55 },
triggeredHardTriggers: [],
requiredDocuments: [
{ documentType: 'vvt', label: 'VVT', required: true, depth: 'Standard', detailItems: ['Bezeichnung', 'Zweck'], estimatedEffort: '2h', triggeredBy: [] },
{ documentType: 'tom', label: 'TOM', required: true, depth: 'Standard', detailItems: ['Verschluesselung'], estimatedEffort: '3h', triggeredBy: [] },
{ documentType: 'lf', label: 'LF', required: true, depth: 'Basis', detailItems: [], estimatedEffort: '1h', triggeredBy: [] },
],
riskFlags: [
{ id: 'rf-1', severity: 'MEDIUM', title: 'Cloud-Nutzung', description: '', recommendation: 'AVV pruefen' },
],
gaps: [
{ id: 'gap-1', severity: 'high', title: 'TOM fehlt', description: 'Keine TOM definiert', relatedDocuments: ['tom'] },
],
nextActions: [],
reasoning: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
answers: [],
} as any,
vvt: [{ id: 'vvt-1', name: 'Kundenverwaltung' }] as any[],
toms: [],
retentionPolicies: [],
})
}
describe('projectForDraft', () => {
it('should return a DraftContext with correct structure', () => {
const state = makeDecisionState()
const result = projector.projectForDraft(state, 'vvt')
expect(result).toHaveProperty('decisions')
expect(result).toHaveProperty('companyProfile')
expect(result).toHaveProperty('constraints')
expect(result.decisions.level).toBe('L2')
})
it('should project company profile', () => {
const state = makeDecisionState()
const result = projector.projectForDraft(state, 'vvt')
expect(result.companyProfile.name).toBe('Test GmbH')
expect(result.companyProfile.industry).toBe('IT-Dienstleistung')
expect(result.companyProfile.employeeCount).toBe(50)
})
it('should provide defaults when no company profile', () => {
const state = makeState()
const result = projector.projectForDraft(state, 'vvt')
expect(result.companyProfile.name).toBe('Unbekannt')
expect(result.companyProfile.industry).toBe('Unbekannt')
expect(result.companyProfile.employeeCount).toBe(0)
})
it('should extract constraints and depth requirements', () => {
const state = makeDecisionState()
const result = projector.projectForDraft(state, 'vvt')
expect(result.constraints.depthRequirements).toBeDefined()
expect(result.constraints.boundaries.length).toBeGreaterThan(0)
})
it('should extract risk flags', () => {
const state = makeDecisionState()
const result = projector.projectForDraft(state, 'vvt')
expect(result.constraints.riskFlags.length).toBe(1)
expect(result.constraints.riskFlags[0].title).toBe('Cloud-Nutzung')
})
it('should include existing document data when available', () => {
const state = makeDecisionState()
const result = projector.projectForDraft(state, 'vvt')
expect(result.existingDocumentData).toBeDefined()
expect((result.existingDocumentData as any).totalCount).toBe(1)
})
it('should return undefined existingDocumentData when none exists', () => {
const state = makeDecisionState()
const result = projector.projectForDraft(state, 'tom')
expect(result.existingDocumentData).toBeUndefined()
})
it('should filter required documents', () => {
const state = makeDecisionState()
const result = projector.projectForDraft(state, 'vvt')
expect(result.decisions.requiredDocuments.length).toBe(3)
expect(result.decisions.requiredDocuments.every(d => d.documentType)).toBe(true)
})
it('should handle empty state gracefully', () => {
const state = makeState()
const result = projector.projectForDraft(state, 'vvt')
expect(result.decisions.level).toBe('L1')
expect(result.decisions.hardTriggers).toEqual([])
expect(result.decisions.requiredDocuments).toEqual([])
})
})
describe('projectForAsk', () => {
it('should return a GapContext with correct structure', () => {
const state = makeDecisionState()
const result = projector.projectForAsk(state)
expect(result).toHaveProperty('unansweredQuestions')
expect(result).toHaveProperty('gaps')
expect(result).toHaveProperty('missingDocuments')
})
it('should identify missing documents', () => {
const state = makeDecisionState()
// vvt exists, tom and lf are missing
const result = projector.projectForAsk(state)
expect(result.missingDocuments.some(d => d.documentType === 'tom')).toBe(true)
expect(result.missingDocuments.some(d => d.documentType === 'lf')).toBe(true)
})
it('should not list existing documents as missing', () => {
const state = makeDecisionState()
const result = projector.projectForAsk(state)
// vvt exists in state
expect(result.missingDocuments.some(d => d.documentType === 'vvt')).toBe(false)
})
it('should include gaps from scope decision', () => {
const state = makeDecisionState()
const result = projector.projectForAsk(state)
expect(result.gaps.length).toBe(1)
expect(result.gaps[0].title).toBe('TOM fehlt')
})
it('should handle empty state', () => {
const state = makeState()
const result = projector.projectForAsk(state)
expect(result.gaps).toEqual([])
expect(result.missingDocuments).toEqual([])
})
})
describe('projectForValidate', () => {
it('should return a ValidationContext with correct structure', () => {
const state = makeDecisionState()
const result = projector.projectForValidate(state, ['vvt', 'tom', 'lf'])
expect(result).toHaveProperty('documents')
expect(result).toHaveProperty('crossReferences')
expect(result).toHaveProperty('scopeLevel')
expect(result).toHaveProperty('depthRequirements')
})
it('should include all requested document types', () => {
const state = makeDecisionState()
const result = projector.projectForValidate(state, ['vvt', 'tom'])
expect(result.documents.length).toBe(2)
expect(result.documents.map(d => d.type)).toContain('vvt')
expect(result.documents.map(d => d.type)).toContain('tom')
})
it('should include cross-references', () => {
const state = makeDecisionState()
const result = projector.projectForValidate(state, ['vvt', 'tom', 'lf'])
expect(result.crossReferences).toHaveProperty('vvtCategories')
expect(result.crossReferences).toHaveProperty('tomControls')
expect(result.crossReferences).toHaveProperty('retentionCategories')
expect(result.crossReferences.vvtCategories.length).toBe(1)
expect(result.crossReferences.vvtCategories[0]).toBe('Kundenverwaltung')
})
it('should include scope level', () => {
const state = makeDecisionState('L3')
const result = projector.projectForValidate(state, ['vvt'])
expect(result.scopeLevel).toBe('L3')
})
it('should include depth requirements per document type', () => {
const state = makeDecisionState()
const result = projector.projectForValidate(state, ['vvt', 'tom'])
expect(result.depthRequirements).toHaveProperty('vvt')
expect(result.depthRequirements).toHaveProperty('tom')
})
it('should summarize documents', () => {
const state = makeDecisionState()
const result = projector.projectForValidate(state, ['vvt', 'tom'])
expect(result.documents[0].contentSummary).toContain('1')
expect(result.documents[1].contentSummary).toContain('Keine TOM')
})
it('should handle empty state', () => {
const state = makeState()
const result = projector.projectForValidate(state, ['vvt', 'tom', 'lf'])
expect(result.scopeLevel).toBe('L1')
expect(result.crossReferences.vvtCategories).toEqual([])
expect(result.crossReferences.tomControls).toEqual([])
})
})
describe('token budget estimation', () => {
it('projectForDraft should produce compact output', () => {
const state = makeDecisionState()
const result = projector.projectForDraft(state, 'vvt')
const json = JSON.stringify(result)
// Rough token estimation: ~4 chars per token
const estimatedTokens = json.length / 4
expect(estimatedTokens).toBeLessThan(2000) // Budget is ~1500
})
it('projectForAsk should produce very compact output', () => {
const state = makeDecisionState()
const result = projector.projectForAsk(state)
const json = JSON.stringify(result)
const estimatedTokens = json.length / 4
expect(estimatedTokens).toBeLessThan(1000) // Budget is ~600
})
it('projectForValidate should stay within budget', () => {
const state = makeDecisionState()
const result = projector.projectForValidate(state, ['vvt', 'tom', 'lf'])
const json = JSON.stringify(result)
const estimatedTokens = json.length / 4
expect(estimatedTokens).toBeLessThan(3000) // Budget is ~2000
})
})
})

View File

@@ -1,221 +0,0 @@
/**
* Constraint Enforcer - Hard Gate vor jedem Draft
*
* Stellt sicher, dass die Drafting Engine NIEMALS die deterministische
* Scope-Engine ueberschreibt. Prueft vor jedem Draft-Vorgang:
*
* 1. Ist der Dokumenttyp in requiredDocuments?
* 2. Passt die Draft-Tiefe zum Level?
* 3. Ist eine DSFA erforderlich (Hard Trigger)?
* 4. Werden Risiko-Flags beruecksichtigt?
*/
import type { ScopeDecision, ScopeDocumentType, ComplianceDepthLevel } from '../compliance-scope-types'
import { DOCUMENT_SCOPE_MATRIX, getDepthLevelNumeric } from '../compliance-scope-types'
import type { ConstraintCheckResult, DraftContext } from './types'
export class ConstraintEnforcer {
/**
* Prueft ob ein Draft fuer den gegebenen Dokumenttyp erlaubt ist.
* Dies ist ein HARD GATE - bei Violation wird der Draft blockiert.
*/
check(
documentType: ScopeDocumentType,
decision: ScopeDecision | null,
requestedDepthLevel?: ComplianceDepthLevel
): ConstraintCheckResult {
const violations: string[] = []
const adjustments: string[] = []
const checkedRules: string[] = []
// Wenn keine Decision vorhanden: Nur Basis-Drafts erlauben
if (!decision) {
checkedRules.push('RULE-NO-DECISION')
if (documentType !== 'vvt' && documentType !== 'tom' && documentType !== 'dsi') {
violations.push(
'Scope-Evaluierung fehlt. Bitte zuerst das Compliance-Profiling durchfuehren.'
)
} else {
adjustments.push(
'Ohne Scope-Evaluierung wird Level L1 (Basis) angenommen.'
)
}
return {
allowed: violations.length === 0,
violations,
adjustments,
checkedRules,
}
}
const level = decision.determinedLevel
const levelNumeric = getDepthLevelNumeric(level)
// -----------------------------------------------------------------------
// Rule 1: Dokumenttyp in requiredDocuments?
// -----------------------------------------------------------------------
checkedRules.push('RULE-DOC-REQUIRED')
const isRequired = decision.requiredDocuments.some(
d => d.documentType === documentType && d.required
)
const scopeReq = DOCUMENT_SCOPE_MATRIX[documentType]?.[level]
if (!isRequired && scopeReq && !scopeReq.required) {
// Nicht blockieren, aber warnen
adjustments.push(
`Dokument "${documentType}" ist auf Level ${level} nicht als Pflicht eingestuft. ` +
`Entwurf ist moeglich, aber optional.`
)
}
// -----------------------------------------------------------------------
// Rule 2: Draft-Tiefe passt zum Level?
// -----------------------------------------------------------------------
checkedRules.push('RULE-DEPTH-MATCH')
if (requestedDepthLevel) {
const requestedNumeric = getDepthLevelNumeric(requestedDepthLevel)
if (requestedNumeric > levelNumeric) {
violations.push(
`Angefragte Tiefe ${requestedDepthLevel} ueberschreitet das bestimmte Level ${level}. ` +
`Die Scope-Engine hat Level ${level} festgelegt. ` +
`Ein Draft mit Tiefe ${requestedDepthLevel} ist nicht erlaubt.`
)
} else if (requestedNumeric < levelNumeric) {
adjustments.push(
`Angefragte Tiefe ${requestedDepthLevel} liegt unter dem bestimmten Level ${level}. ` +
`Draft wird auf Level ${level} angehoben.`
)
}
}
// -----------------------------------------------------------------------
// Rule 3: DSFA-Enforcement
// -----------------------------------------------------------------------
checkedRules.push('RULE-DSFA-ENFORCEMENT')
if (documentType === 'dsfa') {
const dsfaRequired = decision.triggeredHardTriggers.some(
t => t.rule.dsfaRequired
)
if (!dsfaRequired && level !== 'L4') {
adjustments.push(
'DSFA ist laut Scope-Engine nicht verpflichtend. ' +
'Entwurf wird als freiwillige Massnahme gekennzeichnet.'
)
}
}
// Umgekehrt: Wenn DSFA verpflichtend und Typ != dsfa, ggf. hinweisen
if (documentType !== 'dsfa') {
const dsfaRequired = decision.triggeredHardTriggers.some(
t => t.rule.dsfaRequired
)
const dsfaInRequired = decision.requiredDocuments.some(
d => d.documentType === 'dsfa' && d.required
)
if (dsfaRequired && dsfaInRequired) {
// Nur ein Hinweis, kein Block
adjustments.push(
'Hinweis: Eine DSFA ist laut Scope-Engine verpflichtend. ' +
'Bitte sicherstellen, dass auch eine DSFA erstellt wird.'
)
}
}
// -----------------------------------------------------------------------
// Rule 4: Risiko-Flags beruecksichtigt?
// -----------------------------------------------------------------------
checkedRules.push('RULE-RISK-FLAGS')
const criticalRisks = decision.riskFlags.filter(
f => f.severity === 'CRITICAL' || f.severity === 'HIGH'
)
if (criticalRisks.length > 0) {
adjustments.push(
`${criticalRisks.length} kritische/hohe Risiko-Flags erkannt. ` +
`Draft muss diese adressieren: ${criticalRisks.map(r => r.title).join(', ')}`
)
}
// -----------------------------------------------------------------------
// Rule 5: Hard-Trigger Consistency
// -----------------------------------------------------------------------
checkedRules.push('RULE-HARD-TRIGGER-CONSISTENCY')
for (const trigger of decision.triggeredHardTriggers) {
const mandatoryDocs = trigger.rule.mandatoryDocuments
if (mandatoryDocs.includes(documentType)) {
// Gut - wir erstellen ein mandatory document
} else {
// Pruefen ob die mandatory documents des Triggers vorhanden sind
// (nur Hinweis, kein Block)
}
}
return {
allowed: violations.length === 0,
violations,
adjustments,
checkedRules,
}
}
/**
* Convenience: Prueft aus einem DraftContext heraus.
*/
checkFromContext(
documentType: ScopeDocumentType,
context: DraftContext
): ConstraintCheckResult {
// Reconstruct a minimal ScopeDecision from context
const pseudoDecision: ScopeDecision = {
id: 'projected',
determinedLevel: context.decisions.level,
scores: context.decisions.scores,
triggeredHardTriggers: context.decisions.hardTriggers.map(t => ({
rule: {
id: t.id,
label: t.label,
description: '',
conditionField: '',
conditionOperator: 'EQUALS' as const,
conditionValue: null,
minimumLevel: context.decisions.level,
mandatoryDocuments: [],
dsfaRequired: false,
legalReference: t.legalReference,
},
matchedValue: null,
explanation: '',
})),
requiredDocuments: context.decisions.requiredDocuments.map(d => ({
documentType: d.documentType,
label: d.documentType,
required: true,
depth: d.depth,
detailItems: d.detailItems,
estimatedEffort: '',
triggeredBy: [],
})),
riskFlags: context.constraints.riskFlags.map(f => ({
id: `rf-${f.title}`,
severity: f.severity as 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL',
title: f.title,
description: '',
recommendation: f.recommendation,
})),
gaps: [],
nextActions: [],
reasoning: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
return this.check(documentType, pseudoDecision)
}
}
/** Singleton-Instanz */
export const constraintEnforcer = new ConstraintEnforcer()

View File

@@ -1,241 +0,0 @@
/**
* Intent Classifier - Leichtgewichtiger Pattern-Matcher
*
* Erkennt den Agent-Modus anhand des Nutzer-Inputs ohne LLM-Call.
* Deutsche und englische Muster werden unterstuetzt.
*
* Confidence-Schwellen:
* - >0.8: Hohe Sicherheit, automatisch anwenden
* - 0.6-0.8: Mittel, Nutzer kann bestaetigen
* - <0.6: Fallback zu 'explain'
*/
import type { AgentMode, IntentClassification } from './types'
import type { ScopeDocumentType } from '../compliance-scope-types'
// ============================================================================
// Pattern Definitions
// ============================================================================
interface ModePattern {
mode: AgentMode
patterns: RegExp[]
/** Base-Confidence wenn ein Pattern matched */
baseConfidence: number
}
const MODE_PATTERNS: ModePattern[] = [
{
mode: 'draft',
baseConfidence: 0.85,
patterns: [
/\b(erstell|generier|entw[iu]rf|entwer[ft]|schreib|verfass|formulier|anlege)/i,
/\b(draft|create|generate|write|compose)\b/i,
/\b(neues?\s+(?:vvt|tom|dsfa|dokument|loeschkonzept|datenschutzerklaerung))\b/i,
/\b(vorlage|template)\s+(erstell|generier)/i,
/\bfuer\s+(?:uns|mich|unser)\b.*\b(erstell|schreib)/i,
],
},
{
mode: 'validate',
baseConfidence: 0.80,
patterns: [
/\b(pruef|validier|check|kontrollier|ueberpruef)\b/i,
/\b(korrekt|richtig|vollstaendig|konsistent|komplett)\b.*\?/i,
/\b(stimmt|passt)\b.*\b(das|mein|unser)\b/i,
/\b(validate|verify|check|review)\b/i,
/\b(fehler|luecken?|maengel)\b.*\b(find|such|zeig)\b/i,
/\bcross[\s-]?check\b/i,
/\b(vvt|tom|dsfa)\b.*\b(konsisten[tz]|widerspruch|uebereinstimm)/i,
],
},
{
mode: 'ask',
baseConfidence: 0.75,
patterns: [
/\bwas\s+fehlt\b/i,
/\b(luecken?|gaps?)\b.*\b(zeig|find|identifizier|analysier)/i,
/\b(unvollstaendig|unfertig|offen)\b/i,
/\bwelche\s+(dokumente?|informationen?|daten)\b.*\b(fehlen?|brauch|benoetig)/i,
/\b(naechste[rn]?\s+schritt|next\s+step|todo)\b/i,
/\bworan\s+(muss|soll)\b/i,
],
},
]
/** Dokumenttyp-Erkennung */
const DOCUMENT_TYPE_PATTERNS: Array<{
type: ScopeDocumentType
patterns: RegExp[]
}> = [
{
type: 'vvt',
patterns: [
/\bv{1,2}t\b/i,
/\bverarbeitungsverzeichnis\b/i,
/\bverarbeitungstaetigkeit/i,
/\bprocessing\s+activit/i,
/\bart\.?\s*30\b/i,
],
},
{
type: 'tom',
patterns: [
/\btom\b/i,
/\btechnisch.*organisatorisch.*massnahm/i,
/\bart\.?\s*32\b/i,
/\bsicherheitsmassnahm/i,
],
},
{
type: 'dsfa',
patterns: [
/\bdsfa\b/i,
/\bdatenschutz[\s-]?folgenabschaetzung\b/i,
/\bdpia\b/i,
/\bart\.?\s*35\b/i,
/\bimpact\s+assessment\b/i,
],
},
{
type: 'dsi',
patterns: [
/\bdatenschutzerklaerung\b/i,
/\bprivacy\s+policy\b/i,
/\bdsi\b/i,
/\bart\.?\s*13\b/i,
/\bart\.?\s*14\b/i,
],
},
{
type: 'lf',
patterns: [
/\bloeschfrist/i,
/\bloeschkonzept/i,
/\bretention/i,
/\baufbewahr/i,
],
},
{
type: 'av_vertrag',
patterns: [
/\bavv?\b/i,
/\bauftragsverarbeit/i,
/\bdata\s+processing\s+agreement/i,
/\bart\.?\s*28\b/i,
],
},
{
type: 'betroffenenrechte',
patterns: [
/\bbetroffenenrecht/i,
/\bdata\s+subject\s+right/i,
/\bart\.?\s*15\b/i,
/\bauskunft/i,
],
},
{
type: 'einwilligung',
patterns: [
/\beinwillig/i,
/\bconsent/i,
/\bcookie/i,
],
},
]
// ============================================================================
// Classifier
// ============================================================================
export class IntentClassifier {
/**
* Klassifiziert die Nutzerabsicht anhand des Inputs.
*
* @param input - Die Nutzer-Nachricht
* @returns IntentClassification mit Mode, Confidence, Patterns
*/
classify(input: string): IntentClassification {
const normalized = this.normalize(input)
let bestMatch: IntentClassification = {
mode: 'explain',
confidence: 0.3,
matchedPatterns: [],
}
for (const modePattern of MODE_PATTERNS) {
const matched: string[] = []
for (const pattern of modePattern.patterns) {
if (pattern.test(normalized)) {
matched.push(pattern.source)
}
}
if (matched.length > 0) {
// Mehr Matches = hoehere Confidence (bis zum Maximum)
const matchBonus = Math.min(matched.length - 1, 2) * 0.05
const confidence = Math.min(modePattern.baseConfidence + matchBonus, 0.99)
if (confidence > bestMatch.confidence) {
bestMatch = {
mode: modePattern.mode,
confidence,
matchedPatterns: matched,
}
}
}
}
// Dokumenttyp erkennen
const detectedDocType = this.detectDocumentType(normalized)
if (detectedDocType) {
bestMatch.detectedDocumentType = detectedDocType
// Dokumenttyp-Erkennung erhoeht Confidence leicht
bestMatch.confidence = Math.min(bestMatch.confidence + 0.05, 0.99)
}
// Fallback: Bei Confidence <0.6 immer 'explain'
if (bestMatch.confidence < 0.6) {
bestMatch.mode = 'explain'
}
return bestMatch
}
/**
* Erkennt den Dokumenttyp aus dem Input.
*/
detectDocumentType(input: string): ScopeDocumentType | undefined {
const normalized = this.normalize(input)
for (const docPattern of DOCUMENT_TYPE_PATTERNS) {
for (const pattern of docPattern.patterns) {
if (pattern.test(normalized)) {
return docPattern.type
}
}
}
return undefined
}
/**
* Normalisiert den Input fuer Pattern-Matching.
* Ersetzt Umlaute, entfernt Sonderzeichen.
*/
private normalize(input: string): string {
return input
.replace(/ä/g, 'ae')
.replace(/ö/g, 'oe')
.replace(/ü/g, 'ue')
.replace(/ß/g, 'ss')
.replace(/Ä/g, 'Ae')
.replace(/Ö/g, 'Oe')
.replace(/Ü/g, 'Ue')
}
}
/** Singleton-Instanz */
export const intentClassifier = new IntentClassifier()

View File

@@ -1,49 +0,0 @@
/**
* Gap Analysis Prompt - Lueckenanalyse und gezielte Fragen
*/
import type { GapContext } from '../types'
export interface GapAnalysisInput {
context: GapContext
instructions?: string
}
export function buildGapAnalysisPrompt(input: GapAnalysisInput): string {
const { context, instructions } = input
return `## Aufgabe: Compliance-Lueckenanalyse
### Identifizierte Luecken:
${context.gaps.length > 0
? context.gaps.map(g => `- [${g.severity}] ${g.title}: ${g.description}`).join('\n')
: '- Keine Luecken identifiziert'}
### Fehlende Pflichtdokumente:
${context.missingDocuments.length > 0
? context.missingDocuments.map(d => `- ${d.label} (Tiefe: ${d.depth}, Aufwand: ${d.estimatedEffort})`).join('\n')
: '- Alle Pflichtdokumente vorhanden'}
### Unbeantwortete Fragen:
${context.unansweredQuestions.length > 0
? context.unansweredQuestions.map(q => `- [${q.blockId}] ${q.question}`).join('\n')
: '- Alle Fragen beantwortet'}
${instructions ? `### Zusaetzliche Anweisungen: ${instructions}` : ''}
### Aufgabe:
Analysiere den Stand und stelle EINE gezielte Frage, die die wichtigste Luecke adressiert.
Priorisiere nach:
1. Fehlende Pflichtdokumente
2. Kritische Luecken (HIGH/CRITICAL severity)
3. Unbeantwortete Pflichtfragen
4. Mittlere Luecken
### Antwort-Format:
Antworte in dieser Struktur:
1. **Statusuebersicht**: Kurze Zusammenfassung des Compliance-Stands (2-3 Saetze)
2. **Wichtigste Luecke**: Was fehlt am dringendsten?
3. **Gezielte Frage**: Eine konkrete Frage an den Nutzer
4. **Warum wichtig**: Warum muss diese Luecke geschlossen werden?
5. **Empfohlener naechster Schritt**: Link/Verweis zum SDK-Modul`
}

View File

@@ -1,91 +0,0 @@
/**
* DSFA Draft Prompt - Datenschutz-Folgenabschaetzung (Art. 35 DSGVO)
*/
import type { DraftContext } from '../types'
export interface DSFADraftInput {
context: DraftContext
processingDescription?: string
instructions?: string
}
export function buildDSFADraftPrompt(input: DSFADraftInput): string {
const { context, processingDescription, instructions } = input
const level = context.decisions.level
const depthItems = context.constraints.depthRequirements.detailItems
const hardTriggers = context.decisions.hardTriggers
return `## Aufgabe: DSFA entwerfen (Art. 35 DSGVO)
### Unternehmensprofil
- Name: ${context.companyProfile.name}
- Branche: ${context.companyProfile.industry}
- Mitarbeiter: ${context.companyProfile.employeeCount}
### Compliance-Level: ${level}
Tiefe: ${context.constraints.depthRequirements.depth}
### Hard Triggers (Gruende fuer DSFA-Pflicht):
${hardTriggers.length > 0
? hardTriggers.map(t => `- ${t.id}: ${t.label} (${t.legalReference})`).join('\n')
: '- Keine Hard Triggers (DSFA auf Wunsch)'}
### Erforderliche Inhalte:
${depthItems.map((item, i) => `${i + 1}. ${item}`).join('\n')}
${processingDescription ? `### Beschreibung der Verarbeitung: ${processingDescription}` : ''}
${instructions ? `### Zusaetzliche Anweisungen: ${instructions}` : ''}
### Antwort-Format
Antworte als JSON:
{
"sections": [
{
"id": "beschreibung",
"title": "Systematische Beschreibung der Verarbeitung",
"content": "...",
"schemaField": "processingDescription"
},
{
"id": "notwendigkeit",
"title": "Notwendigkeit und Verhaeltnismaessigkeit",
"content": "...",
"schemaField": "necessityAssessment"
},
{
"id": "risikobewertung",
"title": "Bewertung der Risiken fuer die Rechte und Freiheiten",
"content": "...",
"schemaField": "riskAssessment"
},
{
"id": "massnahmen",
"title": "Massnahmen zur Eindaemmung der Risiken",
"content": "...",
"schemaField": "mitigationMeasures"
},
{
"id": "stellungnahme_dsb",
"title": "Stellungnahme des Datenschutzbeauftragten",
"content": "...",
"schemaField": "dpoOpinion"
},
{
"id": "standpunkt_betroffene",
"title": "Standpunkt der betroffenen Personen",
"content": "...",
"schemaField": "dataSubjectView"
},
{
"id": "ergebnis",
"title": "Ergebnis und Empfehlung",
"content": "...",
"schemaField": "conclusion"
}
]
}
Halte die Tiefe exakt auf Level ${level}.
Nutze WP248-Kriterien als Leitfaden fuer die Risikobewertung.`
}

View File

@@ -1,78 +0,0 @@
/**
* Loeschfristen Draft Prompt - Loeschkonzept
*/
import type { DraftContext } from '../types'
export interface LoeschfristenDraftInput {
context: DraftContext
instructions?: string
}
export function buildLoeschfristenDraftPrompt(input: LoeschfristenDraftInput): string {
const { context, instructions } = input
const level = context.decisions.level
const depthItems = context.constraints.depthRequirements.detailItems
return `## Aufgabe: Loeschkonzept / Loeschfristen entwerfen
### Unternehmensprofil
- Name: ${context.companyProfile.name}
- Branche: ${context.companyProfile.industry}
- Mitarbeiter: ${context.companyProfile.employeeCount}
### Compliance-Level: ${level}
Tiefe: ${context.constraints.depthRequirements.depth}
### Erforderliche Inhalte:
${depthItems.map((item, i) => `${i + 1}. ${item}`).join('\n')}
${context.existingDocumentData ? `### Bestehende Loeschfristen: ${JSON.stringify(context.existingDocumentData).slice(0, 500)}` : ''}
${instructions ? `### Zusaetzliche Anweisungen: ${instructions}` : ''}
### Antwort-Format
Antworte als JSON:
{
"sections": [
{
"id": "grundsaetze",
"title": "Grundsaetze der Datenlöschung",
"content": "...",
"schemaField": "principles"
},
{
"id": "kategorien",
"title": "Datenkategorien und Loeschfristen",
"content": "Tabellarische Uebersicht...",
"schemaField": "retentionSchedule"
},
{
"id": "gesetzliche_fristen",
"title": "Gesetzliche Aufbewahrungsfristen",
"content": "HGB, AO, weitere...",
"schemaField": "legalRetention"
},
{
"id": "loeschprozess",
"title": "Technischer Loeschprozess",
"content": "...",
"schemaField": "deletionProcess"
},
{
"id": "verantwortlichkeiten",
"title": "Verantwortlichkeiten",
"content": "...",
"schemaField": "responsibilities"
},
{
"id": "ausnahmen",
"title": "Ausnahmen und Sonderfaelle",
"content": "...",
"schemaField": "exceptions"
}
]
}
Halte die Tiefe exakt auf Level ${level}.
Beruecksichtige branchenspezifische Aufbewahrungsfristen fuer ${context.companyProfile.industry}.`
}

View File

@@ -1,102 +0,0 @@
/**
* Privacy Policy Draft Prompt - Datenschutzerklaerung (Art. 13/14 DSGVO)
*/
import type { DraftContext } from '../types'
export interface PrivacyPolicyDraftInput {
context: DraftContext
websiteUrl?: string
instructions?: string
}
export function buildPrivacyPolicyDraftPrompt(input: PrivacyPolicyDraftInput): string {
const { context, websiteUrl, instructions } = input
const level = context.decisions.level
const depthItems = context.constraints.depthRequirements.detailItems
return `## Aufgabe: Datenschutzerklaerung entwerfen (Art. 13/14 DSGVO)
### Unternehmensprofil
- Name: ${context.companyProfile.name}
- Branche: ${context.companyProfile.industry}
${context.companyProfile.dataProtectionOfficer ? `- DSB: ${context.companyProfile.dataProtectionOfficer.name} (${context.companyProfile.dataProtectionOfficer.email})` : ''}
${websiteUrl ? `- Website: ${websiteUrl}` : ''}
### Compliance-Level: ${level}
Tiefe: ${context.constraints.depthRequirements.depth}
### Erforderliche Inhalte:
${depthItems.map((item, i) => `${i + 1}. ${item}`).join('\n')}
${instructions ? `### Zusaetzliche Anweisungen: ${instructions}` : ''}
### Antwort-Format
Antworte als JSON:
{
"sections": [
{
"id": "verantwortlicher",
"title": "Verantwortlicher",
"content": "...",
"schemaField": "controller"
},
{
"id": "dsb",
"title": "Datenschutzbeauftragter",
"content": "...",
"schemaField": "dpo"
},
{
"id": "verarbeitungen",
"title": "Verarbeitungstaetigkeiten und Zwecke",
"content": "...",
"schemaField": "processingPurposes"
},
{
"id": "rechtsgrundlagen",
"title": "Rechtsgrundlagen der Verarbeitung",
"content": "...",
"schemaField": "legalBases"
},
{
"id": "empfaenger",
"title": "Empfaenger und Datenweitergabe",
"content": "...",
"schemaField": "recipients"
},
{
"id": "drittland",
"title": "Uebermittlung in Drittlaender",
"content": "...",
"schemaField": "thirdCountryTransfers"
},
{
"id": "speicherdauer",
"title": "Speicherdauer",
"content": "...",
"schemaField": "retentionPeriods"
},
{
"id": "betroffenenrechte",
"title": "Ihre Rechte als betroffene Person",
"content": "...",
"schemaField": "dataSubjectRights"
},
{
"id": "cookies",
"title": "Cookies und Tracking",
"content": "...",
"schemaField": "cookies"
},
{
"id": "aenderungen",
"title": "Aenderungen dieser Datenschutzerklaerung",
"content": "...",
"schemaField": "changes"
}
]
}
Halte die Tiefe exakt auf Level ${level}.`
}

View File

@@ -1,99 +0,0 @@
/**
* TOM Draft Prompt - Technische und Organisatorische Massnahmen (Art. 32 DSGVO)
*/
import type { DraftContext } from '../types'
export interface TOMDraftInput {
context: DraftContext
focusArea?: string
instructions?: string
}
export function buildTOMDraftPrompt(input: TOMDraftInput): string {
const { context, focusArea, instructions } = input
const level = context.decisions.level
const depthItems = context.constraints.depthRequirements.detailItems
return `## Aufgabe: TOM-Dokument entwerfen (Art. 32 DSGVO)
### Unternehmensprofil
- Name: ${context.companyProfile.name}
- Branche: ${context.companyProfile.industry}
- Mitarbeiter: ${context.companyProfile.employeeCount}
### Compliance-Level: ${level}
Tiefe: ${context.constraints.depthRequirements.depth}
### Erforderliche Inhalte fuer Level ${level}:
${depthItems.map((item, i) => `${i + 1}. ${item}`).join('\n')}
### Constraints
${context.constraints.boundaries.map(b => `- ${b}`).join('\n')}
${context.constraints.riskFlags.length > 0 ? `### Risiko-Flags
${context.constraints.riskFlags.map(f => `- [${f.severity}] ${f.title}`).join('\n')}` : ''}
${focusArea ? `### Fokusbereich: ${focusArea}` : ''}
${instructions ? `### Zusaetzliche Anweisungen: ${instructions}` : ''}
${context.existingDocumentData ? `### Bestehende TOM: ${JSON.stringify(context.existingDocumentData).slice(0, 500)}` : ''}
### Antwort-Format
Antworte als JSON:
{
"sections": [
{
"id": "zutrittskontrolle",
"title": "Zutrittskontrolle",
"content": "Massnahmen die unbefugten Zutritt zu Datenverarbeitungsanlagen verhindern...",
"schemaField": "accessControl"
},
{
"id": "zugangskontrolle",
"title": "Zugangskontrolle",
"content": "Massnahmen gegen unbefugte Systemnutzung...",
"schemaField": "systemAccessControl"
},
{
"id": "zugriffskontrolle",
"title": "Zugriffskontrolle",
"content": "Massnahmen zur Sicherstellung berechtigter Datenzugriffe...",
"schemaField": "dataAccessControl"
},
{
"id": "weitergabekontrolle",
"title": "Weitergabekontrolle / Uebertragungssicherheit",
"content": "Massnahmen bei Datenuebertragung und -transport...",
"schemaField": "transferControl"
},
{
"id": "eingabekontrolle",
"title": "Eingabekontrolle",
"content": "Nachvollziehbarkeit von Dateneingaben...",
"schemaField": "inputControl"
},
{
"id": "auftragskontrolle",
"title": "Auftragskontrolle",
"content": "Massnahmen zur weisungsgemaessen Auftragsverarbeitung...",
"schemaField": "orderControl"
},
{
"id": "verfuegbarkeitskontrolle",
"title": "Verfuegbarkeitskontrolle",
"content": "Schutz gegen Datenverlust...",
"schemaField": "availabilityControl"
},
{
"id": "trennungsgebot",
"title": "Trennungsgebot",
"content": "Getrennte Verarbeitung fuer verschiedene Zwecke...",
"schemaField": "separationControl"
}
]
}
Fuelle fehlende Informationen mit [PLATZHALTER: ...].
Halte die Tiefe exakt auf Level ${level}.`
}

View File

@@ -1,109 +0,0 @@
/**
* VVT Draft Prompt - Verarbeitungsverzeichnis (Art. 30 DSGVO)
*/
import type { DraftContext } from '../types'
export interface VVTDraftInput {
context: DraftContext
activityName?: string
activityPurpose?: string
instructions?: string
}
export function buildVVTDraftPrompt(input: VVTDraftInput): string {
const { context, activityName, activityPurpose, instructions } = input
const level = context.decisions.level
const depthItems = context.constraints.depthRequirements.detailItems
return `## Aufgabe: VVT-Eintrag entwerfen (Art. 30 DSGVO)
### Unternehmensprofil
- Name: ${context.companyProfile.name}
- Branche: ${context.companyProfile.industry}
- Mitarbeiter: ${context.companyProfile.employeeCount}
- Geschaeftsmodell: ${context.companyProfile.businessModel}
${context.companyProfile.dataProtectionOfficer ? `- DSB: ${context.companyProfile.dataProtectionOfficer.name} (${context.companyProfile.dataProtectionOfficer.email})` : '- DSB: Nicht benannt'}
### Compliance-Level: ${level}
Tiefe: ${context.constraints.depthRequirements.depth}
### Erforderliche Inhalte fuer Level ${level}:
${depthItems.map((item, i) => `${i + 1}. ${item}`).join('\n')}
### Constraints
${context.constraints.boundaries.map(b => `- ${b}`).join('\n')}
${context.constraints.riskFlags.length > 0 ? `### Risiko-Flags
${context.constraints.riskFlags.map(f => `- [${f.severity}] ${f.title}: ${f.recommendation}`).join('\n')}` : ''}
${activityName ? `### Gewuenschte Verarbeitungstaetigkeit: ${activityName}` : ''}
${activityPurpose ? `### Zweck: ${activityPurpose}` : ''}
${instructions ? `### Zusaetzliche Anweisungen: ${instructions}` : ''}
${context.existingDocumentData ? `### Bestehende VVT-Eintraege: ${JSON.stringify(context.existingDocumentData).slice(0, 500)}` : ''}
### Antwort-Format
Antworte als JSON:
{
"sections": [
{
"id": "bezeichnung",
"title": "Bezeichnung der Verarbeitungstaetigkeit",
"content": "...",
"schemaField": "name"
},
{
"id": "verantwortlicher",
"title": "Verantwortlicher",
"content": "...",
"schemaField": "controller"
},
{
"id": "zweck",
"title": "Zweck der Verarbeitung",
"content": "...",
"schemaField": "purpose"
},
{
"id": "rechtsgrundlage",
"title": "Rechtsgrundlage",
"content": "...",
"schemaField": "legalBasis"
},
{
"id": "betroffene",
"title": "Kategorien betroffener Personen",
"content": "...",
"schemaField": "dataSubjects"
},
{
"id": "datenkategorien",
"title": "Kategorien personenbezogener Daten",
"content": "...",
"schemaField": "dataCategories"
},
{
"id": "empfaenger",
"title": "Empfaenger",
"content": "...",
"schemaField": "recipients"
},
{
"id": "speicherdauer",
"title": "Speicherdauer / Loeschfristen",
"content": "...",
"schemaField": "retentionPeriod"
},
{
"id": "tom_referenz",
"title": "TOM-Referenz",
"content": "...",
"schemaField": "tomReference"
}
]
}
Fuelle fehlende Informationen mit [PLATZHALTER: Beschreibung was hier eingetragen werden muss].
Halte die Tiefe exakt auf Level ${level} (${context.constraints.depthRequirements.depth}).`
}

View File

@@ -1,11 +0,0 @@
/**
* Drafting Engine Prompts - Re-Exports
*/
export { buildVVTDraftPrompt, type VVTDraftInput } from './draft-vvt'
export { buildTOMDraftPrompt, type TOMDraftInput } from './draft-tom'
export { buildDSFADraftPrompt, type DSFADraftInput } from './draft-dsfa'
export { buildPrivacyPolicyDraftPrompt, type PrivacyPolicyDraftInput } from './draft-privacy-policy'
export { buildLoeschfristenDraftPrompt, type LoeschfristenDraftInput } from './draft-loeschfristen'
export { buildCrossCheckPrompt, type CrossCheckInput } from './validate-cross-check'
export { buildGapAnalysisPrompt, type GapAnalysisInput } from './ask-gap-analysis'

View File

@@ -1,66 +0,0 @@
/**
* Cross-Document Validation Prompt
*/
import type { ValidationContext } from '../types'
export interface CrossCheckInput {
context: ValidationContext
focusDocuments?: string[]
instructions?: string
}
export function buildCrossCheckPrompt(input: CrossCheckInput): string {
const { context, focusDocuments, instructions } = input
return `## Aufgabe: Cross-Dokument-Konsistenzpruefung
### Scope-Level: ${context.scopeLevel}
### Vorhandene Dokumente:
${context.documents.map(d => `- ${d.type}: ${d.contentSummary}`).join('\n')}
### Cross-Referenzen:
- VVT-Kategorien: ${context.crossReferences.vvtCategories.join(', ') || 'Keine'}
- DSFA-Risiken: ${context.crossReferences.dsfaRisks.join(', ') || 'Keine'}
- TOM-Controls: ${context.crossReferences.tomControls.join(', ') || 'Keine'}
- Loeschfristen-Kategorien: ${context.crossReferences.retentionCategories.join(', ') || 'Keine'}
### Tiefenpruefung pro Dokument:
${context.documents.map(d => {
const req = context.depthRequirements[d.type]
return req ? `- ${d.type}: Erforderlich=${req.required}, Tiefe=${req.depth}` : `- ${d.type}: Keine Requirements`
}).join('\n')}
${focusDocuments ? `### Fokus auf: ${focusDocuments.join(', ')}` : ''}
${instructions ? `### Zusaetzliche Anweisungen: ${instructions}` : ''}
### Pruefkriterien:
1. Jede VVT-Taetigkeit muss einen TOM-Verweis haben
2. Jede VVT-Kategorie muss eine Loeschfrist haben
3. Bei DSFA-pflichtigen Verarbeitungen muss eine DSFA existieren
4. TOM-Massnahmen muessen zum Risikoprofil passen
5. Loeschfristen duerfen gesetzliche Minima nicht unterschreiten
6. Dokument-Tiefe muss Level ${context.scopeLevel} entsprechen
### Antwort-Format
Antworte als JSON:
{
"passed": true/false,
"errors": [
{
"id": "ERR-001",
"severity": "error",
"category": "scope_violation|inconsistency|missing_content|depth_mismatch|cross_reference",
"title": "...",
"description": "...",
"documentType": "vvt|tom|dsfa|...",
"crossReferenceType": "...",
"legalReference": "Art. ... DSGVO",
"suggestion": "..."
}
],
"warnings": [...],
"suggestions": [...]
}`
}

View File

@@ -1,337 +0,0 @@
/**
* State Projector - Token-budgetierte Projektion des SDK-State
*
* Extrahiert aus dem vollen SDKState (der ~50k Tokens betragen kann) nur die
* relevanten Slices fuer den jeweiligen Agent-Modus.
*
* Token-Budgets:
* - Draft: ~1500 Tokens
* - Ask: ~600 Tokens
* - Validate: ~2000 Tokens
*/
import type { SDKState, CompanyProfile } from '../types'
import type {
ComplianceScopeState,
ScopeDecision,
ScopeDocumentType,
ScopeGap,
RequiredDocument,
RiskFlag,
DOCUMENT_SCOPE_MATRIX,
DocumentDepthRequirement,
} from '../compliance-scope-types'
import { DOCUMENT_SCOPE_MATRIX as DOC_MATRIX, DOCUMENT_TYPE_LABELS } from '../compliance-scope-types'
import type {
DraftContext,
GapContext,
ValidationContext,
} from './types'
// ============================================================================
// State Projector
// ============================================================================
export class StateProjector {
/**
* Projiziert den SDKState fuer Draft-Operationen.
* Fokus: Scope-Decision, Company-Profile, Dokument-spezifische Constraints.
*
* ~1500 Tokens
*/
projectForDraft(
state: SDKState,
documentType: ScopeDocumentType
): DraftContext {
const decision = state.complianceScope?.decision ?? null
const level = decision?.determinedLevel ?? 'L1'
const depthReq = DOC_MATRIX[documentType]?.[level] ?? {
required: false,
depth: 'Basis',
detailItems: [],
estimatedEffort: 'N/A',
}
return {
decisions: {
level,
scores: decision?.scores ?? {
risk_score: 0,
complexity_score: 0,
assurance_need: 0,
composite_score: 0,
},
hardTriggers: (decision?.triggeredHardTriggers ?? []).map(t => ({
id: t.rule.id,
label: t.rule.label,
legalReference: t.rule.legalReference,
})),
requiredDocuments: (decision?.requiredDocuments ?? [])
.filter(d => d.required)
.map(d => ({
documentType: d.documentType,
depth: d.depth,
detailItems: d.detailItems,
})),
},
companyProfile: this.projectCompanyProfile(state.companyProfile),
constraints: {
depthRequirements: depthReq,
riskFlags: (decision?.riskFlags ?? []).map(f => ({
severity: f.severity,
title: f.title,
recommendation: f.recommendation,
})),
boundaries: this.deriveBoundaries(decision, documentType),
},
existingDocumentData: this.extractExistingDocumentData(state, documentType),
}
}
/**
* Projiziert den SDKState fuer Ask-Operationen.
* Fokus: Luecken, unbeantwortete Fragen, fehlende Dokumente.
*
* ~600 Tokens
*/
projectForAsk(state: SDKState): GapContext {
const decision = state.complianceScope?.decision ?? null
// Fehlende Pflichtdokumente ermitteln
const requiredDocs = (decision?.requiredDocuments ?? []).filter(d => d.required)
const existingDocTypes = this.getExistingDocumentTypes(state)
const missingDocuments = requiredDocs
.filter(d => !existingDocTypes.includes(d.documentType))
.map(d => ({
documentType: d.documentType,
label: DOCUMENT_TYPE_LABELS[d.documentType] ?? d.documentType,
depth: d.depth,
estimatedEffort: d.estimatedEffort,
}))
// Gaps aus der Scope-Decision
const gaps = (decision?.gaps ?? []).map(g => ({
id: g.id,
severity: g.severity,
title: g.title,
description: g.description,
relatedDocuments: g.relatedDocuments,
}))
// Unbeantwortete Fragen (aus dem Scope-Profiling)
const answers = state.complianceScope?.answers ?? []
const answeredIds = new Set(answers.map(a => a.questionId))
return {
unansweredQuestions: [], // Populated dynamically from question catalog
gaps,
missingDocuments,
}
}
/**
* Projiziert den SDKState fuer Validate-Operationen.
* Fokus: Cross-Dokument-Konsistenz, Scope-Compliance.
*
* ~2000 Tokens
*/
projectForValidate(
state: SDKState,
documentTypes: ScopeDocumentType[]
): ValidationContext {
const decision = state.complianceScope?.decision ?? null
const level = decision?.determinedLevel ?? 'L1'
// Dokument-Zusammenfassungen sammeln
const documents = documentTypes.map(type => ({
type,
contentSummary: this.summarizeDocument(state, type),
structuredData: this.extractExistingDocumentData(state, type),
}))
// Cross-Referenzen extrahieren
const crossReferences = {
vvtCategories: (state.vvt ?? []).map(v =>
typeof v === 'object' && v !== null && 'name' in v ? String((v as Record<string, unknown>).name) : ''
).filter(Boolean),
dsfaRisks: state.dsfa
? ['DSFA vorhanden']
: [],
tomControls: (state.toms ?? []).map(t =>
typeof t === 'object' && t !== null && 'name' in t ? String((t as Record<string, unknown>).name) : ''
).filter(Boolean),
retentionCategories: (state.retentionPolicies ?? []).map(p =>
typeof p === 'object' && p !== null && 'name' in p ? String((p as Record<string, unknown>).name) : ''
).filter(Boolean),
}
// Depth-Requirements fuer alle angefragten Typen
const depthRequirements: Record<string, DocumentDepthRequirement> = {}
for (const type of documentTypes) {
depthRequirements[type] = DOC_MATRIX[type]?.[level] ?? {
required: false,
depth: 'Basis',
detailItems: [],
estimatedEffort: 'N/A',
}
}
return {
documents,
crossReferences,
scopeLevel: level,
depthRequirements: depthRequirements as Record<ScopeDocumentType, DocumentDepthRequirement>,
}
}
// ==========================================================================
// Private Helpers
// ==========================================================================
private projectCompanyProfile(
profile: CompanyProfile | null
): DraftContext['companyProfile'] {
if (!profile) {
return {
name: 'Unbekannt',
industry: 'Unbekannt',
employeeCount: 0,
businessModel: 'Unbekannt',
isPublicSector: false,
}
}
return {
name: profile.companyName ?? profile.name ?? 'Unbekannt',
industry: profile.industry ?? 'Unbekannt',
employeeCount: typeof profile.employeeCount === 'number'
? profile.employeeCount
: parseInt(String(profile.employeeCount ?? '0'), 10) || 0,
businessModel: profile.businessModel ?? 'Unbekannt',
isPublicSector: profile.isPublicSector ?? false,
...(profile.dataProtectionOfficer ? {
dataProtectionOfficer: {
name: profile.dataProtectionOfficer.name ?? '',
email: profile.dataProtectionOfficer.email ?? '',
},
} : {}),
}
}
/**
* Leitet Grenzen (Boundaries) ab, die der Agent nicht ueberschreiten darf.
*/
private deriveBoundaries(
decision: ScopeDecision | null,
documentType: ScopeDocumentType
): string[] {
const boundaries: string[] = []
const level = decision?.determinedLevel ?? 'L1'
// Grundregel: Scope-Engine ist autoritativ
boundaries.push(
`Maximale Dokumenttiefe: ${level} (${DOC_MATRIX[documentType]?.[level]?.depth ?? 'Basis'})`
)
// DSFA-Boundary
if (documentType === 'dsfa') {
const dsfaRequired = decision?.triggeredHardTriggers?.some(
t => t.rule.dsfaRequired
) ?? false
if (!dsfaRequired && level !== 'L4') {
boundaries.push('DSFA ist laut Scope-Engine NICHT erforderlich. Nur auf expliziten Wunsch erstellen.')
}
}
// Dokument nicht in requiredDocuments?
const isRequired = decision?.requiredDocuments?.some(
d => d.documentType === documentType && d.required
) ?? false
if (!isRequired) {
boundaries.push(
`Dokument "${DOCUMENT_TYPE_LABELS[documentType] ?? documentType}" ist auf Level ${level} nicht als Pflicht eingestuft.`
)
}
return boundaries
}
/**
* Extrahiert bereits vorhandene Dokumentdaten aus dem SDK-State.
*/
private extractExistingDocumentData(
state: SDKState,
documentType: ScopeDocumentType
): Record<string, unknown> | undefined {
switch (documentType) {
case 'vvt':
return state.vvt?.length ? { entries: state.vvt.slice(0, 5), totalCount: state.vvt.length } : undefined
case 'tom':
return state.toms?.length ? { entries: state.toms.slice(0, 5), totalCount: state.toms.length } : undefined
case 'lf':
return state.retentionPolicies?.length
? { entries: state.retentionPolicies.slice(0, 5), totalCount: state.retentionPolicies.length }
: undefined
case 'dsfa':
return state.dsfa ? { assessment: state.dsfa } : undefined
case 'dsi':
return state.documents?.length
? { entries: state.documents.slice(0, 3), totalCount: state.documents.length }
: undefined
case 'einwilligung':
return state.consents?.length
? { entries: state.consents.slice(0, 5), totalCount: state.consents.length }
: undefined
default:
return undefined
}
}
/**
* Ermittelt welche Dokumenttypen bereits im State vorhanden sind.
*/
private getExistingDocumentTypes(state: SDKState): ScopeDocumentType[] {
const types: ScopeDocumentType[] = []
if (state.vvt?.length) types.push('vvt')
if (state.toms?.length) types.push('tom')
if (state.retentionPolicies?.length) types.push('lf')
if (state.dsfa) types.push('dsfa')
if (state.documents?.length) types.push('dsi')
if (state.consents?.length) types.push('einwilligung')
if (state.cookieBanner) types.push('einwilligung')
return types
}
/**
* Erstellt eine kurze Zusammenfassung eines Dokuments fuer Validierung.
*/
private summarizeDocument(
state: SDKState,
documentType: ScopeDocumentType
): string {
switch (documentType) {
case 'vvt':
return state.vvt?.length
? `${state.vvt.length} Verarbeitungstaetigkeiten erfasst`
: 'Keine VVT-Eintraege vorhanden'
case 'tom':
return state.toms?.length
? `${state.toms.length} TOM-Massnahmen definiert`
: 'Keine TOM-Massnahmen vorhanden'
case 'lf':
return state.retentionPolicies?.length
? `${state.retentionPolicies.length} Loeschfristen definiert`
: 'Keine Loeschfristen vorhanden'
case 'dsfa':
return state.dsfa
? 'DSFA vorhanden'
: 'Keine DSFA vorhanden'
default:
return `Dokument ${DOCUMENT_TYPE_LABELS[documentType] ?? documentType}`
}
}
}
/** Singleton-Instanz */
export const stateProjector = new StateProjector()

View File

@@ -1,279 +0,0 @@
/**
* Drafting Engine - Type Definitions
*
* Typen fuer die 4 Agent-Rollen: Explain, Ask, Draft, Validate
* Die Drafting Engine erweitert den Compliance Advisor um aktive Dokumententwurfs-
* und Validierungsfaehigkeiten, stets unter Beachtung der deterministischen Scope-Engine.
*/
import type {
ComplianceDepthLevel,
ComplianceScores,
ScopeDecision,
ScopeDocumentType,
ScopeGap,
RequiredDocument,
RiskFlag,
DocumentDepthRequirement,
ScopeProfilingQuestion,
} from '../compliance-scope-types'
import type { CompanyProfile } from '../types'
// ============================================================================
// Agent Mode
// ============================================================================
/** Die 4 Agent-Rollen */
export type AgentMode = 'explain' | 'ask' | 'draft' | 'validate'
/** Confidence-Score fuer Intent-Erkennung */
export interface IntentClassification {
mode: AgentMode
confidence: number
matchedPatterns: string[]
/** Falls Draft oder Validate: erkannter Dokumenttyp */
detectedDocumentType?: ScopeDocumentType
}
// ============================================================================
// Draft Context (fuer Draft-Mode)
// ============================================================================
/** Projizierter State fuer Draft-Operationen (~1500 Tokens) */
export interface DraftContext {
/** Scope-Entscheidung (Level, Scores, Hard Triggers) */
decisions: {
level: ComplianceDepthLevel
scores: ComplianceScores
hardTriggers: Array<{ id: string; label: string; legalReference: string }>
requiredDocuments: Array<{
documentType: ScopeDocumentType
depth: string
detailItems: string[]
}>
}
/** Firmenprofil-Auszug */
companyProfile: {
name: string
industry: string
employeeCount: number
businessModel: string
isPublicSector: boolean
dataProtectionOfficer?: { name: string; email: string }
}
/** Constraints aus der Scope-Engine */
constraints: {
depthRequirements: DocumentDepthRequirement
riskFlags: Array<{ severity: string; title: string; recommendation: string }>
boundaries: string[]
}
/** Optional: bestehende Dokumentdaten aus dem SDK-State */
existingDocumentData?: Record<string, unknown>
}
// ============================================================================
// Gap Context (fuer Ask-Mode)
// ============================================================================
/** Projizierter State fuer Ask-Operationen (~600 Tokens) */
export interface GapContext {
/** Noch unbeantwortete Fragen aus dem Scope-Profiling */
unansweredQuestions: Array<{
id: string
question: string
type: string
blockId: string
}>
/** Identifizierte Luecken */
gaps: Array<{
id: string
severity: string
title: string
description: string
relatedDocuments: ScopeDocumentType[]
}>
/** Fehlende Pflichtdokumente */
missingDocuments: Array<{
documentType: ScopeDocumentType
label: string
depth: string
estimatedEffort: string
}>
}
// ============================================================================
// Validation Context (fuer Validate-Mode)
// ============================================================================
/** Projizierter State fuer Validate-Operationen (~2000 Tokens) */
export interface ValidationContext {
/** Zu validierende Dokumente */
documents: Array<{
type: ScopeDocumentType
/** Zusammenfassung/Auszug des Inhalts */
contentSummary: string
/** Strukturierte Daten falls vorhanden */
structuredData?: Record<string, unknown>
}>
/** Cross-Referenzen zwischen Dokumenten */
crossReferences: {
/** VVT Kategorien (Verarbeitungstaetigkeiten) */
vvtCategories: string[]
/** DSFA Risiken */
dsfaRisks: string[]
/** TOM Controls */
tomControls: string[]
/** Loeschfristen-Kategorien */
retentionCategories: string[]
}
/** Scope-Level fuer Tiefenpruefung */
scopeLevel: ComplianceDepthLevel
/** Relevante Depth-Requirements */
depthRequirements: Record<ScopeDocumentType, DocumentDepthRequirement>
}
// ============================================================================
// Validation Result
// ============================================================================
export type ValidationSeverity = 'error' | 'warning' | 'suggestion'
export interface ValidationFinding {
id: string
severity: ValidationSeverity
category: 'scope_violation' | 'inconsistency' | 'missing_content' | 'depth_mismatch' | 'cross_reference'
title: string
description: string
/** Betroffenes Dokument */
documentType: ScopeDocumentType
/** Optional: Referenz zu anderem Dokument */
crossReferenceType?: ScopeDocumentType
/** Rechtsgrundlage falls relevant */
legalReference?: string
/** Vorschlag zur Behebung */
suggestion?: string
/** Kann automatisch uebernommen werden */
autoFixable?: boolean
}
export interface ValidationResult {
passed: boolean
timestamp: string
scopeLevel: ComplianceDepthLevel
errors: ValidationFinding[]
warnings: ValidationFinding[]
suggestions: ValidationFinding[]
}
// ============================================================================
// Draft Session
// ============================================================================
export interface DraftRevision {
id: string
content: string
sections: DraftSection[]
createdAt: string
instruction?: string
}
export interface DraftSection {
id: string
title: string
content: string
/** Mapping zum Dokumentschema (z.B. VVT-Feld) */
schemaField?: string
}
export interface DraftSession {
id: string
mode: AgentMode
documentType: ScopeDocumentType
/** Aktueller Draft-Inhalt */
currentDraft: DraftRevision | null
/** Alle bisherigen Revisionen */
revisions: DraftRevision[]
/** Validierungszustand */
validationState: ValidationResult | null
/** Constraint-Check Ergebnis */
constraintCheck: ConstraintCheckResult | null
createdAt: string
updatedAt: string
}
// ============================================================================
// Constraint Check (Hard Gate)
// ============================================================================
export interface ConstraintCheckResult {
/** Darf der Draft erstellt werden? */
allowed: boolean
/** Verletzungen die den Draft blockieren */
violations: string[]
/** Anpassungen die vorgenommen werden sollten */
adjustments: string[]
/** Gepruefte Regeln */
checkedRules: string[]
}
// ============================================================================
// Chat / API Types
// ============================================================================
export interface DraftingChatMessage {
role: 'user' | 'assistant' | 'system'
content: string
/** Metadata fuer Agent-Nachrichten */
metadata?: {
mode: AgentMode
documentType?: ScopeDocumentType
hasDraft?: boolean
hasValidation?: boolean
}
}
export interface DraftingChatRequest {
message: string
history: DraftingChatMessage[]
sdkStateProjection: DraftContext | GapContext | ValidationContext
mode?: AgentMode
documentType?: ScopeDocumentType
}
export interface DraftRequest {
documentType: ScopeDocumentType
draftContext: DraftContext
instructions?: string
existingDraft?: DraftRevision
}
export interface DraftResponse {
draft: DraftRevision
constraintCheck: ConstraintCheckResult
tokensUsed: number
}
export interface ValidateRequest {
documentType: ScopeDocumentType
draftContent: string
validationContext: ValidationContext
}
// ============================================================================
// Feature Flag
// ============================================================================
export interface DraftingEngineConfig {
/** Feature-Flag: Drafting Engine aktiviert */
enableDraftingEngine: boolean
/** Verfuegbare Modi (fuer schrittweises Rollout) */
enabledModes: AgentMode[]
/** Max Token-Budget fuer State-Projection */
maxProjectionTokens: number
}
export const DEFAULT_DRAFTING_ENGINE_CONFIG: DraftingEngineConfig = {
enableDraftingEngine: false,
enabledModes: ['explain'],
maxProjectionTokens: 4096,
}

View File

@@ -1,343 +0,0 @@
'use client'
/**
* useDraftingEngine - React Hook fuer die Drafting Engine
*
* Managed: currentMode, activeDocumentType, draftSessions, validationState
* Handled: State-Projection, API-Calls, Streaming
* Provides: sendMessage(), requestDraft(), validateDraft(), acceptDraft()
*/
import { useState, useCallback, useRef } from 'react'
import { useSDK } from '../context'
import { stateProjector } from './state-projector'
import { intentClassifier } from './intent-classifier'
import { constraintEnforcer } from './constraint-enforcer'
import type {
AgentMode,
DraftSession,
DraftRevision,
DraftingChatMessage,
ValidationResult,
ConstraintCheckResult,
DraftContext,
GapContext,
ValidationContext,
} from './types'
import type { ScopeDocumentType } from '../compliance-scope-types'
export interface DraftingEngineState {
currentMode: AgentMode
activeDocumentType: ScopeDocumentType | null
messages: DraftingChatMessage[]
isTyping: boolean
currentDraft: DraftRevision | null
validationResult: ValidationResult | null
constraintCheck: ConstraintCheckResult | null
error: string | null
}
export interface DraftingEngineActions {
setMode: (mode: AgentMode) => void
setDocumentType: (type: ScopeDocumentType) => void
sendMessage: (content: string) => Promise<void>
requestDraft: (instructions?: string) => Promise<void>
validateDraft: () => Promise<void>
acceptDraft: () => void
stopGeneration: () => void
clearMessages: () => void
}
export function useDraftingEngine(): DraftingEngineState & DraftingEngineActions {
const { state, dispatch } = useSDK()
const abortControllerRef = useRef<AbortController | null>(null)
const [currentMode, setCurrentMode] = useState<AgentMode>('explain')
const [activeDocumentType, setActiveDocumentType] = useState<ScopeDocumentType | null>(null)
const [messages, setMessages] = useState<DraftingChatMessage[]>([])
const [isTyping, setIsTyping] = useState(false)
const [currentDraft, setCurrentDraft] = useState<DraftRevision | null>(null)
const [validationResult, setValidationResult] = useState<ValidationResult | null>(null)
const [constraintCheck, setConstraintCheck] = useState<ConstraintCheckResult | null>(null)
const [error, setError] = useState<string | null>(null)
// Get state projection based on mode
const getProjection = useCallback(() => {
switch (currentMode) {
case 'draft':
return activeDocumentType
? stateProjector.projectForDraft(state, activeDocumentType)
: null
case 'ask':
return stateProjector.projectForAsk(state)
case 'validate':
return activeDocumentType
? stateProjector.projectForValidate(state, [activeDocumentType])
: stateProjector.projectForValidate(state, ['vvt', 'tom', 'lf'])
default:
return activeDocumentType
? stateProjector.projectForDraft(state, activeDocumentType)
: null
}
}, [state, currentMode, activeDocumentType])
const setMode = useCallback((mode: AgentMode) => {
setCurrentMode(mode)
}, [])
const setDocumentType = useCallback((type: ScopeDocumentType) => {
setActiveDocumentType(type)
}, [])
const sendMessage = useCallback(async (content: string) => {
if (!content.trim() || isTyping) return
setError(null)
// Auto-detect mode if needed
const classification = intentClassifier.classify(content)
if (classification.confidence > 0.7 && classification.mode !== currentMode) {
setCurrentMode(classification.mode)
}
if (classification.detectedDocumentType && !activeDocumentType) {
setActiveDocumentType(classification.detectedDocumentType)
}
const userMessage: DraftingChatMessage = {
role: 'user',
content: content.trim(),
}
setMessages(prev => [...prev, userMessage])
setIsTyping(true)
abortControllerRef.current = new AbortController()
try {
const projection = getProjection()
const response = await fetch('/api/sdk/drafting-engine/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: content.trim(),
history: messages.map(m => ({ role: m.role, content: m.content })),
sdkStateProjection: projection,
mode: currentMode,
documentType: activeDocumentType,
}),
signal: abortControllerRef.current.signal,
})
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: 'Unbekannter Fehler' }))
throw new Error(errorData.error || `Server-Fehler (${response.status})`)
}
const agentMessageId = `msg-${Date.now()}-agent`
setMessages(prev => [...prev, {
role: 'assistant',
content: '',
metadata: { mode: currentMode, documentType: activeDocumentType ?? undefined },
}])
// Stream response
const reader = response.body!.getReader()
const decoder = new TextDecoder()
let accumulated = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
accumulated += decoder.decode(value, { stream: true })
const text = accumulated
setMessages(prev =>
prev.map((m, i) => i === prev.length - 1 ? { ...m, content: text } : m)
)
}
setIsTyping(false)
} catch (err) {
if ((err as Error).name === 'AbortError') {
setIsTyping(false)
return
}
setError((err as Error).message)
setMessages(prev => [...prev, {
role: 'assistant',
content: `Fehler: ${(err as Error).message}`,
}])
setIsTyping(false)
}
}, [isTyping, messages, currentMode, activeDocumentType, getProjection])
const requestDraft = useCallback(async (instructions?: string) => {
if (!activeDocumentType) {
setError('Bitte waehlen Sie zuerst einen Dokumenttyp.')
return
}
setError(null)
setIsTyping(true)
try {
const draftContext = stateProjector.projectForDraft(state, activeDocumentType)
const response = await fetch('/api/sdk/drafting-engine/draft', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
documentType: activeDocumentType,
draftContext,
instructions,
existingDraft: currentDraft,
}),
})
const result = await response.json()
if (!response.ok) {
throw new Error(result.error || 'Draft-Generierung fehlgeschlagen')
}
setCurrentDraft(result.draft)
setConstraintCheck(result.constraintCheck)
setMessages(prev => [...prev, {
role: 'assistant',
content: `Draft fuer ${activeDocumentType} erstellt (${result.draft.sections.length} Sections). Oeffnen Sie den Editor zur Bearbeitung.`,
metadata: { mode: 'draft', documentType: activeDocumentType, hasDraft: true },
}])
setIsTyping(false)
} catch (err) {
setError((err as Error).message)
setIsTyping(false)
}
}, [activeDocumentType, state, currentDraft])
const validateDraft = useCallback(async () => {
setError(null)
setIsTyping(true)
try {
const docTypes: ScopeDocumentType[] = activeDocumentType
? [activeDocumentType]
: ['vvt', 'tom', 'lf']
const validationContext = stateProjector.projectForValidate(state, docTypes)
const response = await fetch('/api/sdk/drafting-engine/validate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
documentType: activeDocumentType || 'vvt',
draftContent: currentDraft?.content || '',
validationContext,
}),
})
const result = await response.json()
if (!response.ok) {
throw new Error(result.error || 'Validierung fehlgeschlagen')
}
setValidationResult(result)
const summary = result.passed
? `Validierung bestanden. ${result.warnings.length} Warnungen, ${result.suggestions.length} Vorschlaege.`
: `Validierung fehlgeschlagen. ${result.errors.length} Fehler, ${result.warnings.length} Warnungen.`
setMessages(prev => [...prev, {
role: 'assistant',
content: summary,
metadata: { mode: 'validate', hasValidation: true },
}])
setIsTyping(false)
} catch (err) {
setError((err as Error).message)
setIsTyping(false)
}
}, [activeDocumentType, state, currentDraft])
const acceptDraft = useCallback(() => {
if (!currentDraft || !activeDocumentType) return
// Dispatch the draft data into SDK state
switch (activeDocumentType) {
case 'vvt':
dispatch({
type: 'ADD_PROCESSING_ACTIVITY',
payload: {
id: `draft-vvt-${Date.now()}`,
name: currentDraft.sections.find(s => s.schemaField === 'name')?.content || 'Neuer VVT-Eintrag',
...Object.fromEntries(
currentDraft.sections
.filter(s => s.schemaField)
.map(s => [s.schemaField!, s.content])
),
},
})
break
case 'tom':
dispatch({
type: 'ADD_TOM',
payload: {
id: `draft-tom-${Date.now()}`,
name: 'TOM-Entwurf',
...Object.fromEntries(
currentDraft.sections
.filter(s => s.schemaField)
.map(s => [s.schemaField!, s.content])
),
},
})
break
default:
dispatch({
type: 'ADD_DOCUMENT',
payload: {
id: `draft-${activeDocumentType}-${Date.now()}`,
type: activeDocumentType,
content: currentDraft.content,
sections: currentDraft.sections,
},
})
}
setMessages(prev => [...prev, {
role: 'assistant',
content: `Draft wurde in den SDK-State uebernommen.`,
}])
setCurrentDraft(null)
}, [currentDraft, activeDocumentType, dispatch])
const stopGeneration = useCallback(() => {
abortControllerRef.current?.abort()
setIsTyping(false)
}, [])
const clearMessages = useCallback(() => {
setMessages([])
setCurrentDraft(null)
setValidationResult(null)
setConstraintCheck(null)
setError(null)
}, [])
return {
currentMode,
activeDocumentType,
messages,
isTyping,
currentDraft,
validationResult,
constraintCheck,
error,
setMode,
setDocumentType,
sendMessage,
requestDraft,
validateDraft,
acceptDraft,
stopGeneration,
clearMessages,
}
}

View File

@@ -1,881 +0,0 @@
/**
* 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 })
}
)
}
// =============================================================================
// SDK API FUNCTIONS (via Next.js proxy to ai-compliance-sdk)
// =============================================================================
interface BackendDSR {
id: string
tenant_id: string
namespace_id?: string
request_type: string
status: string
subject_name: string
subject_email: string
subject_identifier?: string
request_description: string
request_channel: string
received_at: string
verified_at?: string
verification_method?: string
deadline_at: string
extended_deadline_at?: string
extension_reason?: string
completed_at?: string
response_sent: boolean
response_sent_at?: string
response_method?: string
rejection_reason?: string
notes?: string
affected_systems?: string[]
assigned_to?: string
created_at: string
updated_at: string
}
function mapBackendStatus(status: string): import('./types').DSRStatus {
const mapping: Record<string, import('./types').DSRStatus> = {
'received': 'intake',
'verified': 'identity_verification',
'in_progress': 'processing',
'completed': 'completed',
'rejected': 'rejected',
'extended': 'processing',
}
return mapping[status] || 'intake'
}
function mapBackendChannel(channel: string): import('./types').DSRSource {
const mapping: Record<string, import('./types').DSRSource> = {
'email': 'email',
'form': 'web_form',
'phone': 'phone',
'letter': 'letter',
}
return mapping[channel] || 'other'
}
/**
* Transform flat backend DSR to nested SDK DSRRequest format
*/
export function transformBackendDSR(b: BackendDSR): DSRRequest {
const deadlineAt = b.extended_deadline_at || b.deadline_at
const receivedDate = new Date(b.received_at)
const defaultDeadlineDays = 30
const originalDeadline = b.deadline_at || new Date(receivedDate.getTime() + defaultDeadlineDays * 24 * 60 * 60 * 1000).toISOString()
return {
id: b.id,
referenceNumber: `DSR-${new Date(b.created_at).getFullYear()}-${b.id.slice(0, 6).toUpperCase()}`,
type: b.request_type as DSRRequest['type'],
status: mapBackendStatus(b.status),
priority: 'normal',
requester: {
name: b.subject_name,
email: b.subject_email,
customerId: b.subject_identifier,
},
source: mapBackendChannel(b.request_channel),
requestText: b.request_description,
receivedAt: b.received_at,
deadline: {
originalDeadline,
currentDeadline: deadlineAt,
extended: !!b.extended_deadline_at,
extensionReason: b.extension_reason,
},
completedAt: b.completed_at,
identityVerification: {
verified: !!b.verified_at,
verifiedAt: b.verified_at,
method: b.verification_method as any,
},
assignment: {
assignedTo: b.assigned_to || null,
},
notes: b.notes,
createdAt: b.created_at,
createdBy: 'system',
updatedAt: b.updated_at,
tenantId: b.tenant_id,
}
}
function getSdkHeaders(): HeadersInit {
if (typeof window === 'undefined') return {}
return {
'Content-Type': 'application/json',
'X-Tenant-ID': localStorage.getItem('bp_tenant_id') || '',
'X-User-ID': localStorage.getItem('bp_user_id') || '',
}
}
/**
* Fetch DSR list from SDK backend via proxy
*/
export async function fetchSDKDSRList(): Promise<{ requests: DSRRequest[]; statistics: DSRStatistics }> {
const res = await fetch('/api/sdk/v1/dsgvo/dsr', {
headers: getSdkHeaders(),
})
if (!res.ok) {
throw new Error(`HTTP ${res.status}`)
}
const data = await res.json()
const backendDSRs: BackendDSR[] = data.dsrs || []
const requests = backendDSRs.map(transformBackendDSR)
// Calculate statistics locally
const now = new Date()
const statistics: DSRStatistics = {
total: requests.length,
byStatus: {
intake: requests.filter(r => r.status === 'intake').length,
identity_verification: requests.filter(r => r.status === 'identity_verification').length,
processing: requests.filter(r => r.status === 'processing').length,
completed: requests.filter(r => r.status === 'completed').length,
rejected: requests.filter(r => r.status === 'rejected').length,
cancelled: requests.filter(r => r.status === 'cancelled').length,
},
byType: {
access: requests.filter(r => r.type === 'access').length,
rectification: requests.filter(r => r.type === 'rectification').length,
erasure: requests.filter(r => r.type === 'erasure').length,
restriction: requests.filter(r => r.type === 'restriction').length,
portability: requests.filter(r => r.type === 'portability').length,
objection: requests.filter(r => r.type === 'objection').length,
},
overdue: requests.filter(r => {
if (r.status === 'completed' || r.status === 'rejected' || r.status === 'cancelled') return false
return new Date(r.deadline.currentDeadline) < now
}).length,
dueThisWeek: requests.filter(r => {
if (r.status === 'completed' || r.status === 'rejected' || r.status === 'cancelled') return false
const deadline = new Date(r.deadline.currentDeadline)
const weekFromNow = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000)
return deadline >= now && deadline <= weekFromNow
}).length,
averageProcessingDays: 0,
completedThisMonth: requests.filter(r => {
if (r.status !== 'completed' || !r.completedAt) return false
const completed = new Date(r.completedAt)
return completed.getMonth() === now.getMonth() && completed.getFullYear() === now.getFullYear()
}).length,
}
return { requests, statistics }
}
/**
* Create a new DSR via SDK backend
*/
export async function createSDKDSR(request: DSRCreateRequest): Promise<void> {
const body = {
request_type: request.type,
subject_name: request.requester.name,
subject_email: request.requester.email,
subject_identifier: request.requester.customerId || '',
request_description: request.requestText || '',
request_channel: request.source === 'web_form' ? 'form' : request.source,
notes: '',
}
const res = await fetch('/api/sdk/v1/dsgvo/dsr', {
method: 'POST',
headers: getSdkHeaders(),
body: JSON.stringify(body),
})
if (!res.ok) {
throw new Error(`HTTP ${res.status}`)
}
}
/**
* Fetch a single DSR by ID from SDK backend
*/
export async function fetchSDKDSR(id: string): Promise<DSRRequest | null> {
const res = await fetch(`/api/sdk/v1/dsgvo/dsr/${id}`, {
headers: getSdkHeaders(),
})
if (!res.ok) {
return null
}
const data = await res.json()
if (!data || !data.id) return null
return transformBackendDSR(data)
}
/**
* Update DSR status via SDK backend
*/
export async function updateSDKDSRStatus(id: string, status: string): Promise<void> {
const res = await fetch(`/api/sdk/v1/dsgvo/dsr/${id}`, {
method: 'PUT',
headers: getSdkHeaders(),
body: JSON.stringify({ status }),
})
if (!res.ok) {
throw new Error(`HTTP ${res.status}`)
}
}
// =============================================================================
// MOCK DATA FUNCTIONS (kept as fallback)
// =============================================================================
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

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

View File

@@ -1,581 +0,0 @@
/**
* 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

@@ -1,308 +0,0 @@
/**
* 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

@@ -1,669 +0,0 @@
'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

@@ -1,493 +0,0 @@
// =============================================================================
// 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

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

View File

@@ -1,505 +0,0 @@
// =============================================================================
// 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

@@ -1,595 +0,0 @@
/**
* 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

@@ -1,965 +0,0 @@
/**
* 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

@@ -1,79 +0,0 @@
/**
* 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

@@ -1,838 +0,0 @@
/**
* 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'
}
}

View File

@@ -1,325 +0,0 @@
// =============================================================================
// Loeschfristen Module - Compliance Check Engine
// Prueft Policies auf Vollstaendigkeit, Konsistenz und DSGVO-Konformitaet
// =============================================================================
import {
LoeschfristPolicy,
PolicyStatus,
isPolicyOverdue,
getActiveLegalHolds,
} from './loeschfristen-types'
// =============================================================================
// TYPES
// =============================================================================
export type ComplianceIssueType =
| 'MISSING_TRIGGER'
| 'MISSING_LEGAL_BASIS'
| 'OVERDUE_REVIEW'
| 'NO_RESPONSIBLE'
| 'LEGAL_HOLD_CONFLICT'
| 'STALE_DRAFT'
| 'UNCOVERED_VVT_CATEGORY'
export type ComplianceIssueSeverity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
export interface ComplianceIssue {
id: string
policyId: string
policyName: string
type: ComplianceIssueType
severity: ComplianceIssueSeverity
title: string
description: string
recommendation: string
}
export interface ComplianceCheckResult {
issues: ComplianceIssue[]
score: number // 0-100
stats: {
total: number
passed: number
failed: number
bySeverity: Record<ComplianceIssueSeverity, number>
}
}
// =============================================================================
// HELPERS
// =============================================================================
let issueCounter = 0
function createIssueId(): string {
issueCounter++
return `CI-${Date.now()}-${String(issueCounter).padStart(4, '0')}`
}
function createIssue(
policy: LoeschfristPolicy,
type: ComplianceIssueType,
severity: ComplianceIssueSeverity,
title: string,
description: string,
recommendation: string
): ComplianceIssue {
return {
id: createIssueId(),
policyId: policy.policyId,
policyName: policy.dataObjectName || policy.policyId,
type,
severity,
title,
description,
recommendation,
}
}
function daysBetween(dateStr: string, now: Date): number {
const date = new Date(dateStr)
const diffMs = now.getTime() - date.getTime()
return Math.floor(diffMs / (1000 * 60 * 60 * 24))
}
// =============================================================================
// INDIVIDUAL CHECKS
// =============================================================================
/**
* Check 1: MISSING_TRIGGER (HIGH)
* Policy has no deletionTrigger set, or trigger is PURPOSE_END but no startEvent defined.
*/
function checkMissingTrigger(policy: LoeschfristPolicy): ComplianceIssue | null {
if (!policy.deletionTrigger) {
return createIssue(
policy,
'MISSING_TRIGGER',
'HIGH',
'Kein Loeschtrigger definiert',
`Die Policy "${policy.dataObjectName}" hat keinen Loeschtrigger gesetzt. Ohne Trigger ist unklar, wann die Daten geloescht werden.`,
'Definieren Sie einen Loeschtrigger (Zweckende, Aufbewahrungspflicht oder Legal Hold) fuer diese Policy.'
)
}
if (policy.deletionTrigger === 'PURPOSE_END' && !policy.startEvent.trim()) {
return createIssue(
policy,
'MISSING_TRIGGER',
'HIGH',
'Zweckende ohne Startereignis',
`Die Policy "${policy.dataObjectName}" nutzt "Zweckende" als Trigger, hat aber kein Startereignis definiert. Ohne Startereignis laesst sich der Loeschzeitpunkt nicht berechnen.`,
'Definieren Sie ein konkretes Startereignis (z.B. "Vertragsende", "Abmeldung", "Projektabschluss").'
)
}
return null
}
/**
* Check 2: MISSING_LEGAL_BASIS (HIGH)
* Policy with RETENTION_DRIVER trigger but no retentionDriver set.
*/
function checkMissingLegalBasis(policy: LoeschfristPolicy): ComplianceIssue | null {
if (policy.deletionTrigger === 'RETENTION_DRIVER' && !policy.retentionDriver) {
return createIssue(
policy,
'MISSING_LEGAL_BASIS',
'HIGH',
'Aufbewahrungspflicht ohne Rechtsgrundlage',
`Die Policy "${policy.dataObjectName}" hat "Aufbewahrungspflicht" als Trigger, aber keinen konkreten Aufbewahrungstreiber (z.B. AO 147, HGB 257) zugeordnet.`,
'Waehlen Sie den passenden gesetzlichen Aufbewahrungstreiber aus oder wechseln Sie den Trigger-Typ.'
)
}
return null
}
/**
* Check 3: OVERDUE_REVIEW (MEDIUM)
* Policy where nextReviewDate is in the past.
*/
function checkOverdueReview(policy: LoeschfristPolicy): ComplianceIssue | null {
if (isPolicyOverdue(policy)) {
const overdueDays = daysBetween(policy.nextReviewDate, new Date())
return createIssue(
policy,
'OVERDUE_REVIEW',
'MEDIUM',
'Ueberfaellige Pruefung',
`Die Policy "${policy.dataObjectName}" haette am ${new Date(policy.nextReviewDate).toLocaleDateString('de-DE')} geprueft werden muessen. Die Pruefung ist ${overdueDays} Tag(e) ueberfaellig.`,
'Fuehren Sie umgehend eine Pruefung dieser Policy durch und aktualisieren Sie das naechste Pruefungsdatum.'
)
}
return null
}
/**
* Check 4: NO_RESPONSIBLE (MEDIUM)
* Policy with no responsiblePerson AND no responsibleRole.
*/
function checkNoResponsible(policy: LoeschfristPolicy): ComplianceIssue | null {
if (!policy.responsiblePerson.trim() && !policy.responsibleRole.trim()) {
return createIssue(
policy,
'NO_RESPONSIBLE',
'MEDIUM',
'Keine verantwortliche Person/Rolle',
`Die Policy "${policy.dataObjectName}" hat weder eine verantwortliche Person noch eine verantwortliche Rolle zugewiesen. Ohne Verantwortlichkeit kann die Loeschung nicht zuverlaessig durchgefuehrt werden.`,
'Weisen Sie eine verantwortliche Person oder zumindest eine verantwortliche Rolle (z.B. "Datenschutzbeauftragter", "IT-Leitung") zu.'
)
}
return null
}
/**
* Check 5: LEGAL_HOLD_CONFLICT (CRITICAL)
* Policy has active legal hold but deletionMethod is AUTO_DELETE.
*/
function checkLegalHoldConflict(policy: LoeschfristPolicy): ComplianceIssue | null {
const activeHolds = getActiveLegalHolds(policy)
if (activeHolds.length > 0 && policy.deletionMethod === 'AUTO_DELETE') {
const holdReasons = activeHolds.map((h) => h.reason).join(', ')
return createIssue(
policy,
'LEGAL_HOLD_CONFLICT',
'CRITICAL',
'Legal Hold mit automatischer Loeschung',
`Die Policy "${policy.dataObjectName}" hat ${activeHolds.length} aktive(n) Legal Hold(s) (${holdReasons}), aber die Loeschmethode ist auf "Automatische Loeschung" gesetzt. Dies kann zu unbeabsichtigter Vernichtung von Beweismitteln fuehren.`,
'Aendern Sie die Loeschmethode auf "Manuelle Pruefung & Loeschung" oder deaktivieren Sie die automatische Loeschung, solange der Legal Hold aktiv ist.'
)
}
return null
}
/**
* Check 6: STALE_DRAFT (LOW)
* Policy in DRAFT status older than 90 days.
*/
function checkStaleDraft(policy: LoeschfristPolicy): ComplianceIssue | null {
if (policy.status === 'DRAFT') {
const ageInDays = daysBetween(policy.createdAt, new Date())
if (ageInDays > 90) {
return createIssue(
policy,
'STALE_DRAFT',
'LOW',
'Veralteter Entwurf',
`Die Policy "${policy.dataObjectName}" ist seit ${ageInDays} Tagen im Entwurfsstatus. Entwuerfe, die laenger als 90 Tage nicht finalisiert werden, deuten auf unvollstaendige Dokumentation hin.`,
'Finalisieren Sie den Entwurf und setzen Sie den Status auf "Aktiv", oder archivieren Sie die Policy, falls sie nicht mehr benoetigt wird.'
)
}
}
return null
}
// =============================================================================
// MAIN COMPLIANCE CHECK
// =============================================================================
/**
* Fuehrt einen vollstaendigen Compliance-Check ueber alle Policies durch.
*
* @param policies - Alle Loeschfrist-Policies
* @param vvtDataCategories - Optionale Datenkategorien aus dem VVT (localStorage)
* @returns ComplianceCheckResult mit Issues, Score und Statistiken
*/
export function runComplianceCheck(
policies: LoeschfristPolicy[],
vvtDataCategories?: string[]
): ComplianceCheckResult {
// Reset counter for deterministic IDs within a single check run
issueCounter = 0
const issues: ComplianceIssue[] = []
// Run checks 1-6 for each policy
for (const policy of policies) {
const checks = [
checkMissingTrigger(policy),
checkMissingLegalBasis(policy),
checkOverdueReview(policy),
checkNoResponsible(policy),
checkLegalHoldConflict(policy),
checkStaleDraft(policy),
]
for (const issue of checks) {
if (issue !== null) {
issues.push(issue)
}
}
}
// Check 7: UNCOVERED_VVT_CATEGORY (MEDIUM)
if (vvtDataCategories && vvtDataCategories.length > 0) {
const coveredCategories = new Set<string>()
for (const policy of policies) {
for (const category of policy.dataCategories) {
coveredCategories.add(category.toLowerCase().trim())
}
}
for (const vvtCategory of vvtDataCategories) {
const normalized = vvtCategory.toLowerCase().trim()
if (!coveredCategories.has(normalized)) {
issues.push({
id: createIssueId(),
policyId: '-',
policyName: '-',
type: 'UNCOVERED_VVT_CATEGORY',
severity: 'MEDIUM',
title: `Datenkategorie ohne Loeschfrist: "${vvtCategory}"`,
description: `Die Datenkategorie "${vvtCategory}" ist im Verzeichnis der Verarbeitungstaetigkeiten (VVT) erfasst, hat aber keine zugehoerige Loeschfrist-Policy. Gemaess DSGVO Art. 5 Abs. 1 lit. e muss fuer jede Datenkategorie eine Speicherbegrenzung definiert sein.`,
recommendation: `Erstellen Sie eine neue Loeschfrist-Policy fuer die Datenkategorie "${vvtCategory}" oder ordnen Sie sie einer bestehenden Policy zu.`,
})
}
}
}
// Calculate score
const bySeverity: Record<ComplianceIssueSeverity, number> = {
LOW: 0,
MEDIUM: 0,
HIGH: 0,
CRITICAL: 0,
}
for (const issue of issues) {
bySeverity[issue.severity]++
}
const rawScore =
100 -
(bySeverity.CRITICAL * 15 +
bySeverity.HIGH * 10 +
bySeverity.MEDIUM * 5 +
bySeverity.LOW * 2)
const score = Math.max(0, rawScore)
// Calculate pass/fail per policy
const failedPolicyIds = new Set(
issues.filter((i) => i.policyId !== '-').map((i) => i.policyId)
)
const totalPolicies = policies.length
const failedCount = failedPolicyIds.size
const passedCount = totalPolicies - failedCount
return {
issues,
score,
stats: {
total: totalPolicies,
passed: passedCount,
failed: failedCount,
bySeverity,
},
}
}

View File

@@ -1,353 +0,0 @@
// =============================================================================
// Loeschfristen Module - Export & Report Generation
// JSON, CSV, Markdown-Compliance-Report und Browser-Download
// =============================================================================
import {
LoeschfristPolicy,
RETENTION_DRIVER_META,
DELETION_METHOD_LABELS,
STATUS_LABELS,
TRIGGER_LABELS,
formatRetentionDuration,
getEffectiveDeletionTrigger,
} from './loeschfristen-types'
import {
runComplianceCheck,
ComplianceCheckResult,
ComplianceIssueSeverity,
} from './loeschfristen-compliance'
// =============================================================================
// JSON EXPORT
// =============================================================================
interface PolicyExportEnvelope {
exportDate: string
version: string
totalPolicies: number
policies: LoeschfristPolicy[]
}
/**
* Exportiert alle Policies als pretty-printed JSON.
* Enthaelt Metadaten (Exportdatum, Version, Anzahl).
*/
export function exportPoliciesAsJSON(policies: LoeschfristPolicy[]): string {
const exportData: PolicyExportEnvelope = {
exportDate: new Date().toISOString(),
version: '1.0',
totalPolicies: policies.length,
policies: policies,
}
return JSON.stringify(exportData, null, 2)
}
// =============================================================================
// CSV EXPORT
// =============================================================================
/**
* Escapes a CSV field value according to RFC 4180.
* Fields containing commas, double quotes, or newlines are wrapped in quotes.
* Existing double quotes are doubled.
*/
function escapeCSVField(value: string): string {
if (
value.includes(',') ||
value.includes('"') ||
value.includes('\n') ||
value.includes('\r') ||
value.includes(';')
) {
return `"${value.replace(/"/g, '""')}"`
}
return value
}
/**
* Formats a date string to German locale format (DD.MM.YYYY).
* Returns empty string for null/undefined/empty values.
*/
function formatDateDE(dateStr: string | null | undefined): string {
if (!dateStr) return ''
try {
const date = new Date(dateStr)
if (isNaN(date.getTime())) return ''
return date.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
})
} catch {
return ''
}
}
/**
* Exportiert alle Policies als CSV mit BOM fuer Excel-Kompatibilitaet.
* Trennzeichen ist Semikolon (;) fuer deutschsprachige Excel-Versionen.
*/
export function exportPoliciesAsCSV(policies: LoeschfristPolicy[]): string {
const BOM = '\uFEFF'
const SEPARATOR = ';'
const headers = [
'LF-Nr.',
'Datenobjekt',
'Beschreibung',
'Loeschtrigger',
'Aufbewahrungstreiber',
'Frist',
'Startereignis',
'Loeschmethode',
'Verantwortlich',
'Status',
'Legal Hold aktiv',
'Letzte Pruefung',
'Naechste Pruefung',
]
const rows: string[] = []
// Header row
rows.push(headers.map(escapeCSVField).join(SEPARATOR))
// Data rows
for (const policy of policies) {
const effectiveTrigger = getEffectiveDeletionTrigger(policy)
const triggerLabel = TRIGGER_LABELS[effectiveTrigger]
const driverLabel = policy.retentionDriver
? RETENTION_DRIVER_META[policy.retentionDriver].label
: ''
const durationLabel = formatRetentionDuration(
policy.retentionDuration,
policy.retentionUnit
)
const methodLabel = DELETION_METHOD_LABELS[policy.deletionMethod]
const statusLabel = STATUS_LABELS[policy.status]
// Combine responsiblePerson and responsibleRole
const responsible = [policy.responsiblePerson, policy.responsibleRole]
.filter((s) => s.trim())
.join(' / ')
const legalHoldActive = policy.hasActiveLegalHold ? 'Ja' : 'Nein'
const row = [
policy.policyId,
policy.dataObjectName,
policy.description,
triggerLabel,
driverLabel,
durationLabel,
policy.startEvent,
methodLabel,
responsible || '-',
statusLabel,
legalHoldActive,
formatDateDE(policy.lastReviewDate),
formatDateDE(policy.nextReviewDate),
]
rows.push(row.map(escapeCSVField).join(SEPARATOR))
}
return BOM + rows.join('\r\n')
}
// =============================================================================
// COMPLIANCE SUMMARY (MARKDOWN)
// =============================================================================
const SEVERITY_LABELS: Record<ComplianceIssueSeverity, string> = {
CRITICAL: 'Kritisch',
HIGH: 'Hoch',
MEDIUM: 'Mittel',
LOW: 'Niedrig',
}
const SEVERITY_EMOJI: Record<ComplianceIssueSeverity, string> = {
CRITICAL: '[!!!]',
HIGH: '[!!]',
MEDIUM: '[!]',
LOW: '[i]',
}
/**
* Returns a textual rating based on the compliance score.
*/
function getScoreRating(score: number): string {
if (score >= 90) return 'Ausgezeichnet'
if (score >= 75) return 'Gut'
if (score >= 50) return 'Verbesserungswuerdig'
if (score >= 25) return 'Mangelhaft'
return 'Kritisch'
}
/**
* Generiert einen Markdown-formatierten Compliance-Bericht.
* Enthaelt: Uebersicht, Score, Issue-Liste, Empfehlungen.
*/
export function generateComplianceSummary(
policies: LoeschfristPolicy[],
vvtDataCategories?: string[]
): string {
const result: ComplianceCheckResult = runComplianceCheck(policies, vvtDataCategories)
const now = new Date()
const lines: string[] = []
// Header
lines.push('# Compliance-Bericht: Loeschfristen')
lines.push('')
lines.push(
`**Erstellt am:** ${now.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })} um ${now.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })} Uhr`
)
lines.push('')
// Overview
lines.push('## Uebersicht')
lines.push('')
lines.push(`| Kennzahl | Wert |`)
lines.push(`|----------|------|`)
lines.push(`| Gepruefte Policies | ${result.stats.total} |`)
lines.push(`| Bestanden | ${result.stats.passed} |`)
lines.push(`| Beanstandungen | ${result.stats.failed} |`)
lines.push(`| Compliance-Score | **${result.score}/100** (${getScoreRating(result.score)}) |`)
lines.push('')
// Severity breakdown
lines.push('## Befunde nach Schweregrad')
lines.push('')
lines.push('| Schweregrad | Anzahl |')
lines.push('|-------------|--------|')
const severityOrder: ComplianceIssueSeverity[] = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']
for (const severity of severityOrder) {
const count = result.stats.bySeverity[severity]
lines.push(`| ${SEVERITY_LABELS[severity]} | ${count} |`)
}
lines.push('')
// Status distribution of policies
const statusCounts: Record<string, number> = {}
for (const policy of policies) {
const label = STATUS_LABELS[policy.status]
statusCounts[label] = (statusCounts[label] || 0) + 1
}
lines.push('## Policy-Status-Verteilung')
lines.push('')
lines.push('| Status | Anzahl |')
lines.push('|--------|--------|')
for (const [label, count] of Object.entries(statusCounts)) {
lines.push(`| ${label} | ${count} |`)
}
lines.push('')
// Issues list
if (result.issues.length === 0) {
lines.push('## Befunde')
lines.push('')
lines.push('Keine Beanstandungen gefunden. Alle Policies sind konform.')
lines.push('')
} else {
lines.push('## Befunde')
lines.push('')
// Group issues by severity
for (const severity of severityOrder) {
const issuesForSeverity = result.issues.filter((i) => i.severity === severity)
if (issuesForSeverity.length === 0) continue
lines.push(`### ${SEVERITY_LABELS[severity]} ${SEVERITY_EMOJI[severity]}`)
lines.push('')
for (const issue of issuesForSeverity) {
const policyRef =
issue.policyId !== '-' ? ` (${issue.policyId})` : ''
lines.push(`**${issue.title}**${policyRef}`)
lines.push('')
lines.push(`> ${issue.description}`)
lines.push('')
lines.push(`Empfehlung: ${issue.recommendation}`)
lines.push('')
lines.push('---')
lines.push('')
}
}
}
// Recommendations summary
lines.push('## Zusammenfassung der Empfehlungen')
lines.push('')
if (result.stats.bySeverity.CRITICAL > 0) {
lines.push(
`1. **Sofortmassnahmen erforderlich:** ${result.stats.bySeverity.CRITICAL} kritische(r) Befund(e) muessen umgehend behoben werden (Legal Hold-Konflikte).`
)
}
if (result.stats.bySeverity.HIGH > 0) {
lines.push(
`${result.stats.bySeverity.CRITICAL > 0 ? '2' : '1'}. **Hohe Prioritaet:** ${result.stats.bySeverity.HIGH} Befund(e) mit hoher Prioritaet (fehlende Trigger/Rechtsgrundlagen) sollten zeitnah bearbeitet werden.`
)
}
if (result.stats.bySeverity.MEDIUM > 0) {
lines.push(
`- **Mittlere Prioritaet:** ${result.stats.bySeverity.MEDIUM} Befund(e) betreffen ueberfaellige Pruefungen, fehlende Verantwortlichkeiten oder nicht abgedeckte Datenkategorien.`
)
}
if (result.stats.bySeverity.LOW > 0) {
lines.push(
`- **Niedrige Prioritaet:** ${result.stats.bySeverity.LOW} Befund(e) betreffen veraltete Entwuerfe, die finalisiert oder archiviert werden sollten.`
)
}
if (result.issues.length === 0) {
lines.push(
'Alle Policies sind konform. Stellen Sie sicher, dass die naechsten Pruefungstermine eingehalten werden.'
)
}
lines.push('')
// Footer
lines.push('---')
lines.push('')
lines.push(
'*Dieser Bericht wurde automatisch generiert und ersetzt keine rechtliche Beratung. Die Verantwortung fuer die DSGVO-Konformitaet liegt beim Verantwortlichen (Art. 4 Nr. 7 DSGVO).*'
)
return lines.join('\n')
}
// =============================================================================
// BROWSER DOWNLOAD UTILITY
// =============================================================================
/**
* Loest einen Datei-Download im Browser aus.
* Erstellt ein temporaeres Blob-URL und simuliert einen Link-Klick.
*
* @param content - Der Dateiinhalt als String
* @param filename - Der gewuenschte Dateiname (z.B. "loeschfristen-export.json")
* @param mimeType - Der MIME-Typ (z.B. "application/json", "text/csv;charset=utf-8")
*/
export function downloadFile(
content: string,
filename: string,
mimeType: string
): void {
const blob = new Blob([content], { type: mimeType })
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)
}

View File

@@ -1,538 +0,0 @@
// =============================================================================
// Loeschfristen Module - Profiling Wizard
// 4-Step Profiling (15 Fragen) zur Generierung von Baseline-Loeschrichtlinien
// =============================================================================
import type { LoeschfristPolicy, StorageLocation } from './loeschfristen-types'
import { BASELINE_TEMPLATES, type BaselineTemplate, templateToPolicy } from './loeschfristen-baseline-catalog'
// =============================================================================
// TYPES
// =============================================================================
export type ProfilingStepId = 'organization' | 'data-categories' | 'systems' | 'special'
export interface ProfilingQuestion {
id: string
step: ProfilingStepId
question: string // German
helpText?: string
type: 'single' | 'multi' | 'boolean' | 'number'
options?: { value: string; label: string }[]
required: boolean
}
export interface ProfilingAnswer {
questionId: string
value: string | string[] | boolean | number
}
export interface ProfilingStep {
id: ProfilingStepId
title: string
description: string
questions: ProfilingQuestion[]
}
export interface ProfilingResult {
matchedTemplates: BaselineTemplate[]
generatedPolicies: LoeschfristPolicy[]
additionalStorageLocations: StorageLocation[]
hasLegalHoldRequirement: boolean
}
// =============================================================================
// PROFILING STEPS (4 Steps, 15 Questions)
// =============================================================================
export const PROFILING_STEPS: ProfilingStep[] = [
// =========================================================================
// Step 1: Organisation (4 Fragen)
// =========================================================================
{
id: 'organization',
title: 'Organisation',
description: 'Allgemeine Informationen zu Ihrem Unternehmen, um branchenspezifische Loeschfristen zu ermitteln.',
questions: [
{
id: 'org-branche',
step: 'organization',
question: 'In welcher Branche ist Ihr Unternehmen taetig?',
helpText: 'Die Branche bestimmt, welche branchenspezifischen Aufbewahrungspflichten relevant sind.',
type: 'single',
options: [
{ value: 'it-software', label: 'IT / Software' },
{ value: 'handel', label: 'Handel' },
{ value: 'dienstleistung', label: 'Dienstleistung' },
{ value: 'gesundheitswesen', label: 'Gesundheitswesen' },
{ value: 'bildung', label: 'Bildung' },
{ value: 'fertigung-industrie', label: 'Fertigung / Industrie' },
{ value: 'finanzwesen', label: 'Finanzwesen' },
{ value: 'oeffentlicher-sektor', label: 'Oeffentlicher Sektor' },
{ value: 'sonstige', label: 'Sonstige' },
],
required: true,
},
{
id: 'org-mitarbeiter',
step: 'organization',
question: 'Wie viele Mitarbeiter hat Ihr Unternehmen?',
helpText: 'Die Unternehmensgroesse beeinflusst den Umfang der erforderlichen Loeschkonzepte.',
type: 'single',
options: [
{ value: '<10', label: 'Weniger als 10' },
{ value: '10-49', label: '10 bis 49' },
{ value: '50-249', label: '50 bis 249' },
{ value: '250+', label: '250 und mehr' },
],
required: true,
},
{
id: 'org-geschaeftsmodell',
step: 'organization',
question: 'Welches Geschaeftsmodell verfolgen Sie?',
helpText: 'B2B und B2C haben unterschiedliche Anforderungen an die Datenhaltung.',
type: 'single',
options: [
{ value: 'b2b', label: 'B2B (Geschaeftskunden)' },
{ value: 'b2c', label: 'B2C (Endkunden)' },
{ value: 'beides', label: 'Beides (B2B und B2C)' },
],
required: true,
},
{
id: 'org-website',
step: 'organization',
question: 'Betreiben Sie eine Website oder Online-Praesenz?',
helpText: 'Websites erzeugen Webserver-Logs und erfordern Cookie-Consent-Verwaltung.',
type: 'boolean',
required: true,
},
],
},
// =========================================================================
// Step 2: Datenkategorien (5 Fragen)
// =========================================================================
{
id: 'data-categories',
title: 'Datenkategorien',
description: 'Welche Arten personenbezogener Daten verarbeiten Sie? Dies bestimmt die relevanten Aufbewahrungsfristen.',
questions: [
{
id: 'data-hr',
step: 'data-categories',
question: 'Verarbeiten Sie HR-/Personaldaten (Personalakten, Gehaltsabrechnungen, Zeiterfassung)?',
helpText: 'Personalakten unterliegen umfangreichen gesetzlichen Aufbewahrungspflichten (bis zu 10 Jahre).',
type: 'boolean',
required: true,
},
{
id: 'data-buchhaltung',
step: 'data-categories',
question: 'Fuehren Sie eine Buchhaltung mit Finanzdaten (Rechnungen, Belege, Steuererklarungen)?',
helpText: 'Buchhaltungsunterlagen muessen gemaess HGB und AO bis zu 10 Jahre aufbewahrt werden.',
type: 'boolean',
required: true,
},
{
id: 'data-vertraege',
step: 'data-categories',
question: 'Verwalten Sie Vertraege mit Kunden oder Lieferanten?',
helpText: 'Vertragsunterlagen und Geschaeftsbriefe haben spezifische Aufbewahrungspflichten.',
type: 'boolean',
required: true,
},
{
id: 'data-marketing',
step: 'data-categories',
question: 'Betreiben Sie Marketing-Aktivitaeten (Newsletter, CRM-Kampagnen)?',
helpText: 'Marketing-Einwilligungen und Kontakthistorien muessen dokumentiert und verwaltet werden.',
type: 'boolean',
required: true,
},
{
id: 'data-video',
step: 'data-categories',
question: 'Setzen Sie Videoueberwachung ein?',
helpText: 'Videoueberwachungsdaten haben besonders kurze Loeschfristen (in der Regel 72 Stunden).',
type: 'boolean',
required: true,
},
],
},
// =========================================================================
// Step 3: Systeme (3 Fragen)
// =========================================================================
{
id: 'systems',
title: 'Systeme & Infrastruktur',
description: 'Welche IT-Systeme und Infrastruktur nutzen Sie? Dies beeinflusst die Speicherorte in Ihrem Loeschkonzept.',
questions: [
{
id: 'sys-cloud',
step: 'systems',
question: 'Nutzen Sie Cloud-Dienste zur Datenspeicherung oder -verarbeitung?',
helpText: 'Cloud-Speicherorte muessen in den Loeschrichtlinien als separate Speicherorte dokumentiert werden.',
type: 'boolean',
required: true,
},
{
id: 'sys-backup',
step: 'systems',
question: 'Haben Sie Backup-Systeme im Einsatz?',
helpText: 'Backups erfordern eine eigene Loeschstrategie, da Daten dort nach der primaeren Loeschung weiter existieren koennen.',
type: 'boolean',
required: true,
},
{
id: 'sys-erp',
step: 'systems',
question: 'Setzen Sie ein ERP- oder CRM-System ein?',
helpText: 'ERP-/CRM-Systeme sind haeufig zentrale Speicherorte fuer Kunden- und Geschaeftsdaten.',
type: 'boolean',
required: true,
},
],
},
// =========================================================================
// Step 4: Spezielle Anforderungen (3 Fragen)
// =========================================================================
{
id: 'special',
title: 'Spezielle Anforderungen',
description: 'Gibt es besondere rechtliche oder organisatorische Anforderungen, die Ihr Loeschkonzept beeinflussen?',
questions: [
{
id: 'special-legal-hold',
step: 'special',
question: 'Gibt es Legal-Hold-Anforderungen (z.B. laufende Rechtsstreitigkeiten, behoerdliche Untersuchungen)?',
helpText: 'Bei einem Legal Hold muessen betroffene Daten trotz abgelaufener Loeschfristen aufbewahrt werden.',
type: 'boolean',
required: true,
},
{
id: 'special-archivierung',
step: 'special',
question: 'Benoetigen Sie eine Langzeitarchivierung von Dokumenten?',
helpText: 'Langzeitarchivierung kann ueber die gesetzlichen Mindestfristen hinausgehen und erfordert eine gesonderte Rechtfertigung.',
type: 'boolean',
required: true,
},
{
id: 'special-gesundheit',
step: 'special',
question: 'Verarbeiten Sie Gesundheitsdaten (z.B. Krankmeldungen, Arbeitsmedizin)?',
helpText: 'Gesundheitsdaten sind besonders schuetzenswerte Daten nach Art. 9 DSGVO und unterliegen strengeren Anforderungen.',
type: 'boolean',
required: true,
},
],
},
]
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
/**
* Retrieve the value of a specific answer by question ID.
*/
export function getAnswerValue(answers: ProfilingAnswer[], questionId: string): unknown {
const answer = answers.find(a => a.questionId === questionId)
return answer?.value ?? undefined
}
/**
* Check whether all required questions in a given step have been answered.
*/
export function isStepComplete(answers: ProfilingAnswer[], stepId: ProfilingStepId): boolean {
const step = PROFILING_STEPS.find(s => s.id === stepId)
if (!step) return false
return step.questions
.filter(q => q.required)
.every(q => {
const answer = answers.find(a => a.questionId === q.id)
if (!answer) return false
// Check that the value is not empty
const val = answer.value
if (val === undefined || val === null) return false
if (typeof val === 'string' && val.trim() === '') return false
if (Array.isArray(val) && val.length === 0) return false
return true
})
}
/**
* Calculate overall profiling progress as a percentage (0-100).
*/
export function getProfilingProgress(answers: ProfilingAnswer[]): number {
const totalRequired = PROFILING_STEPS.reduce(
(sum, step) => sum + step.questions.filter(q => q.required).length,
0
)
if (totalRequired === 0) return 100
const answeredRequired = PROFILING_STEPS.reduce((sum, step) => {
return (
sum +
step.questions.filter(q => q.required).filter(q => {
const answer = answers.find(a => a.questionId === q.id)
if (!answer) return false
const val = answer.value
if (val === undefined || val === null) return false
if (typeof val === 'string' && val.trim() === '') return false
if (Array.isArray(val) && val.length === 0) return false
return true
}).length
)
}, 0)
return Math.round((answeredRequired / totalRequired) * 100)
}
// =============================================================================
// CORE GENERATOR
// =============================================================================
/**
* Generate deletion policies based on the profiling answers.
*
* Logic:
* - Match baseline templates based on boolean and categorical answers
* - Deduplicate matched templates by templateId
* - Convert matched templates to full LoeschfristPolicy objects
* - Add additional storage locations (Cloud, Backup) if applicable
* - Detect legal hold requirements
*/
export function generatePoliciesFromProfile(answers: ProfilingAnswer[]): ProfilingResult {
const matchedTemplateIds = new Set<string>()
// -------------------------------------------------------------------------
// Helper to get a boolean answer
// -------------------------------------------------------------------------
const getBool = (questionId: string): boolean => {
const val = getAnswerValue(answers, questionId)
return val === true
}
const getString = (questionId: string): string => {
const val = getAnswerValue(answers, questionId)
return typeof val === 'string' ? val : ''
}
// -------------------------------------------------------------------------
// Always-included templates (universally recommended)
// -------------------------------------------------------------------------
matchedTemplateIds.add('protokolle-gesellschafter')
// -------------------------------------------------------------------------
// HR data (data-hr = true)
// -------------------------------------------------------------------------
if (getBool('data-hr')) {
matchedTemplateIds.add('personal-akten')
matchedTemplateIds.add('gehaltsabrechnungen')
matchedTemplateIds.add('zeiterfassung')
matchedTemplateIds.add('bewerbungsunterlagen')
matchedTemplateIds.add('krankmeldungen')
}
// -------------------------------------------------------------------------
// Buchhaltung (data-buchhaltung = true)
// -------------------------------------------------------------------------
if (getBool('data-buchhaltung')) {
matchedTemplateIds.add('buchhaltungsbelege')
matchedTemplateIds.add('rechnungen')
matchedTemplateIds.add('steuererklaerungen')
}
// -------------------------------------------------------------------------
// Vertraege (data-vertraege = true)
// -------------------------------------------------------------------------
if (getBool('data-vertraege')) {
matchedTemplateIds.add('vertraege')
matchedTemplateIds.add('geschaeftsbriefe')
matchedTemplateIds.add('kundenstammdaten')
}
// -------------------------------------------------------------------------
// Marketing (data-marketing = true)
// -------------------------------------------------------------------------
if (getBool('data-marketing')) {
matchedTemplateIds.add('newsletter-einwilligungen')
matchedTemplateIds.add('crm-kontakthistorie')
matchedTemplateIds.add('cookie-consent-logs')
}
// -------------------------------------------------------------------------
// Video (data-video = true)
// -------------------------------------------------------------------------
if (getBool('data-video')) {
matchedTemplateIds.add('videoueberwachung')
}
// -------------------------------------------------------------------------
// Website (org-website = true)
// -------------------------------------------------------------------------
if (getBool('org-website')) {
matchedTemplateIds.add('webserver-logs')
matchedTemplateIds.add('cookie-consent-logs')
}
// -------------------------------------------------------------------------
// ERP/CRM (sys-erp = true)
// -------------------------------------------------------------------------
if (getBool('sys-erp')) {
matchedTemplateIds.add('kundenstammdaten')
matchedTemplateIds.add('crm-kontakthistorie')
}
// -------------------------------------------------------------------------
// Backup (sys-backup = true)
// -------------------------------------------------------------------------
if (getBool('sys-backup')) {
matchedTemplateIds.add('backup-daten')
}
// -------------------------------------------------------------------------
// Gesundheitsdaten (special-gesundheit = true)
// -------------------------------------------------------------------------
if (getBool('special-gesundheit')) {
// Ensure krankmeldungen is included even without full HR data
matchedTemplateIds.add('krankmeldungen')
}
// -------------------------------------------------------------------------
// Resolve matched templates from catalog
// -------------------------------------------------------------------------
const matchedTemplates: BaselineTemplate[] = []
for (const templateId of matchedTemplateIds) {
const template = BASELINE_TEMPLATES.find(t => t.templateId === templateId)
if (template) {
matchedTemplates.push(template)
}
}
// -------------------------------------------------------------------------
// Convert to policies
// -------------------------------------------------------------------------
const generatedPolicies: LoeschfristPolicy[] = matchedTemplates.map(template =>
templateToPolicy(template)
)
// -------------------------------------------------------------------------
// Additional storage locations
// -------------------------------------------------------------------------
const additionalStorageLocations: StorageLocation[] = []
if (getBool('sys-cloud')) {
const cloudLocation: StorageLocation = {
id: crypto.randomUUID(),
name: 'Cloud-Speicher',
type: 'CLOUD',
isBackup: false,
provider: null,
deletionCapable: true,
}
additionalStorageLocations.push(cloudLocation)
// Add Cloud storage location to all generated policies
for (const policy of generatedPolicies) {
policy.storageLocations.push({ ...cloudLocation, id: crypto.randomUUID() })
}
}
if (getBool('sys-backup')) {
const backupLocation: StorageLocation = {
id: crypto.randomUUID(),
name: 'Backup-System',
type: 'BACKUP',
isBackup: true,
provider: null,
deletionCapable: true,
}
additionalStorageLocations.push(backupLocation)
// Add Backup storage location to all generated policies
for (const policy of generatedPolicies) {
policy.storageLocations.push({ ...backupLocation, id: crypto.randomUUID() })
}
}
// -------------------------------------------------------------------------
// Legal Hold
// -------------------------------------------------------------------------
const hasLegalHoldRequirement = getBool('special-legal-hold')
// If legal hold is active, mark all generated policies accordingly
if (hasLegalHoldRequirement) {
for (const policy of generatedPolicies) {
policy.hasActiveLegalHold = true
policy.deletionTrigger = 'LEGAL_HOLD'
}
}
// -------------------------------------------------------------------------
// Tag policies with profiling metadata
// -------------------------------------------------------------------------
const branche = getString('org-branche')
const mitarbeiter = getString('org-mitarbeiter')
for (const policy of generatedPolicies) {
policy.tags = [
...policy.tags,
'profiling-generated',
...(branche ? [`branche:${branche}`] : []),
...(mitarbeiter ? [`groesse:${mitarbeiter}`] : []),
]
}
return {
matchedTemplates,
generatedPolicies,
additionalStorageLocations,
hasLegalHoldRequirement,
}
}
// =============================================================================
// COMPLIANCE SCOPE INTEGRATION
// =============================================================================
/**
* Prefill Loeschfristen profiling answers from Compliance Scope Engine answers.
* The Scope Engine acts as the "Single Source of Truth" for organizational questions.
*/
export function prefillFromScopeAnswers(
scopeAnswers: import('./compliance-scope-types').ScopeProfilingAnswer[]
): ProfilingAnswer[] {
const { exportToLoeschfristenAnswers } = require('./compliance-scope-profiling')
const exported = exportToLoeschfristenAnswers(scopeAnswers) as Array<{ questionId: string; value: unknown }>
return exported.map(item => ({
questionId: item.questionId,
value: item.value as string | string[] | boolean | number,
}))
}
/**
* Get the list of Loeschfristen question IDs that are prefilled from Scope answers.
* These questions should show "Aus Scope-Analyse uebernommen" hint.
*/
export const SCOPE_PREFILLED_LF_QUESTIONS = [
'org-branche',
'org-mitarbeiter',
'org-geschaeftsmodell',
'org-website',
'data-hr',
'data-buchhaltung',
'data-vertraege',
'data-marketing',
'data-video',
'sys-cloud',
'sys-erp',
]

View File

@@ -1,414 +0,0 @@
// =============================================================================
// 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

@@ -1,427 +0,0 @@
// =============================================================================
// 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

@@ -1,710 +0,0 @@
'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: 'BULK_UPDATE_TOMS'; payload: { updates: Array<{ id: string; data: Partial<DerivedTOM> }> } }
| { 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 'BULK_UPDATE_TOMS': {
let updatedTOMs = [...state.derivedTOMs]
for (const update of action.payload.updates) {
updatedTOMs = updatedTOMs.map((tom) =>
tom.id === update.id ? { ...tom, ...update.data } : tom
)
}
return updateState({ derivedTOMs: updatedTOMs })
}
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
bulkUpdateTOMs: (updates: Array<{ 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

@@ -1,518 +0,0 @@
// =============================================================================
// 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

@@ -1,67 +0,0 @@
// =============================================================================
// 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

@@ -1,525 +0,0 @@
// =============================================================================
// 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

@@ -1,517 +0,0 @@
// =============================================================================
// 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

@@ -1,544 +0,0 @@
// =============================================================================
// 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

@@ -1,206 +0,0 @@
// =============================================================================
// 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

@@ -1,560 +0,0 @@
// =============================================================================
// 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

@@ -1,192 +0,0 @@
// =============================================================================
// SDM (Standard-Datenschutzmodell) Mapping
// Maps ControlCategories to SDM Gewaehrleistungsziele and Spec Modules
// =============================================================================
import { ControlCategory } from './types'
// =============================================================================
// TYPES
// =============================================================================
export type SDMGewaehrleistungsziel =
| 'Verfuegbarkeit'
| 'Integritaet'
| 'Vertraulichkeit'
| 'Nichtverkettung'
| 'Intervenierbarkeit'
| 'Transparenz'
| 'Datenminimierung'
export type TOMModuleCategory =
| 'IDENTITY_AUTH'
| 'LOGGING'
| 'DOCUMENTATION'
| 'SEPARATION'
| 'RETENTION'
| 'DELETION'
| 'TRAINING'
| 'REVIEW'
export const SDM_GOAL_LABELS: Record<SDMGewaehrleistungsziel, string> = {
Verfuegbarkeit: 'Verfuegbarkeit',
Integritaet: 'Integritaet',
Vertraulichkeit: 'Vertraulichkeit',
Nichtverkettung: 'Nichtverkettung',
Intervenierbarkeit: 'Intervenierbarkeit',
Transparenz: 'Transparenz',
Datenminimierung: 'Datenminimierung',
}
export const SDM_GOAL_DESCRIPTIONS: Record<SDMGewaehrleistungsziel, string> = {
Verfuegbarkeit: 'Personenbezogene Daten muessen zeitgerecht zur Verfuegung stehen und ordnungsgemaess verarbeitet werden koennen.',
Integritaet: 'Personenbezogene Daten muessen unversehrt, vollstaendig und aktuell bleiben.',
Vertraulichkeit: 'Nur Befugte duerfen personenbezogene Daten zur Kenntnis nehmen.',
Nichtverkettung: 'Daten duerfen nicht ohne Weiteres fuer andere Zwecke zusammengefuehrt werden.',
Intervenierbarkeit: 'Betroffene muessen ihre Rechte wahrnehmen koennen (Auskunft, Berichtigung, Loeschung).',
Transparenz: 'Verarbeitungsvorgaenge muessen nachvollziehbar dokumentiert sein.',
Datenminimierung: 'Nur die fuer den Zweck erforderlichen Daten duerfen verarbeitet werden.',
}
export const MODULE_LABELS: Record<TOMModuleCategory, string> = {
IDENTITY_AUTH: 'Identitaet & Authentifizierung',
LOGGING: 'Protokollierung',
DOCUMENTATION: 'Dokumentation',
SEPARATION: 'Trennung',
RETENTION: 'Aufbewahrung',
DELETION: 'Loeschung & Vernichtung',
TRAINING: 'Schulung & Vertraulichkeit',
REVIEW: 'Ueberpruefung & Bewertung',
}
// =============================================================================
// MAPPINGS
// =============================================================================
/**
* Maps ControlCategory to its primary SDM Gewaehrleistungsziele
*/
export const SDM_CATEGORY_MAPPING: Record<ControlCategory, SDMGewaehrleistungsziel[]> = {
ACCESS_CONTROL: ['Vertraulichkeit'],
ADMISSION_CONTROL: ['Vertraulichkeit', 'Integritaet'],
ACCESS_AUTHORIZATION: ['Vertraulichkeit', 'Nichtverkettung'],
TRANSFER_CONTROL: ['Vertraulichkeit', 'Integritaet'],
INPUT_CONTROL: ['Integritaet', 'Transparenz'],
ORDER_CONTROL: ['Transparenz', 'Intervenierbarkeit'],
AVAILABILITY: ['Verfuegbarkeit'],
SEPARATION: ['Nichtverkettung', 'Datenminimierung'],
ENCRYPTION: ['Vertraulichkeit', 'Integritaet'],
PSEUDONYMIZATION: ['Datenminimierung', 'Nichtverkettung'],
RESILIENCE: ['Verfuegbarkeit'],
RECOVERY: ['Verfuegbarkeit', 'Integritaet'],
REVIEW: ['Transparenz', 'Intervenierbarkeit'],
}
/**
* Maps ControlCategory to Spec Module Categories
*/
export const MODULE_CATEGORY_MAPPING: Record<ControlCategory, TOMModuleCategory[]> = {
ACCESS_CONTROL: ['IDENTITY_AUTH'],
ADMISSION_CONTROL: ['IDENTITY_AUTH'],
ACCESS_AUTHORIZATION: ['IDENTITY_AUTH', 'DOCUMENTATION'],
TRANSFER_CONTROL: ['DOCUMENTATION'],
INPUT_CONTROL: ['LOGGING'],
ORDER_CONTROL: ['DOCUMENTATION'],
AVAILABILITY: ['REVIEW'],
SEPARATION: ['SEPARATION'],
ENCRYPTION: ['IDENTITY_AUTH'],
PSEUDONYMIZATION: ['SEPARATION', 'DELETION'],
RESILIENCE: ['REVIEW'],
RECOVERY: ['REVIEW'],
REVIEW: ['REVIEW', 'TRAINING'],
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
import type { DerivedTOM, ControlLibraryEntry } from './types'
import { getControlById } from './controls/loader'
/**
* Get SDM goals for a given control (by looking up its category)
*/
export function getSDMGoalsForControl(controlId: string): SDMGewaehrleistungsziel[] {
const control = getControlById(controlId)
if (!control) return []
return SDM_CATEGORY_MAPPING[control.category] || []
}
/**
* Get derived TOMs that map to a specific SDM goal
*/
export function getTOMsBySDMGoal(
toms: DerivedTOM[],
goal: SDMGewaehrleistungsziel
): DerivedTOM[] {
return toms.filter(tom => {
const goals = getSDMGoalsForControl(tom.controlId)
return goals.includes(goal)
})
}
/**
* Get derived TOMs belonging to a specific module
*/
export function getTOMsByModule(
toms: DerivedTOM[],
module: TOMModuleCategory
): DerivedTOM[] {
return toms.filter(tom => {
const control = getControlById(tom.controlId)
if (!control) return false
const modules = MODULE_CATEGORY_MAPPING[control.category] || []
return modules.includes(module)
})
}
/**
* Get SDM goal coverage statistics
*/
export function getSDMCoverageStats(toms: DerivedTOM[]): Record<SDMGewaehrleistungsziel, {
total: number
implemented: number
partial: number
missing: number
}> {
const goals = Object.keys(SDM_GOAL_LABELS) as SDMGewaehrleistungsziel[]
const stats = {} as Record<SDMGewaehrleistungsziel, { total: number; implemented: number; partial: number; missing: number }>
for (const goal of goals) {
const goalTOMs = getTOMsBySDMGoal(toms, goal)
stats[goal] = {
total: goalTOMs.length,
implemented: goalTOMs.filter(t => t.implementationStatus === 'IMPLEMENTED').length,
partial: goalTOMs.filter(t => t.implementationStatus === 'PARTIAL').length,
missing: goalTOMs.filter(t => t.implementationStatus === 'NOT_IMPLEMENTED').length,
}
}
return stats
}
/**
* Get module coverage statistics
*/
export function getModuleCoverageStats(toms: DerivedTOM[]): Record<TOMModuleCategory, {
total: number
implemented: number
}> {
const modules = Object.keys(MODULE_LABELS) as TOMModuleCategory[]
const stats = {} as Record<TOMModuleCategory, { total: number; implemented: number }>
for (const mod of modules) {
const modTOMs = getTOMsByModule(toms, mod)
stats[mod] = {
total: modTOMs.length,
implemented: modTOMs.filter(t => t.implementationStatus === 'IMPLEMENTED').length,
}
}
return stats
}

View File

@@ -1,963 +0,0 @@
// =============================================================================
// 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
// =============================================================================
// SDM TYPES (Standard-Datenschutzmodell)
// =============================================================================
export type SDMGewaehrleistungsziel =
| 'Verfuegbarkeit'
| 'Integritaet'
| 'Vertraulichkeit'
| 'Nichtverkettung'
| 'Intervenierbarkeit'
| 'Transparenz'
| 'Datenminimierung'
export type TOMModuleCategory =
| 'IDENTITY_AUTH'
| 'LOGGING'
| 'DOCUMENTATION'
| 'SEPARATION'
| 'RETENTION'
| 'DELETION'
| 'TRAINING'
| 'REVIEW'
/**
* Maps ControlCategory to SDM Gewaehrleistungsziele.
* Used by the TOM Dashboard to display SDM coverage.
*/
export const SDM_CATEGORY_MAPPING: Record<ControlCategory, SDMGewaehrleistungsziel[]> = {
ACCESS_CONTROL: ['Vertraulichkeit'],
ADMISSION_CONTROL: ['Vertraulichkeit', 'Integritaet'],
ACCESS_AUTHORIZATION: ['Vertraulichkeit', 'Nichtverkettung'],
TRANSFER_CONTROL: ['Vertraulichkeit', 'Integritaet'],
INPUT_CONTROL: ['Integritaet', 'Transparenz'],
ORDER_CONTROL: ['Transparenz', 'Intervenierbarkeit'],
AVAILABILITY: ['Verfuegbarkeit'],
SEPARATION: ['Nichtverkettung', 'Datenminimierung'],
ENCRYPTION: ['Vertraulichkeit', 'Integritaet'],
PSEUDONYMIZATION: ['Datenminimierung', 'Nichtverkettung'],
RESILIENCE: ['Verfuegbarkeit'],
RECOVERY: ['Verfuegbarkeit', 'Integritaet'],
REVIEW: ['Transparenz', 'Intervenierbarkeit'],
}
/**
* Maps ControlCategory to Spec Module Categories.
*/
export const MODULE_CATEGORY_MAPPING: Record<ControlCategory, TOMModuleCategory[]> = {
ACCESS_CONTROL: ['IDENTITY_AUTH'],
ADMISSION_CONTROL: ['IDENTITY_AUTH'],
ACCESS_AUTHORIZATION: ['IDENTITY_AUTH', 'DOCUMENTATION'],
TRANSFER_CONTROL: ['DOCUMENTATION'],
INPUT_CONTROL: ['LOGGING'],
ORDER_CONTROL: ['DOCUMENTATION'],
AVAILABILITY: ['REVIEW'],
SEPARATION: ['SEPARATION'],
ENCRYPTION: ['IDENTITY_AUTH'],
PSEUDONYMIZATION: ['SEPARATION', 'DELETION'],
RESILIENCE: ['REVIEW'],
RECOVERY: ['REVIEW'],
REVIEW: ['REVIEW', 'TRAINING'],
}

View File

@@ -1,492 +0,0 @@
/**
* VVT Profiling — Generator-Fragebogen
*
* ~25 Fragen in 6 Schritten, die auf Basis der Antworten
* Baseline-Verarbeitungstaetigkeiten generieren.
*/
import { VVT_BASELINE_CATALOG, templateToActivity } from './vvt-baseline-catalog'
import { generateVVTId } from './vvt-types'
import type { VVTActivity, BusinessFunction } from './vvt-types'
// =============================================================================
// TYPES
// =============================================================================
export interface ProfilingQuestion {
id: string
step: number
question: string
type: 'single_choice' | 'multi_choice' | 'number' | 'text' | 'boolean'
options?: { value: string; label: string }[]
helpText?: string
triggersTemplates: string[] // Template-IDs that get activated when answered positively
}
export interface ProfilingStep {
step: number
title: string
description: string
}
export interface ProfilingAnswers {
[questionId: string]: string | string[] | number | boolean
}
export interface ProfilingResult {
answers: ProfilingAnswers
generatedActivities: VVTActivity[]
coverageScore: number
art30Abs5Exempt: boolean
}
// =============================================================================
// STEPS
// =============================================================================
export const PROFILING_STEPS: ProfilingStep[] = [
{ step: 1, title: 'Organisation', description: 'Grunddaten zu Ihrem Unternehmen' },
{ step: 2, title: 'Geschaeftsbereiche', description: 'Welche Bereiche sind aktiv?' },
{ step: 3, title: 'Systeme & Tools', description: 'Welche IT-Systeme nutzen Sie?' },
{ step: 4, title: 'Datenkategorien', description: 'Welche besonderen Daten verarbeiten Sie?' },
{ step: 5, title: 'Drittlandtransfers', description: 'Transfers ausserhalb der EU/EWR' },
{ step: 6, title: 'Besondere Verarbeitungen', description: 'KI, Scoring, Ueberwachung' },
]
// =============================================================================
// QUESTIONS
// =============================================================================
export const PROFILING_QUESTIONS: ProfilingQuestion[] = [
// === STEP 1: Organisation ===
{
id: 'org_industry',
step: 1,
question: 'In welcher Branche ist Ihr Unternehmen taetig?',
type: 'single_choice',
options: [
{ value: 'it_software', label: 'IT & Software' },
{ value: 'healthcare', label: 'Gesundheitswesen' },
{ value: 'education', label: 'Bildung & Erziehung' },
{ value: 'finance', label: 'Finanzdienstleistungen' },
{ value: 'retail', label: 'Handel & E-Commerce' },
{ value: 'manufacturing', label: 'Produktion & Industrie' },
{ value: 'consulting', label: 'Beratung & Dienstleistung' },
{ value: 'public', label: 'Oeffentlicher Sektor' },
{ value: 'other', label: 'Sonstige' },
],
triggersTemplates: [],
},
{
id: 'org_employees',
step: 1,
question: 'Wie viele Mitarbeiter hat Ihr Unternehmen?',
type: 'number',
helpText: 'Relevant fuer Art. 30 Abs. 5 DSGVO (Ausnahme < 250 Mitarbeiter)',
triggersTemplates: [],
},
{
id: 'org_locations',
step: 1,
question: 'An wie vielen Standorten ist Ihr Unternehmen taetig?',
type: 'single_choice',
options: [
{ value: '1', label: '1 Standort' },
{ value: '2-5', label: '2-5 Standorte' },
{ value: '6-20', label: '6-20 Standorte' },
{ value: '20+', label: 'Mehr als 20 Standorte' },
],
triggersTemplates: [],
},
{
id: 'org_b2b_b2c',
step: 1,
question: 'Welches Geschaeftsmodell betreiben Sie?',
type: 'single_choice',
options: [
{ value: 'b2b', label: 'B2B (Geschaeftskunden)' },
{ value: 'b2c', label: 'B2C (Endkunden)' },
{ value: 'both', label: 'Beides (B2B + B2C)' },
{ value: 'b2g', label: 'B2G (Oeffentlicher Sektor)' },
],
triggersTemplates: [],
},
// === STEP 2: Geschaeftsbereiche ===
{
id: 'dept_hr',
step: 2,
question: 'Haben Sie eine Personalabteilung / HR?',
type: 'boolean',
triggersTemplates: ['hr-mitarbeiterverwaltung', 'hr-gehaltsabrechnung', 'hr-zeiterfassung'],
},
{
id: 'dept_recruiting',
step: 2,
question: 'Betreiben Sie aktives Recruiting / Bewerbermanagement?',
type: 'boolean',
triggersTemplates: ['hr-bewerbermanagement'],
},
{
id: 'dept_finance',
step: 2,
question: 'Haben Sie eine Finanz-/Buchhaltungsabteilung?',
type: 'boolean',
triggersTemplates: ['finance-buchhaltung', 'finance-zahlungsverkehr'],
},
{
id: 'dept_sales',
step: 2,
question: 'Haben Sie einen Vertrieb / Kundenverwaltung?',
type: 'boolean',
triggersTemplates: ['sales-kundenverwaltung', 'sales-vertriebssteuerung'],
},
{
id: 'dept_marketing',
step: 2,
question: 'Betreiben Sie Marketing-Aktivitaeten?',
type: 'boolean',
triggersTemplates: ['marketing-social-media'],
},
{
id: 'dept_support',
step: 2,
question: 'Haben Sie einen Kundenservice / Support?',
type: 'boolean',
triggersTemplates: ['support-ticketsystem'],
},
// === STEP 3: Systeme & Tools ===
{
id: 'sys_crm',
step: 3,
question: 'Nutzen Sie ein CRM-System (z.B. Salesforce, HubSpot, Pipedrive)?',
type: 'boolean',
triggersTemplates: ['sales-kundenverwaltung'],
},
{
id: 'sys_website_analytics',
step: 3,
question: 'Nutzen Sie Website-Analytics (z.B. Matomo, Google Analytics)?',
type: 'boolean',
triggersTemplates: ['marketing-website-analytics'],
},
{
id: 'sys_newsletter',
step: 3,
question: 'Versenden Sie Newsletter (z.B. Mailchimp, CleverReach)?',
type: 'boolean',
triggersTemplates: ['marketing-newsletter'],
},
{
id: 'sys_video',
step: 3,
question: 'Nutzen Sie Videokonferenz-Tools (z.B. Zoom, Teams, Jitsi)?',
type: 'boolean',
triggersTemplates: ['other-videokonferenz'],
},
{
id: 'sys_erp',
step: 3,
question: 'Nutzen Sie ein ERP-System?',
type: 'boolean',
helpText: 'z.B. SAP, ERPNext, Microsoft Dynamics',
triggersTemplates: ['finance-buchhaltung'],
},
{
id: 'sys_visitor',
step: 3,
question: 'Haben Sie ein Besuchermanagement-System?',
type: 'boolean',
triggersTemplates: ['other-besuchermanagement'],
},
// === STEP 4: Datenkategorien ===
{
id: 'data_health',
step: 4,
question: 'Verarbeiten Sie Gesundheitsdaten (Art. 9 DSGVO)?',
type: 'boolean',
helpText: 'z.B. Krankmeldungen, Arbeitsmedizin, Gesundheitsversorgung',
triggersTemplates: [],
},
{
id: 'data_minors',
step: 4,
question: 'Verarbeiten Sie Daten von Minderjaehrigen?',
type: 'boolean',
helpText: 'z.B. Schueler, Kinder unter 16 Jahren',
triggersTemplates: [],
},
{
id: 'data_biometric',
step: 4,
question: 'Verarbeiten Sie biometrische Daten zur Identifizierung?',
type: 'boolean',
helpText: 'z.B. Fingerabdruck, Gesichtserkennung, Stimmerkennung',
triggersTemplates: [],
},
{
id: 'data_criminal',
step: 4,
question: 'Verarbeiten Sie Daten ueber strafrechtliche Verurteilungen (Art. 10 DSGVO)?',
type: 'boolean',
helpText: 'z.B. Fuehrungszeugnisse',
triggersTemplates: [],
},
// === STEP 5: Drittlandtransfers ===
{
id: 'transfer_cloud_us',
step: 5,
question: 'Nutzen Sie Cloud-Dienste mit Sitz in den USA?',
type: 'boolean',
helpText: 'z.B. AWS, Azure, Google Cloud, Microsoft 365',
triggersTemplates: [],
},
{
id: 'transfer_support_non_eu',
step: 5,
question: 'Haben Sie Support-Mitarbeiter oder Dienstleister ausserhalb der EU?',
type: 'boolean',
triggersTemplates: [],
},
{
id: 'transfer_subprocessor',
step: 5,
question: 'Nutzen Sie Auftragsverarbeiter mit Unteraufragnehmern in Drittlaendern?',
type: 'boolean',
triggersTemplates: [],
},
// === STEP 6: Besondere Verarbeitungen ===
{
id: 'special_ai',
step: 6,
question: 'Setzen Sie KI oder automatisierte Entscheidungsfindung ein?',
type: 'boolean',
helpText: 'z.B. Chatbots, Scoring, Profiling, automatische Bewertungen',
triggersTemplates: [],
},
{
id: 'special_video_surveillance',
step: 6,
question: 'Betreiben Sie Videoueberwachung?',
type: 'boolean',
triggersTemplates: [],
},
{
id: 'special_tracking',
step: 6,
question: 'Betreiben Sie umfangreiches Nutzer-Tracking oder Profiling?',
type: 'boolean',
helpText: 'z.B. Verhaltensprofiling, Cross-Device-Tracking',
triggersTemplates: [],
},
]
// =============================================================================
// GENERATOR LOGIC
// =============================================================================
export function generateActivities(answers: ProfilingAnswers): ProfilingResult {
// Collect all triggered template IDs
const triggeredIds = new Set<string>()
for (const question of PROFILING_QUESTIONS) {
const answer = answers[question.id]
if (!answer) continue
// Boolean questions: if true, trigger templates
if (question.type === 'boolean' && answer === true) {
question.triggersTemplates.forEach(id => triggeredIds.add(id))
}
}
// Always add IT baseline templates (every company needs these)
triggeredIds.add('it-systemadministration')
triggeredIds.add('it-backup')
triggeredIds.add('it-logging')
triggeredIds.add('it-iam')
// Generate activities from triggered templates
const existingIds: string[] = []
const activities: VVTActivity[] = []
for (const templateId of triggeredIds) {
const template = VVT_BASELINE_CATALOG.find(t => t.templateId === templateId)
if (!template) continue
const vvtId = generateVVTId(existingIds)
existingIds.push(vvtId)
const activity = templateToActivity(template, vvtId)
// Enrich with profiling answers
enrichActivityFromAnswers(activity, answers)
activities.push(activity)
}
// Calculate coverage score
const totalFields = activities.length * 12 // 12 key fields per activity
let filledFields = 0
for (const a of activities) {
if (a.name) filledFields++
if (a.description) filledFields++
if (a.purposes.length > 0) filledFields++
if (a.legalBases.length > 0) filledFields++
if (a.dataSubjectCategories.length > 0) filledFields++
if (a.personalDataCategories.length > 0) filledFields++
if (a.recipientCategories.length > 0) filledFields++
if (a.retentionPeriod.description) filledFields++
if (a.tomDescription) filledFields++
if (a.businessFunction !== 'other') filledFields++
if (a.structuredToms.accessControl.length > 0) filledFields++
if (a.responsible || a.owner) filledFields++
}
const coverageScore = totalFields > 0 ? Math.round((filledFields / totalFields) * 100) : 0
// Art. 30 Abs. 5 check
const employeeCount = typeof answers.org_employees === 'number' ? answers.org_employees : 0
const hasSpecialCategories = answers.data_health === true || answers.data_biometric === true || answers.data_criminal === true
const art30Abs5Exempt = employeeCount < 250 && !hasSpecialCategories
return {
answers,
generatedActivities: activities,
coverageScore,
art30Abs5Exempt,
}
}
// =============================================================================
// ENRICHMENT
// =============================================================================
function enrichActivityFromAnswers(activity: VVTActivity, answers: ProfilingAnswers): void {
// Add third-country transfers if US cloud is used
if (answers.transfer_cloud_us === true) {
activity.thirdCountryTransfers.push({
country: 'US',
recipient: 'Cloud-Dienstleister (USA)',
transferMechanism: 'SCC_PROCESSOR',
additionalMeasures: ['Verschluesselung at-rest', 'Transfer Impact Assessment'],
})
}
// Add special data categories if applicable
if (answers.data_health === true) {
if (!activity.personalDataCategories.includes('HEALTH_DATA')) {
// Only add to HR activities
if (activity.businessFunction === 'hr') {
activity.personalDataCategories.push('HEALTH_DATA')
// Ensure Art. 9 legal basis
if (!activity.legalBases.some(lb => lb.type.startsWith('ART9_'))) {
activity.legalBases.push({
type: 'ART9_EMPLOYMENT',
description: 'Arbeitsrechtliche Verarbeitung',
reference: 'Art. 9 Abs. 2 lit. b DSGVO',
})
}
}
}
}
if (answers.data_minors === true) {
if (!activity.dataSubjectCategories.includes('MINORS')) {
// Add to relevant activities (education, app users)
if (activity.businessFunction === 'support' || activity.businessFunction === 'product_engineering') {
activity.dataSubjectCategories.push('MINORS')
}
}
}
// Set DPIA required for special processing
if (answers.special_ai === true || answers.special_video_surveillance === true || answers.special_tracking === true) {
if (answers.special_ai === true && activity.businessFunction === 'product_engineering') {
activity.dpiaRequired = true
}
}
}
// =============================================================================
// HELPERS
// =============================================================================
export function getQuestionsForStep(step: number): ProfilingQuestion[] {
return PROFILING_QUESTIONS.filter(q => q.step === step)
}
export function getStepProgress(answers: ProfilingAnswers, step: number): number {
const questions = getQuestionsForStep(step)
if (questions.length === 0) return 100
const answered = questions.filter(q => {
const a = answers[q.id]
return a !== undefined && a !== null && a !== ''
}).length
return Math.round((answered / questions.length) * 100)
}
export function getTotalProgress(answers: ProfilingAnswers): number {
const total = PROFILING_QUESTIONS.length
if (total === 0) return 100
const answered = PROFILING_QUESTIONS.filter(q => {
const a = answers[q.id]
return a !== undefined && a !== null && a !== ''
}).length
return Math.round((answered / total) * 100)
}
// =============================================================================
// COMPLIANCE SCOPE INTEGRATION
// =============================================================================
/**
* Prefill VVT profiling answers from Compliance Scope Engine answers.
* The Scope Engine acts as the "Single Source of Truth" for organizational questions.
* Redundant questions are auto-filled with a "prefilled" marker.
*/
export function prefillFromScopeAnswers(
scopeAnswers: import('./compliance-scope-types').ScopeProfilingAnswer[]
): ProfilingAnswers {
const { exportToVVTAnswers } = require('./compliance-scope-profiling')
const exported = exportToVVTAnswers(scopeAnswers) as Record<string, unknown>
const prefilled: ProfilingAnswers = {}
for (const [key, value] of Object.entries(exported)) {
if (value !== undefined && value !== null) {
prefilled[key] = value as string | string[] | number | boolean
}
}
return prefilled
}
/**
* Get the list of VVT question IDs that are prefilled from Scope answers.
* These questions should show "Aus Scope-Analyse uebernommen" hint.
*/
export const SCOPE_PREFILLED_VVT_QUESTIONS = [
'org_industry',
'org_employees',
'org_b2b_b2c',
'dept_hr',
'dept_finance',
'dept_marketing',
'data_health',
'data_minors',
'data_biometric',
'data_criminal',
'special_ai',
'special_video_surveillance',
'special_tracking',
'transfer_cloud_us',
'transfer_subprocessor',
'transfer_support_non_eu',
]

View File

@@ -1,247 +0,0 @@
/**
* VVT (Verarbeitungsverzeichnis) Types — Art. 30 DSGVO
*
* Re-exports common types from vendor-compliance/types.ts and adds
* VVT-specific interfaces for the 4-tab VVT module.
*/
// Re-exports from vendor-compliance/types.ts
export type {
DataSubjectCategory,
PersonalDataCategory,
LegalBasisType,
TransferMechanismType,
RecipientCategoryType,
ProcessingActivityStatus,
ProtectionLevel,
ThirdCountryTransfer,
RetentionPeriod,
LegalBasis,
RecipientCategory,
DataSource,
SystemReference,
DataFlow,
DataSourceType,
LocalizedText,
} from './vendor-compliance/types'
export {
DATA_SUBJECT_CATEGORY_META,
PERSONAL_DATA_CATEGORY_META,
LEGAL_BASIS_META,
TRANSFER_MECHANISM_META,
isSpecialCategory,
hasAdequacyDecision,
generateVVTId,
} from './vendor-compliance/types'
// =============================================================================
// VVT-SPECIFIC TYPES
// =============================================================================
export interface VVTOrganizationHeader {
organizationName: string
industry: string
locations: string[]
employeeCount: number
dpoName: string
dpoContact: string
vvtVersion: string
lastReviewDate: string
nextReviewDate: string
reviewInterval: 'quarterly' | 'semi_annual' | 'annual'
}
export type BusinessFunction =
| 'hr'
| 'finance'
| 'sales_crm'
| 'marketing'
| 'support'
| 'it_operations'
| 'product_engineering'
| 'legal'
| 'management'
| 'other'
export interface StructuredTOMs {
accessControl: string[]
confidentiality: string[]
integrity: string[]
availability: string[]
separation: string[]
}
export interface VVTActivity {
// Pflichtfelder Art. 30 Abs. 1 (Controller)
id: string
vvtId: string
name: string
description: string
purposes: string[]
legalBases: { type: string; description?: string; reference?: string }[]
dataSubjectCategories: string[]
personalDataCategories: string[]
recipientCategories: { type: string; name: string; description?: string; isThirdCountry?: boolean; country?: string }[]
thirdCountryTransfers: { country: string; recipient: string; transferMechanism: string; additionalMeasures?: string[] }[]
retentionPeriod: { duration?: number; durationUnit?: string; description: string; legalBasis?: string; deletionProcedure?: string }
tomDescription: string
// Generator-Optimierung (Layer B)
businessFunction: BusinessFunction
systems: { systemId: string; name: string; description?: string; type?: string }[]
deploymentModel: 'cloud' | 'on_prem' | 'hybrid'
dataSources: { type: string; description?: string }[]
dataFlows: { sourceSystem?: string; targetSystem?: string; description: string; dataCategories: string[] }[]
protectionLevel: 'LOW' | 'MEDIUM' | 'HIGH'
dpiaRequired: boolean
structuredToms: StructuredTOMs
// Workflow
status: 'DRAFT' | 'REVIEW' | 'APPROVED' | 'ARCHIVED'
responsible: string
owner: string
createdAt: string
updatedAt: string
}
// Processor-Record (Art. 30 Abs. 2)
export interface VVTProcessorActivity {
id: string
vvtId: string
controllerReference: string
processingCategories: string[]
subProcessorChain: SubProcessor[]
thirdCountryTransfers: { country: string; recipient: string; transferMechanism: string }[]
tomDescription: string
status: 'DRAFT' | 'REVIEW' | 'APPROVED' | 'ARCHIVED'
}
export interface SubProcessor {
name: string
purpose: string
country: string
isThirdCountry: boolean
}
// =============================================================================
// CONSTANTS
// =============================================================================
export const BUSINESS_FUNCTION_LABELS: Record<BusinessFunction, string> = {
hr: 'Personal (HR)',
finance: 'Finanzen & Buchhaltung',
sales_crm: 'Vertrieb & CRM',
marketing: 'Marketing',
support: 'Kundenservice',
it_operations: 'IT-Betrieb',
product_engineering: 'Produktentwicklung',
legal: 'Recht & Compliance',
management: 'Geschaeftsfuehrung',
other: 'Sonstiges',
}
export const STATUS_LABELS: Record<string, string> = {
DRAFT: 'Entwurf',
REVIEW: 'In Pruefung',
APPROVED: 'Genehmigt',
ARCHIVED: 'Archiviert',
}
export const STATUS_COLORS: Record<string, string> = {
DRAFT: 'bg-gray-100 text-gray-700',
REVIEW: 'bg-yellow-100 text-yellow-700',
APPROVED: 'bg-green-100 text-green-700',
ARCHIVED: 'bg-red-100 text-red-700',
}
export const PROTECTION_LEVEL_LABELS: Record<string, string> = {
LOW: 'Niedrig',
MEDIUM: 'Mittel',
HIGH: 'Hoch',
}
export const DEPLOYMENT_LABELS: Record<string, string> = {
cloud: 'Cloud',
on_prem: 'On-Premise',
hybrid: 'Hybrid',
}
export const REVIEW_INTERVAL_LABELS: Record<string, string> = {
quarterly: 'Vierteljaehrlich',
semi_annual: 'Halbjaehrlich',
annual: 'Jaehrlich',
}
// Art. 9 special categories for highlighting
export const ART9_CATEGORIES: string[] = [
'HEALTH_DATA',
'GENETIC_DATA',
'BIOMETRIC_DATA',
'RACIAL_ETHNIC',
'POLITICAL_OPINIONS',
'RELIGIOUS_BELIEFS',
'TRADE_UNION',
'SEX_LIFE',
'CRIMINAL_DATA',
]
// =============================================================================
// HELPER: Create empty activity
// =============================================================================
export function createEmptyActivity(vvtId: string): VVTActivity {
const now = new Date().toISOString()
return {
id: crypto.randomUUID(),
vvtId,
name: '',
description: '',
purposes: [],
legalBases: [],
dataSubjectCategories: [],
personalDataCategories: [],
recipientCategories: [],
thirdCountryTransfers: [],
retentionPeriod: { description: '', legalBasis: '', deletionProcedure: '' },
tomDescription: '',
businessFunction: 'other',
systems: [],
deploymentModel: 'cloud',
dataSources: [],
dataFlows: [],
protectionLevel: 'MEDIUM',
dpiaRequired: false,
structuredToms: {
accessControl: [],
confidentiality: [],
integrity: [],
availability: [],
separation: [],
},
status: 'DRAFT',
responsible: '',
owner: '',
createdAt: now,
updatedAt: now,
}
}
// =============================================================================
// HELPER: Default organization header
// =============================================================================
export function createDefaultOrgHeader(): VVTOrganizationHeader {
return {
organizationName: '',
industry: '',
locations: [],
employeeCount: 0,
dpoName: '',
dpoContact: '',
vvtVersion: '1.0',
lastReviewDate: new Date().toISOString().split('T')[0],
nextReviewDate: '',
reviewInterval: 'annual',
}
}