feat: DSFA vollständiges DB-Schema + PDF-Ingest + Tests
All checks were successful
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) Successful in 36s
CI / test-python-backend-compliance (push) Successful in 37s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 22s

- Migration 030: alle fehlenden Spalten für compliance_dsfas (Sections 0-7)
  flat fields: processing_description, legal_basis, dpo_*, authority_*, ...
  JSONB arrays: risks, mitigations, wp248_criteria_met, ai_trigger_ids, ...
  JSONB objects: section_progress, threshold_analysis, review_schedule, metadata
- dsfa_routes.py: DSFACreate/DSFAUpdate erweitert (60+ neue Optional-Felder)
  _dsfa_to_response: alle neuen Felder mit safe _get() Helper
  PUT-Handler: vollständige JSONB_FIELDS-Liste (22 Felder)
- Tests: 101 (+49) Tests — TestAIUseCaseModules + TestDSFAFullSchema
- ingest-dsfa-bundesland.sh: KNOWN_PDF_URLS (15 direkte URLs), download_pdfs()
  find_pdf_for_state() Helper, PDF-first mit Text-Fallback in ingest_all()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-05 10:03:09 +01:00
parent ff765b2d71
commit 789c215e5e
4 changed files with 774 additions and 42 deletions

View File

@@ -77,18 +77,80 @@ declare -A STATE_NAMES=(
["th"]="Thüringen"
)
# PDF-URLs der Muss-Listen (direkte Download-Links)
declare -A PDF_URLS=(
["bw_privat"]="https://www.baden-wuerttemberg.datenschutz.de/dsfa-muss-liste/"
["hh_beide"]="https://datenschutz.hamburg.de/infothek/datenschutz-folgenabschaetzung"
["nw_oeffentlich"]="https://www.ldi.nrw.de/datenschutz/datenschutz-folgenabschaetzung"
["ni_beide"]="https://lfd.niedersachsen.de/startseite/themen/datenschutzfolgenabschaetzung/"
["be_beide"]="https://www.datenschutz-berlin.de/themen/verarbeitungen-mit-hohem-risiko/datenschutz-folgenabschaetzung/"
["bfdi_liste"]="https://www.bfdi.bund.de/DE/Fachthemen/Inhalte/Datenschutzbehoerden/DSFA.html"
# Direkte PDF-Download-URLs der Behörden-Muss-Listen (Art. 35 Abs. 4 DSGVO)
# Quellen: DSFA_AUTHORITY_RESOURCES in admin-compliance/lib/sdk/dsfa/types.ts
declare -A KNOWN_PDF_URLS=(
["bfdi_public"]="https://www.bfdi.bund.de/SharedDocs/Downloads/DE/Muster/Liste_VerarbeitungsvorgaengeArt35.pdf"
["bw_privat"]="https://www.baden-wuerttemberg.datenschutz.de/wp-content/uploads/2018/05/Liste-von-Verarbeitungsvorg%C3%A4ngen-nach-Art.-35-Abs.-4-DS-GVO-LfDI-BW.pdf"
["be_public"]="https://www.datenschutz-berlin.de/fileadmin/user_upload/pdf/dokumente/2018-BlnBDI_DSFA-oeffentlich.pdf"
["be_privat"]="https://www.datenschutz-berlin.de/fileadmin/user_upload/pdf/dokumente/2018-BlnBDI_DSFA-nicht-oeffentlich.pdf"
["bb_public"]="https://www.lda.brandenburg.de/sixcms/media.php/9/DSFA-Liste_%C3%B6ffentlicher_Bereich.pdf"
["bb_privat"]="https://www.lda.brandenburg.de/sixcms/media.php/9/DSFA-Liste_nicht_%C3%B6ffentlicher_Bereich.pdf"
["hb_public"]="https://www.datenschutz.bremen.de/sixcms/media.php/13/Liste%20von%20Verarbeitungsvorg%C3%A4ngen%20nach%20Artikel%2035.pdf"
["hb_privat"]="https://www.datenschutz.bremen.de/sixcms/media.php/13/DSFA%20Muss-Liste%20LfDI%20HB.pdf"
["hh_public"]="https://datenschutz-hamburg.de/fileadmin/user_upload/HmbBfDI/Datenschutz/Informationen/Liste_Art_35-4_DSGVO_HmbBfDI-oeffentlicher_Bereich_v2.0a.pdf"
["hh_privat"]="https://datenschutz-hamburg.de/fileadmin/user_upload/HmbBfDI/Datenschutz/Informationen/DSFA_Muss-Liste_fuer_den_nicht-oeffentlicher_Bereich_-_Stand_17.10.2018.pdf"
["mv_public"]="https://www.datenschutz-mv.de/static/DS/Dateien/DS-GVO/HilfsmittelzurUmsetzung/MV-DSFA-Muss-Liste-Oeffentlicher-Bereich.pdf"
["ni_public"]="https://www.lfd.niedersachsen.de/download/134414/DSFA_Muss-Liste_fuer_den_oeffentlichen_Bereich.pdf"
["ni_privat"]="https://www.lfd.niedersachsen.de/download/131098/Liste_von_Verarbeitungsvorgaengen_nach_Art._35_Abs._4_DS-GVO.pdf"
["sl_privat"]="https://www.datenschutz.saarland.de/fileadmin/user_upload/uds/alle_Dateien_und_Ordner_bis_2025/Download/dsfa_muss_liste_dsk_de.pdf"
["st_public"]="https://datenschutz.sachsen-anhalt.de/fileadmin/Bibliothek/Landesaemter/LfD/Informationen/Internationales/Datenschutz-Grundverordnung/Liste_DSFA/Art-35-Liste-oeffentlicher_Bereich.pdf"
["st_privat"]="https://datenschutz.sachsen-anhalt.de/fileadmin/Bibliothek/Landesaemter/LfD/Informationen/Internationales/Datenschutz-Grundverordnung/Liste_DSFA/Art-35-Liste-nichtoeffentlicher_Bereich.pdf"
)
# =============================================================================
# Phase 2: Text-Zusammenfassungen (für Bundesländer ohne direkte PDFs)
# Phase 2a: PDF-Downloads
# =============================================================================
download_pdfs() {
log "Lade Behörden-PDFs herunter (${#KNOWN_PDF_URLS[@]} URLs)..."
local success=0
local failed=0
for key in "${!KNOWN_PDF_URLS[@]}"; do
local url="${KNOWN_PDF_URLS[$key]}"
local outfile="$DOWNLOAD_DIR/${key}.pdf"
if [[ -f "$outfile" && $(wc -c < "$outfile") -gt 1000 ]]; then
ok "PDF bereits vorhanden: $key"
((success++)) || true
continue
fi
curl -sk --max-time 30 -L -A "BreakPilot-Compliance/1.0" -o "$outfile" "$url" 2>/dev/null
local exit_code=$?
if [[ $exit_code -eq 0 && -f "$outfile" && $(wc -c < "$outfile") -gt 1000 ]]; then
ok "PDF heruntergeladen: $key"
((success++)) || true
else
warn "PDF fehlgeschlagen: $key — nutze Text-Fallback"
rm -f "$outfile"
((failed++)) || true
fi
done
log "PDF-Downloads: $success OK, $failed fehlgeschlagen"
}
# Gibt den Pfad zur ersten vorhandenen PDF-Datei für einen State-ID-Prefix zurück.
# Gibt leeren String zurück, wenn keine PDF gefunden.
find_pdf_for_state() {
local state_id="$1"
for key in "${!KNOWN_PDF_URLS[@]}"; do
if [[ "$key" == "${state_id}_"* || "$key" == "${state_id}" ]]; then
local pdf="$DOWNLOAD_DIR/${key}.pdf"
if [[ -f "$pdf" && $(wc -c < "$pdf") -gt 1000 ]]; then
echo "$pdf"
return
fi
fi
done
echo ""
}
# =============================================================================
# Phase 2b: Text-Zusammenfassungen (für Bundesländer ohne direkte PDFs)
# =============================================================================
create_text_summaries() {
@@ -331,32 +393,40 @@ ingest_all() {
log "Starte Ingest in Corpus: $COLLECTION"
log "RAG-URL: $RAG_URL"
# WP248-Dokument (für alle Bundesländer relevant)
# WP248-Dokument (für alle Bundesländer relevant — kein PDF verfügbar)
ingest_document \
"$DOWNLOAD_DIR/dsfa_wpk248_kriterien.txt" \
"wp248_rev01" "EU" "Article 29 Working Party / EDPB" "leitlinie"
# BfDI
ingest_document \
"$DOWNLOAD_DIR/bfdi_muss_liste.txt" \
"muss_liste_bfdi" "Bund" "BfDI" "muss_liste"
# BfDI — PDF bevorzugen, Text als Fallback
local bfdi_pdf
bfdi_pdf=$(find_pdf_for_state "bfdi")
if [[ -n "$bfdi_pdf" ]]; then
ingest_document "$bfdi_pdf" "muss_liste_bfdi" "Bund" "BfDI" "muss_liste"
else
ingest_document "$DOWNLOAD_DIR/bfdi_muss_liste.txt" "muss_liste_bfdi" "Bund" "BfDI" "muss_liste"
fi
# Baden-Württemberg
ingest_document \
"$DOWNLOAD_DIR/bw_dsfa_anforderungen.txt" \
"muss_liste_bw" "Baden-Württemberg" "LfDI BW" "muss_liste"
# Baden-Württemberg — PDF bevorzugen, Text als Fallback
local bw_pdf
bw_pdf=$(find_pdf_for_state "bw")
if [[ -n "$bw_pdf" ]]; then
ingest_document "$bw_pdf" "muss_liste_bw" "Baden-Württemberg" "LfDI BW" "muss_liste"
else
ingest_document "$DOWNLOAD_DIR/bw_dsfa_anforderungen.txt" "muss_liste_bw" "Baden-Württemberg" "LfDI BW" "muss_liste"
fi
# Bayern
# Bayern — kein direktes PDF bekannt, Text
ingest_document \
"$DOWNLOAD_DIR/by_dsfa_anforderungen.txt" \
"muss_liste_by" "Bayern" "LDA Bayern" "muss_liste"
# NRW
# NRW — kein direktes PDF bekannt, Text
ingest_document \
"$DOWNLOAD_DIR/nrw_dsfa_anforderungen.txt" \
"muss_liste_nw" "Nordrhein-Westfalen" "LDI NRW" "muss_liste"
# Weitere Bundesländer aus DSFA_AUTHORITY_RESOURCES-Daten (als Text)
# Weitere Bundesländer — PDF bevorzugen, Text als Fallback
for state_id in be bb hb hh he mv ni rp sl sn st sh th; do
local txt_file="$DOWNLOAD_DIR/${state_id}_dsfa_anforderungen.txt"
if [[ ! -f "$txt_file" ]]; then
@@ -383,12 +453,24 @@ Quelle: DSK-Positionspapier, WP248, Art. 35 Abs. 4 DSGVO
EOF
fi
ingest_document \
"$txt_file" \
"muss_liste_${state_id}" \
"${STATE_NAMES[$state_id]:-$state_id}" \
"${AUTHORITY_LABELS[$state_id]:-Datenschutzbehörde $state_id}" \
"muss_liste"
# PDF bevorzugen, Text als Fallback
local state_pdf
state_pdf=$(find_pdf_for_state "$state_id")
if [[ -n "$state_pdf" ]]; then
ingest_document \
"$state_pdf" \
"muss_liste_${state_id}" \
"${STATE_NAMES[$state_id]:-$state_id}" \
"${AUTHORITY_LABELS[$state_id]:-Datenschutzbehörde $state_id}" \
"muss_liste"
else
ingest_document \
"$txt_file" \
"muss_liste_${state_id}" \
"${STATE_NAMES[$state_id]:-$state_id}" \
"${AUTHORITY_LABELS[$state_id]:-Datenschutzbehörde $state_id}" \
"muss_liste"
fi
done
log "Ingest abgeschlossen"
@@ -427,16 +509,17 @@ main() {
log "Download-Dir: $DOWNLOAD_DIR"
log "Skip-Download: $SKIP_DOWNLOAD"
# Schritt 1: Text-Zusammenfassungen erstellen (immer)
# Schritt 1: Text-Zusammenfassungen erstellen (immer als Fallback)
create_text_summaries
# Schritt 2: PDFs herunterladen (wenn nicht --skip-download)
# Schritt 2: PDFs herunterladen (wenn nicht --skip-download oder --only-text)
if [[ "$SKIP_DOWNLOAD" == false && "$ONLY_TEXT" == false ]]; then
log "PDF-Downloads übersprungen (direkte URLs zu Behörden-PDFs variieren) nutze Text-Dateien"
log "Tipp: Laden Sie PDFs manuell herunter und legen Sie sie in $DOWNLOAD_DIR ab"
download_pdfs
else
log "PDF-Downloads übersprungen (--skip-download oder --only-text gesetzt)"
fi
# Schritt 3: Ingest
# Schritt 3: Ingest (PDF bevorzugt, Text als Fallback)
ingest_all
# Schritt 4: Verifikation