Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/invvd/mtg-cheaper-deck/llms.txt

Use this file to discover all available pages before exploring further.

The matching module bridges the gap between Moxfield deck data and the local Scryfall database. Given the list of card dicts returned by parse_deck(), it attempts to find the corresponding row in the local cards table for each entry so that the rest of the pipeline (scoring, suggestions, export) can work from the rich Scryfall data rather than the minimal Moxfield representation. Cards that cannot be matched to any local row are collected into a separate unmatched list and logged as warnings so you can investigate discrepancies without the whole pipeline failing.
Matching by scryfall_id succeeds only when Moxfield and the local database reference the same Scryfall printing. The local database stores one representative printing per oracle_id (as determined by the Scryfall bulk-data sync), so Moxfield’s scryfall_id typically points to a different printing of the same card. As a result, case-insensitive name matching is the normal resolution path in practice, not the fallback.

match_deck_cards

The only public function in the module. Resolves each Moxfield card entry to a local database row and returns the matched and unmatched entries separately.
moxfield_cards
list[dict]
required
The list of card entry dicts produced by parse_deck(). Each dict must have at minimum a name key; the scryfall_id key is used first if present.
Returns tuple[list[dict], list[dict]] — a two-element tuple (matched, unmatched):
matched
list[dict]
Every original Moxfield entry dict merged with a "local" key that holds the full database row as a plain dict. All fields from the cards table are available under entry["local"], including oracle_id, price_usd, keywords, oracle_text, type_line, cmc, and the rest.
unmatched
list[dict]
Moxfield entries for which no database row could be found by either matching strategy. Each entry is logged at WARNING level with the card name and board. Common causes: the local database is incomplete or out of date, or the card name contains unusual Unicode characters that differ between Moxfield and Scryfall.

Matching strategy

The function opens a single database connection and processes all entries in sequence:
  1. scryfall_id lookup — if the entry has a non-null scryfall_id, a query runs against cards.scryfall_id. Succeeds only when Moxfield and the local database share the same printing.
  2. Case-insensitive name lookup — if the ID lookup returns no row (or scryfall_id was absent), a second query runs using func.lower(cards.c.name) == name.lower(). This is the path taken for the vast majority of cards.
  3. If both lookups fail, the entry is added to unmatched.
Both queries use the cards table and get_engine() from db.py.
from moxfield_client import fetch_deck_raw, parse_deck
from matching import match_deck_cards

raw = fetch_deck_raw("https://moxfield.com/decks/YOUR-DECK-ID")
deck_cards = parse_deck(raw)
matched, unmatched = match_deck_cards(deck_cards)

print(f"{len(matched)} matched, {len(unmatched)} unmatched")

# Access the local DB row for the first matched card
first = matched[0]
print(first["name"], "→", first["local"]["oracle_id"])
print("Price:", first["local"]["price_usd"])
If unmatched is non-empty after loading a fresh database, check whether the card names in Moxfield use alternate printings or special characters not present in the local Scryfall data.

Build docs developers (and LLMs) love