Personalized brief: refill to full count when a boundary hides a highlight
When a reader's boundary (avoid-term, muted topic/flavor, pause) removes a brief item, top the highlights back up with other readable, boundary-respecting good news instead of showing fewer cards — so 'Highlights from Today' stays full and still honors what they don't want to see. (Reverses the earlier filter-down-only MVP, now that the count is fixed at seven.) - /api/brief: after filtering by prefs, refill from the accepted pool (same categorical SQL filters + avoid-terms) excluding already-shown items. - Shared _prefs_sql_kw helper for feed/replacement/brief filters. - Tests: refill stays full and respects mute + avoid-terms (89 total). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+31
-6
@@ -57,6 +57,18 @@ def get_conn():
|
||||
conn.close()
|
||||
|
||||
|
||||
def _prefs_sql_kw(fp, now) -> dict:
|
||||
"""Categorical prefs → queries.feed keyword filters (avoid-terms stay Python)."""
|
||||
return dict(
|
||||
include_topics=fp.include_topics or None,
|
||||
include_flavors=fp.include_flavors or None,
|
||||
mute_topics=list(fp.muted_topics(now)) or None,
|
||||
mute_flavors=list(fp.muted_flavors(now)) or None,
|
||||
max_cortisol=fp.max_cortisol,
|
||||
max_ragebait=fp.max_ragebait,
|
||||
)
|
||||
|
||||
|
||||
def _pick_lead(items: list[dict]) -> list[dict]:
|
||||
"""Lead with a gentle, readable, ideally illustrated story.
|
||||
|
||||
@@ -303,15 +315,28 @@ def create_app() -> FastAPI:
|
||||
prefs: str | None = Query(None),
|
||||
) -> BriefResponse:
|
||||
fp = prefs_from_json(prefs)
|
||||
now = datetime.now(timezone.utc)
|
||||
with get_conn() as conn:
|
||||
data = queries.brief(conn, brief_date=date, limit=limit)
|
||||
items = data["items"]
|
||||
if not fp.is_empty():
|
||||
# MVP: filter the stored brief DOWN; no refill from outside the brief.
|
||||
# Runs before hero selection, so personal avoid-terms take precedence.
|
||||
items = filter_articles(items, fp, datetime.now(timezone.utc))
|
||||
items = data["items"]
|
||||
if not fp.is_empty():
|
||||
# Apply personal boundaries (avoid-terms take precedence over curation).
|
||||
items = filter_articles(items, fp, now)
|
||||
# Keep the highlights full: if a boundary hid a story, top up with
|
||||
# other readable, boundary-respecting good news rather than show fewer.
|
||||
if len(items) < limit:
|
||||
have = {a["id"] for a in items}
|
||||
pool = queries.feed(
|
||||
conn, accepted_only=True, limit=limit * 5 + 20, offset=0, **_prefs_sql_kw(fp, now)
|
||||
)
|
||||
for a in filter_articles(pool, fp, now):
|
||||
if len(items) >= limit:
|
||||
break
|
||||
if a["id"] not in have:
|
||||
items.append(a)
|
||||
have.add(a["id"])
|
||||
# Lead with a gentle, readable story (charged or paywalled stories stay
|
||||
# in the five, just not as the first thing seen).
|
||||
# in the set, just not as the first thing seen).
|
||||
items = _pick_lead(items)
|
||||
return BriefResponse(
|
||||
brief_date=data["brief_date"],
|
||||
|
||||
Reference in New Issue
Block a user