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
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>
100 lines
3.5 KiB
Python
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>'
|
|
)
|