refactor(scope+profile): Duplikate eliminieren, UI vereinheitlichen
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Failing after 36s
CI / test-python-backend-compliance (push) Successful in 35s
CI / test-python-document-crawler (push) Successful in 25s
CI / test-python-dsms-gateway (push) Successful in 20s

- Profil: Steps 6 (VVT) + 7 (KI) entfernt, nach Scope verschoben
- Profil: Steps 9→7 renummeriert (Rechtlicher Rahmen→6, Maschinenbau→7)
- Profil: Branche + Jahresumsatz von Dropdown auf Tile-Grid umgestellt
- Profil: usesAI/aiUseCases aus CompanyProfile Interface entfernt
- Scope: 5 Duplikate aus Block 1 entfernt (Branche, MA, Umsatz, Modell, DSB)
- Scope: 2 Duplikate aus Block 6 entfernt (Angebote, Webshop)
- Scope: Block 7 (KI-Systeme) + Block 8 (VVT) neu hinzugefuegt
- Scope: "Aus Profil" Info-Box zeigt Profildaten in betroffenen Blocks
- Scope: Hidden Scoring fuer entfernte Fragen bleibt erhalten via Auto-Fill

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-10 08:52:09 +01:00
parent 90d99bba08
commit fa4cda7627
6 changed files with 418 additions and 235 deletions

View File

