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:
@@ -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'
|
||||
@@ -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]}`
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
1010
admin-compliance/lib/sdk/vendor-compliance/context.tsx
Normal file
1010
admin-compliance/lib/sdk/vendor-compliance/context.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Contract Review exports
|
||||
*/
|
||||
|
||||
export * from './analyzer'
|
||||
export * from './checklists'
|
||||
export * from './findings'
|
||||
72
admin-compliance/lib/sdk/vendor-compliance/export/index.ts
Normal file
72
admin-compliance/lib/sdk/vendor-compliance/export/index.ts
Normal 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'
|
||||
356
admin-compliance/lib/sdk/vendor-compliance/export/ropa-export.ts
Normal file
356
admin-compliance/lib/sdk/vendor-compliance/export/ropa-export.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
444
admin-compliance/lib/sdk/vendor-compliance/export/vvt-export.ts
Normal file
444
admin-compliance/lib/sdk/vendor-compliance/export/vvt-export.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
196
admin-compliance/lib/sdk/vendor-compliance/index.ts
Normal file
196
admin-compliance/lib/sdk/vendor-compliance/index.ts
Normal 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'
|
||||
488
admin-compliance/lib/sdk/vendor-compliance/risk/calculator.ts
Normal file
488
admin-compliance/lib/sdk/vendor-compliance/risk/calculator.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
6
admin-compliance/lib/sdk/vendor-compliance/risk/index.ts
Normal file
6
admin-compliance/lib/sdk/vendor-compliance/risk/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Risk & Controls exports
|
||||
*/
|
||||
|
||||
export * from './calculator'
|
||||
export * from './controls-library'
|
||||
1217
admin-compliance/lib/sdk/vendor-compliance/types.ts
Normal file
1217
admin-compliance/lib/sdk/vendor-compliance/types.ts
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user