diff --git a/admin-compliance/app/sdk/agent/_components/ChecklistView.tsx b/admin-compliance/app/sdk/agent/_components/ChecklistView.tsx index 2279a29..e91b7b7 100644 --- a/admin-compliance/app/sdk/agent/_components/ChecklistView.tsx +++ b/admin-compliance/app/sdk/agent/_components/ChecklistView.tsx @@ -24,6 +24,7 @@ interface DocResult { const DOC_TYPE_LABELS: Record = { dse: 'DSI', agb: 'AGB', impressum: 'Impressum', cookie: 'Cookie', widerruf: 'Widerruf', other: 'Sonstiges', + social_media: 'Social Media', dsfa: 'DSFA', joint_controller: 'Art. 26', } export function ChecklistView({ results }: { results: DocResult[] }) { diff --git a/admin-compliance/app/sdk/agent/_components/DocCheckTab.tsx b/admin-compliance/app/sdk/agent/_components/DocCheckTab.tsx index f4e7897..8ba9c88 100644 --- a/admin-compliance/app/sdk/agent/_components/DocCheckTab.tsx +++ b/admin-compliance/app/sdk/agent/_components/DocCheckTab.tsx @@ -12,6 +12,8 @@ interface DocEntry { const DOC_TYPES = [ { id: 'dse', label: 'DSI (Datenschutzinformation)' }, + { id: 'social_media', label: 'DSE Social Media (Art. 26)' }, + { id: 'dsfa', label: 'DSFA (Art. 35)' }, { id: 'agb', label: 'AGB / Nutzungsbedingungen' }, { id: 'impressum', label: 'Impressum' }, { id: 'cookie', label: 'Cookie-Richtlinie' }, diff --git a/backend-compliance/compliance/api/agent_doc_check_routes.py b/backend-compliance/compliance/api/agent_doc_check_routes.py index 9e1815b..47cccee 100644 --- a/backend-compliance/compliance/api/agent_doc_check_routes.py +++ b/backend-compliance/compliance/api/agent_doc_check_routes.py @@ -263,9 +263,8 @@ SECTION_TYPE_MAP = [ (r"widerrufsrecht|widerrufsbelehrung", "widerruf"), # Widerruf → §355 BGB (r"^impressum$", "impressum"), # Impressum → §5 TMG (r"^(?:agb|allgemeine geschäftsbedingungen|nutzungsbedingungen)$", "agb"), - # NOTE: Social Media, DSFA, Datensicherheit, Betroffenenrechte are NOT - # separate documents — they are sections within the parent DSI. - # DSFA needs its own checklist (RAG-based) — Phase 2. + (r"datenschutzerkl.*social|datenschutz.*social\s*media", "social_media"), + (r"datenschutzfolge|dsfa|risikoanalyse.*social", "dsfa"), ] diff --git a/backend-compliance/compliance/services/dsi_document_checker.py b/backend-compliance/compliance/services/dsi_document_checker.py index 524cd6f..c261f23 100644 --- a/backend-compliance/compliance/services/dsi_document_checker.py +++ b/backend-compliance/compliance/services/dsi_document_checker.py @@ -196,6 +196,58 @@ COOKIE_CHECKLIST = [ "patterns": [r"(?:widerspruch|opt.?out|ablehnen|deaktivieren).*cookie", r"cookie.*(?:ablehnen|deaktivieren|loeschen)"]}, ] +# Art. 26 DSGVO Joint Controller (Social Media DSE) +JOINT_CONTROLLER_CHECKLIST = [ + {"id": "joint_parties", "label": "Gemeinsam Verantwortliche benannt (Art. 26(1))", + "patterns": [r"gemeinsam.*verantwortlich", r"joint.*controller", r"gemeinsame\s+verantwortlichkeit", + r"art\.\s*26", r"mitverantwortlich"]}, + {"id": "arrangement", "label": "Vereinbarung nach Art. 26 DSGVO", + "patterns": [r"vereinbarung.*art\.\s*26", r"art\.\s*26.*vereinbarung", + r"page\s*controller", r"fanpage", r"insights", + r"gemeinsame.*verantwortung.*(?:vertrag|vereinbarung)"]}, + {"id": "contact_point", "label": "Anlaufstelle fuer Betroffene (Art. 26(1) S.3)", + "patterns": [r"anlaufstelle", r"kontaktstelle", r"ansprechpartner.*betroffene", + r"rechte.*(?:gegenueber|gegenüber)\s+(?:uns|beiden)", + r"rechte.*(?:sowohl|grundsaetzlich|grundsätzlich).*(?:uns|als auch)"]}, + {"id": "processing_split", "label": "Verarbeitungsaufteilung (wer macht was)", + "patterns": [r"(?:wir|betreiber).*(?:verarbeiten|erheben|nutzen).*(?:daten|informationen)", + r"(?:facebook|meta|google|youtube|instagram|linkedin|twitter|x\.com).*(?:verarbeit|erhebt|nutzt|speichert)", + r"bei\s+besuch\s+(?:unserer|der)\s+(?:seite|fanpage|profil)"]}, + {"id": "social_data_types", "label": "Kategorien verarbeiteter Daten", + "patterns": [r"(?:nutzungsstatistik|insight|reichweite|interaktion|klick|aufruf)", + r"(?:ip.?adresse|standort|browser|geraet|alter|geschlecht).*(?:social|profil|seite|fanpage)", + r"(?:personenbezogen|daten).*(?:social|netzwerk|plattform)"]}, + {"id": "third_country", "label": "Drittlandtransfer (USA bei Social Media)", + "patterns": [r"(?:usa|vereinigte\s+staaten|drittland|drittstaaten).*(?:social|facebook|meta|google)", + r"(?:social|facebook|meta|google).*(?:usa|drittland|drittstaaten)", + r"privacy\s+shield|data\s+privacy\s+framework|angemessenheitsbeschluss", + r"standardvertragsklausel|standard.*contractual"]}, + {"id": "rights", "label": "Betroffenenrechte (Art. 15-21)", + "patterns": [r"recht\s+auf\s+auskunft", r"recht\s+auf\s+l(?:oe|ö)schung", + r"art\.\s*1[5-9]", r"betroffenenrecht", + r"ihre\s+rechte", r"rechte.*betroffene"]}, +] + +# DSFA minimal checklist (Art. 35 DSGVO) — basic structure checks +DSFA_CHECKLIST = [ + {"id": "description", "label": "Beschreibung der Verarbeitungsvorgaenge (Art. 35(7)(a))", + "patterns": [r"beschreibung.*verarbeitung", r"verarbeitungsvorg(?:ae|ä)ng", + r"systematische\s+beschreibung", r"gegenstand.*verarbeitung"]}, + {"id": "necessity", "label": "Notwendigkeit und Verhaeltnismaessigkeit (Art. 35(7)(b))", + "patterns": [r"notwendigkeit", r"verh(?:ae|ä)ltnism(?:ae|ä)ssigkeit", + r"erforderlichkeit", r"zweckbindung"]}, + {"id": "risks", "label": "Risikobewertung fuer Betroffene (Art. 35(7)(c))", + "patterns": [r"risiko.*(?:bewertung|analyse|einsch(?:ae|ä)tzung)", + r"risiken.*(?:rechte|freiheit)", r"eintrittswahrscheinlichkeit", + r"schwere.*(?:risiko|auswirkung)"]}, + {"id": "measures", "label": "Abhilfemassnahmen (Art. 35(7)(d))", + "patterns": [r"abhilfe", r"massnahmen.*risiko", r"schutzma(?:ss|ß)nahm", + r"(?:technisch|organisatorisch).*massnahm", r"tom"]}, + {"id": "stakeholders", "label": "Einbeziehung des DSB (Art. 35(2))", + "patterns": [r"datenschutzbeauftragt.*(?:einbez|konsult|beteilig|rat)", + r"dsb.*(?:konsult|einbez|rat)", r"stellungnahme.*dsb"]}, +] + def check_document_completeness( text: str, @@ -255,6 +307,12 @@ def check_document_completeness( elif doc_type in ("cookie",): checklist = COOKIE_CHECKLIST label = "§25 TDDDG" + elif doc_type in ("social_media", "joint_controller"): + checklist = JOINT_CONTROLLER_CHECKLIST + label = "Art. 26 DSGVO" + elif doc_type in ("dsfa",): + checklist = DSFA_CHECKLIST + label = "Art. 35 DSGVO" else: checklist = ART13_CHECKLIST # Default: check as DSE label = "Art. 13 DSGVO" @@ -323,6 +381,11 @@ def classify_document_type(title: str, url: str) -> str: """Classify a document by its title/URL into a legal document type.""" combined = f"{title} {url}".lower() + if any(kw in combined for kw in ["datenschutzfolge", "dsfa", "risikoanalyse für nutzung"]): + return "dsfa" + if any(kw in combined for kw in ["social media", "facebook", "instagram", "linkedin", "fanpage"]): + if any(kw in combined for kw in ["datenschutzerkl", "datenschutz für", "datenschutzinformation"]): + return "social_media" if any(kw in combined for kw in ["datenschutz", "privacy", "dsgvo", "data protection", "données"]): return "dse" if any(kw in combined for kw in ["widerruf", "withdrawal", "rétractation", "desistimiento"]):