8cbb513e2c
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 / detect-changes (push) Successful in 11s
CI / branch-name (push) Has been skipped
CI / loc-budget (push) Failing after 16s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / validate-canonical-controls (push) Successful in 15s
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 38s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / test-go (push) Has been skipped
P81 — tests/fixtures/golden_truth/vw_de.json: GT-Fixture mit must_find_cookies (47 VW-Cookies) + expected_vendors (Google, Adobe, Trade Desk, ...). Basis fuer kuenftige Regression-Tests. P85 — banner_screenshot_block.py + consent_scanner.py + main.py: consent-tester macht beim Banner-Detect einen base64-PNG-Screenshot (< 1.5MB). Backend rendert ihn als <img src="data:..."> direkt nach dem GF-1-Pager. Visueller Beweis 'so sah das Banner aus' fuer Dispute mit Marketing/DSB. P70 — rag_provenance.py: classify_finding_provenance() klassifiziert ein Finding als 'rag' (Norm + Quelle), 'mixed' (Norm ohne Quelle) oder 'heuristic' (eigene Interpretation). provenance_badge_html() rendert kleine Badges (✓ RAG / NORM / ⚠ HEURISTIK). Modul ist generisch, kann bei jedem Finding-Renderer einklinkt werden. P83 — scripts/check-rebuild-needed.sh: Prueft ob die im Container deployten BUILD_SHA mit local HEAD uebereinstimmen. Bei Mismatch exit 1 mit 'REBUILD REQUIRED'-Hinweis. Verhindert das 'alter Code im Container'-Problem das uns mehrfach erwischt hat (Frontend-Tabs sichtbar, Backend ohne neuen Service). TCF-Fix — tcf_vendor_authority.py: cookie_library hat keinen UNIQUE-Index auf cookie_name → ON CONFLICT war unmoeglich. Loesung: vor Insert DELETE WHERE source_name='iab_tcf_v2'. Idempotent. + per-Vendor-Commit damit ein Fail die naechsten nicht blockt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
91 lines
3.1 KiB
Python
91 lines
3.1 KiB
Python
"""
|
|
P70 — RAG-Provenance-Marker.
|
|
|
|
Wenn ein Finding aus dem RAG-Korpus belegt ist (z.B. Art-Match auf
|
|
einen konkreten Gesetzes-Paragrafen aus dem ingestierten DSGVO/TDDDG/
|
|
TMG-Korpus), bekommt es einen ✓-Marker. Wenn es nur aus unserer
|
|
Heuristik kommt (Pattern-Match ohne RAG-Belegung), bekommt es ein ⚠
|
|
"Heuristik".
|
|
|
|
Dadurch sieht der Nutzer sofort welche Aussagen rechtlich verbindlich
|
|
gestuetzt sind vs welche unsere Eigeninterpretation sind.
|
|
|
|
Generisch: dataclass-aehnliche Funktion die ein Finding-dict klassifiziert.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import re
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# Pattern fuer "Belegt aus Korpus": Finding enthaelt expliziten
|
|
# Norm-Bezug mit Artikel + Quelle.
|
|
_NORM_RE = re.compile(
|
|
r"(Art\.?\s*\d+(?:\s*Abs\.?\s*\d+)?(?:\s*lit\.?\s*[a-z])?\s*"
|
|
r"(?:DSGVO|GDPR|TDDDG|TMG|BDSG|UWG|TKG|EuGH|EDPB)|"
|
|
r"\(?(EU|VO)\s*\d{4}/\d+\)?|"
|
|
r"§\s*\d+[a-z]?\s*(TMG|UWG|BDSG|TKG|TDDDG))",
|
|
re.I,
|
|
)
|
|
|
|
|
|
def classify_finding_provenance(finding: dict) -> str:
|
|
"""Returns 'rag', 'heuristic', or 'mixed'.
|
|
|
|
rag — Norm-Bezug + Quellen-URL (verbindlich)
|
|
heuristic — Pattern-Match ohne Norm-Bezug (Eigeninterpretation)
|
|
mixed — Norm-Bezug aber ohne Quellen-URL (teilweise belegbar)
|
|
"""
|
|
if not isinstance(finding, dict):
|
|
return "heuristic"
|
|
legal = (finding.get("legal_basis") or "").strip()
|
|
detail = (finding.get("detail") or "").strip()
|
|
rag_id = finding.get("rag_chunk_id")
|
|
rag_url = finding.get("rag_source_url")
|
|
blob = " ".join([legal, detail])
|
|
has_norm = bool(_NORM_RE.search(blob))
|
|
has_source = bool(rag_id or rag_url or
|
|
"https://" in legal or "https://" in detail)
|
|
if has_norm and has_source:
|
|
return "rag"
|
|
if has_norm:
|
|
return "mixed"
|
|
return "heuristic"
|
|
|
|
|
|
def provenance_badge_html(provenance: str) -> str:
|
|
if provenance == "rag":
|
|
return (
|
|
'<span style="background:#dcfce7;color:#166534;'
|
|
'padding:1px 5px;border-radius:8px;font-size:9px;'
|
|
'font-weight:600;margin-left:4px" '
|
|
'title="Aussage durch RAG-Korpus belegt (Gesetzestext + Quelle)">'
|
|
'✓ RAG</span>'
|
|
)
|
|
if provenance == "mixed":
|
|
return (
|
|
'<span style="background:#dbeafe;color:#1e40af;'
|
|
'padding:1px 5px;border-radius:8px;font-size:9px;'
|
|
'font-weight:600;margin-left:4px" '
|
|
'title="Norm-Bezug ohne direkte Quellen-URL">'
|
|
'NORM</span>'
|
|
)
|
|
return (
|
|
'<span style="background:#f1f5f9;color:#475569;'
|
|
'padding:1px 5px;border-radius:8px;font-size:9px;'
|
|
'font-weight:600;margin-left:4px" '
|
|
'title="Heuristik / Eigeninterpretation ohne Korpus-Beleg">'
|
|
'⚠ HEURISTIK</span>'
|
|
)
|
|
|
|
|
|
def annotate_findings(findings: list[dict]) -> list[dict]:
|
|
"""In-place: setzt finding['provenance'] auf jeden Eintrag."""
|
|
for f in (findings or []):
|
|
if isinstance(f, dict) and "provenance" not in f:
|
|
f["provenance"] = classify_finding_provenance(f)
|
|
return findings
|