Daily Art: Codex guardrails (atomic image, attribution/license, blocked lever)
Hardening before it runs further on the cycle: - DB-lock/network: all HTTP (metadata + image) happens before any write; the write txn opens only at the brief INSERT and commits immediately. Images download to a temp file then atomic os.replace into cache (a reader never sees a half-written file). - Site-timezone "daily" already used local_today() (same rhythm as the Brief) — confirmed. - Attribution from day one: store + return title/artist/date/medium/department/credit/ source_url/object_id/source + museum name + is_public_domain license marker + the full- res source URL (for a richer /art view later). UI can show: Title · Artist · The Met. - "highlight != always beautiful": added a manual `blocked` flag on art_pool (excluded from picks) as the cheap curation lever; a featured override can follow. Schema migrated (existing art tables get the new columns). 373 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+16
-3
@@ -253,6 +253,7 @@ CREATE TABLE IF NOT EXISTS art_pool (
|
||||
object_id INTEGER NOT NULL,
|
||||
source TEXT NOT NULL DEFAULT 'met',
|
||||
shown_at TEXT,
|
||||
blocked INTEGER NOT NULL DEFAULT 0, -- manual lever: skip an odd/unsuitable piece
|
||||
added_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (source, object_id)
|
||||
);
|
||||
@@ -265,9 +266,11 @@ CREATE TABLE IF NOT EXISTS daily_art (
|
||||
date_text TEXT,
|
||||
medium TEXT,
|
||||
department TEXT,
|
||||
credit TEXT,
|
||||
source_url TEXT,
|
||||
image_file TEXT,
|
||||
credit TEXT, -- museum credit line
|
||||
source_url TEXT, -- canonical museum object page
|
||||
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
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
@@ -541,6 +544,16 @@ def _migrate(conn: sqlite3.Connection) -> None:
|
||||
if "retry_after_at" not in source_cols:
|
||||
conn.execute("ALTER TABLE sources ADD COLUMN retry_after_at TEXT")
|
||||
|
||||
# Daily Art columns added after the tables first shipped.
|
||||
pool_cols = {row["name"] for row in conn.execute("PRAGMA table_info(art_pool)")}
|
||||
if pool_cols and "blocked" not in pool_cols:
|
||||
conn.execute("ALTER TABLE art_pool ADD COLUMN blocked INTEGER NOT NULL DEFAULT 0")
|
||||
art_cols = {row["name"] for row in conn.execute("PRAGMA table_info(daily_art)")}
|
||||
if art_cols and "image_url_full" not in art_cols:
|
||||
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")
|
||||
|
||||
# feedback.read_at (admin inbox read/unread) added later.
|
||||
fb_cols = {row["name"] for row in conn.execute("PRAGMA table_info(feedback)")}
|
||||
if fb_cols and "read_at" not in fb_cols:
|
||||
|
||||
Reference in New Issue
Block a user