feat(regulations): Automatische Ableitung anwendbarer Gesetze & Aufsichtsbehoerden
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 35s
CI / test-python-backend-compliance (push) Successful in 37s
CI / test-python-document-crawler (push) Successful in 27s
CI / test-python-dsms-gateway (push) Successful in 21s

Nach Abschluss von Profil + Scope werden jetzt automatisch die anwendbaren
Regulierungen (DSGVO, NIS2, AI Act, DORA) ermittelt und die zustaendigen
Aufsichtsbehoerden (Landes-DSB, BSI, BaFin) aus Bundesland + Branche abgeleitet.

- Neues scope-to-facts.ts: Mapping CompanyProfile+Scope → Go SDK Payload
- Neues supervisory-authority-resolver.ts: 16 Landes-DSB + nationale Behoerden
- ScopeDecisionTab: Regulierungs-Report mit Aufsichtsbehoerden-Karten
- Obligations-Seite: Echte Daten statt Dummy in handleAutoProfiling()
- Neue Types: ApplicableRegulation, RegulationAssessmentResult, SupervisoryAuthorityInfo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-10 10:29:24 +01:00
parent fa4cda7627
commit 5da93c5d10
6 changed files with 665 additions and 16 deletions

View File

@@ -1406,3 +1406,63 @@ export function depthLevelFromNumeric(n: number): ComplianceDepthLevel {
};
return map[Math.max(1, Math.min(4, Math.round(n)))] || 'L1';
}
// ============================================================================
// Regulation Assessment Types (from Go AI SDK /assess-from-scope)
// ============================================================================
/**
* Eine anwendbare Regulierung (aus Go SDK ApplicableRegulation)
*/
export interface ApplicableRegulation {
id: string
name: string
classification: string
reason: string
obligation_count: number
control_count: number
}
/**
* Ergebnis der Regulierungs-Bewertung vom Go AI SDK
*/
export interface RegulationAssessmentResult {
applicable_regulations: ApplicableRegulation[]
obligations: RegulationObligation[]
executive_summary: {
total_regulations: number
total_obligations: number
critical_obligations: number
compliance_score: number
key_risks: string[]
recommended_actions: string[]
}
}
/**
* Einzelne Pflicht aus dem Go SDK
*/
export interface RegulationObligation {
id: string
regulation_id: string
title: string
description: string
category: string
responsible: string
priority: string
legal_basis?: Array<{ article: string; name: string }>
how_to_implement?: string
breakpilot_feature?: string
}
/**
* Aufsichtsbehoerden-Ergebnis
*/
export interface SupervisoryAuthorityInfo {
domain: string
authority: {
name: string
abbreviation: string
url: string
}
}

View File

