Brief emotional-mix guardrails + source on its own line
Composition (Codex's priority — content mix was the louder problem): - _select_diverse now guards the daily five's emotional tone: at most 1 health, at most 2 science+health combined, at most 2 of any topic, distinct sources — so at least three of the five are community/culture/animals/environment when available. Caps relax (mix, then source) only to fill on thin days. - Verified live: today's five went to environment x2, health, animals, science. UI: - Source moved to its own line below the tags, left-justified, for uniform rhythm across hero and tiles (was sometimes trailing the tags, right-aligned). - Watermark kept as-is (intentionally subtle; liked). Tests updated for the emotional-mix contract (80 total). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+36
-20
@@ -148,24 +148,44 @@ def _candidate_articles(
|
||||
).fetchall()
|
||||
|
||||
|
||||
def _select_diverse(rows: list[sqlite3.Row], limit: int, max_per_topic: int = 2) -> list[sqlite3.Row]:
|
||||
"""Pick up to `limit` items from `rows` (already ranked best-first).
|
||||
def _select_diverse(rows: list[sqlite3.Row], limit: int) -> list[sqlite3.Row]:
|
||||
"""Pick up to `limit` items for the daily brief (rows ranked best-first).
|
||||
|
||||
Contract:
|
||||
1. Prefer higher-ranked items.
|
||||
2. Source diversity: at most one item per source while other sources remain.
|
||||
3. Topic balance: no more than `max_per_topic` of the same topic while other
|
||||
topics still have candidates — so the five don't cluster (e.g. several
|
||||
medical/science items) when community/culture/animals/environment exist.
|
||||
4. Always fill to `limit` when enough candidates exist: the source and topic
|
||||
caps are relaxed (in that order) only as needed to reach the count.
|
||||
The daily five should feel like *good news*, not a research digest, so the
|
||||
emotional mix is guarded — not just topic count:
|
||||
- at most 1 health item,
|
||||
- at most 2 science+health items combined,
|
||||
- at most 2 of any single topic,
|
||||
- distinct sources.
|
||||
Because science/health are capped at 2 combined, at least three of the five
|
||||
are community/culture/animals/environment whenever those exist — so the page
|
||||
leads with breadth, not clustered medical/science breakthroughs.
|
||||
|
||||
Caps are relaxed (topic first, then source) only as needed to still fill the
|
||||
count on thin days; we never return fewer when candidates exist.
|
||||
"""
|
||||
selected: list[sqlite3.Row] = []
|
||||
selected_ids: set = set()
|
||||
seen_sources: set = set()
|
||||
topic_count: dict = {}
|
||||
|
||||
def consider(enforce_source: bool, enforce_topic: bool) -> None:
|
||||
def add(row: sqlite3.Row) -> None:
|
||||
selected.append(row)
|
||||
selected_ids.add(row["id"])
|
||||
seen_sources.add(row["source_name"])
|
||||
topic_count[row["topic"]] = topic_count.get(row["topic"], 0) + 1
|
||||
|
||||
def emotional_mix_ok(row: sqlite3.Row) -> bool:
|
||||
topic = row["topic"]
|
||||
health = topic_count.get("health", 0)
|
||||
science = topic_count.get("science", 0)
|
||||
if topic == "health" and health >= 1:
|
||||
return False
|
||||
if topic in ("science", "health") and (science + health) >= 2:
|
||||
return False
|
||||
return topic_count.get(topic, 0) < 2
|
||||
|
||||
def fill(enforce_mix: bool, enforce_source: bool) -> None:
|
||||
for row in rows:
|
||||
if len(selected) >= limit:
|
||||
return
|
||||
@@ -173,17 +193,13 @@ def _select_diverse(rows: list[sqlite3.Row], limit: int, max_per_topic: int = 2)
|
||||
continue
|
||||
if enforce_source and row["source_name"] in seen_sources:
|
||||
continue
|
||||
topic = row["topic"]
|
||||
if enforce_topic and topic_count.get(topic, 0) >= max_per_topic:
|
||||
if enforce_mix and not emotional_mix_ok(row):
|
||||
continue
|
||||
selected.append(row)
|
||||
selected_ids.add(row["id"])
|
||||
seen_sources.add(row["source_name"])
|
||||
topic_count[topic] = topic_count.get(topic, 0) + 1
|
||||
add(row)
|
||||
|
||||
consider(enforce_source=True, enforce_topic=True) # distinct source, topic-balanced
|
||||
consider(enforce_source=True, enforce_topic=False) # relax topic cap to fill
|
||||
consider(enforce_source=False, enforce_topic=False) # relax source too, last resort
|
||||
fill(enforce_mix=True, enforce_source=True) # balanced mix, distinct sources
|
||||
fill(enforce_mix=False, enforce_source=True) # relax the mix caps to fill
|
||||
fill(enforce_mix=False, enforce_source=False) # relax source too, last resort
|
||||
return selected
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user