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