"""Deutsch-affine Textnormalisierung für FTS5-Index und -Anfragen. Konvention (einmalig, konsistent): lowercase, Diakritika via NFKD entfernen (ä→a, ü→u), ß→ss. Dies gilt für die FTS-Spalte `norm` und die Query gleich. Die ASCII-Slug-Konvention der Wissensbasis (Umlaute "fallen") betrifft nur `topic`/`tags`/Dateinamen, nicht die Volltextsuche. """ from __future__ import annotations import re import unicodedata # Stopwörter in normalisierter Form (nach Fold), klein halten. STOPWORDS = frozenset( """der die das und oder ein eine einen einem einer eines den dem des ist im in von fur mit auf zu zum zur an am beim wie was wann wird werden kann muss sind hat nicht man als auch aus bei sein ihre ihr es sie er doch noch nur schon sehr mehr hier da durch fuer wird""".split() ) _TOKEN_RE = re.compile(r"[a-z0-9]+") def normalize_text(s: str) -> str: """Lowercase, Diakritika entfernen (NFKD), ß→ss.""" s = unicodedata.normalize("NFKD", s.casefold()) s = "".join(c for c in s if unicodedata.category(c) != "Mn") return s.replace("ß", "ss") def tokenize(s: str) -> list[str]: return _TOKEN_RE.findall(normalize_text(s)) def fts_query(question: str, min_len: int = 2) -> str: """OR-verknüpfte FTS5-Phrasen aus normalisierten Termen; '' wenn leer.""" terms: list[str] = [] seen: set[str] = set() for t in tokenize(question): if len(t) >= min_len and t not in STOPWORDS and t not in seen: seen.add(t) terms.append(t) return " OR ".join(f'"{t}"' for t in terms)