"""Firmenstammdaten: Normalisierung und Suche.""" import re import unicodedata from sqlalchemy import or_, select from sqlalchemy.ext.asyncio import AsyncSession from app.models import Merchant # Umlaute und ß werden ausgeschrieben, damit "Müller" und "Mueller" zusammenfallen. _TRANSLITERATIONS = { "ä": "ae", "ö": "oe", "ü": "ue", "ß": "ss", "æ": "ae", "ø": "oe", "å": "aa", } # Rechtsformen und Zusätze, die für die Wiedererkennung einer Marke irrelevant sind. _LEGAL_SUFFIXES = ( "gmbh co kg", "gmbh", "ag", "kg", "ohg", "ug", "se", "ev", "mbh", "inc", "llc", "ltd", "plc", "sa", "bv", "nv", ) def normalize_name(name: str) -> str: """Kleinschreibung ohne Sonderzeichen – Grundlage für Eindeutigkeit und Logosuche.""" text = name.strip().lower() for source, target in _TRANSLITERATIONS.items(): text = text.replace(source, target) # Verbleibende Akzente auflösen (é -> e). text = unicodedata.normalize("NFKD", text) text = "".join(char for char in text if not unicodedata.combining(char)) # Punkte fallen ersatzlos weg, damit "B.V." und "e.V." als ein Wort erkannt werden. text = text.replace(".", "") text = re.sub(r"[^a-z0-9]+", " ", text).strip() for suffix in _LEGAL_SUFFIXES: if text.endswith(f" {suffix}"): text = text[: -len(suffix) - 1].strip() break return re.sub(r"\s+", "", text) def domain_from_name(name: str) -> str | None: """Rät eine Domain aus dem Firmennamen – nur als letzter Anhaltspunkt gedacht.""" slug = normalize_name(name) if not slug or len(slug) < 3: return None return f"{slug}.com" async def find_by_normalized_name(session: AsyncSession, name: str) -> Merchant | None: stmt = select(Merchant).where(Merchant.normalized_name == normalize_name(name)) return (await session.execute(stmt)).scalar_one_or_none() def search_statement(query: str | None): """Abfrage über Name, normalisierten Namen und Domain.""" stmt = select(Merchant).order_by(Merchant.name) if query: pattern = f"%{query.strip().lower()}%" stmt = stmt.where( or_( Merchant.normalized_name.like(f"%{normalize_name(query)}%"), Merchant.name.ilike(pattern), Merchant.domain.ilike(pattern), ) ) return stmt