Option A: typographic editorial tiles + single treated hero image; balance brief topics
Frontend (the premium baseline): - The hero is now the ONLY image slot. Soft feed images get an atmospheric gradient overlay; no over-reliance on inconsistent RSS image quality. - Every secondary/lane card is a uniform typographic editorial tile: no thumbnails, equal visual weight, a faint topic wordmark watermark, a slim sage top accent, consistent source, reason text as the trust signal, visible Replace with quiet tuning actions. Fixes the jarring mixed-media row rhythm and removes muddy thumbnails entirely. Backend (composition): - _select_diverse now balances topics: no more than 2 of one topic while other topics have candidates (relaxing source then topic caps only to fill), so the daily five stop clustering medical/science items. Candidates now carry s.topic. Tests updated for the topic-balance contract (79 total). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+23
-31
@@ -118,6 +118,8 @@ def _candidate_articles(
|
||||
s.reason_code,
|
||||
s.reason_text,
|
||||
s.model_name,
|
||||
s.topic,
|
||||
s.flavor,
|
||||
CASE WHEN date(COALESCE(a.published_at, a.discovered_at)) = date(?)
|
||||
THEN 1 ELSE 0 END AS is_today
|
||||
FROM articles a
|
||||
@@ -146,52 +148,42 @@ def _candidate_articles(
|
||||
).fetchall()
|
||||
|
||||
|
||||
def _select_diverse(rows: list[sqlite3.Row], limit: int) -> list[sqlite3.Row]:
|
||||
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).
|
||||
|
||||
Contract:
|
||||
1. Prefer higher-ranked items.
|
||||
2. Source diversity: take at most one item per source while other sources
|
||||
remain; only repeat a source once distinct sources are exhausted.
|
||||
3. Category diversity: if the result ended up single-category and a different
|
||||
category is available in the pool, swap in the highest-ranked off-category
|
||||
candidate by evicting the lowest-ranked currently-selected item (so we
|
||||
gain breadth without dropping a higher-ranked pick).
|
||||
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.
|
||||
"""
|
||||
selected: list[sqlite3.Row] = []
|
||||
selected_ids: set = set()
|
||||
seen_sources: set = set()
|
||||
topic_count: dict = {}
|
||||
|
||||
# Pass 1: best-first, one per source.
|
||||
for row in rows:
|
||||
if len(selected) >= limit:
|
||||
break
|
||||
if row["source_name"] in seen_sources:
|
||||
continue
|
||||
selected.append(row)
|
||||
seen_sources.add(row["source_name"])
|
||||
|
||||
# Pass 2: if short on distinct sources, backfill best-first regardless.
|
||||
if len(selected) < limit:
|
||||
selected_ids = {row["id"] for row in selected}
|
||||
def consider(enforce_source: bool, enforce_topic: bool) -> None:
|
||||
for row in rows:
|
||||
if len(selected) >= limit:
|
||||
break
|
||||
return
|
||||
if row["id"] in selected_ids:
|
||||
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:
|
||||
continue
|
||||
selected.append(row)
|
||||
selected_ids.add(row["id"])
|
||||
seen_sources.add(row["source_name"])
|
||||
topic_count[topic] = topic_count.get(topic, 0) + 1
|
||||
|
||||
# Pass 3: ensure >= 2 categories when the pool allows it.
|
||||
categories = {row["default_category"] for row in selected}
|
||||
if len(categories) < 2:
|
||||
selected_ids = {row["id"] for row in selected}
|
||||
for row in rows:
|
||||
if row["id"] in selected_ids:
|
||||
continue
|
||||
if row["default_category"] not in categories:
|
||||
selected[-1] = row # evict the lowest-ranked selected item
|
||||
break
|
||||
|
||||
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
|
||||
return selected
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user