@@ -0,0 +1,220 @@
/**
* Scope-to-Facts Mapper
*
* Konvertiert CompanyProfile + ScopeProfilingAnswer[] + ScopeDecision
* in das Go AI SDK ScopeDecision-Format fuer POST /assess-from-scope.
*/
import type { CompanyProfile } from './types'
import type { ScopeProfilingAnswer, ScopeDecision } from './compliance-scope-types'
/**
* Payload-Format fuer den Go AI SDK /assess-from-scope Endpoint.
* Muss mit ucca.ScopeDecision in scope_facts_mapper.go uebereinstimmen.
*/
export interface ScopeDecisionPayload {
employee_count: number
annual_revenue: number
country: string
industry: string
legal_form: string
processes_personal_data: boolean
is_controller: boolean
is_processor: boolean
data_art9: boolean
data_minors: boolean
large_scale: boolean
systematic_monitoring: boolean
cross_border_transfer: boolean
uses_processors: boolean
automated_decisions: boolean
processes_employee_data: boolean
processes_health_data: boolean
processes_financial_data: boolean
uses_cookies: boolean
uses_tracking: boolean
uses_video_surveillance: boolean
operates_platform: boolean
platform_user_count: number
proc_ai_usage: boolean
is_ai_provider: boolean
is_ai_deployer: boolean
high_risk_ai: boolean
limited_risk_ai: boolean
sector: string
special_services: string[]
is_kritis: boolean
is_financial_institution: boolean
determined_level: string
triggered_rules: string[]
required_documents: string[]
cert_target: string
}
/**
* Konvertiert CompanyProfile + Scope-Daten in das Go SDK Payload-Format.
*/
export function buildAssessmentPayload(
profile: CompanyProfile,
scopeAnswers: ScopeProfilingAnswer[],
decision: ScopeDecision | null
): ScopeDecisionPayload {
const getBool = (questionId: string): boolean => getAnswerBool(scopeAnswers, questionId)
const getMulti = (questionId: string): string[] => getAnswerMulti(scopeAnswers, questionId)
const aiCategories = getMulti('ai_categories')
const isAIProvider = aiCategories.includes('ai_provider') || aiCategories.includes('ai_developer')
const isAIDeployer = aiCategories.includes('ai_deployer') || aiCategories.includes('ai_operator')
const aiRisk = getAnswerString(scopeAnswers, 'ai_risk_assessment')
const industry = Array.isArray(profile.industry) ? profile.industry.join(', ') : (profile.industry || '')
const isFinancial = industry.toLowerCase().includes('finanz') ||
industry.toLowerCase().includes('bank') ||
industry.toLowerCase().includes('versicherung') ||
industry.toLowerCase().includes('financial')
return {
employee_count: parseEmployeeRange(profile.employeeCount),
annual_revenue: parseRevenueRange(profile.annualRevenue),
country: profile.headquartersCountry || 'DE',
industry,
legal_form: profile.legalForm || '',
processes_personal_data: true, // Jedes Unternehmen im Tool verarbeitet personenbezogene Daten
is_controller: profile.isDataController ?? true,
is_processor: profile.isDataProcessor ?? false,
data_art9: getBool('data_art9'),
data_minors: getBool('data_minors'),
large_scale: parseEmployeeRange(profile.employeeCount) >= 250 || getBool('data_volume'),
systematic_monitoring: getBool('proc_employee_monitoring') || getBool('proc_video_surveillance'),
cross_border_transfer: getBool('tech_third_country'),
uses_processors: getBool('tech_subprocessors'),
automated_decisions: getBool('proc_adm_scoring'),
processes_employee_data: getBool('data_hr'),
processes_health_data: getBool('data_art9'),
processes_financial_data: getBool('data_financial'),
uses_cookies: getBool('prod_cookies_consent'),
uses_tracking: getBool('proc_tracking'),
uses_video_surveillance: getBool('proc_video_surveillance'),
operates_platform: (profile.offerings || []).some(o =>
o === 'software_saas' || o === 'app_web' || o === 'app_mobile'
),
platform_user_count: parseCustomerCount(scopeAnswers),
proc_ai_usage: getBool('ai_uses_ai'),
is_ai_provider: isAIProvider,
is_ai_deployer: isAIDeployer,
high_risk_ai: aiRisk === 'high' || aiRisk === 'unacceptable',
limited_risk_ai: aiRisk === 'limited',
sector: mapIndustryToSector(industry),
special_services: [],
is_kritis: false, // Kann spaeter aus Branche abgeleitet werden
is_financial_institution: isFinancial,
determined_level: decision?.determinedLevel || 'L2',
triggered_rules: decision?.triggeredHardTriggers?.map(t => t.rule.id) || [],
required_documents: decision?.requiredDocuments?.map(d => d.documentType) || [],
cert_target: getAnswerString(scopeAnswers, 'org_cert_target'),
}
}
// =============================================================================
// Hilfsfunktionen
// =============================================================================
/** Liest eine boolean-Antwort aus den Scope-Antworten */
function getAnswerBool(answers: ScopeProfilingAnswer[], questionId: string): boolean {
const answer = answers.find(a => a.questionId === questionId)
if (!answer) return false
if (typeof answer.value === 'boolean') return answer.value
if (typeof answer.value === 'string') return answer.value === 'true' || answer.value === 'yes' || answer.value === 'ja'
if (Array.isArray(answer.value)) return answer.value.length > 0
return false
}
/** Liest eine Multi-Select-Antwort aus den Scope-Antworten */
function getAnswerMulti(answers: ScopeProfilingAnswer[], questionId: string): string[] {
const answer = answers.find(a => a.questionId === questionId)
if (!answer) return []
if (Array.isArray(answer.value)) return answer.value as string[]
if (typeof answer.value === 'string') return [answer.value]
return []
}
/** Liest einen String-Wert aus den Scope-Antworten */
function getAnswerString(answers: ScopeProfilingAnswer[], questionId: string): string {
const answer = answers.find(a => a.questionId === questionId)
if (!answer) return ''
if (typeof answer.value === 'string') return answer.value
if (Array.isArray(answer.value)) return answer.value[0] || ''
return String(answer.value)
}
/**
* Konvertiert Mitarbeiter-Range-String in einen numerischen Wert (Mittelwert).
* "1-9" → 5, "10-49" → 30, "50-249" → 150, "250-999" → 625, "1000+" → 1500
*/
export function parseEmployeeRange(range: string | undefined | null): number {
if (!range) return 10
const r = range.trim()
if (r.includes('1000') || r.includes('+')) return 1500
if (r.includes('250')) return 625
if (r.includes('50')) return 150
if (r.includes('10')) return 30
return 5
}
/**
* Konvertiert Umsatz-Range-String in einen numerischen Wert.
* "< 2 Mio" → 1000000, "2-10 Mio" → 6000000, "10-50 Mio" → 30000000, "> 50 Mio" → 75000000
*/
export function parseRevenueRange(range: string | undefined | null): number {
if (!range) return 1000000
const r = range.trim().toLowerCase()
if (r.includes('> 50') || r.includes('>50')) return 75000000
if (r.includes('10-50') || r.includes('10 -')) return 30000000
if (r.includes('2-10') || r.includes('2 -')) return 6000000
return 1000000
}
/** Liest die Kundenzahl aus den Scope-Antworten */
function parseCustomerCount(answers: ScopeProfilingAnswer[]): number {
const answer = answers.find(a => a.questionId === 'org_customer_count')
if (!answer) return 0
if (typeof answer.value === 'number') return answer.value
const str = String(answer.value)
const match = str.match(/\d+/)
return match ? parseInt(match[0], 10) : 0
}
/**
* Mappt deutsche Branchenbezeichnungen auf Go SDK Sektor-IDs.
* Diese Sektoren werden fuer NIS2 Annex I/II Pruefung verwendet.
*/
function mapIndustryToSector(industry: string): string {
const lower = industry.toLowerCase()
// NIS2 Annex I — "Essential" Sektoren
if (lower.includes('energie') || lower.includes('energy')) return 'energy'
if (lower.includes('transport') || lower.includes('logistik')) return 'transport'
if (lower.includes('bank') || lower.includes('finanz') || lower.includes('financial')) return 'banking'
if (lower.includes('versicherung') || lower.includes('insurance')) return 'financial_market'
if (lower.includes('gesundheit') || lower.includes('health') || lower.includes('pharma')) return 'health'
if (lower.includes('wasser') || lower.includes('water')) return 'drinking_water'
if (lower.includes('digital') || lower.includes('cloud') || lower.includes('hosting') || lower.includes('rechenzent')) return 'digital_infrastructure'
if (lower.includes('telekommunikation') || lower.includes('telecom')) return 'digital_infrastructure'
// NIS2 Annex II — "Important" Sektoren
if (lower.includes('it') || lower.includes('software') || lower.includes('technologie')) return 'ict_service_management'
if (lower.includes('lebensmittel') || lower.includes('food')) return 'food'
if (lower.includes('chemie') || lower.includes('chemical')) return 'chemicals'
if (lower.includes('forschung') || lower.includes('research')) return 'research'
if (lower.includes('post') || lower.includes('kurier')) return 'postal'
if (lower.includes('abfall') || lower.includes('waste')) return 'waste_management'
if (lower.includes('fertigung') || lower.includes('manufactur') || lower.includes('produktion')) return 'manufacturing'
// Fallback
if (lower.includes('handel') || lower.includes('retail') || lower.includes('e-commerce')) return 'digital_providers'
if (lower.includes('beratung') || lower.includes('consulting')) return 'ict_service_management'
if (lower.includes('bildung') || lower.includes('education')) return 'research'
if (lower.includes('medien') || lower.includes('media')) return 'digital_providers'
return 'other'
}

