Files
breakpilot-compliance/backend-compliance/compliance/services/doc_checks/widerruf_checks.py
T
Benjamin Admin b363c28539 feat: Add 76 Level-2 regex checks for document correctness verification
Split dsi_document_checker.py (466 LOC) into doc_checks/ package (9 files).
Two-pass L1→L2 logic: L1 checks "Is it mentioned?", L2 checks "Is it correct?"
(e.g. controller has full address, specific Art. 6 lit., concrete time periods).

138 total checks (62 L1 + 76 L2) across 7 doc types:
- DSE Art. 13: 31, Impressum §5 TMG: 16, Cookie §25 TDDDG: 15
- Widerruf §355: 15, AGB §305ff: 21, Social Media Art. 26: 20, DSFA Art. 35: 18

Frontend: hierarchical L1→L2 display with dual progress bars
(green=completeness, blue=correctness).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 12:37:03 +02:00

185 lines
6.6 KiB
Python

"""
Widerrufsbelehrung checks — §355 BGB, §312g BGB.
Level 1: Pflichtangabe erwaehnt?
Level 2: Pflichtangabe korrekt/vollstaendig?
"""
WIDERRUF_CHECKLIST = [
# ── L1: Belehrung ueber Widerrufsrecht ────────────────────────────
{
"id": "right_info",
"label": "Belehrung ueber Widerrufsrecht",
"level": 1, "parent": None,
"patterns": [
r"widerrufsrecht",
r"right\s+of\s+withdrawal",
r"recht\s+(?:zum|auf)\s+widerruf",
],
"severity": "HIGH",
},
# ── L1: Widerrufsfrist ────────────────────────────────────────────
{
"id": "deadline",
"label": "Widerrufsfrist (14 Tage)",
"level": 1, "parent": None,
"patterns": [
r"14\s+tage", r"vierzehn\s+tage",
r"14\s+days", r"fourteen\s+days",
],
"severity": "HIGH",
},
{
"id": "deadline_calendar_days",
"label": "Kalendertage explizit angegeben",
"level": 2, "parent": "deadline",
"patterns": [
r"14\s+kalendertage|vierzehn\s+kalendertage",
r"14\s+calendar\s+days",
],
"severity": "LOW",
},
{
"id": "deadline_receipt_trigger",
"label": "Fristbeginn bei Zugang/Erhalt definiert",
"level": 2, "parent": "deadline",
"patterns": [
r"frist\s+beginnt.*(?:zugang|erhalt|empfang|tag\s+nach)",
r"ab\s+(?:dem\s+)?(?:tag|zeitpunkt).*(?:zugang|erhalt|empfang|lieferung)",
r"beginnt\s+(?:mit|ab)\s+(?:dem\s+)?(?:zugang|erhalt)",
],
"severity": "MEDIUM",
},
# ── L1: Form des Widerrufs ────────────────────────────────────────
{
"id": "form",
"label": "Form des Widerrufs",
"level": 1, "parent": None,
"patterns": [
r"widerrufsformular", r"muster.?widerruf",
r"withdrawal\s+form", r"formular",
],
"severity": "MEDIUM",
},
{
"id": "form_text_required",
"label": "Textform-Anforderung (Brief, E-Mail, Fax)",
"level": 2, "parent": "form",
"patterns": [
r"(?:textform|schriftlich|per\s+(?:brief|e-?mail|fax|post))",
r"(?:mittels|durch)\s+(?:einer?\s+)?(?:eindeutige|klare)\w*\s+erkl(?:ae|ä)rung",
],
"severity": "LOW",
},
{
"id": "model_form",
"label": "Muster-Widerrufsformular beigefuegt/verlinkt",
"level": 2, "parent": "form",
"patterns": [
r"muster[\-\s]?widerrufsformular",
r"(?:beigef(?:ue|ü)gt|anlage|anhang|formular).*widerruf",
r"widerruf.*(?:beigef(?:ue|ü)gt|anlage|anhang|formular)",
],
"severity": "LOW",
},
# ── L1: Folgen des Widerrufs ──────────────────────────────────────
{
"id": "consequences",
"label": "Folgen des Widerrufs",
"level": 1, "parent": None,
"patterns": [
r"folgen\s+des\s+widerrufs",
r"consequences\s+of\s+withdrawal",
r"r(?:ue|ü)ckerstattung",
],
"severity": "MEDIUM",
},
{
"id": "refund_timeline",
"label": "Rueckerstattung innerhalb von 14 Tagen",
"level": 2, "parent": "consequences",
"patterns": [
r"(?:r(?:ue|ü)ckerstattung|r(?:ue|ü)ckzahlung|erstatten).*14\s+tage",
r"14\s+tage.*(?:r(?:ue|ü)ckerstatt|r(?:ue|ü)ckzahl|erstatt)",
r"(?:unverz(?:ue|ü)glich|sp(?:ae|ä)testens).*(?:r(?:ue|ü)ck|erstatt)",
],
"severity": "MEDIUM",
},
{
"id": "return_costs",
"label": "Ruecksendekosten-Regelung",
"level": 2, "parent": "consequences",
"patterns": [
r"(?:r(?:ue|ü)cksende|versand|porto)kosten",
r"kosten\s+(?:der|fuer|für)\s+r(?:ue|ü)cksendung",
r"(?:tragen|uebernehmen|übernehmen)\s+(?:die\s+)?(?:kosten|r(?:ue|ü)cksende)",
],
"severity": "LOW",
},
# ── L1: Empfaenger des Widerrufs ──────────────────────────────────
{
"id": "recipient",
"label": "Empfaenger des Widerrufs (Name + Anschrift)",
"level": 1, "parent": None,
"patterns": [
r"widerruf.*(?:richten|senden|erkl(?:ae|ä)ren)\s+(?:an|gegen(?:ue|ü)ber)",
r"(?:name|firma|anschrift).*widerruf",
r"widerruf.*(?:per|via|an)",
],
"severity": "MEDIUM",
},
{
"id": "recipient_full_address",
"label": "Vollstaendige Adresse des Empfaengers",
"level": 2, "parent": "recipient",
"patterns": [
r"widerruf.*\d{5}\s+[A-Z\u00c0-\u017e]",
r"\d{5}\s+[A-Z\u00c0-\u017e]\w+.*widerruf",
],
"severity": "LOW",
},
# ── L1: Hinweis kein Grund erforderlich ───────────────────────────
{
"id": "no_reason",
"label": "Hinweis: kein Grund erforderlich",
"level": 1, "parent": None,
"patterns": [
r"ohne\s+(?:angabe|nennung).*(?:grund|gr(?:ue|ü)nde)",
r"(?:kein|keine).*(?:begr(?:ue|ü)ndung|grund).*(?:erforderlich|n(?:oe|ö)tig)",
],
"severity": "LOW",
},
# ── L1: Online-Kuendigungsbutton ──────────────────────────────────
{
"id": "digital_button",
"label": "Online-Kuendigungsbutton (§312k BGB)",
"level": 1, "parent": None,
"patterns": [
r"k(?:ue|ü)ndigungsbutton", r"§\s*312k",
r"online.*k(?:ue|ü)ndig",
r"k(?:ue|ü)ndigung.*(?:button|link|formular|online)",
],
"severity": "MEDIUM",
},
# ── Neue L1: Ausnahme digitale Inhalte ────────────────────────────
{
"id": "digital_content_exception",
"label": "Ausnahme fuer digitale Inhalte (§356 BGB)",
"level": 1, "parent": None,
"patterns": [
r"§\s*356",
r"digital\w*\s+(?:inhalte?|g(?:ue|ü)ter|dienstleistung)",
r"(?:erlischt|verl(?:ue|ü)st|kein\s+widerrufsrecht).*digital",
r"(?:ausnahme|ausschluss).*widerruf.*digital",
],
"severity": "LOW",
},
]