Files
breakpilot-compliance/backend-compliance/compliance/services/doc_checks/impressum_checks.py
T
Benjamin Admin bc21480a2a fix(compliance-check): always render 8 doc types + 4 BMW GT-gap fixes
Always-show-8 (user-requested):
- agent_compliance_check_routes.py: _pad_results_with_missing pads the
  results list to always include all 8 canonical doc_types in canonical
  order. Missing types get a placeholder DocCheckResult with error=
  'Nicht eingereicht' + scenario='missing'.
- agent_doc_check_report.py: NICHT EINGEREICHT status label (neutral),
  friendly grey body block instead of red error.
- ChecklistView.tsx: 'Nicht eingereicht' chip (neutral grey, not red
  'Fehler'); SCENARIO_LABELS adds missing entry + header chip counter.

Impressum-Regression fix (#18):
- _fetch_text(url, doc_type): cookie/dse/social_media -> max_documents=1
  (CMP capture authoritative, sub-pages dilute). Other types -> =3
  (Impressum needs Versicherungsvermittler, Aufsicht, Berufsrecht sub-
  pages). 15s networkidle bail keeps timing safe.

ODR/Verbraucherstreitbeilegung filter (#19):
- _apply_profile_filter: when profile.needs_odr=True (B2C), override the
  check's default B2B-oriented hint with action-oriented B2C guidance
  pointing at Art. 14 EU-VO 524/2013 + §36 VSBG. Previously the check
  contradicted itself: 'profile says B2C' + hint 'only relevant for B2C
  online vendors'.

Registergericht regex (#20):
- impressum_checks.py: accept colon/dot/dash between keyword and city
  (BMW writes 'registergericht: münchen hrb 42243'). Add 'sitz und
  registergericht: X' as separate pattern.

Industry detection (#21):
- business_profiler.py: 'automotive' keywords broadened (antriebs,
  motor, leasing, werkstatt, probefahrt, plus brand names BMW/Mercedes/
  Audi/VW/Porsche/Opel). 'it_services' keywords narrowed — software/
  cloud/hosting are mentioned in every privacy policy and were biasing
  the result toward IT for any tech-aware company.
2026-05-17 01:03:58 +02:00

320 lines
15 KiB
Python

"""
Impressum checks — §5 TMG / §18 MStV.
Level 1: Pflichtangabe erwaehnt?
Level 2: Pflichtangabe korrekt/vollstaendig?
Checks mit severity "INFO" sind kontextabhaengig — sie werden nur
als Hinweis angezeigt, nicht als Finding gewertet. Der Pruefer muss
selbst entscheiden ob sie fuer das geprueefte Unternehmen relevant sind.
"""
IMPRESSUM_CHECKLIST = [
# ── L1: Name des Anbieters ────────────────────────────────────────
{
"id": "name",
"label": "Name des Anbieters",
"level": 1, "parent": None,
"patterns": [
r"(?:gmbh|ag|e\.v\.|ohg|kg|gbr|ug|mbh|inc|ltd)",
r"firma", r"unternehmen",
],
"severity": "HIGH",
"hint": "§5(1) Nr.1 TMG: Vollstaendiger Firmenname MIT Rechtsform (z.B. 'Muster GmbH', nicht nur 'Muster'). Bei Einzelunternehmen: Vor- und Nachname plus ggf. Geschaeftsbezeichnung. Haeufiger Abmahngrund: Nur Markenname ohne juristische Person.",
},
# ── L1: Anschrift ─────────────────────────────────────────────────
{
"id": "address",
"label": "Anschrift",
"level": 1, "parent": None,
"patterns": [
r"(?:str(?:asse|\.)|weg|platz|allee)\s*\.?\s*\d",
r"d-\d{5}", r"\d{5}\s+\w+",
],
"severity": "HIGH",
"hint": "§5(1) Nr.1 TMG verlangt eine ladungsfaehige Anschrift fuer Klagezustellungen. Postfach, c/o-Adresse oder nur Ortsangabe genuegen laut BGH (I ZR 228/03) nicht. Erforderlich: Strasse + Hausnummer + PLZ + Ort.",
},
{
"id": "address_zip_city",
"label": "PLZ + Ort vorhanden",
"level": 2, "parent": "address",
"patterns": [
r"(?:d[\-\s]?)?\d{5}\s+[a-z\u00c0-\u017e]\w{2,}",
],
"severity": "MEDIUM",
"hint": "Ohne PLZ und Ort ist die Anschrift nicht ladungsfaehig und damit unvollstaendig i.S.d. §5 TMG.",
},
{
"id": "address_street_number",
"label": "Strasse + Hausnummer vorhanden",
"level": 2, "parent": "address",
"patterns": [
r"[a-z\u00c0-\u017e]\w+(?:str|stra(?:ss|ß)e|weg|platz|allee|gasse|ring|damm|ufer)\s*\.?\s*\d+",
r"\w+\s+(?:str|stra(?:ss|ß)e|weg|platz|allee)\s*\.?\s*\d+",
],
"severity": "MEDIUM",
"hint": "Strasse + Hausnummer fehlen oder sind unvollstaendig. Ohne Hausnummer keine Zustellbarkeit — klassischer Abmahngrund.",
},
# ── L1: Kontaktdaten ──────────────────────────────────────────────
{
"id": "contact",
"label": "Kontaktdaten (E-Mail + Telefon)",
"level": 1, "parent": None,
"patterns": [
r"(?:e-?mail|mail).*@", r"telefon|phone|tel\.",
r"\+?\d[\d\s/\-\(\)]{8,}",
],
"severity": "HIGH",
"hint": "§5(1) Nr.2 TMG verlangt Angaben fuer 'schnelle elektronische Kontaktaufnahme und unmittelbare Kommunikation': E-Mail ist Pflicht. EuGH (C-298/17): Telefon nicht zwingend, aber ein zweiter unmittelbarer Kanal (Telefon, Fax oder Chat) ist erforderlich.",
},
{
"id": "contact_email_format",
"label": "E-Mail-Adresse im korrekten Format",
"level": 2, "parent": "contact",
"patterns": [
r"[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}",
],
"severity": "MEDIUM",
"hint": "E-Mail-Adresse muss direkt im Impressum als Text sichtbar sein. Ein reines Kontaktformular genuegt laut OLG Hamm (4 U 59/20) NICHT.",
},
{
"id": "contact_phone_format",
"label": "Telefonnummer vorhanden",
"level": 2, "parent": "contact",
"patterns": [
r"(?:tel(?:efon)?|phone|fon)\s*[.:]\s*[\+\d][\d\s/\-\(\)]{6,}",
r"\+49\s*[\d\s/\-\(\)]{8,}",
r"0\d{2,4}\s*[/\-\s]\s*[\d\s\-]{4,}",
r"(?:telefon|tel\.?|phone|fon)\s+\d[\d\s/\-]{6,}",
],
"severity": "MEDIUM",
"hint": "Telefonnummer mit Vorwahl angeben (z.B. '+49 30 12345678' oder '0761 / 489 809 01'). Falls kein Telefon: Ein alternativer unmittelbarer Kommunikationskanal ist laut EuGH (C-298/17) noetig.",
},
# ── L1: Handelsregister ───────────────────────────────────────────
{
"id": "register",
"label": "Handelsregister / Registernummer",
"level": 1, "parent": None,
"patterns": [
r"(?:handelsregister|hrb|hra|registergericht|amtsgericht)",
r"register.*(?:nr|nummer)",
r"\bag\s+[a-z\u00c0-\u017e]\w+",
],
"severity": "MEDIUM",
"hint": "§5(1) Nr.4 TMG: Bei Eintragung im Handels-, Vereins-, Partnerschafts- oder Genossenschaftsregister muessen Registergericht UND Registernummer angegeben werden.",
},
{
"id": "register_court",
"label": "Registergericht benannt (Amtsgericht X)",
"level": 2, "parent": "register",
"patterns": [
# "Amtsgericht <Stadt>" or "Registergericht <Stadt>"
# Allow colon/dot/dash between keyword and city (BMW writes
# "registergericht: m\u00fcnchen hrb 42243").
r"(?:amtsgericht|registergericht)[\s:\.\-,]+[a-zA-Z\u00c0-\u017e]\w+",
# "AG <Stadt>" short form
r"\bag\s+[a-zA-Z\u00c0-\u017e]\w+",
# "Handelsregister AG/Amtsgericht <Stadt>"
r"(?:handelsregister|register)\s+(?:ag|amtsgericht)\s+\w+",
# "Sitz und Registergericht: M\u00fcnchen" \u2014 BMW pattern
r"sitz\s+und\s+registergericht[\s:\.\-,]+[a-zA-Z\u00c0-\u017e]\w+",
],
"severity": "LOW",
"hint": "Registergericht benennen (z.B. 'Amtsgericht Freiburg' oder 'AG Freiburg'). Beides ist korrekt.",
},
{
"id": "register_number",
"label": "Registernummer (HRB/HRA + Nummer)",
"level": 2, "parent": "register",
"patterns": [
r"(?:hrb|hra)\s*\d+",
],
"severity": "LOW",
"hint": "Registernummer im Format 'HRB 12345' (Kapitalgesellschaften) oder 'HRA 12345' (Personengesellschaften) angeben.",
},
# ── L1: USt-IdNr ──────────────────────────────────────────────────
{
"id": "vat",
"label": "USt-IdNr.",
"level": 1, "parent": None,
"patterns": [
r"ust[\s.-]*id", r"umsatzsteuer[\s-]*id",
r"umsatzsteuer.*identifikation",
r"vat[\s.-]*id", r"de\s*\d{3}\s*\d{3}\s*\d{3}",
],
"severity": "MEDIUM",
"hint": "§5(1) Nr.6 TMG: Die USt-IdNr. muss angegeben werden, sofern vorhanden. Die Steuernummer ist KEIN Ersatz.",
},
{
"id": "vat_de_format",
"label": "USt-IdNr. im Format DE + 9 Ziffern",
"level": 2, "parent": "vat",
"patterns": [
r"de\s*\d{3}\s*\d{3}\s*\d{3}",
],
"severity": "LOW",
"hint": "Deutsche USt-IdNr.: 'DE' + exakt 9 Ziffern (z.B. DE123456789). Validierung: https://evatr.bff-online.de/",
},
# ── L1: Vertretungsberechtigte ────────────────────────────────────
{
"id": "representative",
"label": "Vertretungsberechtigte",
"level": 1, "parent": None,
"patterns": [
r"vertretungsberechtigt",
r"gesch(?:ae|ä)ftsf(?:ue|ü)hr",
r"vorstand", r"inhaber",
],
"severity": "MEDIUM",
"hint": "§5(1) Nr.1 TMG: Bei juristischen Personen muss der/die Vertretungsberechtigte(n) namentlich benannt werden.",
},
{
"id": "representative_person",
"label": "Name der vertretungsberechtigten Person",
"level": 2, "parent": "representative",
"patterns": [
r"(?:gesch(?:ae|ä)ftsf(?:ue|ü)hr\w*|vorstand|inhaber)\s*:?\s*[a-zA-Z\u00c0-\u017e]",
r"(?:vertreten\s+durch|repr(?:ae|ä)sentiert)\s*:?\s*[a-zA-Z\u00c0-\u017e]",
r"(?:gesch(?:ae|ä)ftsf(?:ue|ü)hrung)\s*:?\s*(?:dr\.?\s+|prof\.?\s+)?[a-zA-Z\u00c0-\u017e]",
],
"severity": "LOW",
"hint": "Voller Vor- und Nachname mit Funktionsbezeichnung erforderlich (z.B. 'Geschaeftsfuehrung: Dr. Max Mustermann').",
},
# ── Kontextabhaengige Checks (INFO — nur Hinweis, kein Finding) ──
{
"id": "editorial_visdp",
"label": "V.i.S.d.P. / Redaktionell Verantwortlicher (§18 MStV)",
"level": 1, "parent": None,
"patterns": [
r"v\.?\s*i\.?\s*s\.?\s*d\.?\s*p",
r"(?:redaktionell|inhaltlich)\s+verantwortlich",
r"§\s*18\s+(?:abs\.?\s*\d+\s+)?m(?:edien)?st(?:aat)?v",
r"verantwortlich\w*\s+i\.?\s*s\.?\s*(?:d\.?\s*)?v\.?",
],
"severity": "INFO",
"hint": "Nur relevant wenn die Website journalistisch-redaktionelle Inhalte hat (Blog, Ratgeber, News, Fachartikel). Reine Unternehmensseiten ohne redaktionelle Inhalte benoetigen keinen V.i.S.d.P. Pruefen Sie, ob die Website einen Blog oder Ratgeber-Bereich hat.",
},
{
"id": "dispute_resolution",
"label": "Verbraucherstreitbeilegung / OS-Plattform",
"level": 1, "parent": None,
"patterns": [
r"verbraucherstreitbeilegung|streitschlichtung",
r"(?:os|odr)[\-\s]plattform",
r"ec\.europa\.eu.*odr",
r"vsbg|verbraucherstreitbeilegungsgesetz",
r"alternative\s+streitbeilegung",
],
"severity": "INFO",
"hint": "Nur relevant fuer B2C-Online-Haendler die Waren oder Dienstleistungen an Verbraucher verkaufen. B2B-Unternehmen ohne Verbrauchergeschaeft sind von §36 VSBG und der ODR-Verordnung nicht betroffen. Pruefen Sie, ob das Unternehmen B2C-Geschaeft betreibt.",
},
{
"id": "regulated_profession",
"label": "Berufsrechtliche Angaben (§5(1) Nr.5 TMG)",
"level": 1, "parent": None,
"patterns": [
r"(?:rechtsanwalt|anwalt|notar|steuerberater|wirtschaftspr(?:ue|ü)fer|arzt|(?:ae|ä)rzt|zahnarzt|apotheker|architekt|ingenieur|psychotherapeut|heilpraktiker)",
r"(?:kammer|berufsordnung|berufsrecht|standesrecht|zulassung)",
r"(?:(?:ae|ä)rztekammer|rechtsanwaltskammer|steuerberaterkammer|architektenkammer|ingenieurkammer)",
],
"severity": "INFO",
"hint": "Nur relevant fuer reglementierte Berufe (Aerzte, Anwaelte, Steuerberater, Architekten, Apotheker). Falls das Unternehmen keinen reglementierten Beruf ausueebt, ist dieser Punkt nicht zutreffend.",
},
{
"id": "profession_chamber",
"label": "Zustaendige Kammer benannt",
"level": 2, "parent": "regulated_profession",
"patterns": [
r"(?:(?:ae|ä)rztekammer|rechtsanwaltskammer|steuerberaterkammer|architektenkammer|ingenieurkammer|apothekerkammer)",
r"(?:mitglied|zugelassen|eingetragen)\s+(?:bei|in|der)\s+(?:der\s+)?(?:\w+)?kammer",
],
"severity": "LOW",
"hint": "Zustaendige Kammer mit vollem Namen und Sitz nennen (z.B. 'Rechtsanwaltskammer Muenchen').",
},
{
"id": "profession_title",
"label": "Berufsbezeichnung + Verleihungsstaat",
"level": 2, "parent": "regulated_profession",
"patterns": [
r"berufsbezeichnung|gesetzliche\s+berufsbezeichnung",
r"verliehen\s+in|verleihungsstaat",
r"(?:rechtsanwalt|steuerberater|arzt|architekt)\s*(?:\(|,)\s*(?:deutschland|bundesrepublik)",
],
"severity": "LOW",
"hint": "Berufsbezeichnung und Staat der Verleihung angeben.",
},
{
"id": "profession_regulations",
"label": "Berufsrechtliche Regelungen + Zugang",
"level": 2, "parent": "regulated_profession",
"patterns": [
r"(?:berufsordnung|brao|bora|steuerberatungsgesetz|stberg|bundes(?:ae|ä)rzteordnung|heilberufe|mbo|architekteng)",
r"berufsrecht|standesrecht|berufsrechtliche\s+regelung",
],
"severity": "LOW",
"hint": "Berufsrechtliche Regelungen nennen und Link zum Volltext bereitstellen.",
},
{
"id": "share_capital",
"label": "Stammkapital / Grundkapital (GmbH/AG/UG)",
"level": 1, "parent": None,
"patterns": [
r"stammkapital|grundkapital|haftkapital",
r"(?:kapital|einlage)\s*:?\s*(?:eur|euro|\u20ac)\s*[\d\.,]+",
r"[\d\.,]+\s*(?:eur|euro|\u20ac)\s*(?:stammkapital|grundkapital)",
],
"severity": "INFO",
"hint": "§35a GmbHG verlangt die Angabe des Stammkapitals auf Geschaeftsbriefen. Ob dies auch fuer Websites gilt, ist umstritten. In der Praxis wird es selten beanstandet. Bei UG (haftungsbeschraenkt) ist die Angabe empfehlenswert, da das geringe Stammkapital fuer Geschaeftspartner relevant ist.",
},
{
"id": "supervisory_authority",
"label": "Aufsichtsbehoerde (genehmigungspflichtige Taetigkeiten)",
"level": 1, "parent": None,
"patterns": [
r"aufsichtsbeh(?:oe|ö)rde|genehmigungsbeh(?:oe|ö)rde|zulassungsbeh(?:oe|ö)rde",
r"gewerbeaufsicht|gewerbeamt|ordnungsamt",
r"(?:zugelassen|genehmigt|erlaubt)\s+(?:durch|von)\s+(?:der|dem|die)",
r"bafin|§\s*34[cdf]\s+gewo",
],
"severity": "INFO",
"hint": "Nur relevant bei genehmigungspflichtigen Taetigkeiten: Immobilienmakler (§34c GewO), Finanzanlagenvermittler (§34f), Versicherungsvermittler (§34d), Gastronomie, Bewachungsgewerbe. Falls das Unternehmen keine solche Taetigkeit ausueebt, ist dieser Punkt nicht zutreffend.",
},
{
"id": "professional_insurance",
"label": "Berufshaftpflichtversicherung (DL-InfoV §2(1) Nr.11)",
"level": 1, "parent": None,
"patterns": [
r"berufshaftpflicht|haftpflichtversicherung|pflichtversicherung",
r"(?:versicherer|versicherung)\s*:?\s*[a-zA-Z\u00c0-\u017e]",
r"deckungssumme|versicherungsschutz|geltungsbereich",
],
"severity": "INFO",
"hint": "Nur relevant bei gesetzlicher Pflichtversicherung (Aerzte, Anwaelte, Architekten, Steuerberater, Makler). Falls das Unternehmen keine Pflichtversicherung benoetigt, ist dieser Punkt nicht zutreffend.",
},
{
"id": "illegal_disclaimer",
"label": "Rechtswidriger Haftungsausschluss fuer Links",
"level": 1, "parent": None,
"patterns": [
r"haftungsausschluss|disclaimer|haftung\s+f(?:ue|ü)r\s+(?:links|inhalte)",
r"keine\s+(?:gew(?:ae|ä)hr|haftung|verantwortung)\s+f(?:ue|ü)r\s+(?:externe|verlinkte|fremde)",
r"distanzier|macht\s+sich\s+(?:nicht|kein)\s+(?:zu\s+eigen|verantwortlich)",
],
"severity": "LOW",
"hint": "Der klassische Link-Disclaimer ('Wir distanzieren uns von verlinkten Inhalten') ist seit BGH (I ZR 317/01) rechtlich wirkungslos. Empfehlung: Entfernen Sie pauschale Disclaimer — sie schuetzen nicht und koennen kontraproduktiv sein.",
},
]