Dateien
AegisSight-Monitor/scripts/bootstrap_umlaut_repair.py
claude-dev 15a650bfc9 QC: Umlaut-Normalisierung + Prompt-Ergaenzung
Drei unabhaengige Schutzschichten gegen falsche Umschreibungen
(ae/oe/ue/ss statt ä/ö/ü/ß) im Lagebild:

1. Prompt-Ergaenzung in INCREMENTAL_ANALYSIS_PROMPT_TEMPLATE und
   INCREMENTAL_BRIEFING_PROMPT_TEMPLATE (analyzer.py): explizite
   Priorisierung, dass die Regel "echte UTF-8-Umlaute" Vorrang vor
   "bestehende Formulierungen beibehalten" hat. Adressiert den Fall,
   dass Claude beim inkrementellen Update Altlasten weitertraegt.

2. Deterministische Normalisierung in post_refresh_qc.py:
   - normalize_german_umlauts(text) - Regex mit Wortgrenzen, case-
     preserving, Whitelist-tauglich, ~140 Eintraege im Woerterbuch
     abgeleitet aus den 140 Hard-Hits in Lage #6
   - normalize_umlaut_fields(db, incident_id) - laedt summary und
     latest_developments, normalisiert, schreibt nur bei Aenderungen
     zurueck (idempotent)
   - Eingehaengt in run_post_refresh_qc() nach dem Location-Check,
     Fehler stoppen die Pipeline nicht (identisches Muster wie
     bestehende Checks)

3. scripts/bootstrap_umlaut_repair.py - Einmal-Skript zur
   Bestandsbereinigung der bereits gespeicherten summary-Felder.
   Idempotent. Beim initialen Lauf auf Produktiv-DB: 14 Lagen
   aktualisiert, 431 Ersetzungen insgesamt, Lage #6 von 140 auf
   15 Rest-Treffer reduziert.

Whitelist (leer): aktuell kein Konflikt zwischen deutschen Ziel-
Woertern und englischen Fremdwoertern. Kann bei Bedarf erweitert
werden ohne Schema-Aenderung.

Verifikation:
- py_compile OK fuer alle drei Dateien
- Service-Restart ohne Errors
- Unit-Tests: positive Faelle ("Oeffnung der Strasse" -> 4 Ersetzungen),
  Whitelist ("Boeing liefert Business-Access" -> 0 Ersetzungen),
  Komposita ("Wasserstrasse", "Parlamentspraesident") korrekt
- Bootstrap 2x ausgefuehrt (erster Lauf 288 Ersetzungen, zweiter 143
  nach Dict-Erweiterung), kumulativ 431

Architektur bleibt dormant ohne Daten-Altlasten: wenn keine Lage
Umschreibungen enthaelt, arbeitet normalize_umlaut_fields in <1ms
und schreibt nichts. Kein Overhead im Refresh-Pfad.
2026-04-18 14:00:00 +00:00

79 Zeilen
2.4 KiB
Python

"""Einmal-Repair: normalisiert Umlaute in summary und latest_developments
aller aktiven Lagen deterministisch (deutsche Umschreibungs-Form -> echte Umlaute).
Idempotent: mehrfaches Ausfuehren hat keinen zusaetzlichen Effekt, wenn
bereits normalisierte Texte vorliegen.
Aufruf (auf dem Monitor-Server):
cd /home/claude-dev/AegisSight-Monitor/src
python3 ../scripts/bootstrap_umlaut_repair.py
"""
import sqlite3
import sys
import os
# Sicherstellen, dass src/ im PYTHONPATH ist, damit services/post_refresh_qc importiert werden kann
_here = os.path.dirname(os.path.abspath(__file__))
_src = os.path.abspath(os.path.join(_here, "..", "src"))
if _src not in sys.path:
sys.path.insert(0, _src)
from services.post_refresh_qc import normalize_german_umlauts # noqa: E402
DB_PATH = "/home/claude-dev/osint-data/osint.db"
def main():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
try:
c = conn.cursor()
rows = c.execute(
"SELECT id, title, summary, latest_developments FROM incidents "
"WHERE status IN ('active', 'archived') ORDER BY id"
).fetchall()
total_summary = 0
total_dev = 0
updated = 0
for r in rows:
iid = r["id"]
title = r["title"] or ""
summary_orig = r["summary"] or ""
dev_orig = r["latest_developments"] or ""
new_summary, n_s = normalize_german_umlauts(summary_orig)
new_dev, n_d = normalize_german_umlauts(dev_orig)
if n_s == 0 and n_d == 0:
continue
c.execute(
"UPDATE incidents SET summary = ?, latest_developments = ? WHERE id = ?",
(
new_summary if n_s > 0 else summary_orig,
new_dev if n_d > 0 else dev_orig,
iid,
),
)
updated += 1
total_summary += n_s
total_dev += n_d
print(
f" Lage #{iid:>3} {title[:50]:50} "
f"summary: {n_s:>4} | latest_developments: {n_d:>3}"
)
conn.commit()
print()
print(f"Ergebnis: {updated} Lagen aktualisiert. "
f"{total_summary} Ersetzungen in summary, {total_dev} in latest_developments "
f"(gesamt {total_summary + total_dev}).")
finally:
conn.close()
if __name__ == "__main__":
main()