From 6baf44ac84aed80f5e34ecbdb73fe3c0d5b0e885 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Fri, 22 May 2026 10:22:51 +0200 Subject: [PATCH] fix(mc-audit): TOM/AVV case-mismatch + Ausnahmen-Pattern Wortabstand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _PROCESS_INTERNAL_PATTERNS: Patterns wurden gegen lowercased Blob geprueft, aber Case-sensitive geschrieben (TOM/AVV/SCC). Matchen nie. Auf lowercase normalisiert. - "Ausnahmen ... dokumentieren": Pattern war zu eng, verlangte direkte Adjazenz. Jetzt bis zu 60 Zeichen Wortabstand. - Test-Suite mit 22 kuratierten DSGVO/AI-Act/eCall-MC-Labels. Alle gruen (vorher 2/22 FAIL — beide vom User explizit als Beispiele genannt: TOM, Ausnahmen). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../compliance/services/mc_audit_type.py | 16 ++- .../test_mc_audit_type_classification.py | 100 ++++++++++++++++++ 2 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 backend-compliance/tests/test_mc_audit_type_classification.py diff --git a/backend-compliance/compliance/services/mc_audit_type.py b/backend-compliance/compliance/services/mc_audit_type.py index 23f68858..afce6e22 100644 --- a/backend-compliance/compliance/services/mc_audit_type.py +++ b/backend-compliance/compliance/services/mc_audit_type.py @@ -38,13 +38,15 @@ _PROCESS_INTERNAL_PATTERNS = [ r"\bsensibilisier", # Vertraege intern r"\bauftragsverarbeitungsvertrag\b", - r"\bAVV\b\s+abgeschlossen", + r"\bavv\b\s+abgeschlossen", r"\bvertrag.*abgeschlossen", r"\bdpa\s+(geschlossen|abgeschlossen|vorhanden)", - r"\bSCC\s+(geschlossen|abgeschlossen|implementiert)", - # Technisch-organisatorische Massnahmen (intern) + r"\bscc\s+(geschlossen|abgeschlossen|implementiert)", + # Technisch-organisatorische Massnahmen (intern). Lowercase: blob + # ist bereits .lower(); Case-sensitive Patterns (TOM/AVV/SCC) matchen + # nie. Daher hier explizit klein. r"\btechnisch[-\s]*organisatorische\s+ma(ß|ss)nahmen?\b", - r"\bTOM\s+(umgesetzt|dokumentiert|implementiert)", + r"\btom\s+(umgesetzt|dokumentiert|implementiert)", r"\bverschluesselung\s+(implementiert|aktiv)", r"\bpseudonymisierung\s+(implementiert|aktiv)", r"\bbackup[s]?\s+(eingerichtet|vorhanden)", @@ -68,7 +70,11 @@ _PROCESS_INTERNAL_PATTERNS = [ r"\bbitte\s+(intern\s+)?dokumentieren", r"\bin\s+der\s+verfahrens", r"\bnach\s+innen\s+geh", - r"\bausnahmen\s+(dokumentieren|protokollieren)", + # "Ausnahmen ... dokumentieren" — Wortabstand bis 60 Zeichen erlauben, + # damit "ausnahmen bei bereits vorhandenen informationen dokumentieren" + # matcht. Sonst Pattern zu eng → process-internal-MC bleibt FAIL. + r"\bausnahmen\b[^\n]{0,60}\b(dokumentieren|protokollieren)\b", + r"\bdokumentations[\s-]?pflicht", r"\bkostenfrei\s+(zur\s+verfuegung|gewaehren|ermoegli)", r"\bunentgeltlich\s+(zur\s+verfuegung)", # Vertragsleistung / Service-Level (intern) diff --git a/backend-compliance/tests/test_mc_audit_type_classification.py b/backend-compliance/tests/test_mc_audit_type_classification.py new file mode 100644 index 00000000..a7e647ee --- /dev/null +++ b/backend-compliance/tests/test_mc_audit_type_classification.py @@ -0,0 +1,100 @@ +"""Heuristic-coverage tests for mc_audit_type.classify_mc_audit_type. + +Hand-kuratierte Beispiele aus DSGVO-/AI-Act-/eCall-Kontext, die nahe an +den real-world FAILs aus VW-/BMW-Audits liegen. Wenn ein Pattern hier +fehlschlaegt, fehlt _PROCESS_INTERNAL_PATTERNS bzw. _VERIFIABLE_PATTERNS +eine entsprechende Regel. +""" + +import pytest + +from compliance.services.mc_audit_type import classify_mc_audit_type + + +VERIFIABLE_CASES = [ + ("Datenschutzerklaerung nennt Kontaktdaten des Verantwortlichen", + "Steht im Impressum / DSE der Name + Adresse + Email?"), + ("Cookie-Banner enthaelt Ablehnen-Button", + "Ist im Cookie-Banner ein 'Ablehnen' Button sichtbar?"), + ("Datenschutzerklaerung verweist auf Recht auf Auskunft", + "Wird in der Datenschutzerklaerung Art. 15 DSGVO erwaehnt?"), + ("Cookie-Richtlinie listet alle Drittanbieter", + "Sind alle Vendors in der Cookie-Richtlinie aufgefuehrt?"), + ("Impressum nennt Telefonnummer", + "Steht im Impressum eine Kontakttelefonnummer?"), + ("Datenschutzerklaerung nennt Speicherdauer", + "Sind in der DSE die Aufbewahrungsfristen angegeben?"), + ("Cookie-Banner Save-Label entspricht UWG-Vorgabe", + "Ist der Speichern-Button im Banner korrekt beschriftet?"), +] + +PROCESS_INTERNAL_CASES = [ + ("Mitarbeiter-Schulung zu Datenschutz durchgefuehrt", + "Wurden alle Mitarbeiter zum Thema Datenschutz geschult?"), + ("AVV mit allen Auftragsverarbeitern abgeschlossen", + "Liegt fuer jeden Dienst ein Auftragsverarbeitungsvertrag vor?"), + ("TOM dokumentiert und implementiert", + "Sind die technisch-organisatorischen Massnahmen umgesetzt?"), + ("DSFA fuer Hochrisiko-Verarbeitung durchgefuehrt", + "Wurde eine DSFA durchgefuehrt und dokumentiert?"), + ("Rollenkonzept eingerichtet", + "Existiert ein Berechtigungskonzept mit klaren Rollen?"), + ("Pseudonymisierung von Daten implementiert", + "Wird Pseudonymisierung aktiv eingesetzt?"), + ("Backup-Strategie eingerichtet", + "Sind Backups vorhanden und werden regelmaessig getestet?"), + ("Abschaltung der Standortdatenverarbeitung kostenfrei ermoeglichen", + "Kann der Nutzer die Standortdatenverarbeitung kostenfrei abschalten?"), + ("Ausnahmen bei bereits vorhandenen Informationen dokumentieren", + "Werden Ausnahmen intern dokumentiert?"), + ("Sensibilisierung neue Mitarbeiter", + "Erhalten neue Mitarbeiter eine Awareness-Schulung?"), + ("72-Stunden-Meldung an Aufsichtsbehoerde eingehalten", + "Wird die Meldepflicht innerhalb von 72 Stunden umgesetzt?"), +] + +DOC_INTERNAL_CASES = [ + ("Verzeichnis von Verarbeitungstaetigkeiten gefuehrt", + "Existiert ein VVT mit allen Pflichtangaben?"), + ("Subprozessor-Liste aktualisiert", + "Wird die Sub-Prozessor-Liste gepflegt?"), + ("Auftragsverarbeitungsverzeichnis vollstaendig", + "Sind alle AVVs im Auftragsverarbeitungsverzeichnis erfasst?"), + ("Aufbewahrungskonzept dokumentiert", + "Existiert ein dokumentiertes Aufbewahrungskonzept?"), +] + + +@pytest.mark.parametrize("title,question", VERIFIABLE_CASES) +def test_verifiable_cases(title: str, question: str) -> None: + """These MCs MUST be classified as verifiable — they describe things + visible from the outside (website / banner / DSE).""" + result = classify_mc_audit_type(title, question) + assert result == "verifiable", ( + f"Expected 'verifiable' for {title!r}, got {result!r}" + ) + + +@pytest.mark.parametrize("title,question", PROCESS_INTERNAL_CASES) +def test_process_internal_cases(title: str, question: str) -> None: + """These MCs MUST be classified as process_internal — they describe + internal customer processes that we cannot verify from the outside.""" + result = classify_mc_audit_type(title, question) + assert result == "process_internal", ( + f"Expected 'process_internal' for {title!r}, got {result!r}" + ) + + +@pytest.mark.parametrize("title,question", DOC_INTERNAL_CASES) +def test_doc_internal_cases(title: str, question: str) -> None: + """These MCs MUST be classified as doc_internal — they describe + internal documentation (VVT, AVV-Verzeichnis) we cannot inspect.""" + result = classify_mc_audit_type(title, question) + assert result == "doc_internal", ( + f"Expected 'doc_internal' for {title!r}, got {result!r}" + ) + + +def test_empty_input_is_ambiguous() -> None: + assert classify_mc_audit_type("", "") == "ambiguous" + assert classify_mc_audit_type(None, None) == "ambiguous"