From 4f2ae5f525a548f80d364647672ccbf4df068d1c Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Sat, 1 Aug 2026 13:16:43 -0400 Subject: [PATCH] Real extraction from shelf photos; test title list moves to tests/data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First live vision run: 4 shelf photos -> 27 unique titles with edition cues (publishers, Bookcase Game/Designer's Edition wording, art notes); cross-photo dedupe merged repeat sightings. data/titles.json now holds real pipeline data, so the hand-typed resolve test list moves to tests/data/titles.json (tests and record_fixtures updated), and the matches.csv generated from stub fixtures for that list is removed — the real resolve run will regenerate it once the BGG token arrives. Co-Authored-By: Claude Fable 5 --- data/matches.csv | 8 - data/titles.json | 355 ++++++++++++++++++++++++++++++++++--- scripts/record_fixtures.py | 2 +- tests/data/titles.json | 40 +++++ tests/test_resolve.py | 2 +- 5 files changed, 377 insertions(+), 30 deletions(-) delete mode 100644 data/matches.csv create mode 100644 tests/data/titles.json diff --git a/data/matches.csv b/data/matches.csv deleted file mode 100644 index f9d4a09..0000000 --- a/data/matches.csv +++ /dev/null @@ -1,8 +0,0 @@ -title_raw,bgg_id,bgg_name,year,type,match_status,version_id,version_name,version_status,candidates_json,version_candidates_json,source_photos -Catan,13,CATAN,1995,boardgame,auto,,,version_unknown,"[{""bgg_id"": 13, ""name"": ""CATAN"", ""year"": 1995, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],hand-typed-test-list -Wingspan,266192,Wingspan,2019,boardgame,auto,465063,English first edition,version_auto,"[{""bgg_id"": 266192, ""name"": ""Wingspan"", ""year"": 2019, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 465063, ""name"": ""English first edition"", ""year"": 2019, ""publishers"": [""Stonemaier Games""], ""languages"": [""English""], ""score"": 5}, {""version_id"": 521212, ""name"": ""English fourth printing"", ""year"": 2020, ""publishers"": [""Stonemaier Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 472430, ""name"": ""German edition"", ""year"": 2019, ""publishers"": [""Feuerland Spiele""], ""languages"": [""German""], ""score"": 2}]",hand-typed-test-list -Wingspan: European Expansion,290448,Wingspan: European Expansion,2019,boardgameexpansion,auto,,,version_unknown,"[{""bgg_id"": 290448, ""name"": ""Wingspan: European Expansion"", ""year"": 2019, ""type"": ""boardgameexpansion"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],hand-typed-test-list -Wingspan Europe,,,,,unmatched,,,,[],[],hand-typed-test-list -Café International,373,Café International,1989,boardgame,auto,,,version_unknown,"[{""bgg_id"": 373, ""name"": ""Café International"", ""year"": 1989, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],hand-typed-test-list -Citadels,,,,,ambiguous,,,,"[{""bgg_id"": 478, ""name"": ""Citadels"", ""year"": 2000, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 85000, ""rank"": 250}, {""bgg_id"": 205398, ""name"": ""Citadels"", ""year"": 2016, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 24000, ""rank"": 400}]",[],hand-typed-test-list -Blorvath: Quest of the Zzyzx,,,,,unmatched,,,,[],[],hand-typed-test-list diff --git a/data/titles.json b/data/titles.json index e25d4e6..3ff923c 100644 --- a/data/titles.json +++ b/data/titles.json @@ -1,40 +1,355 @@ [ { - "title_raw": "Catan", + "title_raw": "ROYALS", "confidence": "high", - "source_photos": ["hand-typed-test-list"] - }, - { - "title_raw": "Wingspan", - "confidence": "high", - "publisher_hint": "Stonemaier Games", - "year_hint": 2019, + "publisher_hint": "", + "edition_hint": "", + "year_hint": null, "language_hint": "English", - "source_photos": ["hand-typed-test-list"] + "art_notes": "Dark maroon/brown spine with ornate gold scroll lettering and a portrait of a woman in period dress on the lower spine", + "source_photos": [ + "IMG_4499.jpeg" + ], + "title_normalized": "royals" }, { - "title_raw": "Wingspan: European Expansion", + "title_raw": "CAT CRIMES", "confidence": "high", - "source_photos": ["hand-typed-test-list"] + "publisher_hint": "ThinkFun", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Top-facing box with subtitle 'Who's to Blame Logic Game', illustration of an orange cat, 'AGES 8+' label", + "source_photos": [ + "IMG_4499.jpeg" + ], + "title_normalized": "cat crimes" }, { - "title_raw": "Wingspan Europe", + "title_raw": "COSMIC ENCOUNTER", + "confidence": "high", + "publisher_hint": "", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Black spine with colorful bubble-style logo text, 'the game that breaks its own rules' tagline, 'For 3 to 6 Players AGES 12 TO ADULT'", + "source_photos": [ + "IMG_4499.jpeg" + ], + "title_normalized": "cosmic encounter" + }, + { + "title_raw": "SHERIFF OF NOTTINGHAM", + "confidence": "high", + "publisher_hint": "", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Orange/wood-toned spine with green stylized text, illustrated medieval character artwork at top", + "source_photos": [ + "IMG_4499.jpeg" + ], + "title_normalized": "sheriff of nottingham" + }, + { + "title_raw": "5 MINUTE DUNGEON", + "confidence": "high", + "publisher_hint": "", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Yellow/orange spine with dark fantasy creature artwork, credited 'Connor Reid' at top, green '5 MINUTE' text near bottom", + "source_photos": [ + "IMG_4499.jpeg" + ], + "title_normalized": "5 minute dungeon" + }, + { + "title_raw": "Britannia", + "confidence": "high", + "publisher_hint": "The Avalon Hill Game Company, Baltimore, Maryland", + "edition_hint": "Bookcase Game", + "year_hint": null, + "language_hint": "English", + "art_notes": "Dark navy spine, illustration of warriors/chariots at top in gold-framed box", + "source_photos": [ + "IMG_4500.jpeg" + ], + "title_normalized": "britannia" + }, + { + "title_raw": "CIVILIZATION Game of the Heroic Age - The Dawn of History 8000 BC to 250 BC", + "confidence": "high", + "publisher_hint": "The Avalon Hill Game Company, Baltimore, Maryland", + "edition_hint": "Bookcase Game", + "year_hint": null, + "language_hint": "English", + "art_notes": "Blue spine with illustrated figures from ancient civilizations", + "source_photos": [ + "IMG_4500.jpeg" + ], + "title_normalized": "civilization game of heroic age dawn of history 8000 bc to 250 bc" + }, + { + "title_raw": "ADVANCED CIVILIZATION Game Expansion of the Heroic Age - Featuring New Civilization, Commodity, and Calamity Cards", + "confidence": "high", + "publisher_hint": "The Avalon Hill Game Company, Baltimore, Maryland", + "edition_hint": "Bookcase Game", + "year_hint": null, + "language_hint": "English", + "art_notes": "Red/orange spine with illustrated ancient figures, similar style to Civilization", + "source_photos": [ + "IMG_4500.jpeg" + ], + "title_normalized": "advanced civilization game expansion of heroic age featuring new civilization commodity and calamity cards" + }, + { + "title_raw": "DOCTOR WHO The Game of Time & Space", + "confidence": "high", + "publisher_hint": "Games Workshop", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Bright red spine with yellow/green Doctor Who logo, star burst design, image of Doctor Who character at top", + "source_photos": [ + "IMG_4500.jpeg" + ], + "title_normalized": "doctor who game of time and space" + }, + { + "title_raw": "SORCERER The Game of Magical Conflict", + "confidence": "high", + "publisher_hint": "Simulations Publications Incorporated (SPI)", + "edition_hint": "Designer's Edition", + "year_hint": null, + "language_hint": "English", + "art_notes": "Black spine with white bold text, worn/aged appearance, SPI logo at bottom", + "source_photos": [ + "IMG_4500.jpeg" + ], + "title_normalized": "sorcerer game of magical conflict" + }, + { + "title_raw": "STARFORCE ALPHA CENTAURI Interstellar Conflict in the 25th Century", + "confidence": "high", + "publisher_hint": "Simulations Publications Incorporated (SPI)", + "edition_hint": "Designer's Edition", + "year_hint": null, + "language_hint": "English", + "art_notes": "Gray/silver spine with futuristic imagery, 'A Future-History Simulation Game' subtitle", + "source_photos": [ + "IMG_4500.jpeg" + ], + "title_normalized": "starforce alpha centauri interstellar conflict in 25th century" + }, + { + "title_raw": "SKIP-BO", "confidence": "medium", - "source_photos": ["hand-typed-test-list"] + "publisher_hint": "", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Red box spine, partially visible on far left edge", + "source_photos": [ + "IMG_4501.jpeg" + ], + "title_normalized": "skip bo" }, { - "title_raw": "Café International", + "title_raw": "a Gentle Rain", "confidence": "high", - "source_photos": ["hand-typed-test-list"] + "publisher_hint": "", + "edition_hint": "Bloom Edition", + "year_hint": null, + "language_hint": "English", + "art_notes": "Dark teal/green box top with colorful vertical striped pattern below title", + "source_photos": [ + "IMG_4501.jpeg" + ], + "title_normalized": "gentle rain" }, { - "title_raw": "Citadels", + "title_raw": "PATCH WORK", + "confidence": "medium", + "publisher_hint": "", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Salmon/pink colored box, partially obscured behind 'a Gentle Rain' box", + "source_photos": [ + "IMG_4501.jpeg" + ], + "title_normalized": "patch work" + }, + { + "title_raw": "SANTORINI", "confidence": "high", - "source_photos": ["hand-typed-test-list"] + "publisher_hint": "", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Blue box spine with white bold lettering, Mediterranean/Greek architecture theme implied by name", + "source_photos": [ + "IMG_4501.jpeg", + "IMG_4502.jpeg" + ], + "title_normalized": "santorini" }, { - "title_raw": "Blorvath: Quest of the Zzyzx", - "confidence": "low", - "source_photos": ["hand-typed-test-list"] + "title_raw": "Utter Nonsense!", + "confidence": "high", + "publisher_hint": "", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Yellow/mustard colored box with vintage illustration of two men in coats and hats facing each other", + "source_photos": [ + "IMG_4501.jpeg" + ], + "title_normalized": "utter nonsense" + }, + { + "title_raw": "Cards Against Humanity", + "confidence": "high", + "publisher_hint": "", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Black box with white bold text, simple minimalist design", + "source_photos": [ + "IMG_4501.jpeg" + ], + "title_normalized": "cards against humanity" + }, + { + "title_raw": "Joking Hazard", + "confidence": "high", + "publisher_hint": "CYANIDE & HAPPINESS", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "White box with yellow stick figure character illustration", + "source_photos": [ + "IMG_4501.jpeg", + "IMG_4502.jpeg" + ], + "title_normalized": "joking hazard" + }, + { + "title_raw": "EXPLODING KITTENS", + "confidence": "high", + "publisher_hint": "", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Bottom right corner, colorful playful cat-themed box art", + "source_photos": [ + "IMG_4501.jpeg" + ], + "title_normalized": "exploding kittens" + }, + { + "title_raw": "Herbaceous", + "confidence": "high", + "publisher_hint": "", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Colorful spine with illustrated herb/plant imagery, 'A player's game of...' text visible", + "source_photos": [ + "IMG_4502.jpeg" + ], + "title_normalized": "herbaceous" + }, + { + "title_raw": "Wiz-War", + "confidence": "high", + "publisher_hint": "", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Dark blue/black box, partial text 'board game of...battle and treasures'", + "source_photos": [ + "IMG_4502.jpeg" + ], + "title_normalized": "wiz war" + }, + { + "title_raw": "SCRAWL", + "confidence": "high", + "publisher_hint": "", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Bright pink/magenta box with striped pattern, tagline 'Doodle your way to disaster'", + "source_photos": [ + "IMG_4502.jpeg" + ], + "title_normalized": "scrawl" + }, + { + "title_raw": "FLAT TOP", + "confidence": "high", + "publisher_hint": "", + "edition_hint": "", + "year_hint": 1942, + "language_hint": "English", + "art_notes": "Blue spine, subtitle 'A GAME of Carrier Battles in the Pacific • 1942'", + "source_photos": [ + "IMG_4502.jpeg" + ], + "title_normalized": "flat top" + }, + { + "title_raw": "Diplomacy", + "confidence": "high", + "publisher_hint": "Avalon Hill", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Black spine, subtitle 'The Exciting Game of International Intrigue - For 2 to 7 Players'", + "source_photos": [ + "IMG_4502.jpeg" + ], + "title_normalized": "diplomacy" + }, + { + "title_raw": "Age of Renaissance", + "confidence": "high", + "publisher_hint": "Avalon Hill", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Tan/brown spine with gold diamond emblem, subtitle about building dominant civilization of Europe", + "source_photos": [ + "IMG_4502.jpeg" + ], + "title_normalized": "age of renaissance" + }, + { + "title_raw": "History of the World", + "confidence": "high", + "publisher_hint": "Avalon Hill", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Blue/purple spine, subtitle 'Game of World Conquest'", + "source_photos": [ + "IMG_4502.jpeg" + ], + "title_normalized": "history of world" + }, + { + "title_raw": "Dungeon!", + "confidence": "high", + "publisher_hint": "TSR", + "edition_hint": "", + "year_hint": null, + "language_hint": "English", + "art_notes": "Purple box bottom with fantasy dungeon-themed artwork, visible at base of stack", + "source_photos": [ + "IMG_4502.jpeg" + ], + "title_normalized": "dungeon" } ] diff --git a/scripts/record_fixtures.py b/scripts/record_fixtures.py index f1042d8..25bf3c6 100644 --- a/scripts/record_fixtures.py +++ b/scripts/record_fixtures.py @@ -30,7 +30,7 @@ def main() -> None: "real recordings." ) client = BGGClient(cache_dir=FIXTURE_CACHE) - for entry in load_titles(Path("data/titles.json")): + for entry in load_titles(Path("tests/data/titles.json")): row = resolve_entry(client, entry) print( f"{entry.title_raw!r}: {row.match_status} " diff --git a/tests/data/titles.json b/tests/data/titles.json new file mode 100644 index 0000000..e25d4e6 --- /dev/null +++ b/tests/data/titles.json @@ -0,0 +1,40 @@ +[ + { + "title_raw": "Catan", + "confidence": "high", + "source_photos": ["hand-typed-test-list"] + }, + { + "title_raw": "Wingspan", + "confidence": "high", + "publisher_hint": "Stonemaier Games", + "year_hint": 2019, + "language_hint": "English", + "source_photos": ["hand-typed-test-list"] + }, + { + "title_raw": "Wingspan: European Expansion", + "confidence": "high", + "source_photos": ["hand-typed-test-list"] + }, + { + "title_raw": "Wingspan Europe", + "confidence": "medium", + "source_photos": ["hand-typed-test-list"] + }, + { + "title_raw": "Café International", + "confidence": "high", + "source_photos": ["hand-typed-test-list"] + }, + { + "title_raw": "Citadels", + "confidence": "high", + "source_photos": ["hand-typed-test-list"] + }, + { + "title_raw": "Blorvath: Quest of the Zzyzx", + "confidence": "low", + "source_photos": ["hand-typed-test-list"] + } +] diff --git a/tests/test_resolve.py b/tests/test_resolve.py index 1cd9859..c518d47 100644 --- a/tests/test_resolve.py +++ b/tests/test_resolve.py @@ -28,7 +28,7 @@ from bggpipe.resolve import ( ) FIXTURES = Path(__file__).parent / "fixtures" / "bgg_cache" -TITLES_JSON = Path(__file__).parent.parent / "data" / "titles.json" +TITLES_JSON = Path(__file__).parent / "data" / "titles.json" def _no_network(request: httpx.Request) -> httpx.Response: