Import: Skip the Memorabilia Sets Scryfall Hides, and Oversized Printings With Them - #918
Import: Skip the Memorabilia Sets Scryfall Hides, and Oversized Printings With Them#918daveycodez wants to merge 4 commits into
Conversation
|
Should we just exclude oversized printings from import entirely? are those ever paper legal? are they every the only printing of a card? On memorabilia sets - excluding them at import makes them never recoverable, always filtering on a printing varying condition makes every scan walk printings instead of cards, which is rough. |
f94dcab to
4ac2680
Compare
|
Rebased onto main @ ccb562a (the admin split moved the tag sync into admin_resource.py; the One thing worth knowing before this merges: the Cloudflare port tried this exact approach — drop memorabilia at import — and then reversed it. An absent row cannot reproduce a query-time gate in either direction: |
Scryfall hides `set_type: memorabilia` printings — World Championship decks,
Collectors' Edition, 30th Anniversary, the oversized promos, 99 sets in all —
from any search that does not name their set. Measured against api.scryfall.com
on 2026-08-11:
!"Ancestral Recall" 9 printings returned, 18 exist
!"Birds of Paradise" 42 printings returned, 43 exist
set:cei returns the hidden printing
-set:lea returns all 17 others, hidden ones included
border:black / year<=1994 / r:rare / cn:48 / st:core / is:promo
no unhiding: only the 9 visible printings
Importing them makes ordinary queries disagree. Concretely they supply the
CHEAPEST printing for 184 cards, which is exactly the printing a price ordering
is defined to return — `order=usd` was ranking Birds of Paradise by a $4.98
World Championship copy where Scryfall ranks it by a $9.60 one.
Dropped at import, beside the `funny` exclusion it mirrors, rather than filtered
at query time. That is the load-bearing decision and it was made on measurement,
having built the query-time version first:
To be correct a query-time predicate has to sit in the FILTER TREE. The
representative walk picks the `prefer` best among printings that pass the
residual, so a flag outside the tree lets a hidden printing stand for its card —
and `total` is derived five different ways across the plans, so a predicate the
filter does not carry has to be re-implemented in each.
But a conjunct present on every query breaks four of the six physical plans.
`PlanePopcountOrder` and `all_match_known` gate on the residual being literally
`FilterExpr::True`; `CardRangePopcount` and `PrintingRangeScan` gate on the range
being BARE. None can be taught to look through it: the two range fastpaths emit
straight from a value-index slice and report the slice WIDTH as their total, so
looking through would emit hidden printings and miscount them in one go.
Measured at +59..115us per query on a 112,932-printing store, with
`PlanePopcountOrder` — the plan serving the default `unique=card` plane shape —
lost on every query.
What this gives up: `set:cei` and its 98 siblings return nothing, where Scryfall
returns them. No CARD is lost — measured, 0 of 31,724 cards are printed only in
memorabilia sets — so this changes which printing represents a card, never
whether the card is findable.
`is:oversized` joins BOOLEAN_IS_TAGS alongside it. It is one of the filters that
unhides on Scryfall, and it stays meaningful after this exclusion: of 726
oversized printings, only 253 are memorabilia — the rest are planechase,
commander, archenemy, vanguard and promo, which this does not touch. Low
cardinality, so it does not carry the foil/promo/reprint memory concern the
existing comment records.
The filter test pins the LITERAL set_type rather than the constant: driving both
sides off the same constant made it self-referential and it passed with the
constant set to anything at all, caught by flipping it.
4ac2680 to
11f2075
Compare
|
Ran your two questions against api.scryfall.com (2026-08-27): Ever paper-legal? No. Legalities are name-level, not printing-level: of 726 oversized prints, the 240 showing a legal/restricted format are all oversized copies of real cards (Vintage Championship trophies, Commander's Arsenal oversized commanders) whose normal-sized printings carry the actual legality. Dropping oversized loses no legality information. Ever the only printing? In Scryfall's universe, yes — 343 of the 546 oversized names exist only oversized: every plane/phenomenon (207/207 — even the physically normal-sized planar cards in recent commander decks carry Scale: after the existing legality/paper/funny gates, exactly 240 oversized printings survive today, and 230 of those are memorabilia — already excluded by this PR. Excluding oversized entirely removes exactly 10 more printings: the p09/p10/p11 oversized box-topper promos (Lightning Bolt, Wurmcoil Engine, Sun Titan, …), each of which has normal printings. So agreed — excluding oversized at import entirely is safe here, and it makes Footnote while measuring: |
…sized Tag Per the measurement in the PR discussion (2026-08-27 against api.scryfall.com): every card that exists only oversized is not_legal in every format and already refused by the legality gate, so excluding oversized at import removes exactly 10 printings past the memorabilia exclusion -- the p09/p10/p11 oversized box-topper promos, each of which has normal-sized printings. With oversized never imported the is:oversized tag would be permanently empty, so it goes too (its BOOLEAN_IS_TAGS entry was dropped in the merge with main).
Based on
main, independent of every open PR.What Scryfall does
set_type: memorabiliaprintings — World Championship decks, Collectors' Edition, 30th Anniversary, the oversized promos, 99 sets — are hidden from any search that does not name their set. Measured against api.scryfall.com on 2026-08-11:Why it matters here
Importing them makes ordinary queries disagree. Concretely, they supply the cheapest printing for 184 cards — exactly the printing a price ordering is defined to return.
order=usdwas ranking Birds of Paradise by a $4.98 World Championship copy where Scryfall ranks it by a $9.60 one, and Counterspell by a $1.29wc00where Scryfall usesbrbat $2.30.Why at import, not at query time
This is the load-bearing decision, and it was made on measurement — I built the query-time version first and threw it away.
To be correct, a query-time predicate has to live in the filter tree. The representative walk picks the
preferbest among printings that pass the residual, so a flag outside the tree lets a hidden printing stand for its card; andtotalis derived five different ways across the plans, so a predicate the filter does not carry has to be reimplemented in each.But a conjunct present on every query breaks four of the six physical plans:
PlanePopcountOrderFilterExpr::Trueall_match_known(both match kernels)CardRangePopcountPrintingRangeScanNone can be taught to look through it. The two range fastpaths emit straight from a value-index slice and report the slice width as their total, so looking through would emit hidden printings and miscount them in one step. Measured cost of the query-time version: +59–115 µs per query on a 112,932-printing store, with
PlanePopcountOrder— the plan serving the defaultunique=cardplane shape — lost on every query.Import-time exclusion costs none of that, and makes the store smaller rather than larger.
What it gives up
set:ceiand its 98 siblings return nothing, where Scryfall returns them.No card is lost. Measured over a 31,724-card corpus: 0 cards are printed only in memorabilia sets. So this changes which printing represents a card, never whether the card is findable. 2,672 of 97,803 printings (2.73%) stop being imported.
Oversized printings are excluded too
Per the review question, resolved by measurement in the decision comment (2026-08-27 against api.scryfall.com):
An earlier revision added an
is:oversizedtag instead; with oversized never imported the tag would be permanently empty, so it is gone.Tests
test_preprocess_card_filters_memorabilia_sets,test_preprocess_card_keeps_ordinary_sets, andtest_preprocess_card_filters_oversized_printings— the keep-side test paired so a predicate that dropped everything fails rather than looking like a working filter.The memorabilia test pins the literal
set_typerather thanMEMORABILIA_SET_TYPE, with the constant asserted separately. Driving both sides off the same constant made it self-referential — it passed with the constant set to anything at all. Caught by flipping the constant and watching it stay green.pytest api/tests/test_card_processing.py api/tests/test_parsing_errors.py→ 72 passed.ruff check api/clean.