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>
145 lines
5.8 KiB
Markdown
145 lines
5.8 KiB
Markdown
# 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 `<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.
|