Neueste Entwicklungen: Kachel fuer adhoc-Lagen
- DB-Migration: Spalte latest_developments (TEXT) in incidents - Analyzer: neuer Prompt LATEST_DEVELOPMENTS_PROMPT_TEMPLATE und Methode generate_latest_developments() liefert chronologische Bullet-Liste (max. 8, neueste oben, Zeitstempel DD.MM. HH:MM) - Orchestrator: nach Analyse+Faktencheck ein Extra-Schritt nur fuer incident_type=adhoc, der die neue Kachel fortschreibt - Analyzer-Prompts (Erst- und inkrementell): erzeugen KEINE Zusammenfassung-Sektion mehr im Lagebild (vermeidet Duplikat mit der neuen Kachel) - models.IncidentResponse um latest_developments erweitert - Frontend: Rendering der Kachel in app.js
Dieser Commit ist enthalten in:
@@ -30,6 +30,7 @@ STRUKTUR:
|
||||
- Wenn verschiedene Aspekte oder Themenfelder aufkommen (z.B. Ereignis + Reaktionen + Hintergrund): Gliedere mit kurzen Markdown-Zwischenüberschriften (##)
|
||||
- Wenn sich Daten strukturiert vergleichen lassen (z.B. Produkte, Unternehmen, Kennzahlen, Modelle), verwende eine Markdown-Tabelle (| Spalte1 | Spalte2 | ... mit Trennzeile |---|---|)
|
||||
- Die Entscheidung liegt bei dir — Überschriften und Tabellen nur wenn sie dem Leser helfen
|
||||
- ERZEUGE KEINE Sektion "## ZUSAMMENFASSUNG", "## ÜBERBLICK" oder "## KERNPUNKTE". Die neuesten Entwicklungen werden separat als eigene Kachel aufbereitet und dürfen im Lagebild NICHT dupliziert werden. Steige direkt mit dem Fließtext oder der ersten inhaltlichen Zwischenüberschrift ein.
|
||||
|
||||
REGELN:
|
||||
- Neutral und sachlich - keine Wertungen oder Spekulationen
|
||||
@@ -43,7 +44,7 @@ REGELN:
|
||||
- Ältere Quellen zeitlich einordnen (z.B. "laut einem Bericht vom Januar", "Anfang Februar berichtete...")
|
||||
|
||||
Antworte AUSSCHLIESSLICH als JSON-Objekt mit diesen Feldern:
|
||||
- "summary": Zusammenfassung auf {output_language} mit Quellenverweisen [1], [2] etc. im Text (Markdown-Überschriften ## erlaubt wenn sinnvoll)
|
||||
- "summary": Zusammenfassung auf {output_language} mit Quellenverweisen [1], [2] etc. im Text (Markdown-Überschriften ## erlaubt wenn sinnvoll, aber KEINE "## ZUSAMMENFASSUNG"/"## ÜBERBLICK"-Sektion)
|
||||
- "sources": Array von Quellenobjekten, je: {{"nr": 1, "name": "Quellenname", "url": "https://..."}}
|
||||
- "key_facts": Array von bestätigten Kernfakten (Strings, in Ausgabesprache)
|
||||
- "translations": Array von Objekten mit "article_id", "headline_de", "content_de" (nur für fremdsprachige Artikel)
|
||||
@@ -133,6 +134,7 @@ STRUKTUR:
|
||||
- Fließtext oder mit Markdown-Zwischenüberschriften (##) — je nach Komplexität
|
||||
- Wenn sich Daten strukturiert vergleichen lassen (z.B. Produkte, Unternehmen, Kennzahlen, Modelle), verwende eine Markdown-Tabelle (| Spalte1 | Spalte2 | ... mit Trennzeile |---|---|)
|
||||
- KEIN Fettdruck (**) verwenden
|
||||
- ERZEUGE KEINE Sektion "## ZUSAMMENFASSUNG", "## ÜBERBLICK" oder "## KERNPUNKTE". Falls das BISHERIGE LAGEBILD eine solche Sektion enthält, ENTFERNE sie vollständig beim Aktualisieren. Die neuesten Entwicklungen werden separat als eigene Kachel gepflegt und dürfen im Lagebild NICHT dupliziert werden.
|
||||
|
||||
REGELN:
|
||||
- Neutral und sachlich - keine Wertungen oder Spekulationen
|
||||
@@ -202,6 +204,41 @@ 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.
|
||||
HEUTIGES DATUM: {today}
|
||||
AUSGABESPRACHE: {output_language}
|
||||
WICHTIG: Verwende IMMER echte UTF-8-Umlaute (ä, ö, ü, ß) — NIEMALS Umschreibungen (ae, oe, ue, ss).
|
||||
|
||||
LAGE: {title}
|
||||
KONTEXT: {description}
|
||||
|
||||
BISHERIGE ENTWICKLUNGEN (chronologisch absteigend, neueste oben):
|
||||
{previous_developments}
|
||||
|
||||
NEUE MELDUNGEN SEIT DEM LETZTEN UPDATE:
|
||||
{new_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).
|
||||
|
||||
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]".
|
||||
- Wenn mehrere Meldungen dasselbe Ereignis betreffen: EIN Bullet, Zeitstempel = frühester Zeitpunkt.
|
||||
- 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 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.
|
||||
- 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):
|
||||
- [DD.MM. HH:MM] Erster Ereignistext.
|
||||
- [DD.MM. HH:MM] Zweiter Ereignistext.
|
||||
..."""
|
||||
|
||||
|
||||
class AnalyzerAgent:
|
||||
"""Analysiert und übersetzt Meldungen über Claude CLI."""
|
||||
|
||||
@@ -336,6 +373,70 @@ class AnalyzerAgent:
|
||||
logger.error(f"Inkrementelle Analyse-Fehler: {e}")
|
||||
return None, None
|
||||
|
||||
async def generate_latest_developments(
|
||||
self,
|
||||
title: str,
|
||||
description: str,
|
||||
new_articles: list[dict],
|
||||
previous_developments: str | None,
|
||||
) -> tuple[str | None, ClaudeUsage | None]:
|
||||
"""Pflegt die Kachel 'Neueste Entwicklungen' für Live-Monitoring-Lagen.
|
||||
|
||||
Gibt Markdown-Bullets mit Zeitstempel zurück (max 8, neueste oben).
|
||||
Wenn keine neuen Artikel vorliegen, werden die bisherigen Bullets unverändert zurückgegeben.
|
||||
"""
|
||||
prev = (previous_developments or "").strip()
|
||||
if not new_articles:
|
||||
return (prev or None), 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)"
|
||||
|
||||
prompt = LATEST_DEVELOPMENTS_PROMPT_TEMPLATE.format(
|
||||
title=title,
|
||||
description=description or "Keine weiteren Details",
|
||||
previous_developments=prev_block,
|
||||
new_articles_text=new_articles_text,
|
||||
today=today,
|
||||
output_language=OUTPUT_LANGUAGE,
|
||||
)
|
||||
|
||||
try:
|
||||
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
|
||||
|
||||
bullets = self._parse_latest_developments(result)
|
||||
if not bullets:
|
||||
logger.info("Latest-Developments: keine Bullets geparst, behalte bisherigen Stand")
|
||||
return (prev or None), usage
|
||||
|
||||
bullets = bullets[:8]
|
||||
output = "\n".join(bullets)
|
||||
logger.info(f"Latest-Developments: {len(bullets)} Bullets generiert")
|
||||
return output, usage
|
||||
|
||||
@staticmethod
|
||||
def _parse_latest_developments(text: str) -> list[str]:
|
||||
"""Extrahiert '- [DD.MM. HH:MM] ...'-Zeilen aus der Claude-Antwort."""
|
||||
if not text:
|
||||
return []
|
||||
bullets: list[str] = []
|
||||
bullet_re = re.compile(r"^\s*[-*•]\s*\[(\d{1,2}\.\d{1,2}\.(?:\d{2,4})?\s+\d{1,2}:\d{2})\]\s*(.+?)\s*$")
|
||||
for raw_line in text.splitlines():
|
||||
line = raw_line.strip()
|
||||
if not line:
|
||||
continue
|
||||
m = bullet_re.match(line)
|
||||
if m:
|
||||
ts = m.group(1)
|
||||
body = m.group(2).rstrip()
|
||||
bullets.append(f"- [{ts}] {body}")
|
||||
return bullets
|
||||
|
||||
def _sanitize_sources(self, analysis: dict) -> dict:
|
||||
"""Entfernt Buchstaben-Suffixe aus Quellennummern (z.B. '1383a' -> 1383).
|
||||
|
||||
|
||||
@@ -674,6 +674,7 @@ class AgentOrchestrator:
|
||||
tenant_id = incident["tenant_id"] if "tenant_id" in incident.keys() else None
|
||||
previous_summary = incident["summary"] or ""
|
||||
previous_sources_json = incident["sources_json"] if "sources_json" in incident.keys() else None
|
||||
previous_developments = incident["latest_developments"] if "latest_developments" in incident.keys() else None
|
||||
|
||||
# Bei Retry: vorherigen running-Eintrag als error markieren
|
||||
if retry_count > 0:
|
||||
@@ -1233,6 +1234,25 @@ class AgentOrchestrator:
|
||||
# Cancel-Check nach paralleler Verarbeitung
|
||||
self._check_cancelled(incident_id)
|
||||
|
||||
# --- Neueste Entwicklungen (nur Live-Monitoring / adhoc) ---
|
||||
if incident_type == "adhoc" and new_articles_for_analysis:
|
||||
try:
|
||||
dev_analyzer = AnalyzerAgent()
|
||||
dev_text, dev_usage = await dev_analyzer.generate_latest_developments(
|
||||
title, description, new_articles_for_analysis, previous_developments,
|
||||
)
|
||||
if dev_usage:
|
||||
usage_acc.add(dev_usage)
|
||||
if dev_text is not None:
|
||||
await db.execute(
|
||||
"UPDATE incidents SET latest_developments = ? WHERE id = ?",
|
||||
(dev_text, incident_id),
|
||||
)
|
||||
await db.commit()
|
||||
previous_developments = dev_text
|
||||
except Exception as e:
|
||||
logger.warning(f"Latest-Developments-Generator fehlgeschlagen: {e}")
|
||||
|
||||
# Cancel-Check nach Analyse+Faktencheck
|
||||
self._check_cancelled(incident_id)
|
||||
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren