From e411c4f0d3e49be7c1b60f53cb18335a23d11712 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Thu, 21 May 2026 18:58:32 +0200 Subject: [PATCH] =?UTF-8?q?feat(audit):=20Text-Paste-Mode=20pro=20Row=20?= =?UTF-8?q?=E2=80=94=20Crawler=20optional=20umgehen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../app/sdk/agent/_components/DocCheckTab.tsx | 130 +++++++---- .../api/agent_compliance_check_routes.py | 111 ++++++++- .../services/cookies_table_parser.py | 217 ++++++++++++++++++ .../compliance/services/doc_input_warnings.py | 99 ++++++++ .../services/doc_type_classifier.py | 162 +++++++++++++ 5 files changed, 670 insertions(+), 49 deletions(-) create mode 100644 backend-compliance/compliance/services/cookies_table_parser.py create mode 100644 backend-compliance/compliance/services/doc_input_warnings.py create mode 100644 backend-compliance/compliance/services/doc_type_classifier.py diff --git a/admin-compliance/app/sdk/agent/_components/DocCheckTab.tsx b/admin-compliance/app/sdk/agent/_components/DocCheckTab.tsx index 4df9a9f7..c92b8e3a 100644 --- a/admin-compliance/app/sdk/agent/_components/DocCheckTab.tsx +++ b/admin-compliance/app/sdk/agent/_components/DocCheckTab.tsx @@ -10,6 +10,8 @@ interface DocEntry { type: string label: string url: string + text: string // P-Paste: User kopiert Doc-Text direkt rein + mode: 'url' | 'text' // welcher Input wird aktiv genutzt } const DOC_TYPES = [ @@ -28,7 +30,8 @@ const DOC_TYPES = [ ] function newEntry(): DocEntry { - return { id: crypto.randomUUID().slice(0, 8), type: 'dse', label: '', url: '' } + return { id: crypto.randomUUID().slice(0, 8), type: 'dse', label: '', + url: '', text: '', mode: 'url' } } export function DocCheckTab() { @@ -81,7 +84,7 @@ export function DocCheckTab() { } const handleSubmit = async () => { - const validEntries = entries.filter(e => e.url.trim()) + const validEntries = entries.filter(e => e.url.trim() || e.text.trim()) if (validEntries.length === 0) return setLoading(true) @@ -96,8 +99,13 @@ export function DocCheckTab() { body: JSON.stringify({ entries: validEntries.map(e => ({ doc_type: e.type, - label: e.label || e.url.split('/').pop() || 'Dokument', - url: e.url.trim(), + label: e.label + || (e.url ? e.url.split('/').pop() : '') + || `${e.type}-paste`, + url: e.mode === 'text' ? '' : e.url.trim(), + // Backend nimmt text > url. Wenn beide gefuellt sind und + // mode='url', schicken wir den text NICHT mit. + text: e.mode === 'text' ? e.text.trim() : '', })), check_cookie_banner: checkCookieBanner, use_agent: useAgent, @@ -148,41 +156,83 @@ export function DocCheckTab() { {/* P79 Pre-Scan-Wizard — 8 Pflichtfelder */} - {/* URL Entries */} -
+ {/* URL / Text Entries */} +
{entries.map((entry, i) => ( -
- - updateEntry(entry.id, 'label', e.target.value)} - placeholder={entry.type === 'other' ? 'Dokumentname' : 'Version / Stand (optional)'} - className="w-40 px-3 py-2.5 border border-gray-300 rounded-lg text-sm shrink-0" - /> - updateEntry(entry.id, 'url', e.target.value)} - onBlur={() => autoLabel(entry)} - placeholder="https://example.com/datenschutz" - className="flex-1 px-3 py-2.5 border border-gray-300 rounded-lg text-sm" - /> - {entries.length > 1 && ( - +
+
+ + updateEntry(entry.id, 'label', e.target.value)} + placeholder={entry.type === 'other' ? 'Dokumentname' : 'Version / Stand (optional)'} + className="w-40 px-3 py-2.5 border border-gray-300 rounded-lg text-sm shrink-0" + /> + + {/* Mode-Toggle URL / Text */} +
+ + +
+ + {entry.mode === 'url' && ( + updateEntry(entry.id, 'url', e.target.value)} + onBlur={() => autoLabel(entry)} + placeholder="https://example.com/datenschutz" + className="flex-1 px-3 py-2.5 border border-gray-300 rounded-lg text-sm" + /> + )} + + {entries.length > 1 && ( + + )} +
+ + {entry.mode === 'text' && ( +
+