#!/usr/bin/env python3 """ P39 Phase B — Fix actual content gaps in legal templates. For each template with a genuine content gap (identified by P39 audit), insert the missing mandatory section. Targeted edits — does NOT overwrite the full template content. Templates fixed: - data_protection_policy: add "Verantwortlicher" section (Art. 13(1)(a)) - applicant_dsi: add "Drittlandtransfer" section (Art. 13(1)(f)) - employee_dsi: add "Drittlandtransfer" section (Art. 13(1)(f)) - cookie_policy: add concrete cookie table example - dsfa: add LfDI / Aufsichtsbehoerden-Referenz - widerruf: add §312k BGB Online-Kuendigungsbutton clause Run inside container: docker exec bp-compliance-backend python /app/scripts/fix_template_content.py (dry-run by default; pass --apply to UPDATE the DB) """ from __future__ import annotations import os import sys import psycopg2 from psycopg2.extras import RealDictCursor # Sentinels: each fix has (a) where to insert, (b) what to insert, # (c) a check string to verify the insertion already happened (idempotent). FIXES = [ { "document_type": "data_protection_policy", "language": "de", "already_done_marker": "## 1. Verantwortlicher", "anchor": None, # Insert at top (after first heading) "insert_block": """## 1. Verantwortlicher Verantwortlich fuer die in dieser Richtlinie beschriebene Verarbeitung personenbezogener Daten im Sinne der DSGVO ist: **{{company_legal_name}}** {{company_address}} {{company_postal}} {{company_city}}, {{company_country}} E-Mail: {{company_email}} Telefon: {{company_phone}} Datenschutzbeauftragte/r: {{dpo_name}} ({{dpo_email}}) """, }, { "document_type": "applicant_dsi", "language": "de", "already_done_marker": "## 7. Drittlandtransfer", "anchor": "## 7.", # generic; we insert before whatever 7 is "insert_block": """## 7. Drittlandtransfer (Art. 13(1)(f) DSGVO) Eine Uebermittlung Ihrer Bewerberdaten in Laender ausserhalb der Europaeischen Union oder des Europaeischen Wirtschaftsraums (Drittland) findet **nicht** statt. Saemtliche Verarbeitung erfolgt ausschliesslich auf Servern innerhalb der EU. Sollten in Ausnahmefaellen Drittlandtransfers erforderlich werden (z.B. Konzern-Verbund mit US-Schwestergesellschaft), erfolgen diese ausschliesslich auf Basis von EU-Standardvertragsklauseln (Art. 46(2)(c) DSGVO) oder eines Angemessenheitsbeschlusses der EU-Kommission (Art. 45 DSGVO). """, }, { "document_type": "employee_dsi", "language": "de", "already_done_marker": "## 7. Drittlandtransfer", "anchor": "## 7.", "insert_block": """## 7. Drittlandtransfer (Art. 13(1)(f) DSGVO) Eine Uebermittlung Ihrer Beschaeftigtendaten in Laender ausserhalb der Europaeischen Union oder des Europaeischen Wirtschaftsraums (Drittland) findet grundsaetzlich **nicht** statt. Eine Ausnahme bilden Cloud-Dienste, die ggf. auf US-Server zugreifen — in diesem Fall erfolgt die Uebermittlung auf Basis von EU-Standardvertragsklauseln (Art. 46(2)(c) DSGVO) oder unter dem EU-US Data Privacy Framework (Angemessenheitsbeschluss vom 10.07.2023, Art. 45 DSGVO). Empfaengerland und Schutzmechanismus pro genutztem Dienst: siehe Verarbeitungsverzeichnis (VVT). """, }, { "document_type": "cookie_policy", "language": "de", "already_done_marker": "### 4.1 Konkrete Cookie-Tabelle", "anchor": None, # append before the final heading or at end "insert_block": """### 4.1 Konkrete Cookie-Tabelle (Beispiel) | Name | Anbieter | Zweck | Speicherdauer | Typ | |---|---|---|---|---| | `__session` | {{company_legal_name}} | Sitzungs-Authentifizierung | Sitzungsende | First-Party, technisch notwendig | | `cookie_consent` | {{company_legal_name}} | Speicherung der Cookie-Einwilligung | 12 Monate | First-Party, technisch notwendig | | `_ga` | Google Ireland Ltd. | Webanalyse (Google Analytics) | 2 Jahre | Third-Party, Statistik — Einwilligung erforderlich | | `_fbp` | Meta Platforms Ireland Ltd. | Marketing / Conversion-Tracking | 90 Tage | Third-Party, Marketing — Einwilligung erforderlich | > Hinweis: Die obenstehende Tabelle ist beispielhaft. Die tatsaechlich von Ihrer Website gesetzten Cookies pflegen Sie im Backend Ihres Consent-Tools (z.B. Cookiebot, Usercentrics, Borlabs). Die DSK-Orientierungshilfe Telemedien 2024 fordert je Cookie: Name, Anbieter, Zweck, Speicherdauer, Typ (First-/Third-Party). """, }, { "document_type": "dsfa", "language": "de", "already_done_marker": "### 0.2 Beruecksichtigung Landesaufsichtsbehoerden", "anchor": None, "insert_block": """### 0.2 Beruecksichtigung Landesaufsichtsbehoerden (LfDI) und DSK-Liste Diese DSFA beruecksichtigt: - **DSK-Positivliste** nach Art. 35(4) DSGVO: Die Datenschutzkonferenz (DSK) hat eine Liste von Verarbeitungen veroeffentlicht, die zwingend eine DSFA erfordern. Pruefen Sie, ob Ihre Verarbeitung dort gelistet ist. - **Landesbeauftragte fuer Datenschutz (LfDI)**: Jedes Bundesland (BfDI, BlnBDI, LfDI BW, LfDI BY, etc.) veroeffentlicht eigene Orientierungshilfen und Branchen-Stellungnahmen. Zustaendige Behoerde: {{supervisory_authority}}. - **EDPB Guidelines** (insbesondere WP248 — Kriterien fuer DSFA-Erforderlichkeit, Art. 29-Datenschutzgruppe). - **Branchenspezifische Aufsichtsempfehlungen** (z.B. Telemedien: DSK-OH 2024, Gesundheit: BfDI-Empfehlungen). """, }, { "document_type": "widerruf", "language": "de", "already_done_marker": "## §312k BGB", "anchor": None, "insert_block": """## §312k BGB — Online-Kuendigungsbutton (bei Dauerschuldverhaeltnissen) Bietet der Unternehmer Vertraege ueber **Dauerschuldverhaeltnisse** (Abonnements, Mitgliedschaften, SaaS-Subscriptions) auf seiner Website an, muss er nach §312k BGB einen Kuendigungsbutton bereitstellen. **Anforderungen** (BGH-Rechtsprechung 2023): - Der Button muss deutlich beschriftet sein mit "Vertraege hier kuendigen" oder gleichwertig. - Direkt nach Klick muss eine Bestaetigungsseite folgen mit Angaben zu Vertragsart, Vertragspartnern und Kuendigungstermin. - Nach Bestaetigung muss eine Bestaetigung der Kuendigung per E-Mail oder dauerhaft auf einem Datentraeger zur Verfuegung gestellt werden. **Verstoss**: Eine Kuendigung kann auch ohne den Button per E-Mail/Brief jederzeit erfolgen — fehlt der Button, kann der Vertrag zudem von der zustaendigen Verbraucherzentrale abgemahnt werden (§312k Abs. 6 BGB). **Ausnahme**: §312k gilt nur fuer Verbraucherkunden (B2C). Bei reinen B2B-Vertraegen besteht keine Pflicht. """, }, ] def apply_fix(content: str, fix: dict) -> tuple[str, str]: """Returns (new_content, status). Status: 'unchanged'/'inserted'/'already-fixed'.""" if fix["already_done_marker"] in content: return content, "already-fixed" anchor = fix["anchor"] if anchor and anchor in content: # Insert BEFORE the anchor new_content = content.replace(anchor, fix["insert_block"] + anchor, 1) else: # Append at end new_content = content.rstrip() + "\n\n" + fix["insert_block"] return new_content, "inserted" def main(apply: bool): dsn = os.environ.get("DATABASE_URL") or os.environ.get("COMPLIANCE_DATABASE_URL") if not dsn: print("ERROR: DATABASE_URL not set", file=sys.stderr) return 1 conn = psycopg2.connect(dsn) cur = conn.cursor(cursor_factory=RealDictCursor) summary = [] for fix in FIXES: cur.execute( "SELECT id, content FROM compliance.compliance_legal_templates " "WHERE document_type=%s AND language=%s AND status='published'", (fix["document_type"], fix["language"]), ) rows = cur.fetchall() if not rows: summary.append((fix["document_type"], fix["language"], "not-found", 0)) continue for row in rows: new_content, status = apply_fix(row["content"], fix) if status == "inserted" and apply: cur.execute( "UPDATE compliance.compliance_legal_templates " "SET content=%s, updated_at=now() WHERE id=%s", (new_content, row["id"]), ) summary.append((fix["document_type"], fix["language"], status, len(new_content) - len(row["content"]))) if apply: conn.commit() print(f"\n== Template Content Fixes ({'APPLIED' if apply else 'DRY-RUN'}) ==") for doc_type, lang, status, delta in summary: marker = "✓" if status == "inserted" else ("·" if status == "already-fixed" else "✗") print(f" {marker} {doc_type:30s} [{lang}] {status:14s} (+{delta} chars)") return 0 if __name__ == "__main__": apply = "--apply" in sys.argv sys.exit(main(apply))