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.extend(f" - {p}" for p in internal_data["stale_projects"])
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
# News — clustered by topic for thematic synthesis
|
# News — clustered by topic, ranked by preference score
|
||||||
rss = external_data.get("rss_items") or []
|
rss = external_data.get("rss_items") or []
|
||||||
|
topic_scores = external_data.get("topic_scores") or {}
|
||||||
if rss:
|
if rss:
|
||||||
# Group articles by their primary topic
|
# Group articles by their primary topic
|
||||||
clusters: dict[str, list[dict]] = {}
|
clusters: dict[str, list[dict]] = {}
|
||||||
@@ -666,19 +667,34 @@ def _unified_user_prompt(internal_data: dict, external_data: dict, slot: str, te
|
|||||||
else:
|
else:
|
||||||
uncategorized.append(item)
|
uncategorized.append(item)
|
||||||
|
|
||||||
# Sort clusters by size (most articles = most active theme)
|
# Score each cluster by aggregate preference (positive = user likes this topic)
|
||||||
sorted_clusters = sorted(clusters.items(), key=lambda x: len(x[1]), reverse=True)
|
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):")
|
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)
|
count = len(items)
|
||||||
titles = [i.get("title", "") for i in items[:3]]
|
titles = [it.get("title", "") for it in items[:3]]
|
||||||
sources = list({i.get("feed_title") or i.get("source") or "News" for i in items})
|
sources = list({it.get("feed_title") or it.get("source") or "News" for it in items})
|
||||||
lines.append(f" [{topic.title()}] ({count} article{'s' if count != 1 else ''} from {', '.join(sources[:2])})")
|
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:
|
for t in titles:
|
||||||
lines.append(f" • {t}")
|
lines.append(f" • {t}")
|
||||||
# Include one excerpt for the top cluster
|
# Include excerpt for preferred clusters or the top cluster
|
||||||
if items == sorted_clusters[0][1]:
|
if pref >= 1.0 or i == 0:
|
||||||
excerpt = (items[0].get("content") or items[0].get("snippet") or "")[:400].strip()
|
excerpt = (items[0].get("content") or items[0].get("snippet") or "")[:400].strip()
|
||||||
if excerpt:
|
if excerpt:
|
||||||
lines.append(f" > {excerpt}")
|
lines.append(f" > {excerpt}")
|
||||||
@@ -793,6 +809,7 @@ async def run_compilation(
|
|||||||
external_data_filtered = {
|
external_data_filtered = {
|
||||||
"rss_items": filtered_rss,
|
"rss_items": filtered_rss,
|
||||||
"weather": [],
|
"weather": [],
|
||||||
|
"topic_scores": topic_scores,
|
||||||
}
|
}
|
||||||
|
|
||||||
briefing_text = await _llm_synthesise(
|
briefing_text = await _llm_synthesise(
|
||||||
|
|||||||
Reference in New Issue
Block a user