feat: DSFA — VVT-Verknüpfung + Residual Risk + Bundesland-Blacklists

1. VVT-Verknüpfung: Dropdown "Verknüpfte VVT-Aktivität" in Step 1,
   lädt Aktivitäten via API, auto-fills Verarbeitungstätigkeit bei Auswahl

2. Residual Risk: Neuer Step 5 im Wizard — Bewertung des Restrisikos
   nach Maßnahmen. Bei hoch/kritisch → Art. 36 Vorabkonsultation Warnung

3. Bundesland-Blacklists (Art. 35 Abs. 4): 16 Landesbehörden mit
   DSK-Muss-Liste (10 gemeinsame Kriterien) + länderspezifische
   Ergänzungen (Bayern: Whistleblower/Drohnen, NRW: Social-Media-
   Monitoring, Berlin: Mieterbonitätsprüfung). Automatische Prüfung
   gegen Scope-Antworten. Blacklist-Matches im DSFA-Banner angezeigt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-04 21:48:59 +02:00
parent 84b21cad08
commit 2b4ff9f422
4 changed files with 228 additions and 8 deletions
@@ -173,11 +173,17 @@ export function prefillDSFAFromScope(
}
/**
* Check if DSFA is required based on scope answers (Art. 35 Abs. 3 triggers).
* Check if DSFA is required based on scope answers (Art. 35 Abs. 3 triggers)
* AND Bundesland-specific blacklist (Art. 35 Abs. 4).
*/
export function isDSFARequired(scopeAnswers: ScopeProfilingAnswer[]): {
export function isDSFARequired(
scopeAnswers: ScopeProfilingAnswer[],
headquartersState?: string,
): {
required: boolean
triggers: string[]
blacklistMatches: string[]
authority?: string
} {
const triggers: string[] = []
@@ -198,5 +204,21 @@ export function isDSFARequired(scopeAnswers: ScopeProfilingAnswer[]): {
triggers.push('Umfangreiche Datenverarbeitung (Art. 35 Abs. 3 lit. b)')
}
return { required: triggers.length > 0, triggers }
// Bundesland-Blacklist (Art. 35 Abs. 4)
let blacklistMatches: string[] = []
let authority: string | undefined
if (headquartersState) {
const { checkBlacklist, scopeAnswersToKeywords } = require('./bundesland-blacklists')
const keywords = scopeAnswersToKeywords(scopeAnswers)
const result = checkBlacklist(headquartersState, keywords)
blacklistMatches = result.matches.map((m: { description: string }) => m.description)
authority = result.authority
}
return {
required: triggers.length > 0 || blacklistMatches.length > 0,
triggers,
blacklistMatches,
authority,
}
}