feat(briefing): cluster-level preference filtering — rank themes by user interest, suppress disliked topics
This commit is contained in:
@@ -652,8 +652,9 @@ def _unified_user_prompt(internal_data: dict, external_data: dict, slot: str, te
|
||||
lines.extend(f" - {p}" for p in internal_data["stale_projects"])
|
||||
lines.append("")
|
||||
|
||||
# News — clustered by topic for thematic synthesis
|
||||
# News — clustered by topic, ranked by preference score
|
||||
rss = external_data.get("rss_items") or []
|
||||
topic_scores = external_data.get("topic_scores") or {}
|
||||
if rss:
|
||||
# Group articles by their primary topic
|
||||
clusters: dict[str, list[dict]] = {}
|
||||
@@ -666,19 +667,34 @@ def _unified_user_prompt(internal_data: dict, external_data: dict, slot: str, te
|
||||
else:
|
||||
uncategorized.append(item)
|
||||
|
||||
# Sort clusters by size (most articles = most active theme)
|
||||
sorted_clusters = sorted(clusters.items(), key=lambda x: len(x[1]), reverse=True)
|
||||
# Score each cluster by aggregate preference (positive = user likes this topic)
|
||||
def cluster_score(topic: str, items: list[dict]) -> float:
|
||||
pref = topic_scores.get(topic, 0.0)
|
||||
return pref * 2.0 + len(items) # preference-weighted, size as tiebreak
|
||||
|
||||
# Filter out clusters where the user has a strongly negative preference
|
||||
filtered_clusters = [
|
||||
(topic, items) for topic, items in clusters.items()
|
||||
if topic_scores.get(topic, 0.0) >= -2.0
|
||||
]
|
||||
|
||||
# Sort by preference-weighted score
|
||||
sorted_clusters = sorted(filtered_clusters, key=lambda x: cluster_score(x[0], x[1]), reverse=True)
|
||||
|
||||
lines.append("NEWS THEMES (synthesize 1-2 themes into your briefing; mention article count per theme):")
|
||||
for topic, items in sorted_clusters[:4]:
|
||||
for i, (topic, items) in enumerate(sorted_clusters[:4]):
|
||||
count = len(items)
|
||||
titles = [i.get("title", "") for i in items[:3]]
|
||||
sources = list({i.get("feed_title") or i.get("source") or "News" for i in items})
|
||||
lines.append(f" [{topic.title()}] ({count} article{'s' if count != 1 else ''} from {', '.join(sources[:2])})")
|
||||
titles = [it.get("title", "") for it in items[:3]]
|
||||
sources = list({it.get("feed_title") or it.get("source") or "News" for it in items})
|
||||
pref_indicator = ""
|
||||
pref = topic_scores.get(topic, 0.0)
|
||||
if pref >= 2.0:
|
||||
pref_indicator = " ★" # user's preferred topic
|
||||
lines.append(f" [{topic.title()}]{pref_indicator} ({count} article{'s' if count != 1 else ''} from {', '.join(sources[:2])})")
|
||||
for t in titles:
|
||||
lines.append(f" • {t}")
|
||||
# Include one excerpt for the top cluster
|
||||
if items == sorted_clusters[0][1]:
|
||||
# Include excerpt for preferred clusters or the top cluster
|
||||
if pref >= 1.0 or i == 0:
|
||||
excerpt = (items[0].get("content") or items[0].get("snippet") or "")[:400].strip()
|
||||
if excerpt:
|
||||
lines.append(f" > {excerpt}")
|
||||
@@ -793,6 +809,7 @@ async def run_compilation(
|
||||
external_data_filtered = {
|
||||
"rss_items": filtered_rss,
|
||||
"weather": [],
|
||||
"topic_scores": topic_scores,
|
||||
}
|
||||
|
||||
briefing_text = await _llm_synthesise(
|
||||
|
||||
Reference in New Issue
Block a user