Daily Art engine: museum-guide blurb (grounded LLM) + extracted palette

- daily_art gains blurb + palette columns (idempotent migration).
- art._palette: Pillow median-cut to ~5 hex colors from the cached image (best-
  effort → [] on any failure). art._blurb: a warm 2-3 sentence "what you're
  looking at" note grounded in the Met catalogue (title/artist/bio/date/medium/
  classification/culture/tags). Prompt leans on context/significance and the
  title+tags for subject — explicitly NOT asserting literal composition (figure
  counts/poses) it can't see, since the model can't view the image. Markdown
  stripped from the output.
- pick_daily generates both (client optional → blurb skipped when absent); cycle
  + art CLI pass an LLM client. /api/art/today exposes blurb + palette.
- Backfilled the last 3 days on host (Veteran / Magnolia Vase / Bierstadt).
- scripts/art_blurb_palette_backfill.py for in-place backfill (no re-pick).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-23 20:12:54 -04:00
parent 79ecb800af
commit ed814c97b9
7 changed files with 172 additions and 10 deletions
+2 -2
View File
@@ -319,7 +319,7 @@ def main() -> None:
if args.harvest:
h = art.harvest_pool(conn)
print(f"art harvest: found={h['found']} added={h['added']} pool={h['pool']} errors={h['errors']}")
picked = art.pick_daily(conn, force=args.force)
picked = art.pick_daily(conn, force=args.force, client=LocalModelClient.from_env())
if picked:
print(f"art pick: {picked['art_date']} -> #{picked['object_id']} "
f"\"{picked['title']}\"{picked['artist'] or 'Unknown'}")
@@ -556,7 +556,7 @@ def _run_cycle_locked(conn: sqlite3.Connection, args: argparse.Namespace) -> Non
# once the day is picked; non-fatal like every other step.
if not args.no_art:
try:
a = art.run_daily(conn)
a = art.run_daily(conn, client=LocalModelClient.from_env()) # client → the guide blurb
print(f"art: pool={a['pool']} picked={a['picked_object']}")
except Exception as exc:
print(f"art: skipped ({exc})")