feat(x): X (Twitter) als Bezugsquelle pro Lage
X-Accounts werden analog zu Telegram als Quelle (source_type=x_account) konfiguriert und pro Lage ueber include_x zugeschaltet. Der Scraper (feeds/x_parser.py, twscrape) liest Account-Timelines, optional ueber einen HTTP-Proxy mit Fallback auf direkten Abruf ueber die Server-IP. - DB-Migration include_x, Pydantic-Modelle, incidents-Router - Orchestrator-X-Pipeline plus Haiku-Account-Vorselektion - sources-Router /x/validate, x_account-Typ in Stats und Frontend - Lage-Einstellungen: X-Toggle neben international und Telegram - twscrape als Abhaengigkeit Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -496,6 +496,24 @@ REGELN:
|
||||
Antworte NUR mit einem JSON-Array der Kanal-Nummern, z.B.: [1, 3, 5, 12]"""
|
||||
|
||||
|
||||
X_ACCOUNT_SELECTION_PROMPT = """Du bist ein OSINT-Analyst. Waehle aus dieser Liste von X-Accounts (Twitter) diejenigen aus, die fuer die Lage relevant sein koennten.
|
||||
|
||||
LAGE: {title}
|
||||
KONTEXT: {description}
|
||||
|
||||
X-ACCOUNTS:
|
||||
{account_list}
|
||||
|
||||
REGELN:
|
||||
- Waehle alle Accounts die thematisch relevant sein koennten
|
||||
- Lieber einen Account zu viel als zu wenig auswaehlen
|
||||
- Beachte die Kategorie und Beschreibung jedes Accounts
|
||||
- Allgemeine OSINT-Accounts sind oft relevant
|
||||
- Bei geopolitischen Themen: Relevante Laender-/Regions-Accounts waehlen
|
||||
|
||||
Antworte NUR mit einem JSON-Array der Account-Nummern, z.B.: [1, 3, 5, 12]"""
|
||||
|
||||
|
||||
class ResearcherAgent:
|
||||
"""Führt OSINT-Recherchen über Claude CLI WebSearch durch."""
|
||||
|
||||
@@ -1016,3 +1034,62 @@ class ResearcherAgent:
|
||||
logger.warning("Telegram-Selektion fehlgeschlagen (%s), nutze alle Kanaele", e)
|
||||
return channels_metadata, None
|
||||
|
||||
async def select_relevant_x_accounts(
|
||||
self,
|
||||
title: str,
|
||||
description: str,
|
||||
accounts_metadata: list[dict],
|
||||
) -> tuple[list[dict], ClaudeUsage | None]:
|
||||
"""Laesst Claude die relevanten X-Accounts fuer eine Lage vorauswaehlen.
|
||||
|
||||
Nutzt Haiku (CLAUDE_MODEL_FAST) fuer diese einfache Aufgabe.
|
||||
|
||||
Returns:
|
||||
(ausgewaehlte Accounts, usage) -- Bei Fehler: (alle Accounts, None)
|
||||
"""
|
||||
if len(accounts_metadata) <= 10:
|
||||
logger.info("X-Selektion: Nur %d Accounts, nutze alle", len(accounts_metadata))
|
||||
return accounts_metadata, None
|
||||
|
||||
account_lines = []
|
||||
for i, acc in enumerate(accounts_metadata, 1):
|
||||
cat = acc.get("category", "sonstige")
|
||||
notes = (acc.get("notes") or "")[:100]
|
||||
account_lines.append(f"{i}. {acc['name']} [{cat}] - {notes}")
|
||||
|
||||
prompt = X_ACCOUNT_SELECTION_PROMPT.format(
|
||||
title=title,
|
||||
description=description or "Keine weitere Beschreibung",
|
||||
account_list="\n".join(account_lines),
|
||||
)
|
||||
|
||||
try:
|
||||
result, usage = await call_claude(prompt, tools=None, model=CLAUDE_MODEL_FAST)
|
||||
|
||||
indices = _extract_json_array(result)
|
||||
if not isinstance(indices, list):
|
||||
logger.warning(
|
||||
"X-Selektion: Kein JSON in Antwort, nutze alle Accounts. Sample: %s",
|
||||
_truncate_for_log(result),
|
||||
)
|
||||
return accounts_metadata, usage
|
||||
|
||||
selected = []
|
||||
for idx in indices:
|
||||
if isinstance(idx, int) and 1 <= idx <= len(accounts_metadata):
|
||||
selected.append(accounts_metadata[idx - 1])
|
||||
|
||||
if not selected:
|
||||
logger.warning("X-Selektion: Keine gueltigen Indizes, nutze alle Accounts")
|
||||
return accounts_metadata, usage
|
||||
|
||||
logger.info(
|
||||
"X-Selektion: %d von %d Accounts ausgewaehlt",
|
||||
len(selected), len(accounts_metadata)
|
||||
)
|
||||
return selected, usage
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("X-Selektion fehlgeschlagen (%s), nutze alle Accounts", e)
|
||||
return accounts_metadata, None
|
||||
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren