Phase 3: two-axis scoring and routing
Scoring (news/score.py): every profile block is embedded and compared by
z-score WITHIN block, then weighted. Veto fires on absolute similarity OR a
margin over the interest signal; demotes apply a penalty instead of excluding,
because those categories sit adjacent to real interests.
Importance is deliberately NOT corroboration count -- the old system's error,
which measured the subscription list rather than the news. It combines block
similarity, corroboration, burst velocity, editorial prominence and aggregator
score, all weighted from config.
Routing (news/route.py): two lanes with separate budgets. Interest never
overrides quiet hours; importance may, within a hard nightly cap. Vetoes are
absolute on BOTH lanes -- the v0.2 bypass lane never consulted relevance, so a
blocklisted item could still fire at priority 5, and a test now asserts it
cannot. Budgets are spent strongest-first rather than first-come.
Measured on 1,318 real clusters: 2.5 notifications/day and a 14-item digest,
inside the 2-5/day target.
Three defects the real data exposed, none of which unit tests would have caught:
- velocity was computed as origins/hours with a 0.5h floor, giving every
SINGLE-source cluster a spurious velocity of 2.0. Growth rate is undefined
for a cluster that has not grown; it is now zero below two origins.
- deferred items (quiet hours, budget exhausted, immature) settled straight
into the digest and bypassed its cap, producing 198 items/day. Everything
that misses an alert lane now competes for one ranked, capped digest.
- feeds return large archives on first fetch -- 88 Strong Towns items going back
five months, and a BBC entry titled "BBC News app" dated 2025. Those belong in
the scoring population but must never notify, so routing now has a recency
requirement.
Reading the output also found two profile gaps: recurring columns and link
round-ups ("Catching Our Eye News Roundup", "Astronomy Picture of the Day")
scored as top-ranked interest items. They are on-topic but publish on a
schedule, so they are demoted rather than vetoed and still reach the digest.
This commit is contained in:
1 parent
a2c812794d
commit
23966a2d63
9 files changed
+809
-1
No files matched your search
@@ -73,6 +73,19 @@ corpus with a specific embedding model. **Changing `[embedding].model` invalidat
|
||||
both**, because a different model puts cosine similarity on a different scale.
|
||||
Re-run the calibration in `profile/CALIBRATION.md` before trusting them.
|
||||
|
||||
## Commands
|
||||
|
||||
news poll one fetch -> embed -> cluster cycle
|
||||
news sources sources and item counts
|
||||
news clusters inspect clusters and their corroboration
|
||||
news score score clusters on both axes
|
||||
news simulate what would these thresholds actually have sent?
|
||||
|
||||
`simulate` is the tuning surface. The old system's replay harness hardcoded its
|
||||
parameters and so could never sweep the thresholds it existed to tune.
|
||||
|
||||
## Status
|
||||
|
||||
Early. `profile/` is written; the pipeline is not yet built.
|
||||
Ingest, embedding, clustering, two-axis scoring and routing are built and
|
||||
running against live feeds. Delivery (ntfy, the digest page) and the web UI are
|
||||
not yet written.
|
||||
Reference in new issue
Block a user