feat(impressum-agent): Tesla-Pattern + KBA-Hint + News-Doc-Type
CI / build-sha-integrity (push) Failing after 4s
CI / validate-canonical-controls (push) Successful in 11s
CI / loc-budget (push) Successful in 14s
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) Successful in 2m20s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 30s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / detect-changes (push) Successful in 6s
CI / branch-name (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 / build-sha-integrity (push) Failing after 4s
CI / validate-canonical-controls (push) Successful in 11s
CI / loc-budget (push) Successful in 14s
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) Successful in 2m20s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 30s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / detect-changes (push) Successful in 6s
CI / branch-name (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
User-Feedback Tesla-Impressum: 10 FAIL bei 46 Worten — viele False-
Positives. Nach Tuning: 5 juristisch saubere Findings.
Impressum-Agent Patterns:
- name_anbieter zusätzlich label-frei matchen (Firma+Rechtsform+
Anschrift, Tesla schreibt ohne "Anbieter:" Label).
- vertretungsberechtigte akzeptiert jetzt "Management" / "Director"
als alternative (US-Konzern-Habit), aber emittiert separates
Sub-Finding "Label sollte Geschäftsführer für § 5 TMG sein".
- aufsichtsbehoerde-Pattern um KBA / Bundesnetzagentur erweitert.
- NEU: verantwortlicher_redaktion (§ 18 MStV bei Blog/News).
- NEU: verbraucher_streitbeilegung (§ 36 VSBG bei B2C).
- Auto-Detection von Automotive-Branche: explizite Begriffe ODER
bekannte Hersteller-Namen (Tesla/BMW/Mercedes/Audi/VW/Porsche…).
Triggert KBA-Hint im aufsichtsbehoerde-Finding-Action.
Frontend (_document_types.ts):
- Extrahiert aus ComplianceCheckTab.tsx (vorher inline).
- NEU: doc_type "news" für Blog/Newsroom-URL → § 18 MStV-Pflicht-
angaben prüfen. User-Hinweis: tesla.com/de_de/blog ist
relevanter Audit-Input neben DSE/Impressum.
Smoke gegen Tesla-Impressum (46 Worte):
Vorher 10 Findings (5 davon FP).
Jetzt 5 Findings — alle juristisch korrekt:
[MED] Management statt Geschäftsführer
[LOW] KBA als Aufsichtsbehörde fehlt
[MED] § 18 MStV-Verantwortlicher fehlt (Tesla Blog!)
[MED] § 36 VSBG-Hinweis fehlt
[MED] ODR-Plattform-Link fehlt
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* DOCUMENT_TYPES — canonical compliance-doc taxonomy for the
|
||||
* /sdk/agent ComplianceCheckTab form.
|
||||
*
|
||||
* Each entry maps to a doc_type that the backend Phase-A discovery /
|
||||
* Phase-B per-doc-check pipeline recognises.
|
||||
*/
|
||||
|
||||
export const DOCUMENT_TYPES = [
|
||||
{ id: 'dse', label: 'DSI (Datenschutzinformation)', required: true },
|
||||
{ id: 'impressum', label: 'Impressum', required: true },
|
||||
{ id: 'social_media', label: 'Social Media DSE', required: false },
|
||||
{ id: 'cookie', label: 'Cookie-Richtlinie', required: false },
|
||||
{ id: 'agb', label: 'AGB', required: false },
|
||||
{ id: 'nutzungsbedingungen', label: 'Nutzungsbedingungen', required: false },
|
||||
{ id: 'widerruf', label: 'Widerrufsbelehrung', required: false },
|
||||
{ id: 'dsb', label: 'DSB-Kontakt', required: false },
|
||||
{ id: 'news', label: 'Blog/Newsroom (für § 18 MStV)', required: false },
|
||||
] as const
|
||||
|
||||
export type DocTypeId = typeof DOCUMENT_TYPES[number]['id']
|
||||
@@ -29,9 +29,20 @@ PFLICHTANGABEN = {
|
||||
"label": "Name + Anschrift des Anbieters",
|
||||
"norm": "§ 5 Abs. 1 Nr. 1 TMG",
|
||||
"patterns": [
|
||||
# Label-Form: "Anbieter:", "Diensteanbieter:", "Verantwortlicher:"
|
||||
re.compile(r"\b(?:Anbieter|Diensteanbieter|"
|
||||
r"Verantwortlich(?:er Anbieter)?)\s*[:.\s]",
|
||||
re.IGNORECASE),
|
||||
# Label-frei: Firma (Rechtsform) + Strasse + PLZ — Tesla-
|
||||
# Pattern. Wenn das Impressum ohne explizites "Anbieter:"-
|
||||
# Label direkt mit Firma+Adresse loslegt, gilt das auch.
|
||||
re.compile(
|
||||
r"\b[A-ZÄÖÜ][\w\-\& ]{1,80}?\s+"
|
||||
r"(?:GmbH|AG|UG|KG|SE|GbR|OHG|Limited|Ltd|LLC)\s*"
|
||||
r"[\s\S]{0,400}?"
|
||||
r"\b\d{5}\s+[A-ZÄÖÜ]",
|
||||
re.IGNORECASE,
|
||||
),
|
||||
],
|
||||
"severity_if_missing": "HIGH",
|
||||
},
|
||||
@@ -75,21 +86,76 @@ PFLICHTANGABEN = {
|
||||
"label": "Vertretungsberechtigte Person",
|
||||
"norm": "§ 5 Abs. 1 Nr. 1 TMG (juristische Personen)",
|
||||
"patterns": [
|
||||
re.compile(r"(?:Geschäftsführer|Vertretungsberechtigt|"
|
||||
r"vertreten\s+durch)\s*[:.\s]",
|
||||
# Korrekte Label: "Geschäftsführer:", "Vertretungsberechtigt:"
|
||||
re.compile(r"(?:Gesch(?:ae|ä)ftsf(?:ue|ü)hrer|"
|
||||
r"Vertretungsberechtigt|vertreten\s+durch)"
|
||||
r"\s*[:.\s]",
|
||||
re.IGNORECASE),
|
||||
# US-Konzern-Habit: "Management" als nicht-rechtskonformes
|
||||
# Alternativlabel (Tesla, Apple, etc.). Erkennen als
|
||||
# vorhanden — separater Sub-Finding meldet die Label-
|
||||
# Korrekturpflicht.
|
||||
re.compile(r"\bManagement\s*[:.\s]\s*[A-ZÄÖÜ]", re.IGNORECASE),
|
||||
re.compile(r"\bDirector(?:s|en)?\s*[:.\s]\s*[A-ZÄÖÜ]",
|
||||
re.IGNORECASE),
|
||||
],
|
||||
"severity_if_missing": "HIGH",
|
||||
},
|
||||
"vertretungsberechtigte_label_korrekt": {
|
||||
"label": "Korrekte Bezeichnung 'Geschäftsführer' statt 'Management'",
|
||||
"norm": "§ 5 Abs. 1 Nr. 1 TMG (Deutsch-Pflicht, gerichtsfest)",
|
||||
"patterns": [
|
||||
# PASSED nur wenn DEUTSCHES Label vorhanden.
|
||||
re.compile(r"(?:Gesch(?:ae|ä)ftsf(?:ue|ü)hrer|"
|
||||
r"Vorstand|"
|
||||
r"Vertretungsberechtigt|vertreten\s+durch)"
|
||||
r"\s*[:.\s]",
|
||||
re.IGNORECASE),
|
||||
],
|
||||
"severity_if_missing": "MEDIUM",
|
||||
},
|
||||
"aufsichtsbehoerde": {
|
||||
"label": "Aufsichtsbehörde (regulierte Branchen)",
|
||||
"norm": "§ 5 Abs. 1 Nr. 3 TMG (Branchen-bedingt)",
|
||||
"patterns": [
|
||||
re.compile(r"Aufsichtsbeh(?:ö|oe)rde\s*[:.\s]", re.IGNORECASE),
|
||||
re.compile(r"\bBAFin\b|\bBNetzA\b|\bLKA\b", re.IGNORECASE),
|
||||
re.compile(
|
||||
r"\bBAFin\b|\bBNetzA\b|\bLKA\b|\bKBA\b|"
|
||||
r"Kraftfahrt-?Bundesamt|Bundesnetzagentur|"
|
||||
r"Bundesanstalt\s+f(?:ü|ue)r",
|
||||
re.IGNORECASE,
|
||||
),
|
||||
],
|
||||
"severity_if_missing": "LOW",
|
||||
},
|
||||
"verantwortlicher_redaktion": {
|
||||
"label": "Verantwortlicher § 18 MStV (journalistisch-redaktionell)",
|
||||
"norm": "§ 18 MStV (bei Blog/News/Magazin/Newsroom Pflicht)",
|
||||
"patterns": [
|
||||
re.compile(
|
||||
r"(?:Verantwortlich(?:er|e)?\s+(?:f(?:ue|ü)r|i\.S\.d\.|"
|
||||
r"nach|gem(?:ae|ä)ß)\s+§\s*18|"
|
||||
r"V\.i\.S\.d\.\s*§?\s*18|"
|
||||
r"redaktionell\s+Verantwortlich)",
|
||||
re.IGNORECASE,
|
||||
),
|
||||
],
|
||||
"severity_if_missing": "MEDIUM",
|
||||
},
|
||||
"verbraucher_streitbeilegung": {
|
||||
"label": "Verbraucher-Streitbeilegung-Hinweis",
|
||||
"norm": "§ 36 VSBG (B2C-Anbieter Pflicht)",
|
||||
"patterns": [
|
||||
re.compile(
|
||||
r"(?:Verbraucherschlichtungs|VSBG|"
|
||||
r"Streitbeilegung|"
|
||||
r"Schlichtungsstelle|"
|
||||
r"alternative\s+Streit(?:beilegung|schlichtung))",
|
||||
re.IGNORECASE,
|
||||
),
|
||||
],
|
||||
"severity_if_missing": "MEDIUM",
|
||||
},
|
||||
"berufsangaben": {
|
||||
"label": "Berufsbezeichnung + Berufsrechtliche Angaben",
|
||||
"norm": "§ 5 Abs. 1 Nr. 5 TMG (Kammerberufe)",
|
||||
@@ -122,23 +188,63 @@ def evaluate(impressum_text: str,
|
||||
return []
|
||||
business_scope = business_scope or set()
|
||||
findings: list[dict] = []
|
||||
# Auto-detect KFZ-Hersteller / Auto-Direktvertrieb für KBA-Hint.
|
||||
# Erst: explizite Begriffe (Fahrzeug, Automobil, …).
|
||||
# Dann: bekannte Hersteller-Namen (BMW, Tesla, Mercedes, …).
|
||||
is_automotive = bool(re.search(
|
||||
r"\b(?:KFZ|Fahrzeug(?:e|herstellung|verkauf)?|Automobil|"
|
||||
r"E-Auto|Elektroauto|Auto-?Konfigurator|"
|
||||
r"Elektrofahrzeug|Hybrid-?Fahrzeug)\b",
|
||||
impressum_text, re.IGNORECASE,
|
||||
)) or bool(re.search(
|
||||
r"\b(?:Tesla|BMW|Mercedes-?Benz|Audi|Volkswagen|Porsche|"
|
||||
r"Volvo|Stellantis|Skoda|Seat|Cupra|MINI|Smart|"
|
||||
r"Opel|Ford\s+Deutschland|Hyundai|Kia|Toyota|Mazda|"
|
||||
r"Nissan|Honda|Subaru|Lexus|Polestar|NIO|BYD|Rivian|"
|
||||
r"Lucid)\s+(?:Germany|Deutschland|Group|Holding|AG|"
|
||||
r"GmbH|S(?:E|\.A\.))\b",
|
||||
impressum_text, re.IGNORECASE,
|
||||
))
|
||||
for field_id, spec in PFLICHTANGABEN.items():
|
||||
# Skip context-dependent fields when scope doesn't match
|
||||
if field_id == "odr_link" and "ecommerce" not in business_scope:
|
||||
continue
|
||||
if field_id == "aufsichtsbehoerde" and (
|
||||
"regulated_profession" not in business_scope
|
||||
and "financial_services" not in business_scope
|
||||
and "insurance" not in business_scope
|
||||
if field_id == "aufsichtsbehoerde" and not (
|
||||
"regulated_profession" in business_scope
|
||||
or "financial_services" in business_scope
|
||||
or "insurance" in business_scope
|
||||
or is_automotive
|
||||
):
|
||||
continue
|
||||
if field_id == "berufsangaben" and (
|
||||
"regulated_profession" not in business_scope
|
||||
):
|
||||
continue
|
||||
if field_id == "verantwortlicher_redaktion" and (
|
||||
"editorial" not in business_scope
|
||||
):
|
||||
continue
|
||||
if field_id == "verbraucher_streitbeilegung" and (
|
||||
"ecommerce" not in business_scope
|
||||
and "b2c" not in business_scope
|
||||
):
|
||||
continue
|
||||
found = any(p.search(impressum_text) for p in spec["patterns"])
|
||||
if found:
|
||||
continue
|
||||
# Context-aware action: KBA-Hint bei Auto-Branche
|
||||
action = (
|
||||
f"{spec['label']} im Impressum ergänzen "
|
||||
f"(Pflichtangabe nach {spec['norm']})."
|
||||
)
|
||||
if field_id == "aufsichtsbehoerde" and is_automotive:
|
||||
action = (
|
||||
"Aufsichtsbehörde im Impressum benennen. Für "
|
||||
"KFZ-Hersteller/-Vertrieb typisch: Kraftfahrt-"
|
||||
"Bundesamt (KBA), Fördestraße 16, 24944 Flensburg, "
|
||||
"www.kba.de. Bei Ladestrom-Vertrieb zusätzlich "
|
||||
"Bundesnetzagentur (BNetzA)."
|
||||
)
|
||||
findings.append({
|
||||
"check_id": f"IMPRESSUM-AGENT-{field_id.upper()}",
|
||||
"agent": "impressum_agent_v1",
|
||||
@@ -147,10 +253,7 @@ def evaluate(impressum_text: str,
|
||||
"severity_reason": "missing",
|
||||
"title": f"Pflichtangabe '{spec['label']}' fehlt im Impressum",
|
||||
"norm": spec["norm"],
|
||||
"action": (
|
||||
f"{spec['label']} im Impressum ergänzen "
|
||||
f"(Pflichtangabe nach {spec['norm']})."
|
||||
),
|
||||
"action": action,
|
||||
})
|
||||
if findings:
|
||||
logger.info(
|
||||
|
||||
Reference in New Issue
Block a user