Durability pass: tests, clearer diversity/classify behavior, Calm Filters foundation
- Add pytest suite (34 tests) covering scoring thresholds, dedup clustering + representative selection + time window, brief source/category diversity, avoid-term phrase matching, and text canonicalization/truncation. - Rewrite _select_diverse with an explicit, tested contract (best-first, one per source, backfill, then inject a second category by evicting the lowest-ranked pick). - classify_articles now returns attempted/succeeded/skipped (ClassifyReport) so silent model failures are visible in both the cycle and classify output. - Fix clean_text truncation to stay within max_len (ellipsis no longer overshoots). - New filters.py: canonical FilterPrefs shape (include/mute topics+flavors, avoid_terms, pauses) and pure word/phrase-boundary matching engine seeding Calm Filters. Not yet wired into the API. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+22
-11
@@ -141,21 +141,30 @@ def _candidate_articles(
|
||||
|
||||
|
||||
def _select_diverse(rows: list[sqlite3.Row], limit: int) -> list[sqlite3.Row]:
|
||||
selected = []
|
||||
seen_sources = set()
|
||||
seen_categories = set()
|
||||
"""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).
|
||||
"""
|
||||
selected: list[sqlite3.Row] = []
|
||||
seen_sources: set = set()
|
||||
|
||||
# Pass 1: best-first, one per source.
|
||||
for row in rows:
|
||||
if len(selected) >= limit:
|
||||
break
|
||||
source = row["source_name"]
|
||||
category = row["default_category"]
|
||||
if source in seen_sources and len(rows) > limit:
|
||||
if row["source_name"] in seen_sources:
|
||||
continue
|
||||
selected.append(row)
|
||||
seen_sources.add(source)
|
||||
seen_categories.add(category)
|
||||
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}
|
||||
for row in rows:
|
||||
@@ -166,13 +175,15 @@ def _select_diverse(rows: list[sqlite3.Row], limit: int) -> list[sqlite3.Row]:
|
||||
selected.append(row)
|
||||
selected_ids.add(row["id"])
|
||||
|
||||
if len(seen_categories) < 2 and len(rows) > limit:
|
||||
# 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 seen_categories:
|
||||
selected[-1] = row
|
||||
if row["default_category"] not in categories:
|
||||
selected[-1] = row # evict the lowest-ranked selected item
|
||||
break
|
||||
|
||||
return selected
|
||||
|
||||
Reference in New Issue
Block a user