Skip to content

Conversation

@ZeldaZach
Copy link
Member

  • Starts with TCGPlayer only
  • Creates a mapping main repo can import into new field
  • Generates on a daily cadence with update tracking

- Starts with TCGPlayer only
- Creates a mapping main repo can import into new field
- Generates on a daily cadence with update tracking
tcgplayer_token_face_details[tcgplayer_token_face_index]["uuid"] = (
mtgjson_token["uuid"]
)
break

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be solved to be an O1 lookup if you build a map. I generated this with GPT

from typing import Dict, List, Any, Tuple, DefaultDict
from collections import defaultdict

def build_uuid_indexes(
    mtgjson_tokens: Dict[str, List[Dict[str, Any]]]
) -> Tuple[Dict[Tuple[str, str], str], Dict[str, List[str]]]:
    by_name_number: Dict[Tuple[str, str], str] = {}
    by_face_name: DefaultDict[str, List[str]] = defaultdict(list)

    for _, token_list in mtgjson_tokens.items():
        for t in token_list:
            name = (t.get("name") or "").strip()
            number = str(t.get("number") or "").strip()
            face_name = (t.get("faceName") or "").strip()
            uuid = t["uuid"]

            if name and number:
                by_name_number[(name, number)] = uuid
            if face_name:
                by_face_name[face_name].append(uuid)

    return by_name_number, by_face_name


def add_mtgjson_uuids_to_tcgplayer_token_face_details(
    mtgjson_tokens: Dict[str, List[Dict[str, Any]]],
    tcgplayer_token_face_details: List[Dict[str, Any]],
) -> None:
    by_name_number, by_face_name = build_uuid_indexes(mtgjson_tokens)

    for face in tcgplayer_token_face_details:
        face_name = (face.get("faceName") or "").strip()
        face_id = str(face.get("faceId") or "").strip()

        # 1) Exact (name, number) match
        uuid = by_name_number.get((face_name, face_id))
        if not uuid:
            # 2) Fallback: faceName match (handles Punchcard // Punchcard, etc.)
            uuids = by_face_name.get(face_name, [])
            uuid = uuids[0] if uuids else None

        if uuid:
            face["uuid"] = uuid


def filter_tokens_with_uuids(
    output_tokens: List[Dict[str, Any]],
) -> Dict[str, List[Dict[str, Any]]]:
    # Map each MTGJSON uuid → list of token product entries that reference it
    out: DefaultDict[str, List[Dict[str, Any]]] = defaultdict(list)
    for ot in output_tokens:
        for part in ot["tokenParts"]:  # <-- fixed key to match builder
            if "uuid" in part:
                out[part["uuid"]].append(ot)
    return out


def build_tokens_mapping(
    mtgjson_tokens: Dict[str, List[Dict[str, Any]]],
    tcgplayer_tokens: List[Dict[str, Any]],
    tcgplayer_token_parser: TcgplayerTokenParser,
):
    output_tokens: List[Dict[str, Any]] = []

    for tcgplayer_token in tcgplayer_tokens:
        faces = tcgplayer_token_parser.split_tcgplayer_token_faces_details(tcgplayer_token)
        add_mtgjson_uuids_to_tcgplayer_token_face_details(mtgjson_tokens, faces)

        output_tokens.append({
            **tcgplayer_token_to_mtgjson_token_products_entry(tcgplayer_token),
            "tokenParts": faces,   # consistently camelCase everywhere
        })

    return filter_tokens_with_uuids(output_tokens)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

personally, I'd take more readable (albeit potentally slower) code than ai slop (I'm sure the code you posted works, but it'd take me far longer to parse and understand than @ZeldaZach's code)

also the size of these arrays is pretty small (like 20 items or less), I really don't think the extra optimization is worth the extra code

def __init__(self) -> None:
self.__emblem_regex = re.compile(r"Emblem - (.*)")
self.__get_all_face_names_regex = re.compile(
r"^(.*?)(?: \(.*?\))? // (.*?)(?: \(.*?\))? Double[- ]Sided Token.*"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works fine as is, but regex makes the parsing logic a little hard to read and kind of brittle if TCGPlayer tweaks their formats to respond to a weird UB token / new mechanic.

One idea would be to make this more rule-based instead of regex-driven. Something like:

def __get_token_face_names(self, token_name: str) -> List[str]:
    if token_name.startswith("Emblem - "):
        return [token_name.split("Emblem - ", 1)[1] + " Emblem"]

    if token_name.lower().startswith(("punch card", "punchcard")):
        return ["Punchcard", "Punchcard"]

    if " // " in token_name and "Double" in token_name:
        faces, *_ = token_name.split(" Double", 1)
        left, right = [part.split(" (")[0] for part in faces.split(" // ")]
        return [left.strip(), right.strip()]

    return [token_name.split(" Token")[0]]

Same end result a little more verbose, but IMO a bit easier to follow.

Another option would be to throw this into a little grammar with something like lark, but that’s probably overkill unless we start running into a lot more weird formats.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call out, I think I'll make a tweak to support this instead

@kodawah
Copy link
Collaborator

kodawah commented Oct 6, 2025

as long as the new fields are added on top of the existing token card objects (which the sample code I've seen seem to do) i'm happy with this change

@ZeldaZach ZeldaZach requested a review from genericlady October 31, 2025 23:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants