Files
breakpilot-compliance/backend-compliance/compliance/services/doc_input_warnings.py
T
Benjamin Admin e411c4f0d3
CI / detect-changes (push) Successful in 12s
CI / branch-name (push) Has been skipped
CI / nodejs-build (push) Successful in 3m27s
CI / iace-gt-coverage (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / validate-canonical-controls (push) Successful in 17s
CI / loc-budget (push) Failing after 20s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go (push) Has been skipped
CI / test-python-backend (push) Successful in 47s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
feat(audit): Text-Paste-Mode pro Row — Crawler optional umgehen
Hintergrund: VW liefert ueber URL-Crawler nur 6 Vendors statt der 100+
die in der echten Cookie-Tabelle stehen. Wenn der User die Tabelle aber
direkt von der Site kopieren kann (was bei den meisten OEM-Sites moeglich
ist), umgehen wir den Crawler komplett und parsen den Text deterministisch.

Backend:
* doc_type_classifier.py — 7 Pattern-Gruppen (§5 TMG, Art.13 DSGVO,
  AGB-Klauseln, Widerrufs-Frist, Cookie-Tabellen-Header, etc). Wenn der
  User Text ins falsche Doc-Type-Feld kopiert (Impressum->DSE),
  detect_mismatch liefert detected + action ('reclassify' bei sehr hoher
  Konfidenz, 'warn' bei medium).
* cookies_table_parser.py — Tab/Pipe/Komma/Semicolon-Separator-Auto-
  Detection, Spalten-Mapping per Header-Keyword. Aggregiert Cookie-
  Eintraege zu Vendor-Records (mit _guess_vendor-Fallback). Voll
  deterministisch, kein LLM.
* doc_input_warnings.py — Mail-Block ueber dem Audit, der Mismatches +
  Auto-Reclassifies dem User transparent macht.
* Pipeline: text gewinnt ueber url (war schon im Schema vermerkt), neue
  Felder declared_doc_type / input_source / reclassify_hint in doc_entries.
  Pasted-Tabellen-Vendors haben Vorrang vor Library-Fallback + LLM-Cascade
  (sind 100% genau).

Frontend (DocCheckTab):
* Pro Row Mode-Toggle 'URL' / 'Text einfuegen' (lila wenn aktiv).
* Textarea (h-32, monospace) im text-mode mit kontext-spezifischem
  Placeholder (Cookie-Hinweis ggue. anderen Doc-Types) und Live-
  Zeichen-/Wort-Counter.
* Submit-Button accepted entries mit URL ODER text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:58:32 +02:00

100 lines
3.5 KiB
Python

"""
Rendert die Doc-Type-Mismatch-Hinweise als Mail-Block.
Wenn der User Text in das falsche Feld kopiert (z.B. Impressum-Text
ins DSE-Feld), zeigt der Block:
- was er deklariert hat
- was der Classifier erkannt hat
- Empfehlung (re-paste oder als unbekannt einreichen)
"""
from __future__ import annotations
import logging
from typing import Iterable
logger = logging.getLogger(__name__)
_DOC_LABELS = {
"dse": "Datenschutzerklaerung",
"cookie": "Cookie-Richtlinie",
"impressum": "Impressum",
"agb": "AGB",
"widerruf": "Widerrufsbelehrung",
"nutzungsbedingungen": "Nutzungsbedingungen",
"social_media": "Social Media DSE",
"dsfa": "DSFA",
"dsa": "DSA-Pflichtangaben",
"legal_notice": "Rechtliche Hinweise",
"lizenzhinweise": "Lizenzhinweise",
}
def _label(dt: str) -> str:
return _DOC_LABELS.get(dt, dt)
def collect_warnings(doc_entries: Iterable[dict]) -> list[dict]:
"""Returns list of {declared, detected, action, scores} fuer alle
doc_entries mit einem reclassify_hint."""
out: list[dict] = []
for e in (doc_entries or []):
hint = e.get("reclassify_hint")
if not hint:
continue
out.append({
"input_source": e.get("input_source"),
"declared": hint.get("declared"),
"detected": hint.get("detected"),
"action": hint.get("action"),
"declared_score": hint.get("declared_score", 0),
"detected_score": hint.get("detected_score", 0),
"all_scores": hint.get("all_scores") or {},
"word_count": e.get("word_count", 0),
})
return out
def build_warnings_block_html(warnings: list[dict]) -> str:
if not warnings:
return ""
items: list[str] = []
for w in warnings:
action = w.get("action")
if action == "reclassify":
color = "#0e7490"
badge = "AUTO-RECLASSIFIZIERT"
body = (
f'Sie haben den Text als <strong>{_label(w["declared"])}</strong> '
f'eingereicht, das System hat ihn aber automatisch als '
f'<strong>{_label(w["detected"])}</strong> erkannt und entsprechend '
f'gepruft (Konfidenz-Score: {w["detected_score"]} vs '
f'{w["declared_score"]} für die deklarierte Kategorie).'
)
else:
color = "#d97706"
badge = "MOEGLICHER MISMATCH"
body = (
f'Sie haben den Text als <strong>{_label(w["declared"])}</strong> '
f'eingereicht. Der Inhalt enthaelt aber Patterns die eher zu '
f'<strong>{_label(w["detected"])}</strong> passen '
f'({w["detected_score"]} vs {w["declared_score"]}). '
'Bitte pruefen Sie ob Sie den richtigen Doc-Typ ausgewaehlt haben.'
)
items.append(
f'<li style="margin-bottom:8px;font-size:11px;line-height:1.5">'
f'<strong style="color:{color}">[{badge}]</strong> {body}'
f'</li>'
)
return (
'<div style="font-family:-apple-system,BlinkMacSystemFont,sans-serif;'
'max-width:760px;margin:0 auto 12px;padding:10px 14px;'
'background:#ecfeff;border:1px solid #67e8f9;border-radius:6px">'
'<div style="font-size:11px;color:#0e7490;text-transform:uppercase;'
'letter-spacing:1.2px;margin-bottom:4px;font-weight:600">'
'Hinweise zum eingefügten Text</div>'
'<ul style="margin:4px 0 0 18px;padding:0">'
+ "".join(items) +
'</ul></div>'
)