Digest v2: restrained "From what you follow" section
Per Codex — give follows an immediate payoff without making the email feel algorithmic. The editorial brief stays the star: subject + brief count unchanged. * followed_digest_items(conn, user_id, exclude_ids, limit=3): recent items from the user's followed sources/tags, same accepted/non-dup/content-visible gate, excludes anything in the brief, capped to one per source so a single follow can't dominate. Empty → section omitted (no empty state in email). * build_digest gains an optional `followed` list → a small "From what you follow" section AFTER the brief, only when there are items. Item rendering factored into shared _item_html / _item_text_lines helpers. * send_due_digests computes the followed items per user (excluding the brief). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -62,3 +62,30 @@ def test_send_respects_opt_out(monkeypatch, tmp_path):
|
||||
c.execute("UPDATE users SET digest_enabled=0 WHERE id=1"); c.commit()
|
||||
monkeypatch.setattr(digest, "local_today", lambda: date)
|
||||
assert digest.send_due_digests(c, force=True) == 0 and sent == []
|
||||
|
||||
|
||||
def test_followed_digest_items_caps_excludes_and_section(tmp_path):
|
||||
c = connect(str(tmp_path / "fd.db")); init_db(c)
|
||||
date = _seed(c, n=5) # brief sources/articles 1..5 + user 1
|
||||
# two NON-brief accepted articles from followed source 1 → cap should keep 1
|
||||
for aid in (10, 11):
|
||||
c.execute("INSERT INTO articles (id,source_id,canonical_url,title,url_hash) VALUES (?,1,?,?,?)",
|
||||
(aid, f"http://a/{aid}", f"Followed {aid}", f"h{aid}"))
|
||||
c.execute("INSERT INTO article_scores (article_id,accepted) VALUES (?,1)", (aid,))
|
||||
c.execute("INSERT INTO user_follows (user_id,kind,value) VALUES (1,'source','1')")
|
||||
c.commit()
|
||||
fol = digest.followed_digest_items(c, 1, exclude_ids=[1, 2, 3, 4, 5], limit=3)
|
||||
assert len(fol) == 1 and fol[0]["source_id"] == 1 # one-per-source cap, brief ids excluded
|
||||
assert fol[0]["id"] in (10, 11)
|
||||
# section appears with followed items, omitted without (brief stays the star)
|
||||
brief = [{"id": 1, "title": "B", "canonical_url": "http://b/1", "source": "S", "summary": "s", "reason_text": "r", "paywalled": False}]
|
||||
_, text, html = digest.build_digest(brief, "2026-06-09", "http://u", followed=fol)
|
||||
assert "From what you follow" in html and "From what you follow" in text
|
||||
_, _, html2 = digest.build_digest(brief, "2026-06-09", "http://u", followed=[])
|
||||
assert "From what you follow" not in html2
|
||||
|
||||
|
||||
def test_followed_digest_items_empty_when_no_follows(tmp_path):
|
||||
c = connect(str(tmp_path / "fd2.db")); init_db(c)
|
||||
_seed(c, n=5)
|
||||
assert digest.followed_digest_items(c, 1, exclude_ids=[], limit=3) == []
|
||||
|
||||
Reference in New Issue
Block a user