diff --git a/backend-compliance/compliance/services/doc_checks/dse_checks.py b/backend-compliance/compliance/services/doc_checks/dse_checks.py index eaf93ae..99b3fc7 100644 --- a/backend-compliance/compliance/services/doc_checks/dse_checks.py +++ b/backend-compliance/compliance/services/doc_checks/dse_checks.py @@ -72,9 +72,10 @@ ART13_CHECKLIST = [ "label": "Kontaktdaten des DSB (E-Mail oder Telefon)", "level": 2, "parent": "dpo", "patterns": [ - r"datenschutz(?:beauftragter?|beauftragte).*?[a-z0-9._%+\-]+@", - r"dsb.*?@|dpo.*?@", + r"datenschutz(?:beauftragter?|beauftragte)[\s\S]{0,300}[a-z0-9._%+\-]+@", + r"dsb[\s\S]{0,100}@|dpo[\s\S]{0,100}@", r"datenschutz@", + r"datenschutzbeauftragt[\s\S]{0,200}(?:e-?mail|telefon|fon)", ], "severity": "MEDIUM", "hint": "Art. 37(7) DSGVO verlangt Veroeffentlichung der Kontaktdaten des DSB. Mindestens eine E-Mail ist noetig — den Namen muessen Sie nicht nennen. Haeufiger Fehler: DSB wird erwaehnt, aber ohne jede Kontaktmoeglichkeit.", @@ -276,7 +277,9 @@ ART13_CHECKLIST = [ "patterns": [ r"l(?:oe|ö)schkonzept", r"l(?:oe|ö)schfrist", r"(?:regel|routinem(?:ae|ä)(?:ss|ß)ig).*l(?:oe|ö)sch", - r"nach\s+(?:ablauf|wegfall).*(?:gel(?:oe|ö)scht|l(?:oe|ö)sch)", + r"nach\s+(?:ablauf|wegfall|fortfall|entfall|beendigung).*(?:gel(?:oe|ö)scht|l(?:oe|ö)sch)", + r"(?:gel(?:oe|ö)scht|l(?:oe|ö)schung)\s+nach\s+(?:ablauf|wegfall|fortfall)", + r"zweck\s+(?:entf(?:ae|ä)llt|wegf(?:ae|ä)llt).*(?:gel(?:oe|ö)scht|l(?:oe|ö)sch)", ], "severity": "LOW", "hint": "Art. 5(1)(e) DSGVO (Speicherbegrenzung) erfordert ein Loeschkonzept. Beschreiben Sie den Prozess: automatische Loeschung nach Fristablauf, regelmaessige Pruefzyklen, oder Verweis auf DIN 66398 (Loeschkonzept). Reine Archivierung ohne Loeschfrist genuegt nicht.", @@ -302,7 +305,7 @@ ART13_CHECKLIST = [ "id": "rights_art15", "label": "Recht auf Auskunft (Art. 15)", "level": 2, "parent": "rights", - "patterns": [r"art\.\s*15", r"recht\s+auf\s+auskunft", r"right\s+(?:of|to)\s+access"], + "patterns": [r"art\.\s*15", r"recht\s+auf\s+(?:\w+\s+)?auskunft", r"auskunft\s+(?:ueber|über)\s+(?:herkunft|ihre)", r"right\s+(?:of|to)\s+access"], "severity": "LOW", "hint": "Art. 15 DSGVO: Betroffene koennen kostenlos Auskunft und eine Kopie aller Daten verlangen. Antwortfrist: 1 Monat (Art. 12(3)). Haeufiger Fehler: Kein Hinweis auf Kostenfreiheit oder den konkreten Anfrageweg (E-Mail-Adresse).", }, @@ -318,7 +321,7 @@ ART13_CHECKLIST = [ "id": "rights_art17", "label": "Recht auf Loeschung (Art. 17)", "level": 2, "parent": "rights", - "patterns": [r"art\.\s*17", r"recht\s+auf\s+l(?:oe|ö)schung", r"right\s+to\s+erasure"], + "patterns": [r"art\.\s*17", r"recht\s+auf\s+(?:\w+\s+)?l(?:oe|ö)schung", r"(?:berichtigung|korrektur)\s+oder\s+l(?:oe|ö)schung", r"right\s+to\s+erasure"], "severity": "LOW", "hint": "Art. 17 DSGVO ('Recht auf Vergessenwerden'): Loeschung ist Pflicht, wenn Zweck entfaellt, Einwilligung widerrufen wird oder Daten unrechtmaessig verarbeitet wurden. Erwaehnen Sie auch die Ausnahmen (z.B. gesetzliche Aufbewahrungspflichten §257 HGB, §147 AO).", }, @@ -334,7 +337,7 @@ ART13_CHECKLIST = [ "id": "rights_art20", "label": "Recht auf Datenportabilitaet (Art. 20)", "level": 2, "parent": "rights", - "patterns": [r"art\.\s*20", r"daten(?:ue|ü)bertragbarkeit|datenportabilit", r"right\s+to\s+data\s+portability"], + "patterns": [r"art\.\s*20", r"daten(?:ue|ü)bertrag(?:ung|barkeit)|datenportabilit", r"maschinenlesbar\w*\s+format", r"right\s+to\s+data\s+portability"], "severity": "LOW", "hint": "Art. 20 DSGVO: Gilt nur bei Verarbeitung auf Basis von Einwilligung (Art. 6(1)(a)) oder Vertrag (Art. 6(1)(b)) UND automatisierter Verarbeitung. Format: strukturiert, gaengig, maschinenlesbar (z.B. JSON, CSV). Nicht anwendbar bei Art. 6(1)(f).", }, diff --git a/backend-compliance/compliance/services/doc_checks/runner.py b/backend-compliance/compliance/services/doc_checks/runner.py index 0c9394a..70e1bd2 100644 --- a/backend-compliance/compliance/services/doc_checks/runner.py +++ b/backend-compliance/compliance/services/doc_checks/runner.py @@ -71,7 +71,10 @@ def check_document_completeness( Returns a list of findings (summary + missing items). """ findings = [] - text_lower = text.lower() + # Strip soft hyphens (­ / \xad) that CMS tools insert for word-breaking + # — they break regex matches on compound words like "Datenübertragbarkeit" + text_clean = text.replace("\xad", "").replace("­", "") + text_lower = text_clean.lower() if not text or len(text) < 50: findings.append({