From 9810c7665787033ab380b1574c90b4eabe54897a Mon Sep 17 00:00:00 2001 From: wilfordgrimley <2397930+WilfordGrimley@users.noreply.github.com> Date: Thu, 30 Jul 2026 09:23:01 +0000 Subject: [PATCH] Document the per-face illustration data model and its refresh semantics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `CanonicalPrintingMetadata.face_illustrations` (migration 0095) has been built, merged (#565) and applied in production since 2026-07-29, and the importer already populates it — but nothing said so in a wiki-facing doc, and production still reads 0 populated rows purely because `import_scryfall_printing_metadata` has not run since #565 merged. Records in `docs/features/printing-tags.md` (wiki: `Printing-Tags`): - the two illustration grains on the model — scalar `illustration_id` is front-face-only, `face_illustrations` retains every face — and the two load-bearing properties of the list (the `DOUBLE_FACED_LAYOUTS` gate, and `None` rather than dropping an art-less face so indices hold). - the refresh answer issue #638 left open: the importer is a full-set, value-diffing upsert, and `face_illustrations` is in `_METADATA_SYNC_FIELDS`, so an ordinary run populates already-stored rows. No backfill command and no flag — running the importer is the backfill. - expected coverage, so a mostly-empty column is not later read as a bug: 1,594 of 113,224 rows (1.4%) populate on a first run. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013NhYmT1PxCcyemA16dFDxN --- docs/features/printing-tags.md | 55 ++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/docs/features/printing-tags.md b/docs/features/printing-tags.md index effcd64a1..2218929b2 100644 --- a/docs/features/printing-tags.md +++ b/docs/features/printing-tags.md @@ -38,6 +38,61 @@ printings, artists, tags, and moderation from one screen. It was called `printings_count` until 2026-07-29 (migration 0099), and this document described it as Scryfall printing data — that was false. `CardPrintingTag.printing` FKs directly to `CanonicalCard`. +- **Artwork identity, per face**: the same model carries `art_crop_url` + and two illustration columns at **different grains**, and consumers must + pick deliberately. `illustration_id` is a **scalar, front-face-only** + value — for a multi-faced row it is `card_faces[0].illustration_id`, so + the back face's own artwork is not addressable through it. + `face_illustrations` (JSON list, migration 0095) retains **every** face's + own `illustration_id` paired with that face's own name, in `card_faces` + order, so index 0 is the front and index 1 is the back. The scalar is + unchanged and still front-face — the list is purely additive. Two + properties of the list are load-bearing, and are pinned by + `TestFaceIllustrations` in + `cardpicker/tests/test_printing_metadata_import.py`: + - It is **`[]` for anything that is not a genuine double-faced card**, + gated on the `DOUBLE_FACED_LAYOUTS` allowlist (`transform`, + `modal_dfc`, `double_faced_token`, `battle`, `reversible_card`) — the + same allowlist `get_back_face_names` uses. + `split`/`adventure`/`flip`/`aftermath`/`mutate`/`prototype` also nest + several named modes under `card_faces`, but those modes are printed on + **one** physical face; giving "Stomp" its own entry would assert a + second scannable side of "Bonecrusher Giant" that does not exist, and + would let a scan of the creature be attributed to the adventure's + artwork. `meld` is out of scope by construction — meld pieces carry no + `card_faces` of their own in this bulk data. + - A face that Scryfall publishes no `illustration_id` for records + **`None` rather than being dropped**, so the list's index keeps + corresponding to the face's position — a consumer walking + `card_faces[1]` must not have the list silently shift under it. +- **How this column refreshes** (it is not a one-off backfill): + `import_scryfall_printing_metadata` is a **full-set, value-diffing + upsert**, not insert-only and not a last-modified diff. Every run + re-derives the desired row for every printing in the `default_cards` + bulk file and compares it field-by-field against the stored row, joined + on `canonical_card_id`: no match → CREATE, any `_METADATA_SYNC_FIELDS` + member differs → UPDATE, all equal → SKIP, stored key absent from the + desired set → DELETE. `face_illustrations` **is** in + `_METADATA_SYNC_FIELDS`, and that membership is what makes an ordinary + run populate rows that already exist — so this needs **no backfill + command and no flag**; running the importer is the backfill. That + membership is the whole load-bearing bit: `bulk_create` writes every + column regardless, so had the field been omitted from + `_METADATA_SYNC_FIELDS`, newly-seen printings would still have populated + while every already-stored row stayed `[]` forever. The column therefore + reads `[]` on every row imported before the field existed, until the + next import run. +- **Expected coverage — a mostly-`[]` column is correct, not a bug.** + Measured against the on-disk `default_cards` bulk file and production on + 2026-07-30: of **116,254** bulk rows, **1,594** carry a genuine + double-faced layout with two or more faces, and all 1,594 join to a + `CanonicalCard` that already has a `CanonicalPrintingMetadata` row. So a + first import populates **1,594 of 113,224 rows (1.4%)** — 1,534 of them + with a real `illustration_id` on every face, exposing **1,534** + back-face illustrations the scalar column cannot address, and 60 with + name-only entries whose `illustration_id` is `None` because Scryfall + publishes no artwork id for those faces. The remaining ~111,630 rows are + single-faced and **correctly** stay `[]`. - **Card payload — machine-suggested printing + tag vote status** (Proposal H §4.4′, issue #184, PR #195; consumed by the Select Version section, issue #167 — see [[grid-selector.md]]'s own "Select Version section" entry):