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
+5
View File
@@ -273,6 +273,8 @@ CREATE TABLE IF NOT EXISTS daily_art (
image_file TEXT, -- our cached (web-large) image
image_url_full TEXT, -- source full-res URL, for a later richer /art view
is_public_domain INTEGER, -- license marker (CC0/public domain), stored for citizenship
blurb TEXT, -- LLM "museum guide" note: what you're looking at (cached)
palette TEXT, -- JSON array of hex colors extracted from the image
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
@@ -640,6 +642,9 @@ def _migrate(conn: sqlite3.Connection) -> None:
conn.execute("ALTER TABLE daily_art ADD COLUMN image_url_full TEXT")
if art_cols and "is_public_domain" not in art_cols:
conn.execute("ALTER TABLE daily_art ADD COLUMN is_public_domain INTEGER")
for column in ("blurb", "palette"): # richer /art page: guide note + extracted colors
if art_cols and column not in art_cols:
conn.execute(f"ALTER TABLE daily_art ADD COLUMN {column} TEXT")
# feedback.read_at (admin inbox read/unread) added later.
fb_cols = {row["name"] for row in conn.execute("PRAGMA table_info(feedback)")}