video discover phase 0: persist TMDB collection (franchise) id per movie

Data layer for the 'complete your collections' gap engine. People/credits/genres are already
normalized + indexed, so the only missing signal was franchise membership:
- movies: + tmdb_collection_id (indexed) + tmdb_collection_name (schema + _COLUMN_MIGRATIONS,
  SCHEMA_VERSION 17->18, _ENRICH_META_COLS whitelist so enrichment_apply backfills them)
- enrichment match() reads belongs_to_collection (a standard movie-detail field, no extra call)
  and writes the id/name into the match metadata.
Additive + backfill-only (COALESCE), nothing existing rewired. (also noqa'd a pre-existing
OMDb S110 in the touched file to keep ruff clean.)
This commit is contained in:
BoulderBadgeDad 2026-06-22 23:24:42 -07:00
parent d162966cee
commit 1f1a239486
3 changed files with 14 additions and 2 deletions

View file

@ -97,6 +97,12 @@ class TMDBClient:
if kind == "movie":
meta["release_date"] = dr.get("release_date")
meta["runtime_minutes"] = dr.get("runtime")
# Franchise/collection (belongs_to_collection is a standard movie-detail
# field) — persisted so "complete your collections" gaps can diff it (#discover).
bc = dr.get("belongs_to_collection")
if bc and bc.get("id"):
meta["tmdb_collection_id"] = bc.get("id")
meta["tmdb_collection_name"] = bc.get("name")
else:
meta["first_air_date"] = dr.get("first_air_date")
meta["last_air_date"] = dr.get("last_air_date")
@ -837,7 +843,7 @@ class OMDBClient:
err = ""
try:
err = ((r.json() or {}).get("Error") or "").strip()
except Exception:
except Exception: # noqa: S110 - best-effort error-body parse; we raise OMDbAuthError below regardless
pass
raise OMDbAuthError(err or "OMDb rejected the API key (HTTP 401)")
r.raise_for_status()

View file

@ -31,7 +31,7 @@ logger = get_logger("video_database")
# Bump when video_schema.sql changes in a way worth recording. Stored in
# PRAGMA user_version as a backstop indicator (nothing gates on it yet).
SCHEMA_VERSION = 17
SCHEMA_VERSION = 18
_DEFAULT_DB_PATH = "database/video_library.db"
_SCHEMA_FILE = Path(__file__).resolve().parent / "video_schema.sql"
@ -66,6 +66,7 @@ _ENRICH = {
_ENRICH_META_COLS = {
"movies": {"overview", "backdrop_url", "logo_url", "release_date", "status", "content_rating",
"runtime_minutes", "studio", "tagline", "rating", "rating_critic",
"tmdb_collection_id", "tmdb_collection_name",
"imdb_id", "tmdb_id"},
"shows": {"overview", "backdrop_url", "logo_url", "status", "network", "content_rating",
"tagline", "rating", "first_air_date", "last_air_date", "airs_time",
@ -139,6 +140,8 @@ _COLUMN_MIGRATIONS = [
("movies", "tagline", "TEXT"),
("movies", "rating", "REAL"),
("movies", "rating_critic", "REAL"),
("movies", "tmdb_collection_id", "INTEGER"),
("movies", "tmdb_collection_name", "TEXT"),
("shows", "tagline", "TEXT"),
("shows", "rating", "REAL"),
("shows", "first_air_date", "TEXT"),

View file

@ -78,6 +78,8 @@ CREATE TABLE IF NOT EXISTS movies (
studio TEXT,
content_rating TEXT, -- e.g. PG-13
tagline TEXT,
tmdb_collection_id INTEGER, -- TMDB belongs_to_collection id (franchise); for "complete your collections" gaps
tmdb_collection_name TEXT, -- collection display name (e.g. "The Matrix Collection")
rating REAL, -- TMDB audience score (0-10)
rating_critic REAL, -- critic score (0-100) when offered
imdb_rating REAL, -- IMDb (0-10, via OMDb)
@ -96,6 +98,7 @@ CREATE TABLE IF NOT EXISTS movies (
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_movies_tmdb ON movies(tmdb_id);
CREATE INDEX IF NOT EXISTS idx_movies_collection ON movies(tmdb_collection_id);
CREATE INDEX IF NOT EXISTS idx_movies_monitored ON movies(monitored, has_file);
CREATE INDEX IF NOT EXISTS idx_movies_release ON movies(release_date);
-- Upsert/stale-removal key: the server's native id. Multiple NULLs are allowed