feat(recency): Frische-Suchfeed (when:14d) + Aktualitaets-Score
Damit die Pipeline das aktuelle Bild einfaengt, nicht nur das relevanteste (oft Monate alt). Bei der Test-Lage Qilin war der neueste Artikel 7 Wochen alt, die Masse 6-7 Monate — weil Google-News-Volltextsuche nach Relevanz rankt, nicht nach Datum. - build_news_search_feeds: neuer Parameter recency_days. Wenn gesetzt, wird der Google-News-Operator "when:Nd" an die Query gehaengt — der Feed liefert nur Artikel der letzten N Tage. Eigene Domain-Gruppe '...-recent'. - orchestrator._rss_pipeline: baut jetzt ZWEI Suchfeed-Saetze — einen Kontext-Feed (alle Zeiten) und einen Frische-Feed (when:14d). Beide laufen durch dieselbe Pipeline, Dedup entfernt Ueberschneidungen. - rss_parser._fetch_feed: relevance_score bekommt einen Aktualitaets-Bonus (<=3d +0.35, <=14d +0.20, <=60d +0.05) bzw. -Malus (>180d -0.15, >365d -0.30). Damit ueberleben frische Artikel den Domain-Cap statt von alten verdraengt zu werden. Nur adhoc-Pfad betroffen — research-Lagen ueberspringen die RSS-Pipeline ohnehin und behalten ihre volle historische Tiefe. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -932,11 +932,21 @@ class AgentOrchestrator:
|
||||
_gnews_langs = list(source_lang_whitelist)
|
||||
else:
|
||||
_gnews_langs = list({output_language_iso, research_language_iso})
|
||||
# Zwei Sets: ein Kontext-Feed (alle Zeiten) + ein Frische-Feed
|
||||
# (when:14d). Der Frische-Feed garantiert, dass das aktuelle
|
||||
# Bild eingefangen wird, auch wenn aeltere Artikel relevanter
|
||||
# ranken. Beide laufen durch dieselbe Pipeline; Dedup entfernt
|
||||
# Ueberschneidungen.
|
||||
_gnews_feeds = build_news_search_feeds(keywords, _gnews_langs)
|
||||
if _gnews_feeds:
|
||||
logger.info(f"Google-News-Suchfeeds ergaenzt: {len(_gnews_feeds)}")
|
||||
_gnews_recent = build_news_search_feeds(keywords, _gnews_langs, recency_days=14)
|
||||
_all_gnews = _gnews_feeds + _gnews_recent
|
||||
if _all_gnews:
|
||||
logger.info(
|
||||
f"Google-News-Suchfeeds ergaenzt: {len(_gnews_feeds)} Kontext "
|
||||
f"+ {len(_gnews_recent)} Frische (when:14d)"
|
||||
)
|
||||
articles = await rss_parser.search_feeds_selective(
|
||||
title, selected_feeds + _gnews_feeds, keywords=keywords,
|
||||
title, selected_feeds + _all_gnews, keywords=keywords,
|
||||
)
|
||||
else:
|
||||
articles = await rss_parser.search_feeds(title, international=international, tenant_id=tenant_id, keywords=keywords, user_id=user_id)
|
||||
|
||||
@@ -30,6 +30,7 @@ def build_news_search_feeds(
|
||||
keywords_by_lang: dict | list | None,
|
||||
languages: list[str],
|
||||
max_keywords: int = 4,
|
||||
recency_days: int | None = None,
|
||||
) -> list[dict]:
|
||||
"""Baut dynamische Google-News-Volltext-Such-Feeds pro Sprache.
|
||||
|
||||
@@ -44,6 +45,9 @@ def build_news_search_feeds(
|
||||
keywords_by_lang: Sprach-Dict {iso: [keyword,...]} aus der Keyword-Extraktion.
|
||||
languages: ISO-Codes, fuer die ein Suchfeed gebaut werden soll.
|
||||
max_keywords: wie viele (spezifischste) Keywords in die Such-Query gehen.
|
||||
recency_days: wenn gesetzt, wird der Google-News-Operator "when:Nd" an die
|
||||
Query gehaengt — der Feed liefert dann nur Artikel der letzten N Tage.
|
||||
Fuer "Frische-Suchfeeds", die das aktuelle Bild garantiert einfangen.
|
||||
|
||||
Returns:
|
||||
Liste von Feed-Config-Dicts (kompatibel mit RSSParser._fetch_feed).
|
||||
@@ -88,28 +92,38 @@ def build_news_search_feeds(
|
||||
if not deduped:
|
||||
continue
|
||||
query = " ".join(deduped)
|
||||
if not query or query in seen_queries:
|
||||
# when:Nd-Operator anhaengen (Google-News-Zeitfilter)
|
||||
effective_query = query
|
||||
if recency_days and recency_days > 0:
|
||||
effective_query = f"{query} when:{recency_days}d"
|
||||
if not effective_query or effective_query in seen_queries:
|
||||
continue
|
||||
seen_queries.add(query)
|
||||
seen_queries.add(effective_query)
|
||||
|
||||
hl, gl = locale
|
||||
ceid_lang = hl.split("-")[0]
|
||||
url = (
|
||||
"https://news.google.com/rss/search?q="
|
||||
+ urllib.parse.quote(query)
|
||||
+ urllib.parse.quote(effective_query)
|
||||
+ f"&hl={hl}&gl={gl}&ceid={gl}:{ceid_lang}"
|
||||
)
|
||||
if recency_days and recency_days > 0:
|
||||
name = f"Google News Suche ({lang_key}, letzte {recency_days}d): {query}"
|
||||
domain = f"google-news-search-{lang_key}-recent"
|
||||
else:
|
||||
name = f"Google News Suche ({lang_key}): {query}"
|
||||
domain = f"google-news-search-{lang_key}"
|
||||
feeds.append({
|
||||
"name": f"Google News Suche ({lang_key}): {query}",
|
||||
"name": name,
|
||||
"url": url,
|
||||
# Eigene Domain-Gruppe, damit der Domain-Cap die Such-Feeds NICHT mit
|
||||
# den site:-Google-News-Feeds in einen Topf wirft.
|
||||
"domain": f"google-news-search-{lang_key}",
|
||||
"domain": domain,
|
||||
"primary_language": lang_key,
|
||||
"category": "international",
|
||||
"media_type": "",
|
||||
})
|
||||
logger.info("Google-News-Suchfeed (%s): q=%r", lang_key, query)
|
||||
logger.info("Google-News-Suchfeed (%s): q=%r", lang_key, effective_query)
|
||||
return feeds
|
||||
|
||||
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren