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 ein politischer Redakteur, der für eine
|
|
|
|
|
|
Gemeinwohl-Ökonomie-Initiative Pressemitteilungen schreibt. Deine Stil-
|
|
|
|
|
|
Richtlinien:
|
|
|
|
|
|
|
|
|
|
|
|
- 200-250 Worte
|
|
|
|
|
|
- Sachlicher, präziser Stil — keine Werbesprache, keine Polemik
|
|
|
|
|
|
- Faktenbasiert: Daten aus dem Antrag und dem News-Kontext explizit nennen
|
|
|
|
|
|
- GWÖ-Werte (Würde, Solidarität, Nachhaltigkeit, Gerechtigkeit, Demokratie)
|
|
|
|
|
|
als Bewertungsmaßstab — nicht parteipolitische Linie
|
|
|
|
|
|
- Klare Struktur: Titel, Lead-Paragraph (Wer? Was? Wann? Warum jetzt?),
|
|
|
|
|
|
Begründung mit Bezug auf GWÖ-Bewertung, Schluss mit Forderung oder
|
|
|
|
|
|
Einladung zum Dialog
|
|
|
|
|
|
- Niemals den Anbieter der News-Quelle (Tagesschau, Bundestag) zitieren —
|
|
|
|
|
|
nur den Sachverhalt aufgreifen, der dort beschrieben ist
|
|
|
|
|
|
|
|
|
|
|
|
Antworte NUR mit gültigem JSON in dieser Struktur:
|
|
|
|
|
|
{
|
|
|
|
|
|
"titel": "<knackiger Titel, max 100 Zeichen>",
|
|
|
|
|
|
"body": "<Pressemitteilungs-Volltext, 200-250 Wörter>"
|
|
|
|
|
|
}"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-03 13:10:20 +02:00
|
|
|
|
def _find_existing_draft(
|
|
|
|
|
|
drucksache: str, news_url: str, db_path: Path,
|
|
|
|
|
|
) -> Optional[dict]:
|
|
|
|
|
|
"""Sucht einen bereits generierten Draft fuer (drucksache, news_url).
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
FROM presse_drafts
|
|
|
|
|
|
WHERE drucksache=? AND news_url=?
|
|
|
|
|
|
ORDER BY id DESC LIMIT 1""",
|
|
|
|
|
|
(drucksache, news_url),
|
|
|
|
|
|
).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],
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
2026-05-03 13:10:20 +02:00
|
|
|
|
force: bool = 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
|
|
|
|
) -> 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.
|
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": ...,
|
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
|
2026-05-03 13:10:20 +02:00
|
|
|
|
|
|
|
|
|
|
# Idempotenz-Check: hat es schon einen Draft fuer das Paar?
|
|
|
|
|
|
if not force:
|
|
|
|
|
|
existing = _find_existing_draft(drucksache, news_url, path)
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
user_prompt=user_prompt,
|
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,
|
|
|
|
|
|
)
|
|
|
|
|
|
result = await bewerter.bewerte(req)
|
|
|
|
|
|
|
|
|
|
|
|
titel = (result.get("titel") or "").strip()[:200]
|
|
|
|
|
|
body = (result.get("body") or "").strip()
|
|
|
|
|
|
if not titel or not body:
|
|
|
|
|
|
raise ValueError("LLM-Response unvollständig (titel oder body leer)")
|
|
|
|
|
|
|
|
|
|
|
|
# Persist
|
|
|
|
|
|
conn = sqlite3.connect(str(path))
|
|
|
|
|
|
try:
|
|
|
|
|
|
cur = conn.execute(
|
|
|
|
|
|
"""INSERT INTO presse_drafts
|
|
|
|
|
|
(drucksache, bundesland, news_url, news_titel, titel, body, model)
|
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)""",
|
2026-05-03 13:10:20 +02:00
|
|
|
|
(drucksache, antrag[0], news_url, news[0], titel, body, 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
|
|
|
|
)
|
|
|
|
|
|
draft_id = cur.lastrowid
|
|
|
|
|
|
row = conn.execute(
|
|
|
|
|
|
"""SELECT id, drucksache, bundesland, news_url, news_titel,
|
|
|
|
|
|
titel, body, model, created_at
|
|
|
|
|
|
FROM presse_drafts WHERE id=?""",
|
|
|
|
|
|
(draft_id,),
|
|
|
|
|
|
).fetchone()
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
finally:
|
|
|
|
|
|
conn.close()
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
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],
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
FROM presse_drafts
|
|
|
|
|
|
ORDER BY id DESC LIMIT ?""",
|
|
|
|
|
|
(limit,),
|
|
|
|
|
|
).fetchall()
|
|
|
|
|
|
finally:
|
|
|
|
|
|
conn.close()
|
|
|
|
|
|
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],
|
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
|
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],
|
|
|
|
|
|
}
|