"""B6 — DPO in DSE genannt, im Impressum aber nicht verlinkt. Best-Practice-Check nach DSGVO Art. 37 + § 5 TMG-Geist: wenn die DSE einen Datenschutzbeauftragten benennt, sollte er auch im Impressum referenziert sein (mind. Verweis "DSB siehe DSE") — sonst geht die Kontaktmöglichkeit verloren, wenn die DSE separat publiziert wird. Severity LOW (nicht zwingend Pflicht), aber relevant für DSBs. """ from __future__ import annotations import logging import re logger = logging.getLogger(__name__) # Phrasen, die einen DSB / DPO in einem Text als benannt markieren _DSB_NAMED_PATTERNS = [ re.compile(r"datenschutzbeauftrag\w+", re.I), re.compile(r"data\s+protection\s+officer\b", re.I), re.compile(r"\bdpo\b", re.I), re.compile(r"privacy@\S+", re.I), re.compile(r"datenschutz@\S+", re.I), ] def _names_dsb(text: str) -> list[str]: if not text: return [] out: list[str] = [] for pat in _DSB_NAMED_PATTERNS: for m in pat.finditer(text): out.append(m.group(0)) if len(out) >= 3: return out return out def check_dpo_cross_doc(state: dict) -> dict | None: """Return a finding when DSE names a DPO but Impressum does not.""" doc_texts = state.get("doc_texts") or {} dse = doc_texts.get("dse") or "" imp = doc_texts.get("impressum") or "" if not dse or not imp: return None dse_hits = _names_dsb(dse) imp_hits = _names_dsb(imp) if dse_hits and not imp_hits: finding = { "check_id": "IMPRESSUM-DPO-001", "severity": "LOW", "severity_reason": "incomplete", "title": "DSB im Impressum nicht verlinkt", "norm": "DSGVO Art. 37 (Best Practice) + § 5 TMG-Geist", "evidence_dse": dse_hits[:2], "action": ( "Im Impressum den DSB-Kontakt verlinken oder Verweis " "auf die Datenschutzerklärung ergänzen, damit Betroffene " "auch über das Impressum den DSB erreichen." ), } logger.info("B6 DPO-cross-doc: DSE has DPO, Impressum doesn't") return finding return None