gwoe-antragspruefer/docs/adr/0011-aktuelle-themen-pm-generator.md
Dotty Dotter 1ba9d8e5d9 docs(adr): 0010 Stimmverhalten×GWÖ-Aggregat + 0011 Aktuelle-Themen+PM
ADR 0010 dokumentiert die JOIN-Aggregat-Entscheidung (Option B) mit
4 Aggregat-Funktionen, Heuchelei- und Konsistenz-Markern im Detail,
Sample-Size-Ehrlichkeit und Cache-Strategie.

ADR 0011 dokumentiert Aktuelle-Themen mit Persona-Prompt-Strategie,
Versionierung statt Force-Override, AI-Bann-Quellen-Filter und
json_object_mode-Recovery.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 15:39:29 +02:00

145 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.
- 320380 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 `<strong>` 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.