# 0011 — Aktuelle-Themen-Dashboard mit PM-Generator | | | |---|---| | **Status** | accepted | | **Datum** | 2026-05-06 | | **Refs** | #170, MEMORY/project_aktuelle_themen.md | ## Kontext Vor diesem ADR existierte kein Bezug zwischen aktuellen Nachrichten (Tagespresse, Bundestagsmeldungen) und den GWÖ-bewerteten Parlamentsanträgen. Die User-Anforderung war: > „Pressemitteilungen zu aktuellen Themen × Anträgen — Matching > warum es ein Thema ist, plus Knopf für PM-Generierung." Initial-Vorschlag war ein RND-Abo (`rnd.de`), aber dort verbietet `robots.txt` per `User-agent: ClaudeBot/GPTBot Disallow: /` die KI-Verarbeitung. Damit waren nur Quellen mit AI-erlaubender Lizenz zulässig: öffentlich-rechtlich (Tagesschau-API) und parlamentarisch (Bundestag-RSS). Zweitens stellte sich die Frage: *Wie* generiert man aus einem politischen Antrag plus einer News-Lage einen brauchbaren PM-Text? Erste qwen-max-Versuche produzierten den GWÖ-Score als Zahl, listeten GWÖ-Werte auf und nutzten Matrix-Codes wie „D2 Würde×Mitarbeitende". Für eine Pressemitteilung an Bürger:innen unbrauchbar. ## Optionen ### Option A — Auto-PM auf jeden News-Match Beim Aggregator-Lauf für jeden News-Antrag-Match einen LLM-Call. - **Pro:** Maximale Abdeckung. - **Kontra:** LLM-Kosten unkontrollierbar, viele PMs sind irrelevant oder qualitativ schwach. Falsches Outcome-Gewicht. ### Option B — Manueller „Generieren"-Knopf pro Match User entscheidet, der Match liefert nur das Match. PM-Erzeugung ist ein expliziter LLM-Call mit qwen-max. - **Pro:** Kostenkontrolle, Qualitätsprüfung pro Stück. - **Kontra:** Volume gering — aber das ist OK, PMs sind editorial. ### Option C — Pre-aggregiertes Cluster, ein PM pro Cluster Cluster die News-Matches und schlage pro Cluster eine PM vor. - **Pro:** Konsolidiert ähnliche News. - **Kontra:** Cluster-Qualität auf den dünnen News-Volumen heute nicht trennscharf genug. ## Entscheidung **Option B** ist Hauptmodus, mit Option-C-Cluster-Anzeige als Übersicht (kein automatischer Cluster-PM). ### Bausteine 1. **News-Aggregator** (`app/news_aggregator.py`): - Cron-Wrapper `scripts/auto-fetch-news.sh` läuft alle 30 min. - Quellen: Tagesschau-API (`/api2u/news?ressort=…`), Bundestag-RSS. Quellen mit AI-Bann (RND, Spiegel, etc.) **strikt ausgeschlossen** siehe MEMORY/project_aktuelle_themen.md. - Ergebnis in `news_articles` (additiv: titel, url, source, ressort, summary, embedded_at, embedding). - Nach Insert: `themen_matching.cache_clear()`. 2. **Match Engine** (`app/themen_matching.py`): - Cosine-Similarity zwischen News-Embedding (v4) und Assessment-Embedding (v4) — beide in gleicher Vector-Space. - `compute_relevance(matches)` aggregiert pro News auf `high|mid|low|none`. - `aggregate_top_themen(only_relevant, single_date, …)` mit TTL-60s-Cache. - `aggregate_news_cluster(days)` als Greedy-Embedding-Cluster. - `aggregate_top_antraege_with_news` als Reverse-View. 3. **PM-Generator** (`app/presse_generator.py`): - Persona-Prompt „Pressereferent:in einer GWÖ-Initiative" mit **harter Verbotsliste** (keine GWÖ-Werte-Listen, keine Scores, keine Matrix-Codes, keine Floskeln) und **Few-Shot-Beispiel** (Schlecht/Gut). - Konkrete Bürger:innen-Lebenslagen pro Absatz (Pflegebedürftige, Mieter:innen, Auszubildende, Pendler:innen, …) — der Antrag muss als „was ändert sich konkret" beschrieben werden. - 320–380 Worte, 6 Absätze, getrennt durch `\n\n`. - **`json_object_mode=True`** + `_recover_unescaped_newlines` Fallback (qwen-max produziert gelegentlich rohe `\n`-Bytes im JSON-String, was den Standard-Parser brechen würde). - Sparsame **fett**-Hervorhebungen (max. 1 pro Absatz, nur Zahlen oder zentrale Effekte) — wird vom Mini-Markdown-Renderer im Frontend (`renderPmBody`) als `` gerendert. 4. **Versionierung statt Force-Override**: - Neuer Draft pro `force=True`-Aufruf — alle Versionen bleiben in `presse_drafts`. - Frontend zeigt Dropdown mit allen Versionen für `(drucksache, news_url)`. - `_find_existing_draft()` für Idempotenz-Schutz im Default-Pfad. 5. **Frontend** (`templates/v2/screens/aktuelle-themen.html`): - 5 Tabs: News×Anträge, Themen-Cluster, GWÖ-Top-Anträge, News-Volumen-Zeitreihe, PM-Entwürfe. - Pre-Filter `only_relevant` (Default an). - GWÖ-Relevanz-Pills rot/orange/grün. - Chart-Click filtert auf Datum. - PM-Modal mit Versions-Dropdown, „Mail"-Direkt-Link (`mailto:` mit prefilled Subject+Body, Längen-Check), Clipboard, `renderPmBody` als Mini-Markdown-Renderer. ## Konsequenzen ### Positiv - LLM-Kosten kontrollierbar (manueller Trigger). - Keine Daten-Lecks zu Quellen mit AI-Bann. - Versionierung erlaubt Iteration ohne Verlust früherer Entwürfe. - Persona-Prompt + Few-Shot-Trick stabilisiert die Sprache; Smoketest zeigt qwen-max nutzt **fett** dezent (1 Hervorhebung pro PM). ### Negativ - Modell-Lock-in auf qwen-max für PMs. Bei Modell-Wechsel müssen die Pattern-Recovery-Heuristiken (Newline-Recovery, Post-Process für `."Großbuchstabe`-Patterns) neu kalibriert werden. - `json_object_mode` ist DashScope-spezifisch — falls künftig auf Anthropic/OpenAI ge-portet, braucht es eine eigene Adapter-Schicht (heute via `LlmRequest.json_object_mode`-Flag in `qwen_bewerter._post_to_dashscope` realisiert). ### Folgen für andere ADRs - **0010 Stimmverhalten-Aggregat**: Der Konsistenz-Block im Antrag-Detail („Mehrheit kontra GWÖ-Empfehlung") ist Anlass für PM-Generierung — kann in einer Phase-4-Iteration als PM-Auto-Vorschlag genutzt werden. - **0001 LLM-Citation-Binding**: PM-Generator zitiert *nicht* aus Wahlprogrammen, daher ist Citation-Binding hier nicht einschlägig. Für News-Zitate gilt: News-URL als Quelle ist authoritativ, PM-Body darf paraphrasieren ohne wörtliches Zitat.