"""Generiert src/services/umlaut_dict.json aus hunspell-de-de. Aufruf (auf dem Monitor-Server): cd /home/claude-dev/AegisSight-Monitor python3 scripts/build_umlaut_dict.py Voraussetzungen: - hunspell-de-de (liefert /usr/share/hunspell/de_DE.dic + de_DE.aff) - hunspell-tools (liefert /usr/bin/unmunch) Ablauf: 1. unmunch rollt alle Flexionsformen aus dem hunspell-Dict aus 2. Wir filtern Woerter mit echten Umlauten (ä, ö, ü, ß) 3. Wir generieren fuer jedes Wort die Umschreibungs-Form (ae/oe/ue/ss) 4. Mehrdeutigkeits-Check: Wenn die Umschreibungs-Form selbst ein gueltiges deutsches Wort ist (z. B. "dass" vs "daß"), skippen 5. Ausgabe als alphabetisch sortiertes JSON (diff-freundlich) """ import json import locale import os import subprocess import sys DIC_PATH = "/usr/share/hunspell/de_DE.dic" AFF_PATH = "/usr/share/hunspell/de_DE.aff" UNMUNCH_BIN = "/usr/bin/unmunch" OUTPUT_PATH = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "src", "services", "umlaut_dict.json", ) UMLAUT_MAP = ( ("ä", "ae"), ("ö", "oe"), ("ü", "ue"), ("ß", "ss"), ("Ä", "Ae"), ("Ö", "Oe"), ("Ü", "Ue"), ) def to_ascii_form(word: str) -> str: """Konvertiert ein Wort mit Umlauten in seine Umschreibungs-Form.""" out = word for uml, asc in UMLAUT_MAP: out = out.replace(uml, asc) return out def has_umlaut(word: str) -> bool: return any(ch in word for ch in "äöüßÄÖÜ") def run_unmunch() -> set: """Fuehrt unmunch aus und gibt die Menge aller hunspell-Woerter zurueck.""" env = os.environ.copy() # unmunch arbeitet mit Latin-1 als Voreinstellung; das .dic/.aff in de_DE # ist aber UTF-8 (siehe SET UTF-8 im .aff). Wir setzen die Locale explizit. env["LC_ALL"] = "C.UTF-8" result = subprocess.run( [UNMUNCH_BIN, DIC_PATH, AFF_PATH], capture_output=True, check=True, env=env, ) raw = result.stdout.decode("utf-8", errors="replace") words = set() for line in raw.splitlines(): w = line.strip() if not w or w.startswith("#"): continue words.add(w) return words def build_mapping(all_words: set) -> tuple[dict, int, int]: """Baut das Umlaut-Ersetzungs-Mapping. Rueckgabe: (mapping, skipped_ambiguous, words_with_umlaut) """ mapping = {} skipped_ambiguous = 0 words_with_umlaut = 0 for word in all_words: if not has_umlaut(word): continue words_with_umlaut += 1 ascii_form = to_ascii_form(word) # Mehrdeutigkeits-Check: Umschreibung ist selbst ein gueltiges Wort? if ascii_form in all_words: skipped_ambiguous += 1 continue # Standardfall: Mapping Umschreibung -> Umlaut-Form mapping[ascii_form] = word # Zusaetzlich Capitalize-Variante erzeugen (wenn anders als Original) if ascii_form[:1].islower(): cap_ascii = ascii_form[:1].upper() + ascii_form[1:] cap_umlaut = word[:1].upper() + word[1:] if cap_ascii != ascii_form and cap_ascii not in all_words: mapping[cap_ascii] = cap_umlaut return mapping, skipped_ambiguous, words_with_umlaut def sanity_spot_check(mapping: dict) -> None: """Prueft ob einige typische Testfaelle korrekt im Mapping abgebildet sind.""" expected_in = [ "oeffnung", "Oeffnung", "strasse", "Strasse", "fuer", "Fuer", "ueber", "Ueber", "koennen", "Koennen", "muessen", "Muessen", "moeglich", "Moeglich", "schliessen", "Schliessen", "aussenminister", "Aussenminister", "praesident", "Praesident", "buerger", "Buerger", "zurueck", "Zurueck", "fuehren", "Fuehren", ] expected_not_in = [ "dass", "Dass", # moderne Form gueltig "masse", "Masse", # Bedeutungsunterschied zu "Masse"/"Maße" "busse", "Busse", # Bedeutungsunterschied zu "Busse"/"Buße" ] missing = [w for w in expected_in if w not in mapping] wrong = [w for w in expected_not_in if w in mapping] print("Sanity-Check:") print(f" Erwartete Eintraege gefunden: {len(expected_in) - len(missing)}/{len(expected_in)}") if missing: print(f" FEHLEND: {missing}") print(f" Erwartete Ausschluesse korrekt: {len(expected_not_in) - len(wrong)}/{len(expected_not_in)}") if wrong: print(f" FAELSCHLICH DRIN: {wrong}") def main(): if not os.path.exists(DIC_PATH): print(f"FEHLER: {DIC_PATH} nicht gefunden. Paket hunspell-de-de installiert?", file=sys.stderr) sys.exit(1) if not os.path.exists(UNMUNCH_BIN): print(f"FEHLER: {UNMUNCH_BIN} nicht gefunden. Paket hunspell-tools installiert?", file=sys.stderr) sys.exit(1) print(f"Lese hunspell-Dict via {UNMUNCH_BIN} ...") all_words = run_unmunch() print(f" {len(all_words)} hunspell-Wortformen geladen") print("Baue Umlaut-Ersetzungs-Mapping ...") mapping, skipped, umlaut_words = build_mapping(all_words) print(f" {umlaut_words} Woerter mit Umlaut gefunden") print(f" {skipped} mehrdeutige Formen uebersprungen (z.B. dass/daß)") print(f" {len(mapping)} Eintraege im finalen Mapping") sanity_spot_check(mapping) print(f"\nSchreibe {OUTPUT_PATH} ...") os.makedirs(os.path.dirname(OUTPUT_PATH), exist_ok=True) # Alphabetisch sortiert (diff-freundlich) sorted_mapping = dict(sorted(mapping.items(), key=lambda kv: kv[0])) with open(OUTPUT_PATH, "w", encoding="utf-8") as f: json.dump(sorted_mapping, f, ensure_ascii=False, indent=None, separators=(",", ":")) size_mb = os.path.getsize(OUTPUT_PATH) / (1024 * 1024) print(f" {size_mb:.2f} MB geschrieben") print("Fertig.") if __name__ == "__main__": main()