Files
news/README.md
T
connor c89a723603 Seed the rewrite: profile, calibration, and repo scaffolding
Ground-up replacement for news_triage, whose signal quality failed because it
had no representation of what its user likes -- three keywords fed to BM25 plus
a two-term substring blocklist.

profile/ holds the taste model as prose and TOML: 11 interest blocks, 3 veto
blocks, 5 importance blocks, each a set of probe sentences to be embedded.

The [scoring] constants are fitted, not guessed. CALIBRATION.md records the
measurements against all 3,885 items in the v0.2 database and 40 real feedback
labels, including two findings that changed the design:

- compare blocks by z-score within block, never raw cosine; raw cosine let
  geopolitics win 78% of items and buried known-liked items at median rank 1246
  of 3885 (293 after normalizing)
- veto needs a margin over the interest signal, not a threshold; a plain 0.55
  cutoff falsely killed "Belgian mayor blocks club from hosting Israeli opponent
  in Europa League", which the old substring blocklist also killed
2026-09-08 14:34:56 -04:00

54 lines
2.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# news
A personal news aggregator: many sources in, two to five well-chosen push
notifications and one readable daily digest out.
This is a ground-up rewrite of [`news_triage`](../news_triage), which is still
running. The rewrite exists because the old system had no representation of what
its user actually likes — its whole preference model was three keywords fed to
BM25 plus a two-term substring blocklist, and everything else was ranking
machinery with nothing meaningful to rank.
## The core idea
Taste is a set of **vectors**, not a bag of keywords.
Every interest, veto and importance category in [`profile/profile.toml`](profile/profile.toml)
is written as prose and embedded with a small CPU model (`bge-small-en-v1.5`,
~130 MB, ONNX, no torch, no GPU — so it never contends with the llama-swap stack).
An item's score is the max cosine similarity across a block's probes, never the
mean: averaging "Middle East geopolitics" with "grid-scale energy storage" gives
a vector near neither.
This is what lets a topic be expressed as a *region* rather than a string. The
old system could not represent "sports" as a concept, only as the literal
characters `s-p-o-r-t-s`, which is why 18 of its 32 feedback clicks were its user
hand-rejecting baseball recaps that `blocklist = ["sports"]` never matched.
A generative LLM is used only for prose — the daily digest write-up and cluster
headlines — at roughly 1–30 calls/day through LiteLLM, never for per-item scoring.
## Two routing lanes
Notifications are decided on two independent axes, because "something I might
like" and "something I need to know about" are different questions:
- **interest** — high similarity to the profile's interest vectors.
- **importance** — burst velocity, independent-source-bucket count, editorial
prominence, and aggregator-native scores. Deliberately *not* raw corroboration
count, which just measures how many wire-reprinting outlets you subscribed to.
Vetoes are absolute on **both** lanes. In the old system the bypass lane ignored
relevance entirely, so a blocklisted item could still fire at priority 5.
## Layout
profile/ the user's taste, as prose and as TOML. Start here.
news/ the pipeline: sources, store, embed, cluster, score, route, deliver
tests/
deploy/
## Status
Early. `profile/` is written; the pipeline is not yet built.