gwoe-antragspruefer/app/presse_generator.py

611 lines
23 KiB
Python
Raw Normal View History

feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
"""Pressemitteilungs-Generator fuer #170 Phase 4.
Erzeugt einen LLM-generierten Pressemitteilungs-Vorschlag, der einen
GWÖ-bewerteten Antrag in den Kontext eines aktuellen News-Artikels stellt.
Manueller Trigger via UI-Button kein Auto-Versand. Drafts werden in
``presse_drafts`` persistiert und in der UI als Liste sichtbar.
Tonalitaet:
- GWÖ-Sicht (Gemeinwohl-orientiert, nicht parteipolitisch)
- Faktenbasiert, keine Lobbying-Sprache
- 200-250 Worte, presseaehnlicher Aufbau (Lead-Paragraph + Begruendung)
"""
from __future__ import annotations
import json
import logging
import sqlite3
from pathlib import Path
from typing import Optional
logger = logging.getLogger(__name__)
SYSTEM_PROMPT = """Du bist Pressereferent:in einer Gemeinwohl-Ökonomie-
fix(#174): PM-Prompt mit harter Verbotsliste + Few-Shot User-Feedback nach Live-Test: PMs waren kuerzer + nicht anschaulicher. Im Output stand "Score von 4,0/10", "in den Bereichen Buerger:innen, Wirtschaft, Staat, Gesellschaft und Natur" (Matrix-Zeilen D+E), "staerkt Solidaritaet, Wuerde und Demokratie" (GWÖ-Werte-Liste), Floskeln wie "innovative Loesungen" und "faktenbasierter Dialog". Komplett-Refactor: **ABSOLUT VERBOTEN im PM-Text:** - Numerische Scores ("GWÖ-Score 4/10", "X von 10 Punkten") - GWÖ-Wert-Listen als Aufzaehlung - Beruehrungsgruppen-Sprache ("Bereiche Buerger, Wirtschaft, Staat, ...") - Matrix-Codes ("Feld D2", "A1") - GWÖ-Begriffe als Schlagwort (max 1× pro Begriff, nur konkret) - Floskeln (zukunftsweisend, innovativ, faktenbasierter Dialog, ...) **PFLICHT: Mindestens 3 Buerger:innen-Lebenslagen mit konkreter Wirkung:** - Familien mit Kindern (Beträge, KiTa-Plätze) - Pflegebeduerftige + Angehoerige (Wartezeiten, Kosten) - Auszubildende / Studierende (Abbruchrisiko, BAföG) - Pendler:innen (Spritpreis, ÖPNV-Tarif) - Mieter:innen (Mietniveau, Nebenkosten) - Rentner:innen / Geringverdiener:innen (Kaufkraft in Euro) - Selbststaendige / kleine Betriebe (Buerokratie-Stunden, Steuern) Pro Lebenslage: konkreter quantifizierter Effekt ("verlaengert Wartezeit auf Heimplatz von 8 auf 12 Wochen", "spart einer vierkoepfigen Familie etwa 1.800 €/Jahr"). **Few-Shot:** Schlechtes Beispiel + Gutes Beispiel im Prompt. Das gute Beispiel zeigt 30%-Abbrecherquote, 2 Stunden Beratung, 800 zusaetzliche Pflegekraefte in 5 Jahren — konkret quantifizierte Wirkungen aus echten Zahlen. **Laenger:** 320–380 Worte (vorher 220–280) — konkrete Beispiele brauchen Platz. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:22:00 +02:00
Initiative. Du schreibst für **Bürger:innen vor Ort**, nicht für
Fachpublikum. Deine PM erklärt nur eines: **Was ändert sich durch diesen
Antrag konkret im Alltag positiv oder negativ?**
## ABSOLUT VERBOTEN im PM-Text
Diese Begriffe und Konstrukte dürfen im Body NICHT vorkommen:
- **Numerische Scores oder Bewertungen** kein "GWÖ-Score 4/10",
kein "X von 10 Punkten", kein "der Antrag erhält". Du verwendest
die Bewertung nur INTERN als Kompass. Im Text: nur die Wirkung.
- **GWÖ-Wert-Listen als Aufzählung** kein "stärkt Menschenwürde,
Solidarität und Demokratie". Stattdessen die konkrete Wirkung beim
Bürger nennen.
- **GWÖ-Berührungsgruppen-Sprache** kein "in den Bereichen Bürger,
Wirtschaft, Staat, Gesellschaft und Natur", kein "ökologischer
Wirkungshorizont", kein "Lieferant:innen-Dimension".
- **Matrix-Codes** nie "Feld D2", "A1", "Würde×Lieferanten".
- **GWÖ-Begriffe als Werte-Schlagwort** Begriffe wie "Solidarität",
"Würde", "Nachhaltigkeit", "Gerechtigkeit", "Demokratie",
"Gemeinwohl" dürfen jeweils maximal EINMAL vorkommen, und nur dann,
wenn sie eine konkrete Handlung qualifizieren ("solidarisch finanziert
durch eine Mehreinnahme aus Erbschaftsteuer" ✓ ja).
- **Floskeln**: "zukunftsweisend", "innovativ", "richtungsweisend",
"Systemwechsel", "faktenbasierter Dialog", "wir laden zum Dialog ein",
"im Sinne von", "zielgerichtet", "ganzheitlich", "umfassend",
"ausgewogen", "nachhaltige Zukunft sichern".
## PFLICHT im PM-Text
Mindestens DREI dieser Bürger:innen-Lebenslagen müssen mit konkreter,
quantifizierter oder qualitativer Wirkung benannt werden:
- **Familien mit Kindern**: konkrete Beträge, KiTa-Plätze, Schulgeld, Wohnraum
- **Pflegebedürftige + ihre Angehörigen**: Wartezeiten, Eigenanteile,
Heimplatz-Kosten
- **Auszubildende / Studierende**: Ausbildungsabbruch-Risiko, BAföG,
Mietkosten
- **Pendler:innen**: Spritpreis, ÖPNV-Tarif, Anbindung
- **Mieter:innen / Eigentümer:innen**: Mietniveau, Nebenkosten,
Sanierungskosten
- **Rentner:innen / Geringverdiener:innen**: Kaufkraft-Effekt in Euro
- **Selbstständige / kleine Betriebe**: bürokratische Pflicht-Stunden,
Energiekosten, Steuern
Pro Lebenslage: ein konkreter Effekt ("verlängert die Wartezeit auf einen
Heimplatz von 8 auf 12 Wochen", "spart einer vierköpfigen Familie etwa
1.800 pro Jahr", "erhöht die Mietnebenkosten in Bestandsgebäuden um
geschätzt 25 /Monat").
## Wenn die GWÖ-Bewertung KRITISCH ist (intern niedrig)
Drücke das in der PM aus über:
- **Wer verliert** ("Mieter:innen in Großstädten zahlen mehr")
- **Was fehlt** ("Der Antrag adressiert nicht die ökologischen Folgen
des Strassenausbaus, obwohl 40 % der CO2-Emissionen aus Verkehr stammen")
- **Was eine bessere Alternative wäre** ("Statt der Pendlerpauschale
würde ein Mobilitätsgeld unabhängig vom Verkehrsmittel auch
ÖPNV-Nutzer:innen entlasten")
## Wenn die GWÖ-Bewertung POSITIV ist
Drücke das aus über:
- **Wer gewinnt konkret** ("Auszubildende mit Lernschwierigkeiten bekommen
2 Stunden Beratung pro Woche")
- **Was sich messbar verbessert** ("die Abbrecherquote in der Pflege
könnte um geschätzt 15 % sinken")
- **Wo der Antrag stärker werden könnte** (1-2 konkrete Vorschläge,
ohne Floskel)
## Stil
- **Mindestens 320 Worte, höchstens 400.** Outputs unter 280 Worten
sind Verstoß gegen die Vorgabe wenn dir die Substanz ausgeht,
vertiefe eine der drei Lebenslagen mit konkreten Zahlen statt
einfach abzubrechen.
fix(#174): PM-Prompt mit harter Verbotsliste + Few-Shot User-Feedback nach Live-Test: PMs waren kuerzer + nicht anschaulicher. Im Output stand "Score von 4,0/10", "in den Bereichen Buerger:innen, Wirtschaft, Staat, Gesellschaft und Natur" (Matrix-Zeilen D+E), "staerkt Solidaritaet, Wuerde und Demokratie" (GWÖ-Werte-Liste), Floskeln wie "innovative Loesungen" und "faktenbasierter Dialog". Komplett-Refactor: **ABSOLUT VERBOTEN im PM-Text:** - Numerische Scores ("GWÖ-Score 4/10", "X von 10 Punkten") - GWÖ-Wert-Listen als Aufzaehlung - Beruehrungsgruppen-Sprache ("Bereiche Buerger, Wirtschaft, Staat, ...") - Matrix-Codes ("Feld D2", "A1") - GWÖ-Begriffe als Schlagwort (max 1× pro Begriff, nur konkret) - Floskeln (zukunftsweisend, innovativ, faktenbasierter Dialog, ...) **PFLICHT: Mindestens 3 Buerger:innen-Lebenslagen mit konkreter Wirkung:** - Familien mit Kindern (Beträge, KiTa-Plätze) - Pflegebeduerftige + Angehoerige (Wartezeiten, Kosten) - Auszubildende / Studierende (Abbruchrisiko, BAföG) - Pendler:innen (Spritpreis, ÖPNV-Tarif) - Mieter:innen (Mietniveau, Nebenkosten) - Rentner:innen / Geringverdiener:innen (Kaufkraft in Euro) - Selbststaendige / kleine Betriebe (Buerokratie-Stunden, Steuern) Pro Lebenslage: konkreter quantifizierter Effekt ("verlaengert Wartezeit auf Heimplatz von 8 auf 12 Wochen", "spart einer vierkoepfigen Familie etwa 1.800 €/Jahr"). **Few-Shot:** Schlechtes Beispiel + Gutes Beispiel im Prompt. Das gute Beispiel zeigt 30%-Abbrecherquote, 2 Stunden Beratung, 800 zusaetzliche Pflegekraefte in 5 Jahren — konkret quantifizierte Wirkungen aus echten Zahlen. **Laenger:** 320–380 Worte (vorher 220–280) — konkrete Beispiele brauchen Platz. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:22:00 +02:00
- Aktive Verben, kurze Sätze (max 22 Worte)
- Drucksachen-Nummer einmal im Lead nennen ("Drucksache 21/4757")
- Bezug zur News-Lage in 1 Satz, ohne den Medienanbieter zu nennen
- Keine Negativ-Polemik gegen Parteien sachliche Kritik am Inhalt
## Struktur
fix(#174): PM-Prompt mit harter Verbotsliste + Few-Shot User-Feedback nach Live-Test: PMs waren kuerzer + nicht anschaulicher. Im Output stand "Score von 4,0/10", "in den Bereichen Buerger:innen, Wirtschaft, Staat, Gesellschaft und Natur" (Matrix-Zeilen D+E), "staerkt Solidaritaet, Wuerde und Demokratie" (GWÖ-Werte-Liste), Floskeln wie "innovative Loesungen" und "faktenbasierter Dialog". Komplett-Refactor: **ABSOLUT VERBOTEN im PM-Text:** - Numerische Scores ("GWÖ-Score 4/10", "X von 10 Punkten") - GWÖ-Wert-Listen als Aufzaehlung - Beruehrungsgruppen-Sprache ("Bereiche Buerger, Wirtschaft, Staat, ...") - Matrix-Codes ("Feld D2", "A1") - GWÖ-Begriffe als Schlagwort (max 1× pro Begriff, nur konkret) - Floskeln (zukunftsweisend, innovativ, faktenbasierter Dialog, ...) **PFLICHT: Mindestens 3 Buerger:innen-Lebenslagen mit konkreter Wirkung:** - Familien mit Kindern (Beträge, KiTa-Plätze) - Pflegebeduerftige + Angehoerige (Wartezeiten, Kosten) - Auszubildende / Studierende (Abbruchrisiko, BAföG) - Pendler:innen (Spritpreis, ÖPNV-Tarif) - Mieter:innen (Mietniveau, Nebenkosten) - Rentner:innen / Geringverdiener:innen (Kaufkraft in Euro) - Selbststaendige / kleine Betriebe (Buerokratie-Stunden, Steuern) Pro Lebenslage: konkreter quantifizierter Effekt ("verlaengert Wartezeit auf Heimplatz von 8 auf 12 Wochen", "spart einer vierkoepfigen Familie etwa 1.800 €/Jahr"). **Few-Shot:** Schlechtes Beispiel + Gutes Beispiel im Prompt. Das gute Beispiel zeigt 30%-Abbrecherquote, 2 Stunden Beratung, 800 zusaetzliche Pflegekraefte in 5 Jahren — konkret quantifizierte Wirkungen aus echten Zahlen. **Laenger:** 320–380 Worte (vorher 220–280) — konkrete Beispiele brauchen Platz. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:22:00 +02:00
1. **Lead-Paragraph** (2-3 Sätze): Welche Bürger:innengruppe wird wie
betroffen? Drucksache nennen.
2. **Konkrete Wirkung 1** (3-4 Sätze): erste Lebenslage + Effekt
3. **Konkrete Wirkung 2** (3-4 Sätze): zweite Lebenslage + Effekt
4. **Konkrete Wirkung 3** (2-3 Sätze): dritte Lebenslage + Effekt
5. **Was fehlt / was wäre besser** (2-3 Sätze): konkreter Vorschlag
6. **Schluss-Satz**: was wir fordern, ohne Floskel
## Paragraphen-Formatierung
WICHTIG: trenne die 6 Abschnitte mit **doppeltem Newline** (`\\n\\n`)
im JSON-String. NIEMALS Anführungszeichen oder andere Sonderzeichen
als Paragraph-Trenner verwenden. Beispiel:
```json
{"body": "Lead-Satz.\\n\\nWirkung 1.\\n\\nWirkung 2.\\n\\nWirkung 3.\\n\\nWas fehlt.\\n\\nForderung."}
```
Im JSON: `\\n` als Escape-Sequenz (zwei Zeichen: Backslash + n).
NICHT: rohe Newline-Bytes im String, NICHT: `"`-Zeichen als Trenner.
## Hervorhebungen (sparsam)
Du darfst pro Absatz **maximal eine** Schlüssel-Zahl oder den
zentralen Effekt mit Markdown-`**fett**` markieren z.B. die
Abbrecherquote, eine Mehrkostensumme, eine Anzahl betroffener
Personen. Mehr als eine Markierung pro Absatz wirkt unruhig.
Niemals ganze Sätze fett, niemals Zwischenüberschriften.
fix(#174): PM-Prompt mit harter Verbotsliste + Few-Shot User-Feedback nach Live-Test: PMs waren kuerzer + nicht anschaulicher. Im Output stand "Score von 4,0/10", "in den Bereichen Buerger:innen, Wirtschaft, Staat, Gesellschaft und Natur" (Matrix-Zeilen D+E), "staerkt Solidaritaet, Wuerde und Demokratie" (GWÖ-Werte-Liste), Floskeln wie "innovative Loesungen" und "faktenbasierter Dialog". Komplett-Refactor: **ABSOLUT VERBOTEN im PM-Text:** - Numerische Scores ("GWÖ-Score 4/10", "X von 10 Punkten") - GWÖ-Wert-Listen als Aufzaehlung - Beruehrungsgruppen-Sprache ("Bereiche Buerger, Wirtschaft, Staat, ...") - Matrix-Codes ("Feld D2", "A1") - GWÖ-Begriffe als Schlagwort (max 1× pro Begriff, nur konkret) - Floskeln (zukunftsweisend, innovativ, faktenbasierter Dialog, ...) **PFLICHT: Mindestens 3 Buerger:innen-Lebenslagen mit konkreter Wirkung:** - Familien mit Kindern (Beträge, KiTa-Plätze) - Pflegebeduerftige + Angehoerige (Wartezeiten, Kosten) - Auszubildende / Studierende (Abbruchrisiko, BAföG) - Pendler:innen (Spritpreis, ÖPNV-Tarif) - Mieter:innen (Mietniveau, Nebenkosten) - Rentner:innen / Geringverdiener:innen (Kaufkraft in Euro) - Selbststaendige / kleine Betriebe (Buerokratie-Stunden, Steuern) Pro Lebenslage: konkreter quantifizierter Effekt ("verlaengert Wartezeit auf Heimplatz von 8 auf 12 Wochen", "spart einer vierkoepfigen Familie etwa 1.800 €/Jahr"). **Few-Shot:** Schlechtes Beispiel + Gutes Beispiel im Prompt. Das gute Beispiel zeigt 30%-Abbrecherquote, 2 Stunden Beratung, 800 zusaetzliche Pflegekraefte in 5 Jahren — konkret quantifizierte Wirkungen aus echten Zahlen. **Laenger:** 320–380 Worte (vorher 220–280) — konkrete Beispiele brauchen Platz. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:22:00 +02:00
## BEISPIELE für den Stil
**SCHLECHT** (verboten):
> Der Antrag stärkt Menschenwürde, Solidarität und Demokratie. Er trägt
> zu einer nachhaltigeren Zukunft bei und stärkt das Gemeinwohl in den
> Bereichen Bürger:innen und Staat. GWÖ-Score: 8.0/10.
**GUT** (gewünscht):
> Auszubildende in der Pflege brechen ihre Ausbildung heute zu rund
> **30 %** ab meist wegen Überlastung oder fehlender Lernunterstützung.
fix(#174): PM-Prompt mit harter Verbotsliste + Few-Shot User-Feedback nach Live-Test: PMs waren kuerzer + nicht anschaulicher. Im Output stand "Score von 4,0/10", "in den Bereichen Buerger:innen, Wirtschaft, Staat, Gesellschaft und Natur" (Matrix-Zeilen D+E), "staerkt Solidaritaet, Wuerde und Demokratie" (GWÖ-Werte-Liste), Floskeln wie "innovative Loesungen" und "faktenbasierter Dialog". Komplett-Refactor: **ABSOLUT VERBOTEN im PM-Text:** - Numerische Scores ("GWÖ-Score 4/10", "X von 10 Punkten") - GWÖ-Wert-Listen als Aufzaehlung - Beruehrungsgruppen-Sprache ("Bereiche Buerger, Wirtschaft, Staat, ...") - Matrix-Codes ("Feld D2", "A1") - GWÖ-Begriffe als Schlagwort (max 1× pro Begriff, nur konkret) - Floskeln (zukunftsweisend, innovativ, faktenbasierter Dialog, ...) **PFLICHT: Mindestens 3 Buerger:innen-Lebenslagen mit konkreter Wirkung:** - Familien mit Kindern (Beträge, KiTa-Plätze) - Pflegebeduerftige + Angehoerige (Wartezeiten, Kosten) - Auszubildende / Studierende (Abbruchrisiko, BAföG) - Pendler:innen (Spritpreis, ÖPNV-Tarif) - Mieter:innen (Mietniveau, Nebenkosten) - Rentner:innen / Geringverdiener:innen (Kaufkraft in Euro) - Selbststaendige / kleine Betriebe (Buerokratie-Stunden, Steuern) Pro Lebenslage: konkreter quantifizierter Effekt ("verlaengert Wartezeit auf Heimplatz von 8 auf 12 Wochen", "spart einer vierkoepfigen Familie etwa 1.800 €/Jahr"). **Few-Shot:** Schlechtes Beispiel + Gutes Beispiel im Prompt. Das gute Beispiel zeigt 30%-Abbrecherquote, 2 Stunden Beratung, 800 zusaetzliche Pflegekraefte in 5 Jahren — konkret quantifizierte Wirkungen aus echten Zahlen. **Laenger:** 320–380 Worte (vorher 220–280) — konkrete Beispiele brauchen Platz. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:22:00 +02:00
> Die in Drucksache 8/310 vorgeschlagene sozialpädagogische Begleitung
> würde diese Lücke schließen. Konkret: zwei Stunden Einzelberatung
> pro Auszubildender pro Woche. Für Familien, deren Kinder einen
> Pflegeberuf wählen, sinkt damit das Risiko, dass die teure Ausbildung
> erfolglos endet. Für Krankenhäuser und Altenheime in Brandenburg
> bedeutet das: in fünf Jahren etwa 800 zusätzliche fertig ausgebildete
> Pflegekräfte. Was der Antrag nicht regelt: die Bezahlung in der
> Ausbildungszeit selbst. Solange Auszubildende neben dem Lernen
> arbeiten müssen, um die Miete zu zahlen, hilft auch die beste Beratung
> nur begrenzt. Wir fordern, eine Mindest-Ausbildungsvergütung
> mitzudenken.
## Output-Format
Antworte NUR mit gültigem JSON:
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
{
fix(#174): PM-Prompt mit harter Verbotsliste + Few-Shot User-Feedback nach Live-Test: PMs waren kuerzer + nicht anschaulicher. Im Output stand "Score von 4,0/10", "in den Bereichen Buerger:innen, Wirtschaft, Staat, Gesellschaft und Natur" (Matrix-Zeilen D+E), "staerkt Solidaritaet, Wuerde und Demokratie" (GWÖ-Werte-Liste), Floskeln wie "innovative Loesungen" und "faktenbasierter Dialog". Komplett-Refactor: **ABSOLUT VERBOTEN im PM-Text:** - Numerische Scores ("GWÖ-Score 4/10", "X von 10 Punkten") - GWÖ-Wert-Listen als Aufzaehlung - Beruehrungsgruppen-Sprache ("Bereiche Buerger, Wirtschaft, Staat, ...") - Matrix-Codes ("Feld D2", "A1") - GWÖ-Begriffe als Schlagwort (max 1× pro Begriff, nur konkret) - Floskeln (zukunftsweisend, innovativ, faktenbasierter Dialog, ...) **PFLICHT: Mindestens 3 Buerger:innen-Lebenslagen mit konkreter Wirkung:** - Familien mit Kindern (Beträge, KiTa-Plätze) - Pflegebeduerftige + Angehoerige (Wartezeiten, Kosten) - Auszubildende / Studierende (Abbruchrisiko, BAföG) - Pendler:innen (Spritpreis, ÖPNV-Tarif) - Mieter:innen (Mietniveau, Nebenkosten) - Rentner:innen / Geringverdiener:innen (Kaufkraft in Euro) - Selbststaendige / kleine Betriebe (Buerokratie-Stunden, Steuern) Pro Lebenslage: konkreter quantifizierter Effekt ("verlaengert Wartezeit auf Heimplatz von 8 auf 12 Wochen", "spart einer vierkoepfigen Familie etwa 1.800 €/Jahr"). **Few-Shot:** Schlechtes Beispiel + Gutes Beispiel im Prompt. Das gute Beispiel zeigt 30%-Abbrecherquote, 2 Stunden Beratung, 800 zusaetzliche Pflegekraefte in 5 Jahren — konkret quantifizierte Wirkungen aus echten Zahlen. **Laenger:** 320–380 Worte (vorher 220–280) — konkrete Beispiele brauchen Platz. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:22:00 +02:00
"titel": "<thesenstark, max 100 Zeichen, NENNT die Bürger:innengruppe oder den konkreten Effekt — nicht den GWÖ-Score>",
"body": "<MINDESTENS 320 Worte (≥ 280 Worte hartes Minimum), höchstens 400. Mindestens 3 Lebenslagen mit konkretem Effekt. Pro Lebenslage: konkrete Zahlen / Personen / Beträge. Keine GWÖ-Werte-Aufzählung. Kein Score.>"
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
}"""
SYSTEM_PROMPT_THREAD = """Du bist Social-Media-Redakteur:in einer
Gemeinwohl-Ökonomie-Initiative. Erzeuge einen knappen Thread (35 Posts)
für Mastodon/Twitter, der einen GWÖ-bewerteten Antrag im Kontext einer
aktuellen Nachricht erklärt für Bürger:innen, nicht für Fachpublikum.
## ABSOLUT VERBOTEN
- Numerische GWÖ-Scores oder Bewertungs-Zahlen.
- GWÖ-Werte-Listen ("Würde, Solidarität, …") als Schlagwortkette.
- Matrix-Codes (D2, A1, ) und GWÖ-Berührungsgruppen-Sprache.
- Lobbyfloskeln ("zukunftsweisend", "innovativ", "richtungsweisend").
- Reine Schlagworte ohne konkrete Folge im Alltag.
## Stil
- Pro Post **maximal 280 Zeichen** (inkl. Hashtags). Kein Post länger.
- Erster Post ist der **Hook**: konkrete Bürger:innengruppe + sichtbare
Folge. Drucksache nennen.
- Mittlere Posts: je eine konkrete Lebenslage + Effekt in Zahlen oder
Personen. Aktive Verben.
- Letzter Post: was wir fordern, klar, ohne Floskel.
- 12 thematische Hashtags am Schluss (z.B. #GWO #Pflege).
## Hervorhebungen
Keine Markdown-Formatierung im Body Mastodon/Twitter rendert das nicht.
Ausnahme: Zahlen können dezent in Klammern ergänzt werden (30 %", „800
Pflegekräfte"). Kein **fett**, kein _kursiv_, KEINE eckigen Klammern
`[]`, KEINE Backslashes vor Klammern, KEINE Markdown-Links.
## Output-Format
Antworte NUR mit gültigem JSON:
{
"titel": "<Hook-Satz, max 100 Zeichen>",
"body": "<3-5 Posts in EINEM String, jeder Post in eigenem Absatz>"
}
**TRENNUNG ZWISCHEN POSTS:** im JSON-`body` als `\\n\\n` (Backslash + n
+ Backslash + n exakt zwei Escape-Sequenzen, nicht roh, nicht
einfach Newline). Jeder Post ist eine Mini-Einheit für sich,
**maximal 280 Zeichen lang**. Mehr als 280 Zeichen pro Post sind ein
Verstoß gegen Mastodon/Twitter-Limits. Beispiel:
```json
{"body": "Post 1 mit Hook und Drucksache.\\n\\nPost 2 mit erster Lebenslage.\\n\\nPost 3 mit zweiter.\\n\\nWir fordern: ... #GWO"}
```"""
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
def _build_user_prompt(
drucksache: str,
bundesland: str,
antrag_titel: str,
antrag_zusammenfassung: str,
gwoe_score: float,
gwoe_begruendung: str,
empfehlung: str,
news_titel: str,
news_summary: str,
news_url: str,
) -> str:
"""Konstruiert den User-Prompt aus Antrags- und News-Daten."""
return f"""## Aktueller Antrag
Drucksache: {drucksache} ({bundesland})
Titel: {antrag_titel}
Zusammenfassung: {antrag_zusammenfassung or "(keine vorhanden)"}
GWÖ-Score: {gwoe_score}/10
GWÖ-Begründung: {gwoe_begruendung or "(keine vorhanden)"}
Empfehlung: {empfehlung or "(keine)"}
## Aktueller Nachrichten-Kontext
Schlagzeile: {news_titel}
Inhalt: {news_summary or "(keine Zusammenfassung verfügbar)"}
Quelle: {news_url}
## Deine Aufgabe
Schreibe eine Pressemitteilung, die diesen Antrag in den Kontext der
aktuellen Nachrichtenlage stellt. Begründe aus GWÖ-Sicht, warum der
Antrag gerade jetzt relevant ist (oder warum er die aktuelle Debatte
ergänzt/korrigiert). Wenn der GWÖ-Score niedrig ist (< 5), sei dabei
kritisch die PM kann auch eine Ablehnung des Antrags begründen.
"""
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
def _find_existing_draft(
drucksache: str, news_url: str, db_path: Path,
style: str = "pm",
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
) -> Optional[dict]:
"""Sucht einen bereits generierten Draft fuer (drucksache, news_url, style).
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
Bei mehreren Treffern wird der NEUESTE zurueckgegeben. Idempotenz-
Schutz vor doppelter LLM-Generierung (#170 Followup).
"""
if not Path(db_path).exists():
return None
conn = sqlite3.connect(str(db_path))
try:
row = conn.execute(
"""SELECT id, drucksache, bundesland, news_url, news_titel,
titel, body, model, created_at, style
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
FROM presse_drafts
WHERE drucksache=? AND news_url=? AND style=?
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
ORDER BY id DESC LIMIT 1""",
(drucksache, news_url, style),
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
).fetchone()
finally:
conn.close()
if not row:
return None
return {
"id": row[0], "drucksache": row[1], "bundesland": row[2],
"news_url": row[3], "news_titel": row[4],
"titel": row[5], "body": row[6], "model": row[7],
"created_at": row[8], "style": row[9],
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
}
def _split_into_thread_posts(body: str, max_chars: int = 280) -> str:
"""Splittet einen langen Thread-Body an Satzgrenzen in mehrere Posts.
Wenn das LLM die Posts nicht selbst mit ``\\n\\n`` getrennt hat,
wird der Body zwischen Sätzen aufgeteilt Greedy: möglichst
viele Sätze pro Post, ohne ``max_chars`` zu überschreiten.
Hashtags am Ende werden zusammen mit dem letzten Post gehalten.
"""
import re as _re
# Newlines vereinheitlichen
text = body.replace("\n\n", " ").replace("\n", " ").strip()
# An Satzgrenzen splitten (.!? gefolgt von Whitespace)
sentences = _re.split(r"(?<=[.!?])\s+(?=[A-ZÄÖÜ#])", text)
if not sentences:
return body
posts: list[str] = []
current = ""
for sent in sentences:
sent = sent.strip()
if not sent:
continue
# Wenn current + sent zu lang würde, current als Post sichern
candidate = (current + " " + sent).strip() if current else sent
if len(candidate) > max_chars and current:
posts.append(current.strip())
current = sent
else:
current = candidate
if current:
posts.append(current.strip())
return "\n\n".join(posts)
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
async def generate_draft(
drucksache: str,
news_url: str,
db_path: Optional[Path] = None,
bewerter=None,
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
force: bool = False,
style: str = "pm",
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
) -> dict:
"""Erzeugt einen Pressemitteilungs-Draft und persistiert ihn.
Args:
drucksache: ID des Antrags (mit Bundesland-Kontext aus DB).
news_url: URL des News-Artikels (Lookup in news_articles).
db_path: optional override fuer Tests.
bewerter: optional injected QwenBewerter (fuer Tests). Wenn None,
wird der Default mit settings instanziiert.
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
force: Wenn True, wird auch bei vorhandenem Draft fuer das gleiche
(drucksache, news_url)-Paar ein neuer LLM-Call gemacht.
Default False Idempotenz-Schutz vor LLM-Kosten.
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
Returns:
``{"id": int, "drucksache": ..., "bundesland": ...,
"news_url": ..., "news_titel": ...,
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
"titel": str, "body": str, "model": str, "created_at": ISO,
"_was_existing": bool}``
``_was_existing=True`` zeigt an, dass kein neuer LLM-Call gemacht
wurde, sondern ein vorhandener Draft zurueckgegeben wurde.
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
Raises:
ValueError: wenn drucksache oder news_url nicht gefunden.
"""
from .config import settings
from .adapters.qwen_bewerter import LlmRequest
path = db_path or settings.db_path
if style not in ("pm", "thread"):
raise ValueError(f"unbekannter style: {style}")
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
# Idempotenz-Check: hat es schon einen Draft fuer das (Paar, style)?
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
if not force:
existing = _find_existing_draft(drucksache, news_url, path, style=style)
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
if existing:
existing["_was_existing"] = True
return existing
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
conn = sqlite3.connect(str(path))
try:
antrag = conn.execute(
"""SELECT bundesland, title, antrag_zusammenfassung, gwoe_score,
gwoe_begruendung, empfehlung
FROM assessments WHERE drucksache=?""",
(drucksache,),
).fetchone()
news = conn.execute(
"SELECT titel, summary FROM news_articles WHERE url=?",
(news_url,),
).fetchone()
finally:
conn.close()
if not antrag:
raise ValueError(f"Drucksache {drucksache} nicht in assessments")
if not news:
raise ValueError(f"News-URL {news_url} nicht in news_articles")
user_prompt = _build_user_prompt(
drucksache=drucksache,
bundesland=antrag[0],
antrag_titel=antrag[1] or "",
antrag_zusammenfassung=antrag[2] or "",
gwoe_score=antrag[3] or 0.0,
gwoe_begruendung=antrag[4] or "",
empfehlung=antrag[5] or "",
news_titel=news[0],
news_summary=news[1] or "",
news_url=news_url,
)
if bewerter is None:
from .adapters.qwen_bewerter import QwenBewerter
bewerter = QwenBewerter()
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
# Premium-Modell (qwen-max) statt -plus, weil PM-Erzeugung hoehere
# Sprachqualitaet braucht als Antrags-Bewertung. Tradeoff: ~3× teurer
# (~6 Cent statt 2 Cent), ~2× langsamer (~30 s statt 15 s).
model = settings.llm_model_premium
system_prompt_active = SYSTEM_PROMPT_THREAD if style == "thread" else SYSTEM_PROMPT
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
req = LlmRequest(
system_prompt=system_prompt_active,
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
user_prompt=user_prompt,
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
model=model,
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
base_temperature=0.3,
max_tokens=1500,
max_retries=2,
json_object_mode=True,
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
)
result = await bewerter.bewerte(req)
titel = (result.get("titel") or "").strip()[:200]
body = (result.get("body") or "").strip()
# Post-Process Step 1: literal-escapte Sequenzen → echte Whitespaces.
# qwen-max liefert manchmal '\\n' als 2 chars statt echtem Newline.
body = body.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t")
# Post-Process Step 2: einsame Anführungszeichen mitten im Text als
# Paragraph-Trenner — qwen tut das gelegentlich trotz Prompt-Anweisung.
# Heuristik: ein " zwischen "Punkt-Whitespace" und "Großbuchstabe" ist
# wahrscheinlich ein Trenn-Klumpen, kein semantischer Anfuehrer.
import re as _re
body = _re.sub(r'([.!?])"([A-ZÄÖÜ])', r'\1\n\n\2', body)
# Thread-Auto-Splitter: wenn das Modell nur einen Block produziert
# (kein \n\n gesetzt) und body > 300 Zeichen, an Satzgrenzen splitten
# bis jeder Post ≤ 280 Zeichen. Konservativ: nur wenn `style='thread'`.
if style == "thread":
existing_posts = [p for p in body.split("\n\n") if p.strip()]
needs_split = len(existing_posts) < 3 or any(len(p) > 290 for p in existing_posts)
if needs_split:
body = _split_into_thread_posts(body)
# PM-Wortzahl-Re-Generate: wenn deutlich unter Mindestwortzahl,
# ein Re-Prompt mit höherer Temperatur. Nur ein Versuch — sonst
# endlose LLM-Calls bei zähen Anträgen.
if style == "pm":
word_count = len(body.split())
if word_count < 280:
logger.info(
"PM-Wortzahl %s zu niedrig (Soll ≥320), re-generate mit Hint",
word_count,
)
req2 = LlmRequest(
system_prompt=system_prompt_active,
user_prompt=user_prompt + (
f"\n\nWICHTIG: Der erste Versuch hatte nur {word_count} Worte "
"und ist zu kurz. Liefere jetzt mindestens 320 Worte mit "
"konkreten Zahlen/Personen/Beträgen pro Lebenslage."
),
model=model,
base_temperature=0.5,
max_tokens=1800,
max_retries=1,
json_object_mode=True,
)
try:
result2 = await bewerter.bewerte(req2)
titel2 = (result2.get("titel") or "").strip()[:200]
body2 = (result2.get("body") or "").strip()
body2 = body2.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t")
body2 = _re.sub(r'([.!?])"([A-ZÄÖÜ])', r'\1\n\n\2', body2)
if len(body2.split()) > word_count and titel2 and body2:
titel = titel2
body = body2
except Exception:
logger.exception("PM-Wortzahl-Re-Generate fehlgeschlagen")
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
if not titel or not body:
raise ValueError("LLM-Response unvollständig (titel oder body leer)")
# Persist (style additiv im Insert)
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
conn = sqlite3.connect(str(path))
try:
cur = conn.execute(
"""INSERT INTO presse_drafts
(drucksache, bundesland, news_url, news_titel, titel, body, model, style)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
(drucksache, antrag[0], news_url, news[0], titel, body, model, style),
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
)
draft_id = cur.lastrowid
row = conn.execute(
"""SELECT id, drucksache, bundesland, news_url, news_titel,
titel, body, model, created_at, style
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
FROM presse_drafts WHERE id=?""",
(draft_id,),
).fetchone()
conn.commit()
finally:
conn.close()
return {
feat(#170 followup): PM-Generator Idempotenz + qwen-max + Wrapper-Verbesserungen User-Feedback nach Live-Test: **1. Idempotenz** — Pressemitteilungen wurden ungespeichert generiert, doppelter Klick erzeugte doppelten Draft + LLM-Kosten. - Neuer Helper `_find_existing_draft(drucksache, news_url)` der den neuesten Draft fuer das Paar zurueckgibt - `generate_draft()` prueft per Default zuerst den Lookup, liefert existing zurueck mit `_was_existing=True` (kein LLM-Call) - `force=True` Parameter fuer bewusste Neu-Generierung - Endpoint nimmt `?force=true` Query-Param entgegen - UI: Modal zeigt klar "Bestehender Entwurf vs Neu generiert" Banner, mit "Neu generieren"-Button im existing-Banner **2. Premium-Modell statt Default** — User wollte hoehere Sprachqualitaet ("Opus oder sowas"). Da das Projekt Qwen via DashScope nutzt (kein Anthropic), Wechsel auf `settings.llm_model_premium` (qwen-max). - Tradeoff: ~3× teurer (~6 Cent statt 2 Cent) und ~2× langsamer (~30 s statt 15 s) — aber spuerbare Qualitaetsverbesserung in Pressemitteilungs-Diktion - confirm-Dialog im Frontend nennt jetzt 6 Cent + 30 s **3. Wrapper-Verbesserungen** — `auto-fetch-news.sh` aufgeraeumt: - Container-Check (skip wenn down) analog zu run-digest.sh - START/END-Timestamps - Ausfuehrliche cron-install-Doku im Header - Auto-Backfill: wenn erster Run >= 100 Embeddings (Limit gehit), wird embed_pending_articles bis zu 500 weitere nachgeholt Tests: 5 neue (idempotency, force, _find_existing_draft × 3). Suite 1053 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:10:20 +02:00
"_was_existing": False,
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
"id": row[0], "drucksache": row[1], "bundesland": row[2],
"news_url": row[3], "news_titel": row[4],
"titel": row[5], "body": row[6], "model": row[7],
"created_at": row[8], "style": row[9],
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
}
def list_drafts(
limit: int = 20,
db_path: Optional[Path] = None,
) -> list[dict]:
"""Liste der zuletzt generierten Drafts. Default-Limit 20."""
from .config import settings
path = db_path or settings.db_path
if not Path(path).exists():
return []
conn = sqlite3.connect(str(path))
try:
rows = conn.execute(
"""SELECT id, drucksache, bundesland, news_url, news_titel,
titel, body, model, created_at, style
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
FROM presse_drafts
ORDER BY id DESC LIMIT ?""",
(limit,),
).fetchall()
finally:
conn.close()
feat(#170 followup 2): Pre-Filter, Cluster, Antrags-Initiative, PM-Versionierung, Mail-Link User-Feedback: Aktuelle-Themen-Dashboard war "Detective-Modus" — durch viele News scrollen, Match-Stärke selbst interpretieren. Komplett-Refactor zur kuratierten Sicht mit Tabs. **1. Pre-Filter + GWÖ-Relevanz-Score (#134)** `compute_relevance(matches)`: Score = max(antrag.gwoe_score × similarity). Level: high (≥4.0) / mid (≥2.5) / low (>0) / none. Pro News in der UI ein farbiger Pill (gruen/orange/grau) + Reason-Text: "GWÖ-9.0/10-Antrag „Klimaschutzgesetz" (GRÜNE) passt mit Similarity 0.55." Default-Filter "Nur GWÖ-relevant" aktiv (only_relevant=true) — zeigt nur high/mid News, blendet Rauschen aus. Toggle-Checkbox. `/api/aktuelle-themen/top` neuer Param `only_relevant=true|false`. **2. PM-Versionierung im Modal (#135)** `list_drafts_for(drucksache, news_url)`: alle Versionen, neueste oben. Endpoint `/api/aktuelle-themen/drafts-versions`. Modal zeigt Dropdown wenn >1 Version, Switch ohne LLM-Call. Force-Regen bleibt als Button im "bestehender Entwurf"-Banner. **3. News-Cluster-View (#136)** `aggregate_news_cluster(intra_threshold=0.55, min_cluster_size=2)`: Greedy-Embedding-Cluster + zentralster Antrags-Match per Centroid- Vektor. Zweiter Tab "Themen-Cluster": 5 News über "Pflege" → 1 Cluster mit gemeinsamem Antrag-Vorschlag, statt 5 separate Cards. Endpoint: `/api/aktuelle-themen/cluster`. **4. Mail-Direkt-Link + Clipboard (#137)** Im PM-Modal zwei Buttons: - "📧 Per Mail versenden" (mailto: mit subject + body, ~1900 Char Limit) - "📋 In Zwischenablage kopieren" (navigator.clipboard.writeText) - Bei langem PM (>1900 Char): mailto-Link wird ausgegraut, Hinweis "PM zu lang für Mail-Link — Clipboard nutzen" **5. Antrags-Initiative (#138)** `aggregate_top_antraege_with_news(min_gwoe_score=8.0, days=14)`: Reverse-Sicht — pro Antrag mit GWÖ ≥ 8 die News-Resonanz. Antraege ohne Match werden trotzdem angezeigt mit "keine News"-Pill. Dritter Tab "GWÖ-Top-Anträge". Endpoint `.../top-antraege`. **UI-Restrukturierung:** statt einer langen Scroll-Liste jetzt 5 Tabs mit gemeinsamer Filter-Bar: - News × Anträge (Default, kuratiert via Pre-Filter) - Themen-Cluster (Bündel ähnlicher News) - GWÖ-Top-Anträge (Reverse) - News-Volumen (Chart) - PM-Entwürfe (Drafts-Liste) Default min_similarity 0.40 → 0.50 erhoeht (weniger Rauschen). Tests: 14 neue (compute_relevance × 5, only_relevant + sort × 3, cluster × 3, top_antraege × 3). Suite 1067 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:41:31 +02:00
return [
{
"id": r[0], "drucksache": r[1], "bundesland": r[2],
"news_url": r[3], "news_titel": r[4],
"titel": r[5], "body": r[6], "model": r[7],
"created_at": r[8], "style": r[9] if len(r) > 9 else "pm",
feat(#170 followup 2): Pre-Filter, Cluster, Antrags-Initiative, PM-Versionierung, Mail-Link User-Feedback: Aktuelle-Themen-Dashboard war "Detective-Modus" — durch viele News scrollen, Match-Stärke selbst interpretieren. Komplett-Refactor zur kuratierten Sicht mit Tabs. **1. Pre-Filter + GWÖ-Relevanz-Score (#134)** `compute_relevance(matches)`: Score = max(antrag.gwoe_score × similarity). Level: high (≥4.0) / mid (≥2.5) / low (>0) / none. Pro News in der UI ein farbiger Pill (gruen/orange/grau) + Reason-Text: "GWÖ-9.0/10-Antrag „Klimaschutzgesetz" (GRÜNE) passt mit Similarity 0.55." Default-Filter "Nur GWÖ-relevant" aktiv (only_relevant=true) — zeigt nur high/mid News, blendet Rauschen aus. Toggle-Checkbox. `/api/aktuelle-themen/top` neuer Param `only_relevant=true|false`. **2. PM-Versionierung im Modal (#135)** `list_drafts_for(drucksache, news_url)`: alle Versionen, neueste oben. Endpoint `/api/aktuelle-themen/drafts-versions`. Modal zeigt Dropdown wenn >1 Version, Switch ohne LLM-Call. Force-Regen bleibt als Button im "bestehender Entwurf"-Banner. **3. News-Cluster-View (#136)** `aggregate_news_cluster(intra_threshold=0.55, min_cluster_size=2)`: Greedy-Embedding-Cluster + zentralster Antrags-Match per Centroid- Vektor. Zweiter Tab "Themen-Cluster": 5 News über "Pflege" → 1 Cluster mit gemeinsamem Antrag-Vorschlag, statt 5 separate Cards. Endpoint: `/api/aktuelle-themen/cluster`. **4. Mail-Direkt-Link + Clipboard (#137)** Im PM-Modal zwei Buttons: - "📧 Per Mail versenden" (mailto: mit subject + body, ~1900 Char Limit) - "📋 In Zwischenablage kopieren" (navigator.clipboard.writeText) - Bei langem PM (>1900 Char): mailto-Link wird ausgegraut, Hinweis "PM zu lang für Mail-Link — Clipboard nutzen" **5. Antrags-Initiative (#138)** `aggregate_top_antraege_with_news(min_gwoe_score=8.0, days=14)`: Reverse-Sicht — pro Antrag mit GWÖ ≥ 8 die News-Resonanz. Antraege ohne Match werden trotzdem angezeigt mit "keine News"-Pill. Dritter Tab "GWÖ-Top-Anträge". Endpoint `.../top-antraege`. **UI-Restrukturierung:** statt einer langen Scroll-Liste jetzt 5 Tabs mit gemeinsamer Filter-Bar: - News × Anträge (Default, kuratiert via Pre-Filter) - Themen-Cluster (Bündel ähnlicher News) - GWÖ-Top-Anträge (Reverse) - News-Volumen (Chart) - PM-Entwürfe (Drafts-Liste) Default min_similarity 0.40 → 0.50 erhoeht (weniger Rauschen). Tests: 14 neue (compute_relevance × 5, only_relevant + sort × 3, cluster × 3, top_antraege × 3). Suite 1067 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:41:31 +02:00
}
for r in rows
]
def list_drafts_for(
drucksache: str,
news_url: str,
db_path: Optional[Path] = None,
) -> list[dict]:
"""Alle Versions-Drafts fuer ein (drucksache, news_url)-Paar, neueste oben."""
from .config import settings
path = db_path or settings.db_path
if not Path(path).exists():
return []
conn = sqlite3.connect(str(path))
try:
rows = conn.execute(
"""SELECT id, drucksache, bundesland, news_url, news_titel,
titel, body, model, created_at, style
feat(#170 followup 2): Pre-Filter, Cluster, Antrags-Initiative, PM-Versionierung, Mail-Link User-Feedback: Aktuelle-Themen-Dashboard war "Detective-Modus" — durch viele News scrollen, Match-Stärke selbst interpretieren. Komplett-Refactor zur kuratierten Sicht mit Tabs. **1. Pre-Filter + GWÖ-Relevanz-Score (#134)** `compute_relevance(matches)`: Score = max(antrag.gwoe_score × similarity). Level: high (≥4.0) / mid (≥2.5) / low (>0) / none. Pro News in der UI ein farbiger Pill (gruen/orange/grau) + Reason-Text: "GWÖ-9.0/10-Antrag „Klimaschutzgesetz" (GRÜNE) passt mit Similarity 0.55." Default-Filter "Nur GWÖ-relevant" aktiv (only_relevant=true) — zeigt nur high/mid News, blendet Rauschen aus. Toggle-Checkbox. `/api/aktuelle-themen/top` neuer Param `only_relevant=true|false`. **2. PM-Versionierung im Modal (#135)** `list_drafts_for(drucksache, news_url)`: alle Versionen, neueste oben. Endpoint `/api/aktuelle-themen/drafts-versions`. Modal zeigt Dropdown wenn >1 Version, Switch ohne LLM-Call. Force-Regen bleibt als Button im "bestehender Entwurf"-Banner. **3. News-Cluster-View (#136)** `aggregate_news_cluster(intra_threshold=0.55, min_cluster_size=2)`: Greedy-Embedding-Cluster + zentralster Antrags-Match per Centroid- Vektor. Zweiter Tab "Themen-Cluster": 5 News über "Pflege" → 1 Cluster mit gemeinsamem Antrag-Vorschlag, statt 5 separate Cards. Endpoint: `/api/aktuelle-themen/cluster`. **4. Mail-Direkt-Link + Clipboard (#137)** Im PM-Modal zwei Buttons: - "📧 Per Mail versenden" (mailto: mit subject + body, ~1900 Char Limit) - "📋 In Zwischenablage kopieren" (navigator.clipboard.writeText) - Bei langem PM (>1900 Char): mailto-Link wird ausgegraut, Hinweis "PM zu lang für Mail-Link — Clipboard nutzen" **5. Antrags-Initiative (#138)** `aggregate_top_antraege_with_news(min_gwoe_score=8.0, days=14)`: Reverse-Sicht — pro Antrag mit GWÖ ≥ 8 die News-Resonanz. Antraege ohne Match werden trotzdem angezeigt mit "keine News"-Pill. Dritter Tab "GWÖ-Top-Anträge". Endpoint `.../top-antraege`. **UI-Restrukturierung:** statt einer langen Scroll-Liste jetzt 5 Tabs mit gemeinsamer Filter-Bar: - News × Anträge (Default, kuratiert via Pre-Filter) - Themen-Cluster (Bündel ähnlicher News) - GWÖ-Top-Anträge (Reverse) - News-Volumen (Chart) - PM-Entwürfe (Drafts-Liste) Default min_similarity 0.40 → 0.50 erhoeht (weniger Rauschen). Tests: 14 neue (compute_relevance × 5, only_relevant + sort × 3, cluster × 3, top_antraege × 3). Suite 1067 gruen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:41:31 +02:00
FROM presse_drafts
WHERE drucksache=? AND news_url=?
ORDER BY id DESC""",
(drucksache, news_url),
).fetchall()
finally:
conn.close()
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
return [
{
"id": r[0], "drucksache": r[1], "bundesland": r[2],
"news_url": r[3], "news_titel": r[4],
"titel": r[5], "body": r[6], "model": r[7],
"created_at": r[8], "style": r[9] if len(r) > 9 else "pm",
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
}
for r in rows
]
def get_draft(
draft_id: int,
db_path: Optional[Path] = None,
) -> Optional[dict]:
"""Einen Draft per ID abrufen."""
from .config import settings
path = db_path or settings.db_path
if not Path(path).exists():
return None
conn = sqlite3.connect(str(path))
try:
row = conn.execute(
"""SELECT id, drucksache, bundesland, news_url, news_titel,
titel, body, model, created_at, style
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
FROM presse_drafts WHERE id=?""",
(draft_id,),
).fetchone()
finally:
conn.close()
if not row:
return None
return {
"id": row[0], "drucksache": row[1], "bundesland": row[2],
"news_url": row[3], "news_titel": row[4],
"titel": row[5], "body": row[6], "model": row[7],
"created_at": row[8], "style": row[9] if len(row) > 9 else "pm",
feat(#170): Aktuelle-Themen-Dashboard — News × Anträge × Pressemitteilungen Vollständiges 4-Phasen-Feature: **Phase 1 — News-Aggregator** (`app/news_aggregator.py`) - Tagesschau-API (`/api2u/news?ressort=...`) für inland/ausland/wirtschaft/wissen - Bundestag-RSS für aktuellethemen / pressemitteilungen / hib - DB-Tabelle `news_articles` (URL-PK, idempotent) - Embeddings via existierender qwen-v4-Pipeline - Cron-Script `scripts/auto-fetch-news.sh` - Bewusst NICHT: RND.de (robots.txt bannt explizit ClaudeBot, GPTBot, CCBot, ChatGPT-User, Google-Extended). Nur AI-erlaubende, öffentlich- rechtliche/parlamentarische Quellen - Volltexte werden NICHT persistiert (nur Titel + erster Satz) **Phase 2 — Themen × Anträge Matching** (`app/themen_matching.py`) - News-Embedding × Assessment-summary_embedding via Cosine-Similarity - `find_anträge_for_news`: pro News die Top-K passenden Anträge - `find_news_for_antrag`: pro Antrag Top-K News mit Datums-Fenster (90d) - `aggregate_top_themen`: primärer Dashboard-Endpoint - `aggregate_themen_zeitreihe`: News-Volumen pro Tag × Source **Phase 3 — Dashboard-View** (`/aktuelle-themen`) - Neuer linker Nav-Eintrag „Aktuelle Themen" - Stacked-Area-Chart News-Volumen pro Quelle (30d) - Pro News-Card: Titel + Summary + Tags + Top-3-Antrags-Match-Liste mit GWÖ-Score-Pill, Drucksache-Link, PM-Vorschlag-Button - Filter: Zeitfenster, Top-N, min_similarity - Auth-protected (require_auth) **Phase 4 — Pressemitteilungs-Generator** (`app/presse_generator.py`) - LLM-Prompt-Template (200-250 Worte, GWÖ-Sicht, JSON-Output) - Reuse von `QwenBewerter` aus app/adapters/qwen_bewerter.py - DB-Tabelle `presse_drafts` (Persistenz) - POST `/api/aktuelle-themen/generate-presse` rate-limited 5/min, auth-only (LLM-Kosten) - GET `/api/aktuelle-themen/drafts` + `/drafts/{id}` für Liste/Detail - Manueller Trigger via UI-Button, kein Auto-Versand - Modal-Anzeige des generierten Texts **Compliance:** - robots.txt-respektierend (ClaudeBot-Bann von RND vermieden, AI- erlaubende Quellen verwendet) - UI zeigt nur Titel+URL+Datum+erster Satz, keine Volltext-Reproduktion - Pressemitteilungen sind explizit Drafts, nicht Auto-Versand - LLM-Calls rate-limited, auth-only **Tests:** 43 neue Tests (19 news_aggregator + 16 themen_matching + 8 presse_generator). Suite jetzt 1048 grün. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 12:39:36 +02:00
}