Some checks failed
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 38s
CI/CD / test-python-backend-compliance (push) Successful in 39s
CI/CD / test-python-document-crawler (push) Successful in 27s
CI/CD / test-python-dsms-gateway (push) Successful in 24s
CI/CD / deploy-hetzner (push) Has been cancelled
- Split HT-H01 into HT-H01a (B2C/Hybrid mit Verbraucherschutzpflichten) und HT-H01b (reiner B2B mit Basis-Pflichten). B2B-Webshops bekommen keine Widerrufsbelehrung/Preisangaben/Fernabsatz mehr. - Add excludeWhen/requireWhen to HardTriggerRule for conditional trigger logic - Register 6 neue ScopeDocumentType: widerrufsbelehrung, preisangaben, fernabsatz_info, streitbeilegung, produktsicherheit, ai_act_doku - Full DOCUMENT_SCOPE_MATRIX L1-L4 for all new types - Align HardTriggerRule interface with actual engine field names - Add Phase H (Verbraucherschutz) to RAG ingestion script: 10 deutsche Gesetze + 4 EU-Verordnungen + HLEG Ethics Guidelines - Add scripts/rag-sources.md with license documentation - 9 new tests for B2B/B2C trigger split, all 326 tests pass Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1768 lines
54 KiB
TypeScript
1768 lines
54 KiB
TypeScript
import type {
|
|
ComplianceDepthLevel,
|
|
ComplianceScores,
|
|
ScopeProfilingAnswer,
|
|
ScopeDecision,
|
|
HardTriggerRule,
|
|
TriggeredHardTrigger,
|
|
RequiredDocument,
|
|
RiskFlag,
|
|
ScopeGap,
|
|
NextAction,
|
|
ScopeReasoning,
|
|
ScopeDocumentType,
|
|
DocumentScopeRequirement,
|
|
} from './compliance-scope-types'
|
|
import type { CompanyProfile, MachineBuilderProfile } from './types'
|
|
import {
|
|
getDepthLevelNumeric,
|
|
depthLevelFromNumeric,
|
|
maxDepthLevel,
|
|
createEmptyScopeDecision,
|
|
DOCUMENT_SCOPE_MATRIX,
|
|
DOCUMENT_TYPE_LABELS,
|
|
DOCUMENT_SDK_STEP_MAP,
|
|
} from './compliance-scope-types'
|
|
|
|
// ============================================================================
|
|
// SCORE WEIGHTS PRO FRAGE
|
|
// ============================================================================
|
|
|
|
export const QUESTION_SCORE_WEIGHTS: Record<
|
|
string,
|
|
{ risk: number; complexity: number; assurance: number }
|
|
> = {
|
|
// Organisationsprofil (6 Fragen)
|
|
org_employee_count: { risk: 3, complexity: 5, assurance: 4 },
|
|
org_industry: { risk: 6, complexity: 4, assurance: 5 },
|
|
org_business_model: { risk: 5, complexity: 3, assurance: 4 },
|
|
org_customer_count: { risk: 4, complexity: 6, assurance: 5 },
|
|
org_cert_target: { risk: 2, complexity: 8, assurance: 9 },
|
|
org_has_dpo: { risk: 7, complexity: 2, assurance: 8 },
|
|
|
|
// Datenarten (5 Fragen)
|
|
data_art9: { risk: 10, complexity: 7, assurance: 9 },
|
|
data_minors: { risk: 10, complexity: 6, assurance: 9 },
|
|
data_volume: { risk: 6, complexity: 7, assurance: 6 },
|
|
data_retention_years: { risk: 5, complexity: 4, assurance: 5 },
|
|
data_sources: { risk: 4, complexity: 5, assurance: 4 },
|
|
|
|
// Verarbeitungszwecke (9 Fragen)
|
|
proc_adm_scoring: { risk: 9, complexity: 7, assurance: 8 },
|
|
proc_ai_usage: { risk: 8, complexity: 8, assurance: 8 },
|
|
proc_video_surveillance: { risk: 7, complexity: 5, assurance: 7 },
|
|
proc_employee_monitoring: { risk: 7, complexity: 5, assurance: 7 },
|
|
proc_tracking: { risk: 6, complexity: 4, assurance: 6 },
|
|
proc_dsar_process: { risk: 8, complexity: 6, assurance: 8 },
|
|
proc_deletion_concept: { risk: 7, complexity: 5, assurance: 7 },
|
|
proc_incident_response: { risk: 9, complexity: 6, assurance: 9 },
|
|
proc_regular_audits: { risk: 5, complexity: 7, assurance: 8 },
|
|
|
|
// Technik (7 Fragen)
|
|
tech_hosting_location: { risk: 7, complexity: 5, assurance: 7 },
|
|
tech_third_country: { risk: 8, complexity: 6, assurance: 8 },
|
|
tech_encryption_transit: { risk: 8, complexity: 4, assurance: 8 },
|
|
tech_encryption_rest: { risk: 8, complexity: 4, assurance: 8 },
|
|
tech_access_control: { risk: 7, complexity: 5, assurance: 7 },
|
|
tech_logging: { risk: 6, complexity: 5, assurance: 7 },
|
|
tech_backup_recovery: { risk: 6, complexity: 5, assurance: 7 },
|
|
|
|
// Produkt/Features (5 Fragen)
|
|
prod_webshop: { risk: 5, complexity: 4, assurance: 5 },
|
|
prod_data_broker: { risk: 9, complexity: 7, assurance: 8 },
|
|
prod_api_external: { risk: 6, complexity: 5, assurance: 6 },
|
|
prod_consent_management: { risk: 7, complexity: 5, assurance: 8 },
|
|
prod_data_portability: { risk: 4, complexity: 5, assurance: 5 },
|
|
|
|
// Compliance Reife (3 Fragen)
|
|
comp_training: { risk: 5, complexity: 4, assurance: 7 },
|
|
comp_vendor_management: { risk: 6, complexity: 6, assurance: 7 },
|
|
comp_documentation_level: { risk: 6, complexity: 7, assurance: 8 },
|
|
}
|
|
|
|
// ============================================================================
|
|
// ANSWER MULTIPLIERS FÜR SINGLE-CHOICE FRAGEN
|
|
// ============================================================================
|
|
|
|
export const ANSWER_MULTIPLIERS: Record<string, Record<string, number>> = {
|
|
org_employee_count: {
|
|
'1-9': 0.1,
|
|
'10-49': 0.3,
|
|
'50-249': 0.5,
|
|
'250-999': 0.7,
|
|
'1000+': 1.0,
|
|
},
|
|
org_industry: {
|
|
tech: 0.4,
|
|
finance: 0.8,
|
|
healthcare: 0.9,
|
|
public: 0.7,
|
|
retail: 0.5,
|
|
education: 0.6,
|
|
other: 0.3,
|
|
},
|
|
org_business_model: {
|
|
b2b: 0.4,
|
|
b2c: 0.7,
|
|
b2b2c: 0.6,
|
|
internal: 0.3,
|
|
},
|
|
org_customer_count: {
|
|
'0-100': 0.1,
|
|
'100-1000': 0.2,
|
|
'1000-10000': 0.4,
|
|
'10000-100000': 0.7,
|
|
'100000+': 1.0,
|
|
},
|
|
data_volume: {
|
|
'<1000': 0.1,
|
|
'1000-10000': 0.2,
|
|
'10000-100000': 0.4,
|
|
'100000-1000000': 0.7,
|
|
'>1000000': 1.0,
|
|
},
|
|
data_retention_years: {
|
|
'<1': 0.2,
|
|
'1-3': 0.4,
|
|
'3-5': 0.6,
|
|
'5-10': 0.8,
|
|
'>10': 1.0,
|
|
},
|
|
tech_hosting_location: {
|
|
eu: 0.2,
|
|
eu_us_adequacy: 0.4,
|
|
us_adequacy: 0.6,
|
|
drittland: 1.0,
|
|
},
|
|
tech_access_control: {
|
|
none: 1.0,
|
|
basic: 0.6,
|
|
rbac: 0.3,
|
|
advanced: 0.1,
|
|
},
|
|
tech_logging: {
|
|
none: 1.0,
|
|
basic: 0.6,
|
|
comprehensive: 0.2,
|
|
},
|
|
tech_backup_recovery: {
|
|
none: 1.0,
|
|
basic: 0.5,
|
|
tested: 0.2,
|
|
},
|
|
comp_documentation_level: {
|
|
none: 1.0,
|
|
basic: 0.6,
|
|
structured: 0.3,
|
|
comprehensive: 0.1,
|
|
},
|
|
}
|
|
|
|
// ============================================================================
|
|
// 50 HARD TRIGGER RULES
|
|
// ============================================================================
|
|
|
|
export const HARD_TRIGGER_RULES: HardTriggerRule[] = [
|
|
// ========== A: Art. 9 Besondere Kategorien (9 rules) ==========
|
|
{
|
|
id: 'HT-A01',
|
|
category: 'art9',
|
|
questionId: 'data_art9',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'gesundheit',
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
|
legalReference: 'Art. 9 Abs. 1 DSGVO',
|
|
description: 'Verarbeitung von Gesundheitsdaten',
|
|
},
|
|
{
|
|
id: 'HT-A02',
|
|
category: 'art9',
|
|
questionId: 'data_art9',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'biometrie',
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
|
legalReference: 'Art. 9 Abs. 1 DSGVO',
|
|
description: 'Verarbeitung biometrischer Daten zur eindeutigen Identifizierung',
|
|
},
|
|
{
|
|
id: 'HT-A03',
|
|
category: 'art9',
|
|
questionId: 'data_art9',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'genetik',
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
|
legalReference: 'Art. 9 Abs. 1 DSGVO',
|
|
description: 'Verarbeitung genetischer Daten',
|
|
},
|
|
{
|
|
id: 'HT-A04',
|
|
category: 'art9',
|
|
questionId: 'data_art9',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'politisch',
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
|
legalReference: 'Art. 9 Abs. 1 DSGVO',
|
|
description: 'Verarbeitung politischer Meinungen',
|
|
},
|
|
{
|
|
id: 'HT-A05',
|
|
category: 'art9',
|
|
questionId: 'data_art9',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'religion',
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
|
legalReference: 'Art. 9 Abs. 1 DSGVO',
|
|
description: 'Verarbeitung religiöser oder weltanschaulicher Überzeugungen',
|
|
},
|
|
{
|
|
id: 'HT-A06',
|
|
category: 'art9',
|
|
questionId: 'data_art9',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'gewerkschaft',
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
|
legalReference: 'Art. 9 Abs. 1 DSGVO',
|
|
description: 'Verarbeitung von Gewerkschaftszugehörigkeit',
|
|
},
|
|
{
|
|
id: 'HT-A07',
|
|
category: 'art9',
|
|
questionId: 'data_art9',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'sexualleben',
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
|
legalReference: 'Art. 9 Abs. 1 DSGVO',
|
|
description: 'Verarbeitung von Daten zum Sexualleben oder zur sexuellen Orientierung',
|
|
},
|
|
{
|
|
id: 'HT-A08',
|
|
category: 'art9',
|
|
questionId: 'data_art9',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'strafrechtlich',
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
|
legalReference: 'Art. 10 DSGVO',
|
|
description: 'Verarbeitung strafrechtlicher Verurteilungen',
|
|
},
|
|
{
|
|
id: 'HT-A09',
|
|
category: 'art9',
|
|
questionId: 'data_art9',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'ethnisch',
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
|
legalReference: 'Art. 9 Abs. 1 DSGVO',
|
|
description: 'Verarbeitung der rassischen oder ethnischen Herkunft',
|
|
},
|
|
|
|
// ========== B: Vulnerable Gruppen (3 rules) ==========
|
|
{
|
|
id: 'HT-B01',
|
|
category: 'vulnerable',
|
|
questionId: 'data_minors',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA', 'DSE'],
|
|
legalReference: 'Art. 8 DSGVO',
|
|
description: 'Verarbeitung von Daten Minderjähriger',
|
|
},
|
|
{
|
|
id: 'HT-B02',
|
|
category: 'vulnerable',
|
|
questionId: 'data_minors',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L4',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA', 'DSE'],
|
|
legalReference: 'Art. 8 + Art. 9 DSGVO',
|
|
description: 'Verarbeitung besonderer Kategorien von Daten Minderjähriger',
|
|
combineWithArt9: true,
|
|
},
|
|
{
|
|
id: 'HT-B03',
|
|
category: 'vulnerable',
|
|
questionId: 'data_minors',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L4',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA', 'AI_ACT_DOKU'],
|
|
legalReference: 'Art. 8 DSGVO + AI Act',
|
|
description: 'KI-gestützte Verarbeitung von Daten Minderjähriger',
|
|
combineWithAI: true,
|
|
},
|
|
|
|
// ========== C: ADM/KI (6 rules) ==========
|
|
{
|
|
id: 'HT-C01',
|
|
category: 'adm',
|
|
questionId: 'proc_adm_scoring',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
|
legalReference: 'Art. 22 DSGVO',
|
|
description: 'Automatisierte Einzelentscheidung mit Rechtswirkung oder erheblicher Beeinträchtigung',
|
|
},
|
|
{
|
|
id: 'HT-C02',
|
|
category: 'adm',
|
|
questionId: 'proc_ai_usage',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'autonom',
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA', 'AI_ACT_DOKU'],
|
|
legalReference: 'Art. 22 DSGVO + AI Act',
|
|
description: 'Autonome KI-Systeme mit Entscheidungsbefugnis',
|
|
},
|
|
{
|
|
id: 'HT-C03',
|
|
category: 'adm',
|
|
questionId: 'proc_ai_usage',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'scoring',
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM'],
|
|
legalReference: 'Art. 22 DSGVO',
|
|
description: 'KI-gestütztes Scoring',
|
|
},
|
|
{
|
|
id: 'HT-C04',
|
|
category: 'adm',
|
|
questionId: 'proc_ai_usage',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'profiling',
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
|
legalReference: 'Art. 22 DSGVO',
|
|
description: 'KI-gestütztes Profiling mit erheblicher Wirkung',
|
|
},
|
|
{
|
|
id: 'HT-C05',
|
|
category: 'adm',
|
|
questionId: 'proc_ai_usage',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'generativ',
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'AI_ACT_DOKU'],
|
|
legalReference: 'AI Act',
|
|
description: 'Generative KI-Systeme',
|
|
},
|
|
{
|
|
id: 'HT-C06',
|
|
category: 'adm',
|
|
questionId: 'proc_ai_usage',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'chatbot',
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'AI_ACT_DOKU'],
|
|
legalReference: 'AI Act',
|
|
description: 'Chatbots mit Personendatenverarbeitung',
|
|
},
|
|
|
|
// ========== D: Überwachung (5 rules) ==========
|
|
{
|
|
id: 'HT-D01',
|
|
category: 'surveillance',
|
|
questionId: 'proc_video_surveillance',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSE'],
|
|
legalReference: 'Art. 6 DSGVO',
|
|
description: 'Videoüberwachung',
|
|
},
|
|
{
|
|
id: 'HT-D02',
|
|
category: 'surveillance',
|
|
questionId: 'proc_employee_monitoring',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
|
legalReference: 'Art. 88 DSGVO + BetrVG',
|
|
description: 'Mitarbeiterüberwachung',
|
|
},
|
|
{
|
|
id: 'HT-D03',
|
|
category: 'surveillance',
|
|
questionId: 'proc_tracking',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'COOKIE_BANNER', 'EINWILLIGUNGEN'],
|
|
legalReference: 'Art. 6 DSGVO + ePrivacy',
|
|
description: 'Online-Tracking',
|
|
},
|
|
{
|
|
id: 'HT-D04',
|
|
category: 'surveillance',
|
|
questionId: 'proc_video_surveillance',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
|
legalReference: 'Art. 35 Abs. 3 DSGVO',
|
|
description: 'Videoüberwachung kombiniert mit Mitarbeitermonitoring',
|
|
combineWithEmployeeMonitoring: true,
|
|
},
|
|
{
|
|
id: 'HT-D05',
|
|
category: 'surveillance',
|
|
questionId: 'proc_video_surveillance',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
|
legalReference: 'Art. 35 Abs. 3 DSGVO',
|
|
description: 'Videoüberwachung kombiniert mit automatisierter Bewertung',
|
|
combineWithADM: true,
|
|
},
|
|
|
|
// ========== E: Drittland (5 rules) ==========
|
|
{
|
|
id: 'HT-E01',
|
|
category: 'third_country',
|
|
questionId: 'tech_third_country',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TRANSFER_DOKU'],
|
|
legalReference: 'Art. 44 ff. DSGVO',
|
|
description: 'Datenübermittlung in Drittland',
|
|
},
|
|
{
|
|
id: 'HT-E02',
|
|
category: 'third_country',
|
|
questionId: 'tech_hosting_location',
|
|
condition: 'EQUALS',
|
|
conditionValue: 'drittland',
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'TRANSFER_DOKU'],
|
|
legalReference: 'Art. 44 ff. DSGVO',
|
|
description: 'Hosting in Drittland',
|
|
},
|
|
{
|
|
id: 'HT-E03',
|
|
category: 'third_country',
|
|
questionId: 'tech_hosting_location',
|
|
condition: 'EQUALS',
|
|
conditionValue: 'us_adequacy',
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['TRANSFER_DOKU'],
|
|
legalReference: 'Art. 45 DSGVO',
|
|
description: 'Hosting in USA mit Angemessenheitsbeschluss',
|
|
},
|
|
{
|
|
id: 'HT-E04',
|
|
category: 'third_country',
|
|
questionId: 'tech_third_country',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'TRANSFER_DOKU', 'DSFA'],
|
|
legalReference: 'Art. 44 ff. + Art. 9 DSGVO',
|
|
description: 'Drittlandtransfer besonderer Kategorien',
|
|
combineWithArt9: true,
|
|
},
|
|
{
|
|
id: 'HT-E05',
|
|
category: 'third_country',
|
|
questionId: 'tech_third_country',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'TRANSFER_DOKU', 'DSFA'],
|
|
legalReference: 'Art. 44 ff. + Art. 8 DSGVO',
|
|
description: 'Drittlandtransfer von Daten Minderjähriger',
|
|
combineWithMinors: true,
|
|
},
|
|
|
|
// ========== F: Zertifizierung (5 rules) ==========
|
|
{
|
|
id: 'HT-F01',
|
|
category: 'certification',
|
|
questionId: 'org_cert_target',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'ISO27001',
|
|
minimumLevel: 'L4',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['TOM', 'AUDIT_CHECKLIST'],
|
|
legalReference: 'ISO/IEC 27001',
|
|
description: 'Angestrebte ISO 27001 Zertifizierung',
|
|
},
|
|
{
|
|
id: 'HT-F02',
|
|
category: 'certification',
|
|
questionId: 'org_cert_target',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'ISO27701',
|
|
minimumLevel: 'L4',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['TOM', 'VVT', 'AUDIT_CHECKLIST'],
|
|
legalReference: 'ISO/IEC 27701',
|
|
description: 'Angestrebte ISO 27701 Zertifizierung',
|
|
},
|
|
{
|
|
id: 'HT-F03',
|
|
category: 'certification',
|
|
questionId: 'org_cert_target',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'SOC2',
|
|
minimumLevel: 'L4',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['TOM', 'AUDIT_CHECKLIST'],
|
|
legalReference: 'SOC 2 Type II',
|
|
description: 'Angestrebte SOC 2 Zertifizierung',
|
|
},
|
|
{
|
|
id: 'HT-F04',
|
|
category: 'certification',
|
|
questionId: 'org_cert_target',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'TISAX',
|
|
minimumLevel: 'L4',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['TOM', 'AUDIT_CHECKLIST', 'VENDOR_MANAGEMENT'],
|
|
legalReference: 'TISAX',
|
|
description: 'Angestrebte TISAX Zertifizierung',
|
|
},
|
|
{
|
|
id: 'HT-F05',
|
|
category: 'certification',
|
|
questionId: 'org_cert_target',
|
|
condition: 'CONTAINS',
|
|
conditionValue: 'BSI-Grundschutz',
|
|
minimumLevel: 'L4',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['TOM', 'AUDIT_CHECKLIST'],
|
|
legalReference: 'BSI IT-Grundschutz',
|
|
description: 'Angestrebte BSI-Grundschutz Zertifizierung',
|
|
},
|
|
|
|
// ========== G: Volumen/Skala (5 rules) ==========
|
|
{
|
|
id: 'HT-G01',
|
|
category: 'scale',
|
|
questionId: 'data_volume',
|
|
condition: 'EQUALS',
|
|
conditionValue: '>1000000',
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'LOESCHKONZEPT'],
|
|
legalReference: 'Art. 35 Abs. 3 lit. b DSGVO',
|
|
description: 'Umfangreiche Verarbeitung personenbezogener Daten (>1 Mio. Datensätze)',
|
|
},
|
|
{
|
|
id: 'HT-G02',
|
|
category: 'scale',
|
|
questionId: 'data_volume',
|
|
condition: 'EQUALS',
|
|
conditionValue: '100000-1000000',
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM'],
|
|
legalReference: 'Art. 35 Abs. 3 lit. b DSGVO',
|
|
description: 'Großvolumige Datenverarbeitung (100k-1M Datensätze)',
|
|
},
|
|
{
|
|
id: 'HT-G03',
|
|
category: 'scale',
|
|
questionId: 'org_customer_count',
|
|
condition: 'EQUALS',
|
|
conditionValue: '100000+',
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSR_PROZESS'],
|
|
legalReference: 'Art. 15-22 DSGVO',
|
|
description: 'Großer Kundenstamm (>100k) mit hoher Betroffenenanzahl',
|
|
},
|
|
{
|
|
id: 'HT-G04',
|
|
category: 'scale',
|
|
questionId: 'org_employee_count',
|
|
condition: 'GREATER_THAN',
|
|
conditionValue: 249,
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'LOESCHKONZEPT', 'NOTFALLPLAN'],
|
|
legalReference: 'Art. 37 DSGVO',
|
|
description: 'Große Organisation (>250 Mitarbeiter) mit erhöhten Compliance-Anforderungen',
|
|
},
|
|
{
|
|
id: 'HT-G05',
|
|
category: 'scale',
|
|
questionId: 'org_employee_count',
|
|
condition: 'GREATER_THAN',
|
|
conditionValue: 999,
|
|
minimumLevel: 'L4',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA', 'LOESCHKONZEPT'],
|
|
legalReference: 'Art. 35 + Art. 37 DSGVO',
|
|
description: 'Sehr große Organisation (>1000 Mitarbeiter) mit Art. 9 Daten',
|
|
combineWithArt9: true,
|
|
},
|
|
|
|
// ========== H: Produkt/Business (7 rules) ==========
|
|
{
|
|
id: 'HT-H01a',
|
|
category: 'product',
|
|
questionId: 'prod_webshop',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
excludeWhen: { questionId: 'org_business_model', value: 'B2B' },
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['DSE', 'AGB', 'COOKIE_BANNER', 'EINWILLIGUNGEN',
|
|
'WIDERRUFSBELEHRUNG', 'PREISANGABEN', 'FERNABSATZ_INFO', 'STREITBEILEGUNG'],
|
|
legalReference: 'Art. 6 DSGVO + Fernabsatzrecht + PAngV + VSBG',
|
|
description: 'E-Commerce / Webshop (B2C) — Verbraucherschutzpflichten',
|
|
},
|
|
{
|
|
id: 'HT-H01b',
|
|
category: 'product',
|
|
questionId: 'prod_webshop',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
requireWhen: { questionId: 'org_business_model', value: 'B2B' },
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['DSE', 'AGB', 'COOKIE_BANNER'],
|
|
legalReference: 'Art. 6 DSGVO + eCommerce',
|
|
description: 'E-Commerce / Webshop (B2B) — Basis-Pflichten',
|
|
},
|
|
{
|
|
id: 'HT-H02',
|
|
category: 'product',
|
|
questionId: 'prod_data_broker',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA', 'EINWILLIGUNGEN'],
|
|
legalReference: 'Art. 35 Abs. 3 DSGVO',
|
|
description: 'Datenhandel oder Datenmakler-Tätigkeit',
|
|
},
|
|
{
|
|
id: 'HT-H03',
|
|
category: 'product',
|
|
questionId: 'prod_api_external',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['TOM', 'AVV'],
|
|
legalReference: 'Art. 28 DSGVO',
|
|
description: 'Externe API mit Datenweitergabe',
|
|
},
|
|
{
|
|
id: 'HT-H04',
|
|
category: 'product',
|
|
questionId: 'org_business_model',
|
|
condition: 'EQUALS',
|
|
conditionValue: 'b2c',
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['DSE', 'COOKIE_BANNER', 'EINWILLIGUNGEN'],
|
|
legalReference: 'Art. 6 DSGVO',
|
|
description: 'B2C-Geschäftsmodell mit Endkundenkontakt',
|
|
},
|
|
{
|
|
id: 'HT-H05',
|
|
category: 'product',
|
|
questionId: 'org_industry',
|
|
condition: 'EQUALS',
|
|
conditionValue: 'finance',
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM'],
|
|
legalReference: 'Art. 6 DSGVO + Finanzaufsicht',
|
|
description: 'Finanzbranche mit erhöhten regulatorischen Anforderungen',
|
|
},
|
|
{
|
|
id: 'HT-H06',
|
|
category: 'product',
|
|
questionId: 'org_industry',
|
|
condition: 'EQUALS',
|
|
conditionValue: 'healthcare',
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: true,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSFA'],
|
|
legalReference: 'Art. 9 DSGVO + Gesundheitsrecht',
|
|
description: 'Gesundheitsbranche mit sensiblen Daten',
|
|
},
|
|
{
|
|
id: 'HT-H07',
|
|
category: 'product',
|
|
questionId: 'org_industry',
|
|
condition: 'EQUALS',
|
|
conditionValue: 'public',
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM', 'DSR_PROZESS'],
|
|
legalReference: 'Art. 6 Abs. 1 lit. e DSGVO',
|
|
description: 'Öffentlicher Sektor',
|
|
},
|
|
|
|
// ========== I: Prozessreife - Gap Flags (5 rules) ==========
|
|
{
|
|
id: 'HT-I01',
|
|
category: 'process_maturity',
|
|
questionId: 'proc_dsar_process',
|
|
condition: 'EQUALS',
|
|
conditionValue: false,
|
|
minimumLevel: 'L1',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: [],
|
|
legalReference: 'Art. 15-22 DSGVO',
|
|
description: 'Fehlender Prozess für Betroffenenrechte',
|
|
},
|
|
{
|
|
id: 'HT-I02',
|
|
category: 'process_maturity',
|
|
questionId: 'proc_deletion_concept',
|
|
condition: 'EQUALS',
|
|
conditionValue: false,
|
|
minimumLevel: 'L1',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: [],
|
|
legalReference: 'Art. 17 DSGVO',
|
|
description: 'Fehlendes Löschkonzept',
|
|
},
|
|
{
|
|
id: 'HT-I03',
|
|
category: 'process_maturity',
|
|
questionId: 'proc_incident_response',
|
|
condition: 'EQUALS',
|
|
conditionValue: false,
|
|
minimumLevel: 'L1',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: [],
|
|
legalReference: 'Art. 33 DSGVO',
|
|
description: 'Fehlender Incident-Response-Prozess',
|
|
},
|
|
{
|
|
id: 'HT-I04',
|
|
category: 'process_maturity',
|
|
questionId: 'proc_regular_audits',
|
|
condition: 'EQUALS',
|
|
conditionValue: false,
|
|
minimumLevel: 'L1',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: [],
|
|
legalReference: 'Art. 24 DSGVO',
|
|
description: 'Fehlende regelmäßige Audits',
|
|
},
|
|
{
|
|
id: 'HT-I05',
|
|
category: 'process_maturity',
|
|
questionId: 'comp_training',
|
|
condition: 'EQUALS',
|
|
conditionValue: false,
|
|
minimumLevel: 'L1',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: [],
|
|
legalReference: 'Art. 39 Abs. 1 lit. b DSGVO',
|
|
description: 'Fehlende Schulungen zum Datenschutz',
|
|
},
|
|
|
|
// ========== J: IACE — AI Act Produkt-Triggers (3 rules) ==========
|
|
{
|
|
id: 'HT-J01',
|
|
category: 'iace_ai_act_product',
|
|
questionId: 'machineBuilder.containsAI',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM'],
|
|
legalReference: 'EU AI Act Annex I + EU Maschinenverordnung 2023/1230',
|
|
description: 'KI mit Sicherheitsfunktion in Maschine → AI Act High-Risk',
|
|
combineWithMachineBuilder: { field: 'hasSafetyFunction', value: true },
|
|
riskWeight: 9,
|
|
},
|
|
{
|
|
id: 'HT-J02',
|
|
category: 'iace_ai_act_product',
|
|
questionId: 'machineBuilder.containsAI',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM'],
|
|
legalReference: 'EU AI Act + EU Maschinenverordnung 2023/1230',
|
|
description: 'Autonome KI in Maschine → AI Act + Maschinenverordnung',
|
|
combineWithMachineBuilder: { field: 'autonomousBehavior', value: true },
|
|
riskWeight: 8,
|
|
},
|
|
{
|
|
id: 'HT-J03',
|
|
category: 'iace_ai_act_product',
|
|
questionId: 'machineBuilder.hasSafetyFunction',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['VVT', 'TOM'],
|
|
legalReference: 'EU AI Act Annex III',
|
|
description: 'KI-Bildverarbeitung mit Sicherheitsbezug',
|
|
combineWithMachineBuilder: { field: 'aiIntegrationType', includes: 'vision' },
|
|
riskWeight: 8,
|
|
},
|
|
|
|
// ========== K: IACE — CRA Triggers (3 rules) ==========
|
|
{
|
|
id: 'HT-K01',
|
|
category: 'iace_cra',
|
|
questionId: 'machineBuilder.isNetworked',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['TOM'],
|
|
legalReference: 'EU Cyber Resilience Act (CRA)',
|
|
description: 'Vernetztes Produkt → Cyber Resilience Act',
|
|
riskWeight: 6,
|
|
},
|
|
{
|
|
id: 'HT-K02',
|
|
category: 'iace_cra',
|
|
questionId: 'machineBuilder.hasRemoteAccess',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['TOM'],
|
|
legalReference: 'CRA + NIS2 Art. 21',
|
|
description: 'Remote-Zugriff → CRA + NIS2 Supply Chain',
|
|
riskWeight: 7,
|
|
},
|
|
{
|
|
id: 'HT-K03',
|
|
category: 'iace_cra',
|
|
questionId: 'machineBuilder.hasOTAUpdates',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['TOM'],
|
|
legalReference: 'CRA Art. 10 - Patch Management',
|
|
description: 'OTA-Updates → CRA Patch Management Pflicht',
|
|
riskWeight: 7,
|
|
},
|
|
|
|
// ========== L: IACE — NIS2 indirekt (2 rules) ==========
|
|
{
|
|
id: 'HT-L01',
|
|
category: 'iace_nis2_indirect',
|
|
questionId: 'machineBuilder.criticalSectorClients',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['TOM'],
|
|
legalReference: 'NIS2 Art. 21 - Supply Chain',
|
|
description: 'Lieferant an KRITIS → NIS2 Supply Chain Anforderungen',
|
|
riskWeight: 7,
|
|
},
|
|
{
|
|
id: 'HT-L02',
|
|
category: 'iace_nis2_indirect',
|
|
questionId: 'machineBuilder.oemClients',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: [],
|
|
legalReference: 'NIS2 + EU Maschinenverordnung',
|
|
description: 'OEM-Zulieferer → Compliance-Nachweispflicht',
|
|
riskWeight: 5,
|
|
},
|
|
|
|
// ========== M: IACE — Maschinenverordnung Triggers (4 rules) ==========
|
|
{
|
|
id: 'HT-M01',
|
|
category: 'iace_machinery_regulation',
|
|
questionId: 'machineBuilder.containsSoftware',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['TOM'],
|
|
legalReference: 'EU Maschinenverordnung 2023/1230 Anhang III',
|
|
description: 'Software als Sicherheitskomponente → Maschinenverordnung',
|
|
combineWithMachineBuilder: { field: 'hasSafetyFunction', value: true },
|
|
riskWeight: 9,
|
|
},
|
|
{
|
|
id: 'HT-M02',
|
|
category: 'iace_machinery_regulation',
|
|
questionId: 'machineBuilder.ceMarkingRequired',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: [],
|
|
legalReference: 'EU Maschinenverordnung 2023/1230',
|
|
description: 'CE-Kennzeichnung erforderlich',
|
|
riskWeight: 6,
|
|
},
|
|
{
|
|
id: 'HT-M03',
|
|
category: 'iace_machinery_regulation',
|
|
questionId: 'machineBuilder.ceMarkingRequired',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L3',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: [],
|
|
legalReference: 'EU Maschinenverordnung 2023/1230 Art. 10',
|
|
description: 'CE ohne bestehende Risikobeurteilung → Dringend!',
|
|
combineWithMachineBuilder: { field: 'hasRiskAssessment', value: false },
|
|
riskWeight: 9,
|
|
},
|
|
{
|
|
id: 'HT-M04',
|
|
category: 'iace_machinery_regulation',
|
|
questionId: 'machineBuilder.containsFirmware',
|
|
condition: 'EQUALS',
|
|
conditionValue: true,
|
|
minimumLevel: 'L2',
|
|
requiresDSFA: false,
|
|
mandatoryDocuments: ['TOM'],
|
|
legalReference: 'EU Maschinenverordnung + CRA',
|
|
description: 'Firmware mit Remote-Update → Change Management Pflicht',
|
|
combineWithMachineBuilder: { field: 'hasOTAUpdates', value: true },
|
|
riskWeight: 7,
|
|
},
|
|
]
|
|
|
|
// ============================================================================
|
|
// COMPLIANCE SCOPE ENGINE
|
|
// ============================================================================
|
|
|
|
export class ComplianceScopeEngine {
|
|
/**
|
|
* Haupteinstiegspunkt: Evaluiert alle Profiling-Antworten und produziert eine ScopeDecision
|
|
* Optional: companyProfile fuer machineBuilder-basierte IACE Triggers
|
|
*/
|
|
evaluate(answers: ScopeProfilingAnswer[], companyProfile?: CompanyProfile | null): ScopeDecision {
|
|
const decision = createEmptyScopeDecision()
|
|
|
|
// 1. Scores berechnen
|
|
decision.scores = this.calculateScores(answers)
|
|
|
|
// 2. Hard Triggers prüfen (inkl. IACE machineBuilder Triggers)
|
|
decision.triggeredHardTriggers = this.evaluateHardTriggers(answers, companyProfile)
|
|
|
|
// 3. Finales Level bestimmen
|
|
decision.determinedLevel = this.determineLevel(
|
|
decision.scores,
|
|
decision.triggeredHardTriggers
|
|
)
|
|
|
|
// 4. Dokumenten-Scope aufbauen
|
|
decision.requiredDocuments = this.buildDocumentScope(
|
|
decision.determinedLevel,
|
|
decision.triggeredHardTriggers,
|
|
answers
|
|
)
|
|
|
|
// 5. Risk Flags ermitteln
|
|
decision.riskFlags = this.evaluateRiskFlags(answers, decision.determinedLevel)
|
|
|
|
// 6. Gaps berechnen
|
|
decision.gaps = this.calculateGaps(answers, decision.determinedLevel)
|
|
|
|
// 7. Next Actions ableiten
|
|
decision.nextActions = this.buildNextActions(
|
|
decision.requiredDocuments,
|
|
decision.gaps
|
|
)
|
|
|
|
// 8. Reasoning (Audit Trail) aufbauen
|
|
decision.reasoning = this.buildReasoning(
|
|
decision.scores,
|
|
decision.triggeredHardTriggers,
|
|
decision.determinedLevel,
|
|
decision.requiredDocuments
|
|
)
|
|
|
|
decision.evaluatedAt = new Date().toISOString()
|
|
|
|
return decision
|
|
}
|
|
|
|
/**
|
|
* Berechnet Risk-, Complexity- und Assurance-Scores aus den Profiling-Antworten
|
|
*/
|
|
calculateScores(answers: ScopeProfilingAnswer[]): ComplianceScores {
|
|
let riskSum = 0
|
|
let complexitySum = 0
|
|
let assuranceSum = 0
|
|
let riskWeightSum = 0
|
|
let complexityWeightSum = 0
|
|
let assuranceWeightSum = 0
|
|
|
|
for (const answer of answers) {
|
|
const weights = QUESTION_SCORE_WEIGHTS[answer.questionId]
|
|
if (!weights) continue
|
|
|
|
const multiplier = this.getAnswerMultiplier(answer)
|
|
|
|
riskSum += weights.risk * multiplier
|
|
complexitySum += weights.complexity * multiplier
|
|
assuranceSum += weights.assurance * multiplier
|
|
|
|
riskWeightSum += weights.risk
|
|
complexityWeightSum += weights.complexity
|
|
assuranceWeightSum += weights.assurance
|
|
}
|
|
|
|
const riskScore =
|
|
riskWeightSum > 0 ? (riskSum / riskWeightSum) * 10 : 0
|
|
const complexityScore =
|
|
complexityWeightSum > 0 ? (complexitySum / complexityWeightSum) * 10 : 0
|
|
const assuranceScore =
|
|
assuranceWeightSum > 0 ? (assuranceSum / assuranceWeightSum) * 10 : 0
|
|
|
|
const composite = riskScore * 0.4 + complexityScore * 0.3 + assuranceScore * 0.3
|
|
|
|
return {
|
|
risk_score: Math.round(riskScore * 10) / 10,
|
|
complexity_score: Math.round(complexityScore * 10) / 10,
|
|
assurance_need: Math.round(assuranceScore * 10) / 10,
|
|
composite_score: Math.round(composite * 10) / 10,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bestimmt den Multiplikator für eine Antwort (0.0 - 1.0)
|
|
*/
|
|
private getAnswerMultiplier(answer: ScopeProfilingAnswer): number {
|
|
const { questionId, value } = answer
|
|
|
|
// Boolean
|
|
if (typeof value === 'boolean') {
|
|
return value ? 1.0 : 0.0
|
|
}
|
|
|
|
// Number
|
|
if (typeof value === 'number') {
|
|
return this.normalizeNumericAnswer(questionId, value)
|
|
}
|
|
|
|
// Single choice
|
|
if (typeof value === 'string') {
|
|
const multipliers = ANSWER_MULTIPLIERS[questionId]
|
|
if (multipliers && multipliers[value] !== undefined) {
|
|
return multipliers[value]
|
|
}
|
|
return 0.5 // Fallback
|
|
}
|
|
|
|
// Multi choice
|
|
if (Array.isArray(value)) {
|
|
if (value.length === 0) return 0.0
|
|
// Simplified: count selected items
|
|
return Math.min(value.length / 5, 1.0)
|
|
}
|
|
|
|
return 0.0
|
|
}
|
|
|
|
/**
|
|
* Normalisiert numerische Antworten
|
|
*/
|
|
private normalizeNumericAnswer(questionId: string, value: number): number {
|
|
// Hier könnten spezifische Ranges definiert werden
|
|
// Vereinfacht: logarithmische Normalisierung
|
|
if (value <= 0) return 0.0
|
|
if (value >= 1000) return 1.0
|
|
return Math.log10(value + 1) / 3 // 0-1000 → ~0-1
|
|
}
|
|
|
|
/**
|
|
* Evaluiert Hard Trigger Rules
|
|
* Optional: companyProfile fuer machineBuilder-basierte IACE Triggers
|
|
*/
|
|
evaluateHardTriggers(answers: ScopeProfilingAnswer[], companyProfile?: CompanyProfile | null): TriggeredHardTrigger[] {
|
|
const triggered: TriggeredHardTrigger[] = []
|
|
const answerMap = new Map(answers.map((a) => [a.questionId, a.value]))
|
|
|
|
for (const rule of HARD_TRIGGER_RULES) {
|
|
const isTriggered = this.checkTriggerCondition(rule, answerMap, answers, companyProfile)
|
|
|
|
if (isTriggered) {
|
|
triggered.push({
|
|
ruleId: rule.id,
|
|
category: rule.category,
|
|
description: rule.description,
|
|
legalReference: rule.legalReference,
|
|
minimumLevel: rule.minimumLevel,
|
|
requiresDSFA: rule.requiresDSFA,
|
|
mandatoryDocuments: rule.mandatoryDocuments,
|
|
})
|
|
}
|
|
}
|
|
|
|
return triggered
|
|
}
|
|
|
|
/**
|
|
* Liest einen Wert aus dem MachineBuilderProfile anhand eines Feldnamens
|
|
*/
|
|
private getMachineBuilderValue(mb: MachineBuilderProfile, field: string): unknown {
|
|
return (mb as Record<string, unknown>)[field]
|
|
}
|
|
|
|
/**
|
|
* Prüft, ob eine Trigger-Regel erfüllt ist
|
|
*/
|
|
private checkTriggerCondition(
|
|
rule: HardTriggerRule,
|
|
answerMap: Map<string, any>,
|
|
answers: ScopeProfilingAnswer[],
|
|
companyProfile?: CompanyProfile | null,
|
|
): boolean {
|
|
// IACE machineBuilder-basierte Triggers
|
|
if (rule.questionId.startsWith('machineBuilder.')) {
|
|
const mb = companyProfile?.machineBuilder
|
|
if (!mb) return false
|
|
|
|
const fieldName = rule.questionId.replace('machineBuilder.', '')
|
|
const fieldValue = this.getMachineBuilderValue(mb, fieldName)
|
|
if (fieldValue === undefined) return false
|
|
|
|
let baseCondition = false
|
|
switch (rule.condition) {
|
|
case 'EQUALS':
|
|
baseCondition = fieldValue === rule.conditionValue
|
|
break
|
|
case 'CONTAINS':
|
|
if (Array.isArray(fieldValue)) {
|
|
baseCondition = fieldValue.includes(rule.conditionValue)
|
|
}
|
|
break
|
|
default:
|
|
baseCondition = fieldValue === rule.conditionValue
|
|
}
|
|
|
|
if (!baseCondition) return false
|
|
|
|
// combineWithMachineBuilder: additional AND condition on another MB field
|
|
const combine = (rule as any).combineWithMachineBuilder
|
|
if (combine) {
|
|
const combineVal = this.getMachineBuilderValue(mb, combine.field)
|
|
if (combine.value !== undefined && combineVal !== combine.value) return false
|
|
if (combine.includes !== undefined) {
|
|
if (!Array.isArray(combineVal) || !combineVal.includes(combine.includes)) return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Standard answer-based triggers
|
|
const value = answerMap.get(rule.questionId)
|
|
if (value === undefined) return false
|
|
|
|
// Basis-Check
|
|
let baseCondition = false
|
|
|
|
switch (rule.condition) {
|
|
case 'EQUALS':
|
|
baseCondition = value === rule.conditionValue
|
|
break
|
|
case 'CONTAINS':
|
|
if (Array.isArray(value)) {
|
|
baseCondition = value.includes(rule.conditionValue)
|
|
} else if (typeof value === 'string') {
|
|
baseCondition = value.includes(rule.conditionValue)
|
|
}
|
|
break
|
|
case 'IN':
|
|
if (Array.isArray(rule.conditionValue)) {
|
|
baseCondition = rule.conditionValue.includes(value)
|
|
}
|
|
break
|
|
case 'GREATER_THAN':
|
|
if (typeof value === 'number' && typeof rule.conditionValue === 'number') {
|
|
baseCondition = value > rule.conditionValue
|
|
} else if (typeof value === 'string') {
|
|
// Parse employee count from string like "1000+"
|
|
const parsed = this.parseEmployeeCount(value)
|
|
baseCondition = parsed > (rule.conditionValue as number)
|
|
}
|
|
break
|
|
case 'NOT_EQUALS':
|
|
baseCondition = value !== rule.conditionValue
|
|
break
|
|
}
|
|
|
|
if (!baseCondition) return false
|
|
|
|
// Exclude-Bedingung: Regel feuert NICHT wenn excludeWhen zutrifft
|
|
if (rule.excludeWhen) {
|
|
const exVal = answerMap.get(rule.excludeWhen.questionId)
|
|
if (Array.isArray(rule.excludeWhen.value)
|
|
? rule.excludeWhen.value.includes(exVal)
|
|
: exVal === rule.excludeWhen.value) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Require-Bedingung: Regel feuert NUR wenn requireWhen zutrifft
|
|
if (rule.requireWhen) {
|
|
const reqVal = answerMap.get(rule.requireWhen.questionId)
|
|
if (Array.isArray(rule.requireWhen.value)
|
|
? !rule.requireWhen.value.includes(reqVal)
|
|
: reqVal !== rule.requireWhen.value) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Combined checks
|
|
if (rule.combineWithArt9) {
|
|
const art9 = answerMap.get('data_art9')
|
|
if (!art9 || (Array.isArray(art9) && art9.length === 0)) return false
|
|
}
|
|
|
|
if (rule.combineWithMinors) {
|
|
const minors = answerMap.get('data_minors')
|
|
if (minors !== true) return false
|
|
}
|
|
|
|
if (rule.combineWithAI) {
|
|
const ai = answerMap.get('proc_ai_usage')
|
|
if (!ai || (Array.isArray(ai) && (ai.length === 0 || ai.includes('keine')))) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
if (rule.combineWithEmployeeMonitoring) {
|
|
const empMon = answerMap.get('proc_employee_monitoring')
|
|
if (empMon !== true) return false
|
|
}
|
|
|
|
if (rule.combineWithADM) {
|
|
const adm = answerMap.get('proc_adm_scoring')
|
|
if (adm !== true) return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* Parsed Mitarbeiterzahl aus String
|
|
*/
|
|
private parseEmployeeCount(value: string): number {
|
|
if (value === '1-9') return 9
|
|
if (value === '10-49') return 49
|
|
if (value === '50-249') return 249
|
|
if (value === '250-999') return 999
|
|
if (value === '1000+') return 1000
|
|
return 0
|
|
}
|
|
|
|
/**
|
|
* Bestimmt das finale Compliance-Level basierend auf Scores und Triggers
|
|
*/
|
|
determineLevel(
|
|
scores: ComplianceScores,
|
|
triggers: TriggeredHardTrigger[]
|
|
): ComplianceDepthLevel {
|
|
// Score-basiertes Level
|
|
let levelFromScore: ComplianceDepthLevel
|
|
if (scores.composite_score <= 25) levelFromScore = 'L1'
|
|
else if (scores.composite_score <= 50) levelFromScore = 'L2'
|
|
else if (scores.composite_score <= 75) levelFromScore = 'L3'
|
|
else levelFromScore = 'L4'
|
|
|
|
// Höchstes Level aus Triggers
|
|
let maxTriggerLevel: ComplianceDepthLevel = 'L1'
|
|
for (const trigger of triggers) {
|
|
if (getDepthLevelNumeric(trigger.minimumLevel) > getDepthLevelNumeric(maxTriggerLevel)) {
|
|
maxTriggerLevel = trigger.minimumLevel
|
|
}
|
|
}
|
|
|
|
// Maximum von beiden
|
|
return maxDepthLevel(levelFromScore, maxTriggerLevel)
|
|
}
|
|
|
|
/**
|
|
* Baut den Dokumenten-Scope basierend auf Level und Triggers
|
|
*/
|
|
buildDocumentScope(
|
|
level: ComplianceDepthLevel,
|
|
triggers: TriggeredHardTrigger[],
|
|
answers: ScopeProfilingAnswer[]
|
|
): RequiredDocument[] {
|
|
const requiredDocs: RequiredDocument[] = []
|
|
const mandatoryFromTriggers = new Set<ScopeDocumentType>()
|
|
|
|
// Sammle mandatory docs aus Triggern
|
|
for (const trigger of triggers) {
|
|
for (const doc of trigger.mandatoryDocuments) {
|
|
mandatoryFromTriggers.add(doc as ScopeDocumentType)
|
|
}
|
|
}
|
|
|
|
// Für jeden Dokumenttyp prüfen
|
|
for (const docType of Object.keys(DOCUMENT_SCOPE_MATRIX) as ScopeDocumentType[]) {
|
|
const requirement = DOCUMENT_SCOPE_MATRIX[docType][level]
|
|
const isMandatoryFromTrigger = mandatoryFromTriggers.has(docType)
|
|
|
|
if (requirement === 'mandatory' || isMandatoryFromTrigger) {
|
|
requiredDocs.push({
|
|
documentType: docType,
|
|
label: DOCUMENT_TYPE_LABELS[docType],
|
|
requirement: 'mandatory',
|
|
priority: this.getDocumentPriority(docType, isMandatoryFromTrigger),
|
|
estimatedEffort: this.estimateEffort(docType),
|
|
sdkStepUrl: DOCUMENT_SDK_STEP_MAP[docType],
|
|
triggeredBy: isMandatoryFromTrigger
|
|
? triggers
|
|
.filter((t) => t.mandatoryDocuments.includes(docType as any))
|
|
.map((t) => t.ruleId)
|
|
: [],
|
|
})
|
|
} else if (requirement === 'recommended') {
|
|
requiredDocs.push({
|
|
documentType: docType,
|
|
label: DOCUMENT_TYPE_LABELS[docType],
|
|
requirement: 'recommended',
|
|
priority: 'medium',
|
|
estimatedEffort: this.estimateEffort(docType),
|
|
sdkStepUrl: DOCUMENT_SDK_STEP_MAP[docType],
|
|
triggeredBy: [],
|
|
})
|
|
}
|
|
}
|
|
|
|
// Sortieren: mandatory zuerst, dann nach Priority
|
|
requiredDocs.sort((a, b) => {
|
|
if (a.requirement === 'mandatory' && b.requirement !== 'mandatory') return -1
|
|
if (a.requirement !== 'mandatory' && b.requirement === 'mandatory') return 1
|
|
|
|
const priorityOrder: Record<string, number> = { high: 3, medium: 2, low: 1 }
|
|
return priorityOrder[b.priority] - priorityOrder[a.priority]
|
|
})
|
|
|
|
return requiredDocs
|
|
}
|
|
|
|
/**
|
|
* Bestimmt die Priorität eines Dokuments
|
|
*/
|
|
private getDocumentPriority(
|
|
docType: ScopeDocumentType,
|
|
isMandatoryFromTrigger: boolean
|
|
): 'high' | 'medium' | 'low' {
|
|
if (isMandatoryFromTrigger) return 'high'
|
|
|
|
// Basis-Dokumente haben hohe Priorität
|
|
if (['VVT', 'TOM', 'DSE'].includes(docType)) return 'high'
|
|
if (['DSFA', 'AVV', 'EINWILLIGUNGEN'].includes(docType)) return 'high'
|
|
|
|
return 'medium'
|
|
}
|
|
|
|
/**
|
|
* Schätzt den Aufwand für ein Dokument (in Stunden)
|
|
*/
|
|
private estimateEffort(docType: ScopeDocumentType): number {
|
|
const effortMap: Record<ScopeDocumentType, number> = {
|
|
VVT: 8,
|
|
TOM: 12,
|
|
DSFA: 16,
|
|
AVV: 4,
|
|
DSE: 6,
|
|
EINWILLIGUNGEN: 6,
|
|
LOESCHKONZEPT: 10,
|
|
TRANSFER_DOKU: 8,
|
|
DSR_PROZESS: 8,
|
|
NOTFALLPLAN: 12,
|
|
COOKIE_BANNER: 4,
|
|
AGB: 6,
|
|
WIDERRUFSBELEHRUNG: 3,
|
|
PREISANGABEN: 2,
|
|
FERNABSATZ_INFO: 4,
|
|
STREITBEILEGUNG: 1,
|
|
PRODUKTSICHERHEIT: 8,
|
|
AI_ACT_DOKU: 12,
|
|
AUDIT_CHECKLIST: 8,
|
|
VENDOR_MANAGEMENT: 10,
|
|
}
|
|
return effortMap[docType] || 6
|
|
}
|
|
|
|
/**
|
|
* Evaluiert Risk Flags basierend auf Process Maturity Gaps und anderen Risiken
|
|
*/
|
|
evaluateRiskFlags(
|
|
answers: ScopeProfilingAnswer[],
|
|
level: ComplianceDepthLevel
|
|
): RiskFlag[] {
|
|
const flags: RiskFlag[] = []
|
|
const answerMap = new Map(answers.map((a) => [a.questionId, a.value]))
|
|
|
|
// Process Maturity Gaps (Kategorie I Trigger)
|
|
const maturityRules = HARD_TRIGGER_RULES.filter((r) => r.category === 'process_maturity')
|
|
for (const rule of maturityRules) {
|
|
if (this.checkTriggerCondition(rule, answerMap, answers)) {
|
|
flags.push({
|
|
severity: 'medium',
|
|
category: 'process',
|
|
message: rule.description,
|
|
legalReference: rule.legalReference,
|
|
recommendation: this.getMaturityRecommendation(rule.id),
|
|
})
|
|
}
|
|
}
|
|
|
|
// Verschlüsselung fehlt bei L2+
|
|
if (getDepthLevelNumeric(level) >= 2) {
|
|
const encTransit = answerMap.get('tech_encryption_transit')
|
|
const encRest = answerMap.get('tech_encryption_rest')
|
|
|
|
if (encTransit === false) {
|
|
flags.push({
|
|
severity: 'high',
|
|
category: 'technical',
|
|
message: 'Fehlende Verschlüsselung bei Datenübertragung',
|
|
legalReference: 'Art. 32 DSGVO',
|
|
recommendation: 'TLS 1.2+ für alle Datenübertragungen implementieren',
|
|
})
|
|
}
|
|
|
|
if (encRest === false) {
|
|
flags.push({
|
|
severity: 'high',
|
|
category: 'technical',
|
|
message: 'Fehlende Verschlüsselung gespeicherter Daten',
|
|
legalReference: 'Art. 32 DSGVO',
|
|
recommendation: 'Verschlüsselung at-rest für sensitive Daten implementieren',
|
|
})
|
|
}
|
|
}
|
|
|
|
// Drittland ohne adäquate Grundlage
|
|
const thirdCountry = answerMap.get('tech_third_country')
|
|
const hostingLocation = answerMap.get('tech_hosting_location')
|
|
if (
|
|
thirdCountry === true &&
|
|
hostingLocation !== 'eu' &&
|
|
hostingLocation !== 'eu_us_adequacy'
|
|
) {
|
|
flags.push({
|
|
severity: 'high',
|
|
category: 'legal',
|
|
message: 'Drittlandtransfer ohne angemessene Garantien',
|
|
legalReference: 'Art. 44 ff. DSGVO',
|
|
recommendation:
|
|
'Standardvertragsklauseln (SCCs) oder Binding Corporate Rules (BCRs) implementieren',
|
|
})
|
|
}
|
|
|
|
// Fehlender DSB bei großen Organisationen
|
|
const hasDPO = answerMap.get('org_has_dpo')
|
|
const employeeCount = answerMap.get('org_employee_count')
|
|
if (hasDPO === false && this.parseEmployeeCount(employeeCount as string) >= 250) {
|
|
flags.push({
|
|
severity: 'medium',
|
|
category: 'organizational',
|
|
message: 'Kein Datenschutzbeauftragter bei großer Organisation',
|
|
legalReference: 'Art. 37 DSGVO',
|
|
recommendation: 'Bestellung eines Datenschutzbeauftragten prüfen',
|
|
})
|
|
}
|
|
|
|
return flags
|
|
}
|
|
|
|
/**
|
|
* Gibt Empfehlung für Maturity Gap
|
|
*/
|
|
private getMaturityRecommendation(ruleId: string): string {
|
|
const recommendations: Record<string, string> = {
|
|
'HT-I01': 'Prozess für Betroffenenrechte (DSAR) etablieren und dokumentieren',
|
|
'HT-I02': 'Löschkonzept gemäß Art. 17 DSGVO entwickeln und implementieren',
|
|
'HT-I03':
|
|
'Incident-Response-Plan für Datenschutzverletzungen (Art. 33 DSGVO) erstellen',
|
|
'HT-I04': 'Regelmäßige interne Audits und Reviews einführen',
|
|
'HT-I05': 'Schulungsprogramm für Mitarbeiter zum Datenschutz etablieren',
|
|
}
|
|
return recommendations[ruleId] || 'Prozess etablieren und dokumentieren'
|
|
}
|
|
|
|
/**
|
|
* Berechnet Gaps zwischen Ist-Zustand und Soll-Anforderungen
|
|
*/
|
|
calculateGaps(
|
|
answers: ScopeProfilingAnswer[],
|
|
level: ComplianceDepthLevel
|
|
): ScopeGap[] {
|
|
const gaps: ScopeGap[] = []
|
|
const answerMap = new Map(answers.map((a) => [a.questionId, a.value]))
|
|
|
|
// DSFA Gap (bei L3+)
|
|
if (getDepthLevelNumeric(level) >= 3) {
|
|
const hasDSFA = answerMap.get('proc_regular_audits') // Proxy
|
|
if (hasDSFA === false) {
|
|
gaps.push({
|
|
gapType: 'documentation',
|
|
severity: 'high',
|
|
description: 'Datenschutz-Folgenabschätzung (DSFA) fehlt',
|
|
requiredFor: level,
|
|
currentState: 'Keine DSFA durchgeführt',
|
|
targetState: 'DSFA für Hochrisiko-Verarbeitungen durchgeführt und dokumentiert',
|
|
effort: 16,
|
|
priority: 'high',
|
|
})
|
|
}
|
|
}
|
|
|
|
// Löschkonzept Gap
|
|
const hasDeletion = answerMap.get('proc_deletion_concept')
|
|
if (hasDeletion === false && getDepthLevelNumeric(level) >= 2) {
|
|
gaps.push({
|
|
gapType: 'process',
|
|
severity: 'medium',
|
|
description: 'Löschkonzept fehlt',
|
|
requiredFor: level,
|
|
currentState: 'Kein systematisches Löschkonzept',
|
|
targetState: 'Dokumentiertes Löschkonzept mit definierten Fristen',
|
|
effort: 10,
|
|
priority: 'high',
|
|
})
|
|
}
|
|
|
|
// DSAR Prozess Gap
|
|
const hasDSAR = answerMap.get('proc_dsar_process')
|
|
if (hasDSAR === false) {
|
|
gaps.push({
|
|
gapType: 'process',
|
|
severity: 'high',
|
|
description: 'Prozess für Betroffenenrechte fehlt',
|
|
requiredFor: level,
|
|
currentState: 'Kein etablierter DSAR-Prozess',
|
|
targetState: 'Dokumentierter Prozess zur Bearbeitung von Betroffenenrechten',
|
|
effort: 8,
|
|
priority: 'high',
|
|
})
|
|
}
|
|
|
|
// Incident Response Gap
|
|
const hasIncident = answerMap.get('proc_incident_response')
|
|
if (hasIncident === false) {
|
|
gaps.push({
|
|
gapType: 'process',
|
|
severity: 'high',
|
|
description: 'Incident-Response-Plan fehlt',
|
|
requiredFor: level,
|
|
currentState: 'Kein Prozess für Datenschutzverletzungen',
|
|
targetState: 'Dokumentierter Incident-Response-Plan gemäß Art. 33 DSGVO',
|
|
effort: 12,
|
|
priority: 'high',
|
|
})
|
|
}
|
|
|
|
// Schulungen Gap
|
|
const hasTraining = answerMap.get('comp_training')
|
|
if (hasTraining === false && getDepthLevelNumeric(level) >= 2) {
|
|
gaps.push({
|
|
gapType: 'organizational',
|
|
severity: 'medium',
|
|
description: 'Datenschutzschulungen fehlen',
|
|
requiredFor: level,
|
|
currentState: 'Keine regelmäßigen Schulungen',
|
|
targetState: 'Etabliertes Schulungsprogramm für alle Mitarbeiter',
|
|
effort: 6,
|
|
priority: 'medium',
|
|
})
|
|
}
|
|
|
|
return gaps
|
|
}
|
|
|
|
/**
|
|
* Baut priorisierte Next Actions aus Required Documents und Gaps
|
|
*/
|
|
buildNextActions(
|
|
requiredDocuments: RequiredDocument[],
|
|
gaps: ScopeGap[]
|
|
): NextAction[] {
|
|
const actions: NextAction[] = []
|
|
|
|
// Dokumente zu Actions
|
|
for (const doc of requiredDocuments) {
|
|
if (doc.requirement === 'mandatory') {
|
|
actions.push({
|
|
actionType: 'create_document',
|
|
title: `${doc.label} erstellen`,
|
|
description: `Pflichtdokument für Compliance-Level erstellen`,
|
|
priority: doc.priority,
|
|
estimatedEffort: doc.estimatedEffort,
|
|
documentType: doc.documentType,
|
|
sdkStepUrl: doc.sdkStepUrl,
|
|
blockers: [],
|
|
})
|
|
}
|
|
}
|
|
|
|
// Gaps zu Actions
|
|
for (const gap of gaps) {
|
|
let actionType: NextAction['actionType'] = 'establish_process'
|
|
if (gap.gapType === 'documentation') actionType = 'create_document'
|
|
else if (gap.gapType === 'technical') actionType = 'implement_technical'
|
|
else if (gap.gapType === 'organizational') actionType = 'organizational_change'
|
|
|
|
actions.push({
|
|
actionType,
|
|
title: `Gap schließen: ${gap.description}`,
|
|
description: `Von "${gap.currentState}" zu "${gap.targetState}"`,
|
|
priority: gap.priority,
|
|
estimatedEffort: gap.effort,
|
|
blockers: [],
|
|
})
|
|
}
|
|
|
|
// Nach Priority sortieren
|
|
const priorityOrder: Record<string, number> = { high: 3, medium: 2, low: 1 }
|
|
actions.sort((a, b) => priorityOrder[b.priority] - priorityOrder[a.priority])
|
|
|
|
return actions
|
|
}
|
|
|
|
/**
|
|
* Baut Reasoning (Audit Trail) für Transparenz
|
|
*/
|
|
buildReasoning(
|
|
scores: ComplianceScores,
|
|
triggers: TriggeredHardTrigger[],
|
|
level: ComplianceDepthLevel,
|
|
docs: RequiredDocument[]
|
|
): ScopeReasoning[] {
|
|
const reasoning: ScopeReasoning[] = []
|
|
|
|
// 1. Score-Berechnung
|
|
reasoning.push({
|
|
step: 'score_calculation',
|
|
description: 'Risikobasierte Score-Berechnung aus Profiling-Antworten',
|
|
factors: [
|
|
`Risiko-Score: ${scores.risk_score}/10`,
|
|
`Komplexitäts-Score: ${scores.complexity_score}/10`,
|
|
`Assurance-Score: ${scores.assurance_need}/10`,
|
|
`Composite Score: ${scores.composite_score}/10`,
|
|
],
|
|
impact: `Score-basiertes Level: ${this.getLevelFromScore(scores.composite_score)}`,
|
|
})
|
|
|
|
// 2. Hard Trigger Evaluation
|
|
if (triggers.length > 0) {
|
|
reasoning.push({
|
|
step: 'hard_trigger_evaluation',
|
|
description: `${triggers.length} Hard Trigger Rule(s) aktiviert`,
|
|
factors: triggers.map(
|
|
(t) => `${t.ruleId}: ${t.description}${t.legalReference ? ` (${t.legalReference})` : ''}`
|
|
),
|
|
impact: `Höchstes Trigger-Level: ${this.getMaxTriggerLevel(triggers)}`,
|
|
})
|
|
}
|
|
|
|
// 3. Level-Bestimmung
|
|
reasoning.push({
|
|
step: 'level_determination',
|
|
description: 'Finales Compliance-Level durch Maximum aus Score und Triggers',
|
|
factors: [
|
|
`Score-Level: ${this.getLevelFromScore(scores.composite_score)}`,
|
|
`Trigger-Level: ${this.getMaxTriggerLevel(triggers)}`,
|
|
],
|
|
impact: `Finales Level: ${level}`,
|
|
})
|
|
|
|
// 4. Dokumenten-Scope
|
|
const mandatoryDocs = docs.filter((d) => d.requirement === 'mandatory')
|
|
reasoning.push({
|
|
step: 'document_scope',
|
|
description: `Dokumenten-Scope für ${level} bestimmt`,
|
|
factors: [
|
|
`${mandatoryDocs.length} Pflichtdokumente`,
|
|
`${docs.length - mandatoryDocs.length} empfohlene Dokumente`,
|
|
],
|
|
impact: `Gesamtaufwand: ~${docs.reduce((sum, d) => sum + d.estimatedEffort, 0)} Stunden`,
|
|
})
|
|
|
|
return reasoning
|
|
}
|
|
|
|
/**
|
|
* Hilfsfunktion: Level aus Score ableiten
|
|
*/
|
|
private getLevelFromScore(composite: number): ComplianceDepthLevel {
|
|
if (composite <= 25) return 'L1'
|
|
if (composite <= 50) return 'L2'
|
|
if (composite <= 75) return 'L3'
|
|
return 'L4'
|
|
}
|
|
|
|
/**
|
|
* Hilfsfunktion: Höchstes Level aus Triggern
|
|
*/
|
|
private getMaxTriggerLevel(triggers: TriggeredHardTrigger[]): ComplianceDepthLevel {
|
|
if (triggers.length === 0) return 'L1'
|
|
let max: ComplianceDepthLevel = 'L1'
|
|
for (const t of triggers) {
|
|
if (getDepthLevelNumeric(t.minimumLevel) > getDepthLevelNumeric(max)) {
|
|
max = t.minimumLevel
|
|
}
|
|
}
|
|
return max
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// SINGLETON EXPORT
|
|
// ============================================================================
|
|
|
|
export const complianceScopeEngine = new ComplianceScopeEngine()
|