Images phase 1: attention-triggered og:image coverage

Tie image enrichment to attention (per review): when an article earns a summary
(i.e. a reader reached it), best-effort fetch a real og:image if it lacks one —
never blanket-fetch every ingested article. Adds:

* enrich_article_image() — single-article fetch, leaves existing images alone,
  retries an imageless article only after 7 days, stamps image_checked_at.
* generate_summary() calls it after caching (wrapped; never breaks summaries).
* enrich_summarized_images() + `goodnews enrich-images` CLI — slow background
  backfill of already-summarized, accepted, imageless articles.
* Quality gate: extend the generic-image skip list with data:/tracking-pixel/
  spacer markers (on top of the existing logo/placeholder + unbranded-BBC logic).

This is coverage only; display (editorial rhythm, tile treatment) comes next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-07 12:30:11 -04:00
parent 9813af40ed
commit 403749e26f
4 changed files with 143 additions and 1 deletions
+9 -1
View File
@@ -9,7 +9,7 @@ from .briefs import build_daily_brief, show_brief
from .db import connect, init_db
from .localtime import local_today
from .dedup import DEFAULT_THRESHOLD, DEFAULT_WINDOW_DAYS, dedup as run_dedup
from .enrich import enrich_brief_images
from .enrich import enrich_brief_images, enrich_summarized_images
from .summarize import generate_summary, get_summary
from .feeds import (
fetch_feed,
@@ -127,6 +127,11 @@ def main() -> None:
cycle_parser.add_argument("--base-url", help="OpenAI-compatible base URL for classify")
cycle_parser.add_argument("--model", help="Local model name for classify")
enrich_images_parser = subparsers.add_parser(
"enrich-images", help="Backfill og:images for already-summarized articles that lack one"
)
enrich_images_parser.add_argument("--limit", type=int, default=50, help="Max articles to fetch this batch")
dedup_parser = subparsers.add_parser("dedup", help="Cluster near-duplicate stories via local embeddings")
dedup_parser.add_argument("--threshold", type=float, default=DEFAULT_THRESHOLD, help="Cosine similarity cutoff")
dedup_parser.add_argument("--window-days", type=int, default=DEFAULT_WINDOW_DAYS)
@@ -266,6 +271,9 @@ def main() -> None:
print("Dry run only; database was not updated.")
elif args.command == "cycle":
run_cycle(conn, args)
elif args.command == "enrich-images":
found = enrich_summarized_images(conn, limit=args.limit)
print(f"enrich-images: {found} new image(s) for summarized articles")
elif args.command == "dedup":
init_db(conn)
client = llm_client_from_args(args)