Two Ways to Be Explainable
You can build a ranker and then add explanations to it, or you can build a ranker whose structure is the explanation. These sound similar and are not. The first produces a plausible story generated alongside the score — and a story that is generated separately can drift from the thing it describes, which is worse than no story at all. The second constrains the score to be a sum of named terms, so the explanation is not produced, it is read off.
For a personal reader the second is not a sacrifice. The interesting question is never "could a bigger model rank these better?" — it is "why is this on my shelf, and how do I make it stop?" A sum of named contributions answers both. A learned reranker answers neither, and buys accuracy nobody asked for on a corpus of a few hundred visible cards.
What the Structure Looks Like
Features are strings with namespaces — the publisher, the source that delivered it, and a bounded set of keyword tokens from the title and summary. The profile maps those strings to weights. An article's score is the sum, over its own features, of the profile weight for each, plus a freshness term. That is the whole model, and each addend is nameable.
Because it is a sum of named terms, the top few contributions are the explanation. The card can say it matched a subject you have been saving, or that it came from a publisher you keep opening, and that sentence is not a narrative about the score — it is the largest terms in it.
One caveat, and it is this lesson's own warning turned on itself: in the shipped scorer the freshness term is added to the total but is not among the contributions the card reports. So a card with any affinity match at all is ranked partly by recency and never says so — only a card with no contributions whatsoever falls back to reporting bare freshness. The structure makes drift impossible only for the terms it actually collects, and a term added outside that list is exactly the gap the approach is supposed to close. Collect every addend, or the guarantee is a habit rather than a property.
Cap the Match Count, and Notice What the Cap Implies
One detail that looks like tuning and is actually structural: an article may bank only a limited number of keyword matches. Without a cap, a long article about a familiar subject accumulates matches until it dominates purely by surface area, which rewards verbosity rather than relevance.
The cap has a consequence worth holding on to, because a later lesson turns on it: if an article gets six slots, then which six matters enormously. Six slots spent on grammatical particles are six not spent on real subjects, and no amount of correct arithmetic downstream can recover them.
Say It in the Payload
The explanation has to travel with the article, computed at scoring time, or it will be reconstructed later by different code and be subtly wrong. Ship the top contributions as data on the row. Then the interface can render them, a debugging view can dump them, and — the part that matters most — the reader can disagree with a specific term rather than with the shelf as a whole.