View File

@@ -0,0 +1,153 @@
/**
* Supervisory Authority Resolver
*
* Ermittelt automatisch die zustaendigen Aufsichtsbehoerden basierend auf
* Bundesland/Land des Unternehmens und den anwendbaren Regulierungen.
*/
/**
* Ergebnis der Aufsichtsbehoerden-Ermittlung
*/
export interface SupervisoryAuthorityResult {
domain: string
authority: {
name: string
abbreviation: string
url: string
}
}
/**
* Mapping: Bundesland-Kuerzel → Landes-Datenschutzbehoerde
*/
const DATA_PROTECTION_AUTHORITIES_DE: Record<string, { name: string; abbreviation: string; url: string }> = {
'BW': { name: 'Landesbeauftragter fuer den Datenschutz und die Informationsfreiheit Baden-Wuerttemberg', abbreviation: 'LfDI BW', url: 'https://www.baden-wuerttemberg.datenschutz.de' },
'BY': { name: 'Bayerisches Landesamt fuer Datenschutzaufsicht', abbreviation: 'BayLDA', url: 'https://www.lda.bayern.de' },
'BE': { name: 'Berliner Beauftragte fuer Datenschutz und Informationsfreiheit', abbreviation: 'BlnBDI', url: 'https://www.datenschutz-berlin.de' },
'BB': { name: 'Landesbeauftragte fuer den Datenschutz und fuer das Recht auf Akteneinsicht Brandenburg', abbreviation: 'LDA BB', url: 'https://www.lda.brandenburg.de' },
'HB': { name: 'Landesbeauftragte fuer Datenschutz und Informationsfreiheit Bremen', abbreviation: 'LfDI HB', url: 'https://www.datenschutz.bremen.de' },
'HH': { name: 'Hamburgischer Beauftragter fuer Datenschutz und Informationsfreiheit', abbreviation: 'HmbBfDI', url: 'https://datenschutz-hamburg.de' },
'HE': { name: 'Hessischer Beauftragter fuer Datenschutz und Informationsfreiheit', abbreviation: 'HBDI', url: 'https://datenschutz.hessen.de' },
'MV': { name: 'Landesbeauftragter fuer Datenschutz und Informationsfreiheit Mecklenburg-Vorpommern', abbreviation: 'LfDI MV', url: 'https://www.datenschutz-mv.de' },
'NI': { name: 'Landesbeauftragte fuer den Datenschutz Niedersachsen', abbreviation: 'LfD NI', url: 'https://www.lfd.niedersachsen.de' },
'NW': { name: 'Landesbeauftragte fuer Datenschutz und Informationsfreiheit Nordrhein-Westfalen', abbreviation: 'LDI NRW', url: 'https://www.ldi.nrw.de' },
'RP': { name: 'Landesbeauftragter fuer den Datenschutz und die Informationsfreiheit Rheinland-Pfalz', abbreviation: 'LfDI RP', url: 'https://www.datenschutz.rlp.de' },
'SL': { name: 'Unabhaengiges Datenschutzzentrum Saarland', abbreviation: 'UDZ SL', url: 'https://www.datenschutz.saarland.de' },
'SN': { name: 'Saechsischer Datenschutz- und Transparenzbeauftragter', abbreviation: 'SDTB', url: 'https://www.saechsdsb.de' },
'ST': { name: 'Landesbeauftragter fuer den Datenschutz Sachsen-Anhalt', abbreviation: 'LfD ST', url: 'https://datenschutz.sachsen-anhalt.de' },
'SH': { name: 'Unabhaengiges Landeszentrum fuer Datenschutz Schleswig-Holstein', abbreviation: 'ULD SH', url: 'https://www.datenschutzzentrum.de' },
'TH': { name: 'Thueringer Landesbeauftragter fuer den Datenschutz und die Informationsfreiheit', abbreviation: 'TLfDI', url: 'https://www.tlfdi.de' },
}
/**
* Nationale Datenschutzbehoerden fuer Nicht-DE-Laender
*/
const DATA_PROTECTION_AUTHORITIES_NATIONAL: Record<string, { name: string; abbreviation: string; url: string }> = {
'DE': { name: 'Bundesbeauftragter fuer den Datenschutz und die Informationsfreiheit', abbreviation: 'BfDI', url: 'https://www.bfdi.bund.de' },
'AT': { name: 'Oesterreichische Datenschutzbehoerde', abbreviation: 'DSB AT', url: 'https://www.dsb.gv.at' },
'CH': { name: 'Eidgenoessischer Datenschutz- und Oeffentlichkeitsbeauftragter', abbreviation: 'EDOEB', url: 'https://www.edoeb.admin.ch' },
'FR': { name: 'Commission Nationale de l\'Informatique et des Libertes', abbreviation: 'CNIL', url: 'https://www.cnil.fr' },
'NL': { name: 'Autoriteit Persoonsgegevens', abbreviation: 'AP', url: 'https://www.autoriteitpersoonsgegevens.nl' },
'IT': { name: 'Garante per la protezione dei dati personali', abbreviation: 'Garante', url: 'https://www.garanteprivacy.it' },
'ES': { name: 'Agencia Espanola de Proteccion de Datos', abbreviation: 'AEPD', url: 'https://www.aepd.es' },
'GB': { name: 'Information Commissioner\'s Office', abbreviation: 'ICO', url: 'https://ico.org.uk' },
}
/**
* Ermittelt die Datenschutz-Aufsichtsbehoerde basierend auf Bundesland und Land.
*/
function resolveDataProtectionAuthority(
state: string | undefined,
country: string
): { name: string; abbreviation: string; url: string } {
// Fuer Deutschland: Landes-Datenschutzbehoerde verwenden
if (country === 'DE' && state) {
const stateUpper = state.toUpperCase()
const landesAuth = DATA_PROTECTION_AUTHORITIES_DE[stateUpper]
if (landesAuth) return landesAuth
}
// Nationale Behoerde
const national = DATA_PROTECTION_AUTHORITIES_NATIONAL[country]
if (national) return national
// Fallback fuer EU-Laender
return { name: 'Nationale Datenschutzbehoerde', abbreviation: 'DSB', url: '' }
}
/**
* Ermittelt alle zustaendigen Aufsichtsbehoerden basierend auf
* CompanyProfile-Daten und den anwendbaren Regulierungen.
*
* @param headquartersState - Bundesland-Kuerzel (z.B. "BW", "BY")
* @param headquartersCountry - ISO-Laendercode (z.B. "DE", "AT")
* @param applicableRegulationIds - IDs der anwendbaren Regulierungen aus dem Go SDK
*/
export function resolveAuthorities(
headquartersState: string | undefined,
headquartersCountry: string,
applicableRegulationIds: string[]
): SupervisoryAuthorityResult[] {
const results: SupervisoryAuthorityResult[] = []
// Datenschutz-Aufsichtsbehoerde (DSGVO gilt fuer fast alle)
if (applicableRegulationIds.includes('dsgvo')) {
results.push({
domain: 'Datenschutz',
authority: resolveDataProtectionAuthority(headquartersState, headquartersCountry),
})
}
// NIS2 → BSI (fuer Deutschland)
if (applicableRegulationIds.includes('nis2')) {
if (headquartersCountry === 'DE') {
results.push({
domain: 'IT-Sicherheit (NIS2)',
authority: {
name: 'Bundesamt fuer Sicherheit in der Informationstechnik',
abbreviation: 'BSI',
url: 'https://www.bsi.bund.de',
},
})
} else {
results.push({
domain: 'IT-Sicherheit (NIS2)',
authority: {
name: 'Nationale Cybersicherheitsbehoerde',
abbreviation: 'NCSA',
url: '',
},
})
}
}
// Finanzaufsicht → BaFin
if (applicableRegulationIds.includes('financial_policy')) {
if (headquartersCountry === 'DE') {
results.push({
domain: 'Finanzaufsicht (DORA/MaRisk)',
authority: {
name: 'Bundesanstalt fuer Finanzdienstleistungsaufsicht',
abbreviation: 'BaFin',
url: 'https://www.bafin.de',
},
})
}
}
// AI Act → Marktueberwachung
if (applicableRegulationIds.includes('ai_act')) {
if (headquartersCountry === 'DE') {
results.push({
domain: 'KI-Aufsicht (AI Act)',
authority: {
name: 'Bundesnetzagentur (voraussichtlich)',
abbreviation: 'BNetzA',
url: 'https://www.bundesnetzagentur.de',
},
})
}
}
return results
}