feat(articles): headline_en persistieren + Sprache aus Quell-Konfig übernehmen

Zwei Lücken beim Befund Lage 96 (Verfassungsänderung Japan): die japanische
Asahi-Shimbun-Quelle wurde durch das Sprach-aware Keyword-Matching (#27) und
Pre-Topic-Translate (#28) erstmals durchgereicht, landete aber mit
language='en' und ohne englische Headline in der DB. Damit ist sie im
Frontend nur als Kanji-Headline zu lesen und das Summary-LLM kann den
Treffer nicht aussagekräftig referenzieren.

1. INSERT INTO articles erweitert um headline_en und content_en. Werte
   stammen primär vom Translator (headline_en, falls TRANSLATOR_ENABLED den
   Pfad einmal in Englisch befüllt), Fallback auf die für den Topic-Filter
   angefertigte Mini-Übersetzung (headline_en_for_topic /
   content_en_for_topic). So liegt die englische Variante dauerhaft in der
   DB statt nur während des Refresh-Laufs im Speicher.

2. RSS- und Telegram-Parser setzen 'language' nun primär aus der Quell-/
   Kanal-Konfiguration (primary_language). Vorher war es hart 'de' wenn die
   Headline deutsch wirkte, sonst 'en' - mit dem Resultat, dass ein
   Kanji-Titel als language='en' landete. Mit dem Fix bekommen Asahi & Co.
   korrekt language='ja', russische Telegram-Kanäle 'ru' etc.

- src/agents/orchestrator.py: INSERT erweitert, Kommentar zur Fallback-Logik
- src/feeds/rss_parser.py: language aus feed_config.primary_language
- src/feeds/telegram_parser.py: channel_lang durch _fetch_channel reichen

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-05-21 17:05:47 +00:00
Ursprung 4b193d5784
Commit a41c8ae529
3 geänderte Dateien mit 26 neuen und 7 gelöschten Zeilen

Datei anzeigen

@@ -229,7 +229,13 @@ class RSSParser:
"source_domain": feed_config.get("domain") or "",
"content_original": summary[:1000] if summary else None,
"content_de": summary[:1000] if summary and self._is_german(summary) else None,
"language": "de" if self._is_german(title) else "en",
# Sprache primär aus der Quell-Konfiguration übernehmen
# (z.B. "ja" für Asahi Shimbun, "ru" für TASS). Nur wenn
# die Quelle kein primary_language gesetzt hat, auf die
# alte de/en-Heuristik zurückfallen. Sonst landen
# CJK/kyrillische Headlines fälschlich als language="en"
# und verlieren Pre-Topic-Übersetzung + Translator-Pfad.
"language": feed_config.get("primary_language") or ("de" if self._is_german(title) else "en"),
"published_at": published,
"relevance_score": relevance_score,
})

Datei anzeigen

@@ -104,7 +104,7 @@ class TelegramParser:
search_words = [w.lower() for w in search_words]
else:
search_words = fallback_words or []
tasks.append(self._fetch_channel(client, channel_id, search_words))
tasks.append(self._fetch_channel(client, channel_id, search_words, channel_lang=channel_lang))
results = await asyncio.gather(*tasks, return_exceptions=True)
@@ -150,7 +150,7 @@ class TelegramParser:
return []
async def _fetch_channel(self, client, channel_id: str, search_words: list[str],
limit: int = 50) -> list[dict]:
limit: int = 50, channel_lang: str | None = None) -> list[dict]:
"""Letzte N Nachrichten eines Kanals abrufen und nach Keywords filtern."""
articles = []
try:
@@ -217,7 +217,10 @@ class TelegramParser:
"source_url": source_url,
"content_original": content[:2000],
"content_de": content[:2000] if self._is_german(content) else None,
"language": "de" if self._is_german(content) else "en",
# Sprache primär aus der Kanal-Konfiguration übernehmen
# (z.B. "ru" für russische Kanäle). Sonst Fallback auf die
# de/en-Heuristik. Symmetrisch zur RSS-Pfad-Logik.
"language": channel_lang or ("de" if self._is_german(content) else "en"),
"published_at": published,
"relevance_score": relevance_score,
})