Foil Finish Detection via TCGCSV subTypeName
| Date | 2026-04-07 |
| Status | Accepted |
| Decision Makers | @benjaminW78 |
| Amends | TCGPlayer suffix-based parsing |
Context and Problem Statement
Cards in early SWU sets (Spark of Rebellion, Shadows of the Galaxy, Twilight of the Republic) were missing their Foil finish on CardNexus — ~800 cards affected across 3 sets.
Root cause: TCGPlayer handles Foil differently between old and new sets:
| Behavior | Sets | TCGCSV structure |
|---|---|---|
| Legacy model | SOR, SHD, TWI | ONE productId per card, two CSV rows: subTypeName=Normal and subTypeName=Foil |
| Current model | JTL, LOF, SEC, LAW | Separate productId per finish, with (Foil) suffix in product name |
The suffix-based parsing ADR chose to ignore subTypeName because it was believed to be “inconsistent across sets.” An exhaustive audit of all 27 SWU TCGCSV groups (9,847 CSV rows, 6,950 unique productIds) found this is not the case for SWU — subTypeName is consistently one of "Normal", "Foil", or empty (sealed/special products).
TCGCSV data (ProductsAndPrices.csv):
| Set | Unique PIDs | CSV Rows | Normal | Foil | Empty |
|---|---|---|---|---|---|
| Spark of Rebellion | 550 | 985 | 533 | 451 | 1 |
| Shadows of the Galaxy | 546 | 983 | 528 | 455 | 0 |
| Twilight of the Republic | 548 | 985 | 530 | 454 | 1 |
| Jump to Lightspeed | 1,182 | 1,182 | 607 | 558 | 17 |
| Legends of the Force | 1,203 | 1,203 | 605 | 584 | 14 |
| Secrets of Power | 1,222 | 1,222 | 597 | 572 | 53 |
| A Lawless Time | 945 | 945 | 608 | 325 | 12 |
Early sets have more CSV rows than unique productIds because Normal and Foil are separate rows of the same product. Newer sets have 1:1 because Foil is a separate product.
Decision Drivers
- TCGCSV is the source of truth — avoid depending on external APIs (SWU API
hasFoil) for finish detection subTypeNameis consistently"Normal"/"Foil"/ empty across all 27 SWU groups — verified exhaustively- Suffix-based parsing remains correct for newer sets — the fix must not break them
- The base importer’s
flattenCardsToPrintingsalready merges printings with different finishes into one product
Considered Options
- Use
subTypeNameas Foil fallback — when suffix parsing resolves to NORMAL butsubTypeNameis"Foil", override to FOIL - Use SWU API
hasFoilflag — extracthasFoilboolean from the SWU API to determine foil availability - Hardcode early set codes — for SOR/SHD/TWI, always add Foil to Standard/Hyperspace cards
Decision Outcome
Chosen option: Use subTypeName as Foil fallback, because it keeps TCGCSV as the source of truth, requires no external API changes, and is self-detecting (no hardcoded set lists).
Implementation
Logic Flow
Impacted Files
| File | Change |
|---|---|
packages/games/game-importer/src/swu-importer.ts | resolveVariantAndFinish() now accepts subTypeName as second parameter and handles Foil override internally |
Key Code
const { variant, finish } = this.resolveVariantAndFinish(suffix, product.subTypeName)The subTypeName override is handled inside resolveVariantAndFinish — single source of truth for finish resolution.
Consequences
Positive
- Restores ~800 missing Foil finishes across SOR, SHD, TWI
- TCGCSV remains the sole source of truth — no SWU API dependency for finish data
- Self-detecting: works for any future set that uses the legacy or current model
- No impact on newer sets — suffix parsing already produces the correct finish, and
subTypeNameagrees - Minimal code change (~3 lines)
Negative
- Relies on TCGCSV maintaining consistent
subTypeNamevalues for SWU — if they change the format (e.g., to card types like “Unit”/“Leader” as seen in some other games), the fallback would need revisiting - For early sets, both finishes share the same TCGPlayer
productIdinexternalIds— pricing relies on the price importer correctly matchingsubTypeNameto finish
More Information
- TCGPlayer suffix-based parsing ADR — the original decision to use suffix parsing; this ADR amends it by using
subTypeNameas a secondary signal - Variant/Finish model ADR — defines the variant + finish enum model
- TCGCSV
ProductsAndPrices.csvincludessubTypeNameon each row alongside product metadata and pricing