Skip to Content

Image Path Collision Disambiguation

Date2026-04-08
StatusAccepted
Decision Makers@benjaminW78

Context and Problem Statement

The SWU importer generates image destination paths from the TCGCSV extNumber (card collector number) and finish suffix:

destination: `${groupSlug}/${printKey}${slugSuffix}.${imageExt}`

This assumes extNumber is unique per card within an expansion. However, in SWU, different cards can share the same collector number within the same set. This happens in two scenarios:

  1. TCGCSV data errors — e.g., Massassi Group Marines assigned 148/264 instead of 146/264 in A Lawless Time, colliding with Smuggler’s YT-2400 which is correctly 148/264
  2. Genuine shared numbers — e.g., Willrow Hood and Bardottan Ornithopter both officially numbered 571 (Foil) and 817 (Hyperspace Foil) in Secrets of Power
  3. Promo sets — Judge Promos, Organized Play Promos, and Prerelease Promos routinely assign the same number to different promo waves

This causes 67 products across 21 collision groups in production where the last card processed overwrites the other’s image file on S3. For example, Massassi Group Marines displays Smuggler’s YT-2400’s image.

Decision Drivers

  • Image destination paths must be unique per product within an expansion
  • The fix should only affect colliding products — changing all ~5000 SWU image paths would trigger a full S3 re-upload on next import
  • The solution must handle both TCGCSV data errors and genuinely shared card numbers
  • Serialized Gold/Rose Gold variants of the same card already have unique paths via slugSuffix (_serialized-gold, _serialized-rose-gold) and are not affected

Considered Options

  1. Post-processing deduplication — After building all printings, detect duplicate image destinations and prepend cardSlug only to colliding paths
  2. Always include cardSlug in image path — Change the destination template to ${groupSlug}/${cardSlug}_${printKey}${slugSuffix}.${imageExt} for all products
  3. Use SWU API cardNumber to correct TCGCSV errors — When the API has a different card number, prefer it over the TCGCSV extNumber

Decision Outcome

Chosen option: Post-processing deduplication, because it fixes all collision types (TCGCSV errors, genuine shared numbers, promo sets) while leaving ~5000 non-colliding product images untouched.

Option 2 was rejected because it would change every SWU image path, triggering a full re-upload of all images on the next import run.

Option 3 was rejected because it only fixes TCGCSV data errors, not genuinely shared card numbers or promo sets.

Implementation

Logic Flow

Impacted Files

FileChange
packages/games/game-importer/src/swu-importer.tsAdded post-loop deduplication step in getCards() that detects and disambiguates colliding image destinations

Key Code

// After building all printings, detect duplicate image destinations const destinationToEntries = new Map<string, { cardKey: string; printingIndex: number }[]>() for (const [cardKey, card] of cardMap) { for (let i = 0; i < card.printings.length; i++) { const dest = card.printings[i].image?.destination if (!dest) continue if (!destinationToEntries.has(dest)) destinationToEntries.set(dest, []) destinationToEntries.get(dest)?.push({ cardKey, printingIndex: i }) } } // Disambiguate only collisions by prepending cardSlug to filename for (const [dest, entries] of destinationToEntries) { const uniqueCards = new Set(entries.map((e) => e.cardKey)) if (uniqueCards.size <= 1) continue // same card, different finish -- not a collision for (const { cardKey, printingIndex } of entries) { const card = cardMap.get(cardKey)! const printing = card.printings[printingIndex] const image = printing.image! const parts = image.destination!.split("/") parts[parts.length - 1] = `${card.slug}_${parts[parts.length - 1]}` image.destination = parts.join("/") } }

Consequences

Positive

  • Fixes all 21 collision groups (67 products) in one shot
  • Non-colliding images (~5000 products) keep their existing S3 paths — no unnecessary re-upload
  • Handles all collision types: TCGCSV data errors, genuine shared numbers, promo sets
  • Logs a warning for each disambiguated group, providing visibility into upstream data issues
  • Future collisions (new sets, new promos) are automatically handled

Negative

  • The ~67 colliding products will get new image paths, requiring a one-time re-upload on next import

More Information

Last updated on