Article pages: structured "Why it belongs" editorial read

Per Codex — make /a/<id> feel like Upbeat Bytes has editorial judgment, not just
a summary wrapper. Trust-building, short, not an essay.

* article_summaries gains what_happened / why_matters / why_belongs (+ migration).
* summarize.explain_article: a separate, fallback-able LLM pass producing three
  short notes (parsed from a labelled WHAT/MATTERS/BELONGS format). generate_summary
  now stores them alongside the summary, and tops up older summaries on next view.
  get_explanation returns them only when all three are present.
* API: share_page + /api/summary expose the explanation.
* share.py: renders the three-part section (accent rule) when complete; otherwise
  the single "Why it's here" reason line is the calm fallback. The page polls and
  swaps in both the summary and the section as they cache.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-09 20:05:26 -04:00
parent 9befbffd94
commit 337dc3f901
6 changed files with 192 additions and 21 deletions
+9
View File
@@ -208,6 +208,9 @@ CREATE TABLE IF NOT EXISTS user_prefs (
CREATE TABLE IF NOT EXISTS article_summaries (
article_id INTEGER PRIMARY KEY REFERENCES articles(id) ON DELETE CASCADE,
summary TEXT NOT NULL,
what_happened TEXT,
why_matters TEXT,
why_belongs TEXT,
model TEXT,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
@@ -363,3 +366,9 @@ def _migrate(conn: sqlite3.Connection) -> None:
rep_cols = {row["name"] for row in conn.execute("PRAGMA table_info(feedback_replies)")}
if rep_cols and "message_html" not in rep_cols:
conn.execute("ALTER TABLE feedback_replies ADD COLUMN message_html TEXT")
# article_summaries: structured "Why it belongs" fields added later.
sum_cols = {row["name"] for row in conn.execute("PRAGMA table_info(article_summaries)")}
for column in ("what_happened", "why_matters", "why_belongs"):
if sum_cols and column not in sum_cols:
conn.execute(f"ALTER TABLE article_summaries ADD COLUMN {column} TEXT")