"""B9 — Multi-Entity-Impressum-Check. Findings, wenn ein Impressum mehrere Entitäten (mehrere GmbH/AG/UG) nennt, aber Pflichtangaben nur bei einer davon vollständig sind. Konkreter Elli-Pattern (GT IMPRESSUM-001): - Entity 1: "Elli Mobility GmbH ... USt-IdNr DE814424009 ..." - Entity 2: "VW Group Charging GmbH ... [keine USt-IdNr] ..." → USt-IdNr fehlt bei Entity 2. Heuristik: 1. Entitäten erkennen: jede Match auf " (GmbH|AG|UG|KG|SE)" als Entity-Boundary; Text-Slice von dort bis zur nächsten Entity. 2. Pro Entity prüfen: USt-IdNr, Handelsregister, Vertretungsberechtigte. 3. Wenn Entity N ein Feld nennt, das Entity M nicht hat → MEDIUM. """ from __future__ import annotations import logging import re logger = logging.getLogger(__name__) _ENTITY_PAT = re.compile( r"([A-ZÄÖÜ][\w\-\&\s]{1,50}?\s+(?:GmbH|AG|UG|KG|SE|" r"e\.V\.|GbR|OHG|Limited|Ltd|LLC))", re.IGNORECASE, ) _USTID_PAT = re.compile(r"\b(?:USt-?Id(?:Nr)?\.?|VAT(?:-?Id)?)\s*[:.\s]\s*" r"(DE\d{8,10}|[A-Z]{2}\d{6,12})", re.IGNORECASE) _HR_PAT = re.compile(r"\b(?:HR[BA]|Handelsregister|Registergericht)" r"\s*[:.\s]*([\w\s\d\-/]{4,80})", re.IGNORECASE) _GF_PAT = re.compile(r"(?:Geschäftsführer|Vertretungsberechtigt|" r"vertreten\s+durch)\s*[:.\s]+", re.IGNORECASE) def _slice_entities(text: str) -> list[tuple[str, str]]: """Return [(entity_name, text_slice)] for each detected entity.""" matches = list(_ENTITY_PAT.finditer(text)) if len(matches) < 2: return [] slices: list[tuple[str, str]] = [] for i, m in enumerate(matches): start = m.start() end = matches[i + 1].start() if i + 1 < len(matches) else len(text) slices.append((m.group(1).strip(), text[start:end])) return slices def check_multi_entity_impressum(state: dict) -> list[dict]: doc_texts = state.get("doc_texts") or {} imp = doc_texts.get("impressum") or "" if not imp: return [] slices = _slice_entities(imp) if not slices: return [] # Compute features per entity features = [] for name, slc in slices: features.append({ "name": name, "ust_id": bool(_USTID_PAT.search(slc)), "hr": bool(_HR_PAT.search(slc)), "gf": bool(_GF_PAT.search(slc)), }) # If ALL share the same flags → no inconsistency findings: list[dict] = [] for field, label in ( ("ust_id", "USt-IdNr."), ("hr", "Handelsregister-Eintrag"), ("gf", "Vertretungsberechtigte"), ): present = [f for f in features if f[field]] missing = [f for f in features if not f[field]] if present and missing and len(present) >= 1: findings.append({ "check_id": f"IMPRESSUM-MULTI-{field.upper()}", "severity": "MEDIUM", "severity_reason": "incomplete", "title": ( f"{label} fehlt bei " f"{len(missing)} von {len(features)} Entitäten" ), "norm": "§ 5 Abs. 1 TMG (Pflichtangabe pro Diensteanbieter)", "entities_present": [f["name"] for f in present], "entities_missing": [f["name"] for f in missing], "action": ( f"{label} im Impressum für " f"{', '.join(f['name'] for f in missing)} ergänzen. " "Pflichtangabe ist pro Diensteanbieter zu erfüllen, " "nicht 'eine reicht für alle'." ), }) if findings: logger.info("B9 multi-entity impressum: %d findings", len(findings)) return findings