Read-time: full-article "Full story · ~N min" badge (Option B)
Replaces the gist-based read-time with the SOURCE article's full read time — the
contrast that sells the gist ("calm 1-min version here; ~10 min for the deep dive").
- goodnews/readtime.py: word_count_from_html (strips script/style/nav/header/
footer/form/button/aside furniture before counting) + source_read_minutes
(~225 wpm, 200-word floor, None when extraction looks failed/too thin).
- articles.source_words + read_checked_at columns (count only, never the body;
fits the privacy posture). Idempotent migration.
- enrich.fetch_source_words + enrich_read_times: a bounded, retry-guarded cycle
step (mirrors the image enrichers) that counts words for recent accepted
articles. Only ever writes a real count; never overwrites good with zero. Wired
into the cycle after recent-image enrichment.
- queries: source_words flows through _ARTICLE_COLUMNS; api exposes
source_read_minutes on Article (null when unknown).
- home3: News card shows "Full story · ~N min", hidden entirely when null (no
misleading "1 min").
- Tests: furniture stripping, threshold/rounding, enrich idempotency + no
zero-overwrite, API null handling. 412 backend.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+10
-1
@@ -14,7 +14,7 @@ from .localtime import local_today
|
||||
from .dedup import DEFAULT_THRESHOLD, DEFAULT_WINDOW_DAYS, cluster_duplicates, dedup as run_dedup
|
||||
from .geo import tag_articles as tag_geo
|
||||
from . import art, onthisday, quote, wotd
|
||||
from .enrich import enrich_brief_images, enrich_recent_images, enrich_summarized_images
|
||||
from .enrich import enrich_brief_images, enrich_read_times, enrich_recent_images, enrich_summarized_images
|
||||
from .summarize import generate_summary, get_summary
|
||||
from .feeds import (
|
||||
fetch_feed,
|
||||
@@ -599,6 +599,15 @@ def _run_cycle_locked(conn: sqlite3.Connection, args: argparse.Namespace) -> Non
|
||||
except Exception as exc:
|
||||
print(f"recent images: skipped ({exc})")
|
||||
|
||||
# Full-article read-times: count words for recent accepted articles so the
|
||||
# front door can show "Full story · ~N min" next to our gist (bounded per cycle).
|
||||
try:
|
||||
reads = enrich_read_times(conn)
|
||||
if reads:
|
||||
print(f"read-times: {reads} counted")
|
||||
except Exception as exc:
|
||||
print(f"read-times: skipped ({exc})")
|
||||
|
||||
# Pre-warm summaries for today's brief so Today reads as a calm briefing.
|
||||
# Idempotent: cached items are skipped, so this only hits the LLM for new ones.
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user