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:
2026-05-22 02:32:55 +02:00
Ursprung 309c97f40a
Commit 86b12a156e
3 geänderte Dateien mit 53 neuen und 10 gelöschten Zeilen

Datei anzeigen

@@ -218,14 +218,33 @@ class RSSParser:
if match_count >= min_matches:
published = None
published_dt = None
if hasattr(entry, "published_parsed") and entry.published_parsed:
try:
published = datetime(*entry.published_parsed[:6], tzinfo=timezone.utc).astimezone(TIMEZONE).isoformat()
published_dt = datetime(*entry.published_parsed[:6], tzinfo=timezone.utc)
published = published_dt.astimezone(TIMEZONE).isoformat()
except (TypeError, ValueError):
pass
# Relevanz-Score: Anteil der gematchten Suchworte (0.0-1.0)
relevance_score = match_count / len(search_words) if search_words else 0.0
# Aktualitaets-Bonus/Malus: frische Artikel sollen den
# Domain-Cap (sortiert nach relevance_score) ueberleben und
# nicht von Monate alten verdraengt werden. Damit faengt die
# Pipeline das aktuelle Bild ein. Nur adhoc-Pfad — research
# nutzt diesen Code nicht.
if published_dt is not None:
age_days = (datetime.now(timezone.utc) - published_dt).days
if age_days <= 3:
relevance_score += 0.35
elif age_days <= 14:
relevance_score += 0.20
elif age_days <= 60:
relevance_score += 0.05
elif age_days > 365:
relevance_score -= 0.30
elif age_days > 180:
relevance_score -= 0.15
# Bei Google-News-Feeds: echten Publisher aus <source>-Tag holen
article_source = name