Neueste Entwicklungen aus Lagebild statt aus Artikel-Strom

Bisher extrahierte der Generator Bullets direkt aus den neu eingesammelten
Artikeln und mergte sie mit den bestehenden Developments. Das fuehrte zu
zwei wiederkehrenden Problemen:

1. Off-topic Artikel, die den Keyword-Prefilter aber nicht den Topic-Filter
   passiert hatten, konnten als Bullet landen (die Kachel bildete dann
   Nebenschauplaetze des Weltgeschehens ab statt der Lage).
2. Alte Bullets blieben stehen, auch wenn sie laengst nicht mehr die
   'neuesten' Entwicklungen waren — nur sehr ueberholte Eintraege fielen
   durch das 8-Bullet-Cap raus.

Neue Logik: Der Generator nimmt das frisch erzeugte Lagebild als autoritative
inhaltliche Grundlage und waehlt daraus Bullets aus, die durch eine aktuelle
belegende Meldung (<~7 Tage) gestuetzt sind. Dadurch:

- Thematisch sauber: Lagebild enthaelt bereits nur relevante Inhalte.
- Echt 'neueste': Alte Hintergrund-Erwaehnungen im Lagebild fallen raus,
  weil kein aktueller Artikel sie belegt.
- Klar datiert: Zeitstempel zwingend aus article.published_at der
  belegenden Meldung.
- Kompakt: 4-6 Bullets (vorher 8), nach Zeitstempel absteigend.

Kein Merge mit previous_developments mehr — bei jedem Refresh neu generiert
(behebt das Drift-Problem). previous_developments bleibt nur als Fallback,
falls der Generator im Einzelfall 0 Bullets parst.
Dieser Commit ist enthalten in:
claude-dev
2026-04-21 14:23:18 +00:00
Ursprung efae707fa9
Commit a579e2c275
2 geänderte Dateien mit 83 neuen und 46 gelöschten Zeilen

Datei anzeigen

@@ -206,7 +206,7 @@ Antworte AUSSCHLIESSLICH als JSON-Objekt mit diesen Feldern:
Antworte NUR mit dem JSON-Objekt. Keine Einleitung, keine Erklärung."""
LATEST_DEVELOPMENTS_PROMPT_TEMPLATE = """Du pflegst eine Kachel "Neueste Entwicklungen" für eine Live-Monitoring-Lage.
LATEST_DEVELOPMENTS_PROMPT_TEMPLATE = """Du erzeugst die Kachel "Neueste Entwicklungen" für eine Live-Monitoring-Lage.
HEUTIGES DATUM: {today}
AUSGABESPRACHE: {output_language}
WICHTIG: Verwende IMMER echte UTF-8-Umlaute (ä, ö, ü, ß) — NIEMALS Umschreibungen (ae, oe, ue, ss).
@@ -214,39 +214,34 @@ WICHTIG: Verwende IMMER echte UTF-8-Umlaute (ä, ö, ü, ß) — NIEMALS Umschre
LAGE: {title}
KONTEXT: {description}
BISHERIGE ENTWICKLUNGEN (chronologisch absteigend, neueste oben):
{previous_developments}
AKTUELLES LAGEBILD (autoritative inhaltliche Grundlage):
{summary}
NEUE MELDUNGEN SEIT DEM LETZTEN UPDATE:
{new_articles_text}
BELEGENDE MELDUNGEN (chronologisch absteigend, neueste zuerst — nur hieraus dürfen Zeitstempel und Quellen-Klammern stammen):
{articles_text}
AUFTRAG:
Extrahiere aus den NEUEN Meldungen konkrete Ereignisse und aktualisiere die Liste. Fasse die bisherigen und neuen Ereignisse zu EINER Liste zusammen (max. 8 Bullets, neueste oben).
Extrahiere aus dem LAGEBILD die wichtigsten jüngsten Ereignisse und stelle sie als chronologisch absteigende Bullet-Liste dar. Für jedes Bullet wählst du eine oder mehrere belegende Meldungen aus der obigen Liste und übernimmst deren Publikationsdatum als Zeitstempel.
REGELN:
- Jedes Bullet = EIN konkretes Ereignis (1-2 Sätze, faktenbasiert). Keine Themen-Zusammenfassungen.
- Jedes Bullet beginnt mit dem Zeitstempel der frühesten belegenden Quelle im Format "[DD.MM. HH:MM]".
- Jedes Bullet ENDET mit einer Quellen-Klammer — ZWINGEND. Bullets ohne Klammer werden verworfen.
- NEUE Bullets (aus den NEUEN MELDUNGEN): {{M<ID1>, M<ID2>}} mit den ganzzahligen IDs aus der "ID:"-Zeile der belegenden Meldung(en). Beispiele: {{M42}} oder {{M42, M17}}.
- UEBERNOMMENE Bullets aus BISHERIGE ENTWICKLUNGEN: behalten ihre bestehende Klammer KOMPLETT UND UNVERAENDERT, inklusive des Pipe-Zeichens und der URL. Beispiel: {{Reuters|https://reuters.com/article, Rybar|https://t.me/rybar/123}}. NICHT in M-IDs umwandeln, NICHT die URL entfernen, NICHT umformatieren.
- Wenn mehrere Meldungen dasselbe Ereignis belegen: EIN Bullet, Zeitstempel = frühester Zeitpunkt, ALLE IDs in der Klammer.
- Bestehende Bullets aus BISHERIGE ENTWICKLUNGEN sinngemäß übernehmen, NICHT umformulieren. Nur entfernen, wenn sie durch neue Meldungen nachweislich überholt sind oder die 8-Bullet-Grenze überschritten wird (dann älteste fallen raus). Wenn einem uebernommenen Bullet die Quellen-Klammer fehlt (Altformat): Bullet VERWERFEN und nicht in die neue Liste uebernehmen.
- Wenn eine Quelle eine erkennbare politische Ausrichtung hat (z.B. pro-russisch, staatsnah, rechtsextrem), im Bullet-Text erwähnen ("laut pro-russischem Telegram-Kanal Rybar...").
- Neutral und sachlich — keine Wertungen oder Spekulationen.
- KEINE Gedankenstriche (—, –) — stattdessen Kommas, Doppelpunkte oder neue Sätze.
REGELN zur Auswahl der Bullets:
- Ziel: 4 bis 6 Bullets. Wenn das Lagebild weniger tatsächlich AKTUELLE Ereignisse hergibt, dann lieber 3 ehrliche Bullets als 6 mit veralteten. Kein Auffüllen.
- "AKTUELL" bedeutet: belegende Meldung ist spätestens ~7 Tage alt (relativ zu HEUTIGES DATUM). Ältere Ereignisse — auch wenn sie im Lagebild stehen — gehören NICHT rein. Sie sind Hintergrund, keine Neuesten Entwicklungen.
- Wenn das Lagebild ein Ereignis erwähnt, aber KEINE aktuelle belegende Meldung dafür existiert: Bullet verwerfen. Lieber weglassen als fabulieren.
- Bevorzuge Ereignisse mit hohem Neuigkeitswert und konkretem Vorfall/Aussage gegenüber allgemeinen Hintergrundkonstatierungen.
REGELN zur Formulierung:
- Jedes Bullet = EIN konkretes Ereignis oder eine konkrete Aussage, 1-2 Sätze, präzise und neutral.
- Beginne JEDES Bullet mit dem Zeitstempel der frühesten belegenden Meldung im Format "[DD.MM. HH:MM]".
- Ende JEDES Bullet mit einer Quellen-Klammer mit Pipe-getrennten Paaren "Name|URL", kommagetrennt bei mehreren Belegen: {{Reuters|https://reuters.com/..., Rybar|https://t.me/rybar/123}}. Maximal 3 Quellen pro Bullet. Bullets ohne Klammer werden verworfen.
- Sortiere die Bullets nach Zeitstempel absteigend — neueste zuerst.
- Wenn eine Quelle eine erkennbare politische Ausrichtung hat (pro-russisch, staatsnah, rechtsextrem etc.), im Bullet-Text erwähnen ("laut pro-russischem Telegram-Kanal Rybar...").
- KEINE Gedankenstriche (—, –). Stattdessen Kommas, Doppelpunkte, neue Sätze.
- Bei widersprüchlichen Angaben beide Seiten knapp nennen.
- KEINE Einleitung, KEINE Überschrift, KEINE Nachbemerkungen.
- Wenn aus den neuen Meldungen kein neues Ereignis extrahierbar ist: BISHERIGE ENTWICKLUNGEN unverändert zurückgeben.
OUTPUT-FORMAT (ausschliesslich, keine Anführungszeichen, kein Code-Fence, JEDE Zeile beginnt mit "- "):
- [DD.MM. HH:MM] Ereignistext neu. {{M<ID>}}
- [DD.MM. HH:MM] Ereignistext neu mit mehreren Belegen. {{M<ID1>, M<ID2>}}
- [DD.MM. HH:MM] Ereignistext aus BISHERIGE ENTWICKLUNGEN. {{Quellenname1|URL1, Quellenname2|URL2}}
RELEVANZ-GATE (vor der Bullet-Erzeugung anwenden):
- Jedes Bullet MUSS einen klaren inhaltlichen Bezug zum SPEZIFISCHEN Kernthema der Lage (siehe LAGE + KONTEXT) haben. Keyword-Überschneidungen reichen NICHT.
- Beispiel: Lautet das Thema "Russische Militärblogger bewerten die Lage der russischen Armee", ist ein allgemeiner Iran-Konflikt- oder Nahost-Bericht NICHT relevant, auch wenn er in den neuen Meldungen auftaucht. Nur Einschätzungen/Aussagen russischer Militärblogger zur russischen Armee gehören rein.
- Im Zweifel: Meldung ignorieren, kein Bullet erzeugen. Lieber weniger Bullets als off-topic.
OUTPUT-FORMAT (ausschliesslich, kein Code-Fence, JEDE Zeile beginnt mit "- "):
- [DD.MM. HH:MM] Ereignistext. {{Quellenname1|URL1}}
- [DD.MM. HH:MM] Ereignistext mit mehreren Belegen. {{Quellenname1|URL1, Quellenname2|URL2}}
..."""
@@ -477,28 +472,57 @@ class AnalyzerAgent:
self,
title: str,
description: str,
new_articles: list[dict],
previous_developments: str | None,
summary: str,
recent_articles: list[dict],
previous_developments: str | None = None,
) -> tuple[str | None, ClaudeUsage | None]:
"""Pflegt die Kachel 'Neueste Entwicklungen' für Live-Monitoring-Lagen.
"""Generiert die Kachel 'Neueste Entwicklungen' aus dem Lagebild.
Gibt Markdown-Bullets mit Zeitstempel zurück (max 8, neueste oben).
Wenn keine neuen Artikel vorliegen, werden die bisherigen Bullets unverändert zurückgegeben.
Der LLM extrahiert aus dem Summary die jüngsten Ereignisse und bindet sie an
das Publikationsdatum der belegenden Meldungen (recent_articles). Damit bleiben
die Einträge zwingend aktuell und thematisch an das Lagebild gekoppelt. Alte
Hintergrund-Erwähnungen im Lagebild erzeugen keine Bullets, weil keine aktuelle
Meldung sie belegen würde.
Gibt 4–6 Bullets (absteigend nach Zeitstempel) zurück. Bei Fehler/Parsing-Leer:
Fallback auf previous_developments (falls vorhanden), sonst None.
"""
prev = (previous_developments or "").strip()
if not new_articles:
return (prev or None), None
prev = (previous_developments or "").strip() or None
if not summary or not summary.strip():
return prev, None
if not recent_articles:
return prev, None
from config import OUTPUT_LANGUAGE, CLAUDE_MODEL_FAST
today = datetime.now(TIMEZONE).strftime("%d.%m.%Y")
new_articles_text = self._format_articles_text(new_articles, max_articles=25)
prev_block = prev if prev else "(noch keine Einträge)"
# Kompakter Artikel-Block: nur die für Zeitstempel/Quellen nötigen Felder.
# Sortiert nach published_at absteigend — damit der LLM die jüngsten sofort sieht.
def _pub_sort_key(a: dict) -> str:
return a.get("published_at") or ""
sorted_articles = sorted(recent_articles, key=_pub_sort_key, reverse=True)
lines: list[str] = []
for a in sorted_articles[:60]:
headline = a.get("headline_de") or a.get("headline", "")
source = a.get("source", "Unbekannt")
url = a.get("source_url", "")
published = a.get("published_at") or "unbekannt"
bias = a.get("source_bias") or ""
line = f"- [{published}] {source}"
if bias:
line += f" ({bias})"
line += f" | {headline}"
if url:
line += f" | {url}"
lines.append(line)
articles_text = "\n".join(lines) if lines else "(keine belegenden Meldungen verfügbar)"
prompt = LATEST_DEVELOPMENTS_PROMPT_TEMPLATE.format(
title=title,
description=description or "Keine weiteren Details",
previous_developments=prev_block,
new_articles_text=new_articles_text,
summary=summary.strip(),
articles_text=articles_text,
today=today,
output_language=OUTPUT_LANGUAGE,
)
@@ -507,16 +531,16 @@ class AnalyzerAgent:
result, usage = await call_claude(prompt, tools=None, model=CLAUDE_MODEL_FAST, raw_text=True)
except Exception as e:
logger.error(f"Latest-Developments-Fehler: {e}")
return (prev or None), None
return prev, None
bullets = self._parse_latest_developments(result, new_articles)
bullets = self._parse_latest_developments(result, recent_articles)
if not bullets:
logger.info("Latest-Developments: keine Bullets geparst, behalte bisherigen Stand")
return (prev or None), usage
return prev, usage
bullets = bullets[:8]
bullets = bullets[:6]
output = "\n".join(bullets)
logger.info(f"Latest-Developments: {len(bullets)} Bullets generiert")
logger.info(f"Latest-Developments: {len(bullets)} Bullets aus Lagebild generiert")
return output, usage
@staticmethod