@@ -10,21 +10,26 @@ import type { CompanyProfile } from './types'
/**
* Block 1: Organisation & Reife
*/
/**
* IDs of questions that are auto-filled from company profile.
* These are no longer shown as interactive questions but still contribute to scoring.
*/
export const PROFILE_AUTOFILL_QUESTION_IDS = [
'org_employee_count',
'org_annual_revenue',
'org_industry',
'org_business_model',
'org_has_dsb',
'prod_type',
'prod_webshop',
] as const
const BLOCK_1_ORGANISATION: ScopeQuestionBlock = {
id: 'organisation',
title: 'Organisation & Reife',
description: 'Grundlegende Informationen zu Ihrer Organisation und Compliance-Zielen',
order: 1,
questions: [
{
id: 'org_employee_count',
type: 'number',
question: 'Wie viele Mitarbeiter hat Ihre Organisation?',
helpText: 'Geben Sie die Gesamtzahl aller Beschäftigten an (inkl. Teilzeit, Minijobs)',
required: true,
scoreWeights: { risk: 5, complexity: 8, assurance: 6 },
mapsToCompanyProfile: 'employeeCount',
},
{
id: 'org_customer_count',
type: 'single',
@@ -40,21 +45,6 @@ const BLOCK_1_ORGANISATION: ScopeQuestionBlock = {
],
scoreWeights: { risk: 6, complexity: 7, assurance: 6 },
},
{
id: 'org_annual_revenue',
type: 'single',
question: 'Wie hoch ist Ihr jährlicher Umsatz?',
helpText: 'Wählen Sie die zutreffende Umsatzklasse',
required: true,
options: [
{ value: '<2Mio', label: 'Unter 2 Mio. EUR' },
{ value: '2-10Mio', label: '2 bis 10 Mio. EUR' },
{ value: '10-50Mio', label: '10 bis 50 Mio. EUR' },
{ value: '>50Mio', label: 'Über 50 Mio. EUR' },
],
scoreWeights: { risk: 4, complexity: 6, assurance: 7 },
mapsToCompanyProfile: 'annualRevenue',
},
{
id: 'org_cert_target',
type: 'multi',
@@ -71,53 +61,6 @@ const BLOCK_1_ORGANISATION: ScopeQuestionBlock = {
],
scoreWeights: { risk: 3, complexity: 5, assurance: 10 },
},
{
id: 'org_industry',
type: 'single',
question: 'In welcher Branche sind Sie tätig?',
helpText: 'Ihre Branche beeinflusst Risikobewertung und regulatorische Anforderungen',
required: true,
options: [
{ value: 'it_software', label: 'IT & Software' },
{ value: 'healthcare', label: 'Gesundheitswesen' },
{ value: 'education', label: 'Bildung & Forschung' },
{ value: 'finance', label: 'Finanzdienstleistungen' },
{ value: 'retail', label: 'Einzelhandel & E-Commerce' },
{ value: 'manufacturing', label: 'Produktion & Fertigung' },
{ value: 'consulting', label: 'Beratung & Dienstleistungen' },
{ value: 'public', label: 'Öffentliche Verwaltung' },
{ value: 'other', label: 'Sonstige' },
],
scoreWeights: { risk: 7, complexity: 5, assurance: 6 },
mapsToCompanyProfile: 'industry',
mapsToVVTQuestion: 'org_industry',
mapsToLFQuestion: 'org-branche',
},
{
id: 'org_business_model',
type: 'single',
question: 'Was ist Ihr primäres Geschäftsmodell?',
helpText: 'B2C-Modelle haben höhere Datenschutzanforderungen',
required: true,
options: [
{ value: 'b2b', label: 'B2B (Business-to-Business)' },
{ value: 'b2c', label: 'B2C (Business-to-Consumer)' },
{ value: 'both', label: 'B2B und B2C gemischt' },
{ value: 'b2g', label: 'B2G (Business-to-Government)' },
],
scoreWeights: { risk: 6, complexity: 5, assurance: 5 },
mapsToCompanyProfile: 'businessModel',
mapsToVVTQuestion: 'org_b2b_b2c',
mapsToLFQuestion: 'org-geschaeftsmodell',
},
{
id: 'org_has_dsb',
type: 'boolean',
question: 'Haben Sie einen Datenschutzbeauftragten bestellt?',
helpText: 'Ein DSB ist bei mehr als 20 Personen mit regelmäßiger Datenverarbeitung Pflicht',
required: true,
scoreWeights: { risk: 5, complexity: 3, assurance: 6 },
},
],
}
@@ -414,24 +357,6 @@ const BLOCK_6_PRODUCT: ScopeQuestionBlock = {
description: 'Spezifische Merkmale Ihrer Produkte und Services',
order: 6,
questions: [
{
id: 'prod_type',
type: 'multi',
question: 'Welche Art von Produkten/Services bieten Sie an?',
helpText: 'Mehrfachauswahl möglich',
required: true,
options: [
{ value: 'webapp', label: 'Web-Anwendung' },
{ value: 'mobile', label: 'Mobile App (iOS/Android)' },
{ value: 'saas', label: 'SaaS-Plattform' },
{ value: 'onpremise', label: 'On-Premise Software' },
{ value: 'api', label: 'API/Schnittstellen' },
{ value: 'iot', label: 'IoT/Hardware' },
{ value: 'beratung', label: 'Beratungsleistungen' },
{ value: 'handel', label: 'Handel/Vertrieb' },
],
scoreWeights: { risk: 5, complexity: 6, assurance: 5 },
},
{
id: 'prod_cookies_consent',
type: 'boolean',
@@ -440,14 +365,6 @@ const BLOCK_6_PRODUCT: ScopeQuestionBlock = {
required: true,
scoreWeights: { risk: 5, complexity: 4, assurance: 6 },
},
{
id: 'prod_webshop',
type: 'boolean',
question: 'Betreiben Sie einen Online-Shop?',
helpText: 'E-Commerce mit Zahlungsabwicklung, Bestellverwaltung',
required: true,
scoreWeights: { risk: 7, complexity: 6, assurance: 6 },
},
{
id: 'prod_api_external',
type: 'boolean',
@@ -467,6 +384,201 @@ const BLOCK_6_PRODUCT: ScopeQuestionBlock = {
],
}
/**
* Hidden questions — removed from UI but still contribute to scoring.
* These are auto-filled from the Company Profile.
*/
export const HIDDEN_SCORING_QUESTIONS: ScopeProfilingQuestion[] = [
{
id: 'org_employee_count',
type: 'number',
question: 'Mitarbeiterzahl (aus Profil)',
required: false,
scoreWeights: { risk: 5, complexity: 8, assurance: 6 },
mapsToCompanyProfile: 'employeeCount',
},
{
id: 'org_annual_revenue',
type: 'single',
question: 'Jahresumsatz (aus Profil)',
required: false,
scoreWeights: { risk: 4, complexity: 6, assurance: 7 },
mapsToCompanyProfile: 'annualRevenue',
},
{
id: 'org_industry',
type: 'single',
question: 'Branche (aus Profil)',
required: false,
scoreWeights: { risk: 7, complexity: 5, assurance: 6 },
mapsToCompanyProfile: 'industry',
mapsToVVTQuestion: 'org_industry',
mapsToLFQuestion: 'org-branche',
},
{
id: 'org_business_model',
type: 'single',
question: 'Geschäftsmodell (aus Profil)',
required: false,
scoreWeights: { risk: 6, complexity: 5, assurance: 5 },
mapsToCompanyProfile: 'businessModel',
mapsToVVTQuestion: 'org_b2b_b2c',
mapsToLFQuestion: 'org-geschaeftsmodell',
},
{
id: 'org_has_dsb',
type: 'boolean',
question: 'DSB vorhanden (aus Profil)',
required: false,
scoreWeights: { risk: 5, complexity: 3, assurance: 6 },
},
{
id: 'prod_type',
type: 'multi',
question: 'Angebotstypen (aus Profil)',
required: false,
scoreWeights: { risk: 5, complexity: 6, assurance: 5 },
},
{
id: 'prod_webshop',
type: 'boolean',
question: 'Webshop (aus Profil)',
required: false,
scoreWeights: { risk: 7, complexity: 6, assurance: 6 },
},
]
/**
* Block 7: KI-Systeme (portiert aus Company Profile Step 7)
*/
const BLOCK_7_AI_SYSTEMS: ScopeQuestionBlock = {
id: 'ai_systems',
title: 'KI-Systeme',
description: 'Erfassung eingesetzter KI-Systeme für EU AI Act und DSGVO-Dokumentation',
order: 7,
questions: [
{
id: 'ai_uses_ai',
type: 'boolean',
question: 'Setzt Ihr Unternehmen KI-Systeme ein?',
helpText: 'Chatbots, Empfehlungssysteme, automatisierte Entscheidungen, Copilot, etc.',
required: true,
scoreWeights: { risk: 8, complexity: 7, assurance: 6 },
},
{
id: 'ai_categories',
type: 'multi',
question: 'Welche Kategorien von KI-Systemen setzen Sie ein?',
helpText: 'Mehrfachauswahl möglich. Wird nur angezeigt, wenn KI im Einsatz ist.',
required: false,
options: [
{ value: 'chatbot', label: 'Text-KI / Chatbots (ChatGPT, Claude, Gemini)' },
{ value: 'office', label: 'Office / Produktivität (Copilot, Workspace AI)' },
{ value: 'code', label: 'Code-Assistenz (GitHub Copilot, Cursor)' },
{ value: 'image', label: 'Bildgenerierung (DALL-E, Midjourney, Firefly)' },
{ value: 'translation', label: 'Übersetzung / Sprache (DeepL)' },
{ value: 'crm', label: 'CRM / Sales KI (Salesforce Einstein, HubSpot AI)' },
{ value: 'internal', label: 'Eigene / interne KI-Systeme' },
{ value: 'other', label: 'Sonstige KI-Systeme' },
],
scoreWeights: { risk: 5, complexity: 5, assurance: 5 },
},
{
id: 'ai_personal_data',
type: 'boolean',
question: 'Werden personenbezogene Daten an KI-Systeme übermittelt?',
helpText: 'Z.B. Kundendaten in ChatGPT eingeben, E-Mails mit Copilot verarbeiten',
required: false,
scoreWeights: { risk: 10, complexity: 5, assurance: 7 },
},
{
id: 'ai_risk_assessment',
type: 'boolean',
question: 'Haben Sie eine KI-Risikobewertung nach EU AI Act durchgeführt?',
helpText: 'Risikoeinstufung der KI-Systeme (verboten / hochriskant / begrenzt / minimal)',
required: false,
scoreWeights: { risk: -5, complexity: 3, assurance: 8 },
},
],
}
/**
* Block 8: Verarbeitungstätigkeiten (portiert aus Company Profile Step 6)
*/
const BLOCK_8_VVT: ScopeQuestionBlock = {
id: 'vvt',
title: 'Verarbeitungstätigkeiten',
description: 'Übersicht der Datenverarbeitungen nach Art. 30 DSGVO',
order: 8,
questions: [
{
id: 'vvt_departments',
type: 'multi',
question: 'In welchen Abteilungen werden personenbezogene Daten verarbeitet?',
helpText: 'Wählen Sie alle Abteilungen, in denen Verarbeitungstätigkeiten stattfinden',
required: true,
options: [
{ value: 'personal', label: 'Personal / HR' },
{ value: 'finanzen', label: 'Finanzen / Buchhaltung' },
{ value: 'vertrieb', label: 'Vertrieb / Sales' },
{ value: 'marketing', label: 'Marketing' },
{ value: 'it', label: 'IT / Administration' },
{ value: 'recht', label: 'Recht / Compliance' },
{ value: 'kundenservice', label: 'Kundenservice / Support' },
{ value: 'produktion', label: 'Produktion / Fertigung' },
{ value: 'logistik', label: 'Logistik / Versand' },
{ value: 'einkauf', label: 'Einkauf / Beschaffung' },
{ value: 'facility', label: 'Facility Management' },
],
scoreWeights: { risk: 10, complexity: 10, assurance: 8 },
},
{
id: 'vvt_data_categories',
type: 'multi',
question: 'Welche Datenkategorien werden verarbeitet?',
helpText: 'Wählen Sie alle zutreffenden Kategorien personenbezogener Daten',
required: true,
options: [
{ value: 'stammdaten', label: 'Stammdaten (Name, Geburtsdatum)' },
{ value: 'kontaktdaten', label: 'Kontaktdaten (E-Mail, Telefon, Adresse)' },
{ value: 'vertragsdaten', label: 'Vertragsdaten' },
{ value: 'zahlungsdaten', label: 'Zahlungs-/Bankdaten' },
{ value: 'beschaeftigtendaten', label: 'Beschäftigtendaten (Gehalt, Arbeitszeiten)' },
{ value: 'kommunikation', label: 'Kommunikationsdaten (E-Mail, Chat)' },
{ value: 'nutzungsdaten', label: 'Nutzungs-/Logdaten (IP, Klicks)' },
{ value: 'standortdaten', label: 'Standortdaten' },
{ value: 'bilddaten', label: 'Bild-/Videodaten' },
{ value: 'bewerberdaten', label: 'Bewerberdaten' },
],
scoreWeights: { risk: 8, complexity: 7, assurance: 7 },
},
{
id: 'vvt_special_categories',
type: 'boolean',
question: 'Verarbeiten Sie besondere Kategorien (Art. 9 DSGVO) in Ihren Tätigkeiten?',
helpText: 'Gesundheit, Biometrie, Religion, Gewerkschaft — über die bereits in Block 2 erfassten hinaus',
required: true,
scoreWeights: { risk: 10, complexity: 5, assurance: 8 },
},
{
id: 'vvt_has_vvt',
type: 'boolean',
question: 'Haben Sie bereits ein Verarbeitungsverzeichnis (VVT)?',
helpText: 'Dokumentation aller Verarbeitungstätigkeiten nach Art. 30 DSGVO',
required: true,
scoreWeights: { risk: -5, complexity: 3, assurance: 8 },
},
{
id: 'vvt_external_processors',
type: 'boolean',
question: 'Setzen Sie externe Dienstleister als Auftragsverarbeiter ein?',
helpText: 'Lohnbüro, Hosting-Provider, Cloud-Dienste, externe IT etc.',
required: true,
scoreWeights: { risk: 7, complexity: 6, assurance: 7 },
},
],
}
/**
* All question blocks in order
*/
@@ -477,49 +589,23 @@ export const SCOPE_QUESTION_BLOCKS: ScopeQuestionBlock[] = [
BLOCK_4_TECH,
BLOCK_5_PROCESSES,
BLOCK_6_PRODUCT,
BLOCK_7_AI_SYSTEMS,
BLOCK_8_VVT,
]
/**
* Prefill scope answers from CompanyProfile
* Prefill scope answers from CompanyProfile.
*
* Questions that were removed from the UI (org_employee_count, org_annual_revenue,
* org_industry, org_business_model, org_has_dsb, prod_type, prod_webshop) are
* still auto-filled here so their scoreWeights continue to affect the scoring.
*/
export function prefillFromCompanyProfile(
profile: CompanyProfile
): ScopeProfilingAnswer[] {
const answers: ScopeProfilingAnswer[] = []
// employeeCount
if (profile.employeeCount != null) {
answers.push({
questionId: 'org_employee_count',
value: profile.employeeCount,
})
}
// annualRevenue
if (profile.annualRevenue) {
answers.push({
questionId: 'org_annual_revenue',
value: profile.annualRevenue,
})
}
// industry
if (profile.industry) {
answers.push({
questionId: 'org_industry',
value: profile.industry,
})
}
// businessModel
if (profile.businessModel) {
answers.push({
questionId: 'org_business_model',
value: profile.businessModel,
})
}
// dpoName -> org_has_dsb
// dpoName -> org_has_dsb (auto-filled, not shown in UI)
if (profile.dpoName && profile.dpoName.trim() !== '') {
answers.push({
questionId: 'org_has_dsb',
@@ -527,21 +613,7 @@ export function prefillFromCompanyProfile(
})
}
// usesAI -> proc_ai_usage
if (profile.usesAI === true) {
// We don't know which specific AI type, so just mark as "generativ" as a default
answers.push({
questionId: 'proc_ai_usage',
value: ['generativ'],
})
} else if (profile.usesAI === false) {
answers.push({
questionId: 'proc_ai_usage',
value: ['keine'],
})
}
// offerings -> prod_type mapping
// offerings -> prod_type mapping (auto-filled, not shown in UI)
if (profile.offerings && profile.offerings.length > 0) {
const prodTypes: string[] = []
const offeringsLower = profile.offerings.map((o) => o.toLowerCase())
@@ -591,11 +663,99 @@ export function prefillFromCompanyProfile(
value: prodTypes,
})
}
// webshop auto-fill
if (offeringsLower.some((o) => o.includes('webshop') || o.includes('shop'))) {
answers.push({
questionId: 'prod_webshop',
value: true,
})
}
}
return answers
}
/**
* Get auto-filled scoring values for questions removed from UI.
* These contribute to scoring even though the user doesn't answer them interactively.
*/
export function getAutoFilledScoringAnswers(
profile: CompanyProfile
): ScopeProfilingAnswer[] {
const answers: ScopeProfilingAnswer[] = []
// employeeCount -> org_employee_count
if (profile.employeeCount != null) {
answers.push({
questionId: 'org_employee_count',
value: profile.employeeCount,
})
}
// annualRevenue -> org_annual_revenue
if (profile.annualRevenue) {
answers.push({
questionId: 'org_annual_revenue',
value: profile.annualRevenue,
})
}
// industry -> org_industry
if (profile.industry) {
answers.push({
questionId: 'org_industry',
value: profile.industry,
})
}
// businessModel -> org_business_model
if (profile.businessModel) {
answers.push({
questionId: 'org_business_model',
value: profile.businessModel,
})
}
// dpoName -> org_has_dsb
if (profile.dpoName && profile.dpoName.trim() !== '') {
answers.push({
questionId: 'org_has_dsb',
value: true,
})
}
return answers
}
/**
* Get profile info summary for display in "Aus Profil" info boxes.
*/
export function getProfileInfoForBlock(
profile: CompanyProfile,
blockId: ScopeQuestionBlockId
): { label: string; value: string }[] {
const items: { label: string; value: string }[] = []
if (blockId === 'organisation') {
if (profile.industry) items.push({ label: 'Branche', value: profile.industry })
if (profile.employeeCount) items.push({ label: 'Mitarbeiter', value: profile.employeeCount })
if (profile.annualRevenue) items.push({ label: 'Umsatz', value: profile.annualRevenue })
if (profile.businessModel) items.push({ label: 'Geschäftsmodell', value: profile.businessModel })
if (profile.dpoName) items.push({ label: 'DSB', value: profile.dpoName })
}
if (blockId === 'product') {
if (profile.offerings && profile.offerings.length > 0) {
items.push({ label: 'Angebote', value: profile.offerings.join(', ') })
}
const hasWebshop = profile.offerings?.some(o => o.toLowerCase().includes('webshop') || o.toLowerCase().includes('shop'))
if (hasWebshop) items.push({ label: 'Webshop', value: 'Ja' })
}
return items
}
/**
* Prefill scope answers from VVT profiling answers
*/
@@ -814,8 +974,11 @@ export function getAnswerValue(
}
/**
* Get all questions as a flat array
* Get all questions as a flat array (including hidden auto-filled questions)
*/
export function getAllQuestions(): ScopeProfilingQuestion[] {
return SCOPE_QUESTION_BLOCKS.flatMap((block) => block.questions)
return [
...SCOPE_QUESTION_BLOCKS.flatMap((block) => block.questions),
...HIDDEN_SCORING_QUESTIONS,
]
}

View File

@@ -47,7 +47,9 @@ export type ScopeQuestionBlockId =
| 'processing' // Verarbeitung & Zweck
| 'tech' // Technik & Hosting
| 'processes' // Rechte & Prozesse
| 'product'; // Produktkontext
| 'product' // Produktkontext
| 'ai_systems' // KI-Systeme (aus Profil portiert)
| 'vvt'; // Verarbeitungstaetigkeiten (aus Profil portiert)
/**
* Eine einzelne Frage im Scope-Profiling

View File

@@ -79,8 +79,6 @@ export function generateDemoState(tenantId: string, userId: string): Partial<SDK
primaryJurisdiction: 'DE',
isDataController: true,
isDataProcessor: true,
usesAI: true,
aiUseCases: ['KI-gestützte Kundenberatung', 'Automatisierte Dokumentenanalyse'],
dpoName: 'Max Mustermann',
dpoEmail: 'dsb@techstart.de',
legalContactName: null,

View File

@@ -195,10 +195,6 @@ export interface CompanyProfile {
isDataController: boolean // Verantwortlicher (Art. 4 Nr. 7 DSGVO)
isDataProcessor: boolean // Auftragsverarbeiter (Art. 4 Nr. 8 DSGVO)
// AI Usage
usesAI: boolean
aiUseCases: string[] // Brief descriptions
// Contact Persons
dpoName: string | null // Data Protection Officer
dpoEmail: string | null