68a401eed6
Per the agreed model: the brief is server-authoritative and a client Replace is a soft override that yields when genuinely new data arrives. - build_daily_brief is now idempotent: if the composed selection is unchanged it leaves the brief (and its created_at) alone, so the timer's 15-min rebuilds are no-ops when no new data landed. - /api/brief exposes generated_at (the brief's created_at = a content-change stamp). The client pins its view against generated_at and keeps it across plain refreshes, but drops it and shows the fresh server brief when generated_at advances. Missed stories remain in the mood feeds. Tests: idempotent rebuild (no-op vs content change) — 93 total. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
46 lines
2.2 KiB
Python
46 lines
2.2 KiB
Python
from datetime import date
|
|
|
|
from goodnews.db import connect, init_db
|
|
from goodnews.briefs import build_daily_brief
|
|
|
|
|
|
def _setup():
|
|
c = connect(":memory:"); init_db(c)
|
|
c.execute("INSERT INTO sources (id,name,feed_url,trust_score) VALUES (1,'S','http://s/f',5)")
|
|
today = date.today().isoformat()
|
|
for i, t in enumerate(["environment", "animals", "community", "culture", "science", "health", "environment"], 1):
|
|
c.execute("INSERT INTO articles (id,source_id,canonical_url,title,published_at,url_hash) VALUES (?,1,?,?,?,?)",
|
|
(i, f"http://s/{i}", f"t{i}", today + "T10:00:00+00:00", f"h{i}"))
|
|
c.execute("INSERT INTO article_scores (article_id,constructive_score,agency_score,human_benefit_score,"
|
|
"cortisol_score,ragebait_score,pr_risk_score,accepted,topic,flavor) VALUES (?,5,2,2,1,0,2,1,?,'solution')",
|
|
(i, t))
|
|
c.commit()
|
|
return c, today
|
|
|
|
|
|
def _created_at(c, day):
|
|
return c.execute("SELECT created_at FROM daily_briefs WHERE brief_date=?", (day,)).fetchone()[0]
|
|
|
|
|
|
def test_rebuild_with_same_selection_is_noop():
|
|
c, today = _setup()
|
|
build_daily_brief(c, brief_date=today, limit=7, replace=True)
|
|
before = _created_at(c, today)
|
|
build_daily_brief(c, brief_date=today, limit=7, replace=True) # nothing new
|
|
assert _created_at(c, today) == before # freshness stamp preserved
|
|
c.close()
|
|
|
|
|
|
def test_new_higher_ranked_article_changes_the_brief():
|
|
c, today = _setup()
|
|
build_daily_brief(c, brief_date=today, limit=7, replace=True)
|
|
c.execute("INSERT INTO articles (id,source_id,canonical_url,title,published_at,url_hash) VALUES (99,1,'http://s/99','t99',?,'h99')",
|
|
(today + "T11:00:00+00:00",))
|
|
c.execute("INSERT INTO article_scores (article_id,constructive_score,agency_score,human_benefit_score,"
|
|
"cortisol_score,ragebait_score,pr_risk_score,accepted,topic,flavor) VALUES (99,9,3,3,0,0,1,1,'animals','solution')")
|
|
c.commit()
|
|
build_daily_brief(c, brief_date=today, limit=7, replace=True)
|
|
ids = [r[0] for r in c.execute("SELECT article_id FROM daily_brief_items bi JOIN daily_briefs b ON b.id=bi.brief_id WHERE b.brief_date=?", (today,))]
|
|
assert 99 in ids
|
|
c.close()
|