fix(b9): clean entity names in multi-entity-impressum (GT IMPRESSUM-001)

Der Multi-Entity-Check fängt Elli's USt-IdNr-Lücke (VW Group Charging
GmbH hat keine, Elli Mobility GmbH hat eine), aber Entity-Namen waren
mit Header-Noise verunreinigt:

  'Impressum\n\nVolkswagen Group Charging GmbH'
  'eco\n\nElli Mobility GmbH'

Behoben:
  - _ENTITY_PAT lässt nur Space im Namen zu (kein \s/\n mehr)
  - _clean_entity_name() trimmt Header-Worte (Impressum, Anbieter, ...)
    und nimmt nur die letzte Zeile vor Legal-Form-Suffix
  - 11 neue Tests, davon einer mit Elli-like Impressum als
    Charakterisierungs-Test

Damit ist die finale Finding-Ausgabe für Audit-Reports lesbar
('Fehlt bei: Volkswagen Group Charging GmbH') statt verunreinigt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-07 00:08:18 +02:00
parent b9baa8c603
commit 8b9cad88ae
2 changed files with 128 additions and 2 deletions
@@ -23,11 +23,17 @@ import re
logger = logging.getLogger(__name__)
_ENTITY_PAT = re.compile(
r"([A-ZÄÖÜ][\w\-\&\s]{1,50}?\s+(?:GmbH|AG|UG|KG|SE|"
r"([A-ZÄÖÜ][\w\-\& ]{1,50}?\s+(?:GmbH|AG|UG|KG|SE|"
r"e\.V\.|GbR|OHG|Limited|Ltd|LLC))",
re.IGNORECASE,
)
_NAME_NOISE_PAT = re.compile(
r"^(?:Impressum|Anbieter|Anbieterkennzeichnung|Diensteanbieter|"
r"Verantwortlich(?:er)?|Kontakt|Adresse|@\S+|.+@.+)\s*[:|\-]?\s*",
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)"
@@ -36,6 +42,17 @@ _GF_PAT = re.compile(r"(?:Geschäftsführer|Vertretungsberechtigt|"
r"vertreten\s+durch)\s*[:.\s]+", re.IGNORECASE)
def _clean_entity_name(raw: str) -> str:
"""Strip leading header noise + collapse whitespace."""
name = raw.strip()
# If the match spans multiple lines (regex captured a header before
# the actual company name), keep only the last line.
if "\n" in name:
name = name.rsplit("\n", 1)[-1].strip()
name = _NAME_NOISE_PAT.sub("", name).strip()
return re.sub(r"\s+", " ", name)
def _slice_entities(text: str) -> list[tuple[str, str]]:
"""Return [(entity_name, text_slice)] for each detected entity."""
matches = list(_ENTITY_PAT.finditer(text))
@@ -45,7 +62,7 @@ def _slice_entities(text: str) -> 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]))
slices.append((_clean_entity_name(m.group(1)), text[start:end]))
return slices