Initial commit: breakpilot-compliance - Compliance SDK Platform

Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Boenisch
2026-02-11 23:47:28 +01:00
commit 4435e7ea0a
734 changed files with 251369 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff