Off-BGG games become local library citizens

An unmatched title that's a REAL game BGG doesn't have dead-ended:
manual id or reject. The RPG local-citizen pattern generalizes to a
human decision — review (web + TUI, key l) gains "not on BGG — keep
locally": match_status "local" clears any BGG identity, diff routes it
to local_only (never queued), and enrich synthesizes a library entry
from the game's own photo reads (name, year, publisher cue — no API
call, so even a blocked run lands them; pruning keeps local keys).
Library and Titles show a "local — not on BGG" chip; Help's legend,
review description, and shortcuts cover the new verb, distinguishing
it from reject (bad read / not a game).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
This commit is contained in:
Eric Wagoner
2026-08-05 18:59:56 -04:00
co-authored by Claude Fable 5
parent 59f4b8c43c
commit e187ed4f3f
15 changed files with 184 additions and 24 deletions
+2 -5
View File
@@ -29,7 +29,6 @@ TICKET TO RIDE,9209,Ticket to Ride,2004,boardgame,auto,,,version_ambiguous,"[{""
CATAN,13,Catan,1995,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 13, ""name"": ""Catan"", ""year"": 1995, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 243104, ""rank"": 626}, {""bgg_id"": 66056, ""name"": ""Catan Двубоят"", ""year"": 2010, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 21829, ""rank"": 1187}]","[{""version_id"": 347121, ""name"": ""English edition 2016"", ""year"": 2016, ""publishers"": [""Catan Studio""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 495726, ""name"": ""English edition 2019"", ""year"": 2019, ""publishers"": [""Catan Studio""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 568647, ""name"": ""English edition 2020"", ""year"": 2020, ""publishers"": [""Catan Studio""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 733924, ""name"": ""English edition 2023"", ""year"": 2023, ""publishers"": [""Catan Studio""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 416798, ""name"": ""Afrikaans edition"", ""year"": 0, ""publishers"": [""Catan Studio""], ""languages"": [""Afrikaans""], ""score"": 2}]",IMG_4504.jpeg;IMG_4542.jpeg,, CATAN,13,Catan,1995,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 13, ""name"": ""Catan"", ""year"": 1995, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 243104, ""rank"": 626}, {""bgg_id"": 66056, ""name"": ""Catan Двубоят"", ""year"": 2010, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 21829, ""rank"": 1187}]","[{""version_id"": 347121, ""name"": ""English edition 2016"", ""year"": 2016, ""publishers"": [""Catan Studio""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 495726, ""name"": ""English edition 2019"", ""year"": 2019, ""publishers"": [""Catan Studio""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 568647, ""name"": ""English edition 2020"", ""year"": 2020, ""publishers"": [""Catan Studio""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 733924, ""name"": ""English edition 2023"", ""year"": 2023, ""publishers"": [""Catan Studio""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 416798, ""name"": ""Afrikaans edition"", ""year"": 0, ""publishers"": [""Catan Studio""], ""languages"": [""Afrikaans""], ""score"": 2}]",IMG_4504.jpeg;IMG_4542.jpeg,,
RAMPAGE,97903,Rampage,2013,boardgame,auto,,,version_unknown,"[{""bgg_id"": 97903, ""name"": ""Rampage"", ""year"": 2013, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4504.jpeg,, RAMPAGE,97903,Rampage,2013,boardgame,auto,,,version_unknown,"[{""bgg_id"": 97903, ""name"": ""Rampage"", ""year"": 2013, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4504.jpeg,,
Onitama,160477,Onitama,2014,boardgame,auto,,,version_unknown,"[{""bgg_id"": 160477, ""name"": ""Onitama"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4505.jpeg,, Onitama,160477,Onitama,2014,boardgame,auto,,,version_unknown,"[{""bgg_id"": 160477, ""name"": ""Onitama"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4505.jpeg,,
MANTIS FALLS (a game of trust),,,,,unmatched,,,,[],[],IMG_4505.jpeg,,
Herbaceous,195314,Herbaceous,2017,boardgame,auto,,,version_unknown,"[{""bgg_id"": 195314, ""name"": ""Herbaceous"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4507.jpeg,, Herbaceous,195314,Herbaceous,2017,boardgame,auto,,,version_unknown,"[{""bgg_id"": 195314, ""name"": ""Herbaceous"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4507.jpeg,,
LABYRINTH,1219,Labyrinth,1986,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 1219, ""name"": ""Labyrinth"", ""year"": 1986, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 35117, ""rank"": 2116}, {""bgg_id"": 6546, ""name"": ""Labyrinth"", ""year"": 1940, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 1078, ""rank"": 31020}, {""bgg_id"": 23074, ""name"": ""Labyrinth"", ""year"": 2000, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 63, ""rank"": null}, {""bgg_id"": 24422, ""name"": ""Labyrinth"", ""year"": 2001, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 121, ""rank"": null}, {""bgg_id"": 65696, ""name"": ""Labyrinth"", ""year"": null, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 46, ""rank"": null}]","[{""version_id"": 488621, ""name"": ""English edition 2017"", ""year"": 2017, ""publishers"": [""Ravensburger AG""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 591493, ""name"": ""English/German edition"", ""year"": 2018, ""publishers"": [""Ravensburger AG""], ""languages"": [""English"", ""German""], ""score"": 3}, {""version_id"": 361899, ""name"": ""Multilingual 30th anniversary edition"", ""year"": 2016, ""publishers"": [""Ravensburger AG""], ""languages"": [""English"", ""French"", ""German"", ""Italian""], ""score"": 3}, {""version_id"": 338520, ""name"": ""Multilingual edition"", ""year"": 2017, ""publishers"": [""Ravensburger AG""], ""languages"": [""English"", ""French"", ""German"", ""Italian"", ""Portuguese"", ""Spanish""], ""score"": 3}, {""version_id"": 440013, ""name"": ""Multilingual edition"", ""year"": 2019, ""publishers"": [""Ravensburger AG""], ""languages"": [""Dutch"", ""English"", ""French"", ""German"", ""Italian""], ""score"": 3}, {""version_id"": 487001, ""name"": ""Multilingual edition"", ""year"": 2019, ""publishers"": [""Ravensburger AG""], ""languages"": [""Dutch"", ""English"", ""French"", ""German"", ""Italian"", ""Spanish""], ""score"": 3}, {""version_id"": 571233, ""name"": ""Multilingual edition"", ""year"": 2021, ""publishers"": [""Ravensburger AG""], ""languages"": [""Dutch"", ""English"", ""French"", ""German"", ""Italian"", ""Portuguese"", ""Spanish""], ""score"": 3}, {""version_id"": 630855, ""name"": ""Multilingual edition"", ""year"": 2022, ""publishers"": [""Ravensburger AG""], ""languages"": [""Dutch"", ""English"", ""French"", ""German"", ""Italian"", ""Portuguese"", ""Spanish""], ""score"": 3}]",IMG_4507.jpeg;IMG_4508.jpeg,, LABYRINTH,1219,Labyrinth,1986,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 1219, ""name"": ""Labyrinth"", ""year"": 1986, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 35117, ""rank"": 2116}, {""bgg_id"": 6546, ""name"": ""Labyrinth"", ""year"": 1940, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 1078, ""rank"": 31020}, {""bgg_id"": 23074, ""name"": ""Labyrinth"", ""year"": 2000, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 63, ""rank"": null}, {""bgg_id"": 24422, ""name"": ""Labyrinth"", ""year"": 2001, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 121, ""rank"": null}, {""bgg_id"": 65696, ""name"": ""Labyrinth"", ""year"": null, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 46, ""rank"": null}]","[{""version_id"": 488621, ""name"": ""English edition 2017"", ""year"": 2017, ""publishers"": [""Ravensburger AG""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 591493, ""name"": ""English/German edition"", ""year"": 2018, ""publishers"": [""Ravensburger AG""], ""languages"": [""English"", ""German""], ""score"": 3}, {""version_id"": 361899, ""name"": ""Multilingual 30th anniversary edition"", ""year"": 2016, ""publishers"": [""Ravensburger AG""], ""languages"": [""English"", ""French"", ""German"", ""Italian""], ""score"": 3}, {""version_id"": 338520, ""name"": ""Multilingual edition"", ""year"": 2017, ""publishers"": [""Ravensburger AG""], ""languages"": [""English"", ""French"", ""German"", ""Italian"", ""Portuguese"", ""Spanish""], ""score"": 3}, {""version_id"": 440013, ""name"": ""Multilingual edition"", ""year"": 2019, ""publishers"": [""Ravensburger AG""], ""languages"": [""Dutch"", ""English"", ""French"", ""German"", ""Italian""], ""score"": 3}, {""version_id"": 487001, ""name"": ""Multilingual edition"", ""year"": 2019, ""publishers"": [""Ravensburger AG""], ""languages"": [""Dutch"", ""English"", ""French"", ""German"", ""Italian"", ""Spanish""], ""score"": 3}, {""version_id"": 571233, ""name"": ""Multilingual edition"", ""year"": 2021, ""publishers"": [""Ravensburger AG""], ""languages"": [""Dutch"", ""English"", ""French"", ""German"", ""Italian"", ""Portuguese"", ""Spanish""], ""score"": 3}, {""version_id"": 630855, ""name"": ""Multilingual edition"", ""year"": 2022, ""publishers"": [""Ravensburger AG""], ""languages"": [""Dutch"", ""English"", ""French"", ""German"", ""Italian"", ""Portuguese"", ""Spanish""], ""score"": 3}]",IMG_4507.jpeg;IMG_4508.jpeg,,
Super Mario Checkers Collector's Edition,,,,,unmatched,,,,[],[],IMG_4508.jpeg,, Super Mario Checkers Collector's Edition,,,,,unmatched,,,,[],[],IMG_4508.jpeg,,
@@ -49,14 +48,12 @@ ROOK,1260,Rook,1906,boardgame,auto,,,version_unknown,"[{""bgg_id"": 1260, ""name
Kensington,2197,Kensington,1979,boardgame,auto,,,version_unknown,"[{""bgg_id"": 2197, ""name"": ""Kensington"", ""year"": 1979, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 1207, ""rank"": 29937}, {""bgg_id"": 276459, ""name"": ""Kensington"", ""year"": 2019, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 99, ""rank"": 20091}]",[],IMG_4512.jpeg,, Kensington,2197,Kensington,1979,boardgame,auto,,,version_unknown,"[{""bgg_id"": 2197, ""name"": ""Kensington"", ""year"": 1979, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 1207, ""rank"": 29937}, {""bgg_id"": 276459, ""name"": ""Kensington"", ""year"": 2019, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 99, ""rank"": 20091}]",[],IMG_4512.jpeg,,
Guillotine,116,Guillotine,1998,boardgame,auto,,,version_unknown,"[{""bgg_id"": 116, ""name"": ""Guillotine"", ""year"": 1998, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 34783, ""rank"": 1630}, {""bgg_id"": 11629, ""name"": ""Guillotine"", ""year"": 1983, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 87, ""rank"": 18073}, {""bgg_id"": 27373, ""name"": ""Guillotine"", ""year"": 1987, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 163, ""rank"": 19576}]",[],IMG_4512.jpeg,, Guillotine,116,Guillotine,1998,boardgame,auto,,,version_unknown,"[{""bgg_id"": 116, ""name"": ""Guillotine"", ""year"": 1998, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 34783, ""rank"": 1630}, {""bgg_id"": 11629, ""name"": ""Guillotine"", ""year"": 1983, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 87, ""rank"": 18073}, {""bgg_id"": 27373, ""name"": ""Guillotine"", ""year"": 1987, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 163, ""rank"": 19576}]",[],IMG_4512.jpeg,,
THE DOOM THAT CAME TO ATLANTIC CITY,124548,The Doom That Came to Atlantic City,2013,boardgame,auto,,,version_unknown,"[{""bgg_id"": 124548, ""name"": ""The Doom That Came to Atlantic City"", ""year"": 2013, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4513.jpeg,, THE DOOM THAT CAME TO ATLANTIC CITY,124548,The Doom That Came to Atlantic City,2013,boardgame,auto,,,version_unknown,"[{""bgg_id"": 124548, ""name"": ""The Doom That Came to Atlantic City"", ""year"": 2013, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4513.jpeg,,
SMALLWORLD,,,,,unmatched,,,,[],[],IMG_4513.jpeg,,
Flick'em UP!,169124,Flick 'em Up!,2015,boardgame,auto,,,version_unknown,"[{""bgg_id"": 169124, ""name"": ""Flick 'em Up!"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4513.jpeg,, Flick'em UP!,169124,Flick 'em Up!,2015,boardgame,auto,,,version_unknown,"[{""bgg_id"": 169124, ""name"": ""Flick 'em Up!"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4513.jpeg,,
Castle Panic,43443,Castle Panic,2009,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 43443, ""name"": ""Castle Panic"", ""year"": 2009, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 468123, ""name"": ""English edition, eighteenth printing"", ""year"": 2018, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 222131, ""name"": ""English edition, eighth printing"", ""year"": 2013, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 265959, ""name"": ""English edition, eleventh printing"", ""year"": 2014, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 343185, ""name"": ""English edition, fifteenth printing"", ""year"": 2016, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 169933, ""name"": ""English edition, fifth printing"", ""year"": 2012, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 24624, ""name"": ""English edition, first printing"", ""year"": 2009, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 337551, ""name"": ""English edition, fourteenth printing"", ""year"": 2016, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 103068, ""name"": ""English edition, fourth printing"", ""year"": 2010, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4513.jpeg,, Castle Panic,43443,Castle Panic,2009,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 43443, ""name"": ""Castle Panic"", ""year"": 2009, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 468123, ""name"": ""English edition, eighteenth printing"", ""year"": 2018, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 222131, ""name"": ""English edition, eighth printing"", ""year"": 2013, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 265959, ""name"": ""English edition, eleventh printing"", ""year"": 2014, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 343185, ""name"": ""English edition, fifteenth printing"", ""year"": 2016, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 169933, ""name"": ""English edition, fifth printing"", ""year"": 2012, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 24624, ""name"": ""English edition, first printing"", ""year"": 2009, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 337551, ""name"": ""English edition, fourteenth printing"", ""year"": 2016, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 103068, ""name"": ""English edition, fourth printing"", ""year"": 2010, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4513.jpeg,,
The Red Dragon Inn,24310,The Red Dragon Inn,2007,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 24310, ""name"": ""The Red Dragon Inn"", ""year"": 2007, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 16870, ""rank"": 2164}, {""bgg_id"": 33451, ""name"": ""The Red Dragon Inn 2"", ""year"": 2008, ""type"": ""boardgame"", ""exact"": false, ""fuzzy"": 93.3, ""owned"": 8355, ""rank"": 2056}, {""bgg_id"": 66510, ""name"": ""The Red Dragon Inn 3"", ""year"": 2011, ""type"": ""boardgame"", ""exact"": false, ""fuzzy"": 93.3, ""owned"": 6246, ""rank"": 2363}, {""bgg_id"": 142402, ""name"": ""The Red Dragon Inn 4"", ""year"": 2013, ""type"": ""boardgame"", ""exact"": false, ""fuzzy"": 93.3, ""owned"": 4107, ""rank"": 3367}, {""bgg_id"": 455834, ""name"": ""The Red Dragon Inn 10"", ""year"": 2026, ""type"": ""boardgame"", ""exact"": false, ""fuzzy"": 90.3, ""owned"": 162, ""rank"": null}]","[{""version_id"": 400341, ""name"": ""English eighth edition"", ""year"": 2017, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 620786, ""name"": ""English eleventh edition"", ""year"": 2021, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 762888, ""name"": ""English fifteenth edition"", ""year"": 2024, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 374258, ""name"": ""English fifth edition"", ""year"": 2012, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 33932, ""name"": ""English first edition"", ""year"": 2007, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 728027, ""name"": ""English fourteenth edition"", ""year"": 2023, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 312374, ""name"": ""English seventh edition"", ""year"": 2015, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 389768, ""name"": ""English sixth edition"", ""year"": 2014, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4514.jpeg,, The Red Dragon Inn,24310,The Red Dragon Inn,2007,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 24310, ""name"": ""The Red Dragon Inn"", ""year"": 2007, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 16870, ""rank"": 2164}, {""bgg_id"": 33451, ""name"": ""The Red Dragon Inn 2"", ""year"": 2008, ""type"": ""boardgame"", ""exact"": false, ""fuzzy"": 93.3, ""owned"": 8355, ""rank"": 2056}, {""bgg_id"": 66510, ""name"": ""The Red Dragon Inn 3"", ""year"": 2011, ""type"": ""boardgame"", ""exact"": false, ""fuzzy"": 93.3, ""owned"": 6246, ""rank"": 2363}, {""bgg_id"": 142402, ""name"": ""The Red Dragon Inn 4"", ""year"": 2013, ""type"": ""boardgame"", ""exact"": false, ""fuzzy"": 93.3, ""owned"": 4107, ""rank"": 3367}, {""bgg_id"": 455834, ""name"": ""The Red Dragon Inn 10"", ""year"": 2026, ""type"": ""boardgame"", ""exact"": false, ""fuzzy"": 90.3, ""owned"": 162, ""rank"": null}]","[{""version_id"": 400341, ""name"": ""English eighth edition"", ""year"": 2017, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 620786, ""name"": ""English eleventh edition"", ""year"": 2021, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 762888, ""name"": ""English fifteenth edition"", ""year"": 2024, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 374258, ""name"": ""English fifth edition"", ""year"": 2012, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 33932, ""name"": ""English first edition"", ""year"": 2007, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 728027, ""name"": ""English fourteenth edition"", ""year"": 2023, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 312374, ""name"": ""English seventh edition"", ""year"": 2015, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 389768, ""name"": ""English sixth edition"", ""year"": 2014, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4514.jpeg,,
Dixit,39856,Dixit,2008,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 39856, ""name"": ""Dixit"", ""year"": 2008, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 482308, ""name"": ""Afrikaans/English edition"", ""year"": 2019, ""publishers"": [""Libellud""], ""languages"": [""Afrikaans"", ""English""], ""score"": 3}, {""version_id"": 488774, ""name"": ""Arabic/English edition"", ""year"": 2019, ""publishers"": [""Boardgame Space"", ""Libellud""], ""languages"": [""Arabic"", ""English""], ""score"": 3}, {""version_id"": 296529, ""name"": ""Asmodee/Libellud English edition 2015"", ""year"": 2015, ""publishers"": [""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 160064, ""name"": ""English edition 2011"", ""year"": 2011, ""publishers"": [""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 193872, ""name"": ""English edition 2012"", ""year"": 2012, ""publishers"": [""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 241272, ""name"": ""English edition 2013"", ""year"": 2013, ""publishers"": [""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 256721, ""name"": ""English edition 2014"", ""year"": 2014, ""publishers"": [""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 273210, ""name"": ""English edition 2014"", ""year"": 2014, ""publishers"": [""Libellud""], ""languages"": [""English""], ""score"": 3}]",IMG_4514.jpeg,, Dixit,39856,Dixit,2008,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 39856, ""name"": ""Dixit"", ""year"": 2008, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 482308, ""name"": ""Afrikaans/English edition"", ""year"": 2019, ""publishers"": [""Libellud""], ""languages"": [""Afrikaans"", ""English""], ""score"": 3}, {""version_id"": 488774, ""name"": ""Arabic/English edition"", ""year"": 2019, ""publishers"": [""Boardgame Space"", ""Libellud""], ""languages"": [""Arabic"", ""English""], ""score"": 3}, {""version_id"": 296529, ""name"": ""Asmodee/Libellud English edition 2015"", ""year"": 2015, ""publishers"": [""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 160064, ""name"": ""English edition 2011"", ""year"": 2011, ""publishers"": [""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 193872, ""name"": ""English edition 2012"", ""year"": 2012, ""publishers"": [""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 241272, ""name"": ""English edition 2013"", ""year"": 2013, ""publishers"": [""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 256721, ""name"": ""English edition 2014"", ""year"": 2014, ""publishers"": [""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 273210, ""name"": ""English edition 2014"", ""year"": 2014, ""publishers"": [""Libellud""], ""languages"": [""English""], ""score"": 3}]",IMG_4514.jpeg,,
Huggermugger,2387,Huggermugger,1989,boardgame,auto,,,version_unknown,"[{""bgg_id"": 2387, ""name"": ""Huggermugger"", ""year"": 1989, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4514.jpeg,, Huggermugger,2387,Huggermugger,1989,boardgame,auto,,,version_unknown,"[{""bgg_id"": 2387, ""name"": ""Huggermugger"", ""year"": 1989, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4514.jpeg,,
The Rocky Horror Trivia Game,20866,The Rocky Horror Trivia Game,2005,boardgame,auto,,,version_unknown,"[{""bgg_id"": 20866, ""name"": ""The Rocky Horror Trivia Game"", ""year"": 2005, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4514.jpeg,, The Rocky Horror Trivia Game,20866,The Rocky Horror Trivia Game,2005,boardgame,auto,,,version_unknown,"[{""bgg_id"": 20866, ""name"": ""The Rocky Horror Trivia Game"", ""year"": 2005, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4514.jpeg,,
ART NOUVEAU ARTISTRY,,,,,unmatched,,,,[],[],IMG_4515.jpeg,,
SPEECHLESS,196105,Speechless,2016,boardgame,approved,,,version_unknown,"[{""bgg_id"": 14370, ""name"": ""Speechless"", ""year"": 1985, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 94, ""rank"": null}, {""bgg_id"": 196105, ""name"": ""Speechless"", ""year"": 2016, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 493, ""rank"": 10370}]",[],IMG_4516.jpeg,, SPEECHLESS,196105,Speechless,2016,boardgame,approved,,,version_unknown,"[{""bgg_id"": 14370, ""name"": ""Speechless"", ""year"": 1985, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 94, ""rank"": null}, {""bgg_id"": 196105, ""name"": ""Speechless"", ""year"": 2016, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 493, ""rank"": 10370}]",[],IMG_4516.jpeg,,
CATAN JUNIOR,125921,Catan: Junior,2012,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 184842, ""name"": ""Catan Junior"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 3961, ""rank"": 4440}, {""bgg_id"": 5824, ""name"": ""Catan Junior"", ""year"": 2003, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 1225, ""rank"": 30649}, {""bgg_id"": 125921, ""name"": ""Catan: Junior"", ""year"": 2012, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 9917, ""rank"": 2616}]","[{""version_id"": 791740, ""name"": ""English edition 2025"", ""year"": 2025, ""publishers"": [""Catan GmbH"", ""Catan Studio""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 558911, ""name"": ""English fourth edition"", ""year"": 2018, ""publishers"": [""Catan GmbH"", ""Catan Studio""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 414618, ""name"": ""English second edition"", ""year"": 2016, ""publishers"": [""Catan GmbH"", ""Catan Studio""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 476263, ""name"": ""English third edition"", ""year"": 2016, ""publishers"": [""Catan Studio""], ""languages"": [""English""], ""score"": 3}]",IMG_4516.jpeg,, CATAN JUNIOR,125921,Catan: Junior,2012,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 184842, ""name"": ""Catan Junior"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 3961, ""rank"": 4440}, {""bgg_id"": 5824, ""name"": ""Catan Junior"", ""year"": 2003, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 1225, ""rank"": 30649}, {""bgg_id"": 125921, ""name"": ""Catan: Junior"", ""year"": 2012, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 9917, ""rank"": 2616}]","[{""version_id"": 791740, ""name"": ""English edition 2025"", ""year"": 2025, ""publishers"": [""Catan GmbH"", ""Catan Studio""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 558911, ""name"": ""English fourth edition"", ""year"": 2018, ""publishers"": [""Catan GmbH"", ""Catan Studio""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 414618, ""name"": ""English second edition"", ""year"": 2016, ""publishers"": [""Catan GmbH"", ""Catan Studio""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 476263, ""name"": ""English third edition"", ""year"": 2016, ""publishers"": [""Catan Studio""], ""languages"": [""English""], ""score"": 3}]",IMG_4516.jpeg,,
SKYLANDERS PORTAL MASTER,156994,Skylanders Portal Master,2013,boardgame,auto,,,version_unknown,"[{""bgg_id"": 156994, ""name"": ""Skylanders Portal Master"", ""year"": 2013, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4516.jpeg,, SKYLANDERS PORTAL MASTER,156994,Skylanders Portal Master,2013,boardgame,auto,,,version_unknown,"[{""bgg_id"": 156994, ""name"": ""Skylanders Portal Master"", ""year"": 2013, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4516.jpeg,,
@@ -66,7 +63,6 @@ Pie Face!,18755,Pie Face!,1964,boardgame,approved,,,version_unknown,"[{""bgg_id"
Walk the Plank!,35505,Walk the Plank!,2013,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 21834, ""name"": ""Walk the Plank"", ""year"": 2002, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 18, ""rank"": null}, {""bgg_id"": 29010, ""name"": ""Walk the Plank"", ""year"": 2007, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 179, ""rank"": 28080}, {""bgg_id"": 152950, ""name"": ""Walk the Plank"", ""year"": null, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 5, ""rank"": null}, {""bgg_id"": 35505, ""name"": ""Walk the Plank!"", ""year"": 2013, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 3127, ""rank"": 3700}]","[{""version_id"": 781669, ""name"": ""English MDG-4311 edition"", ""year"": 2013, ""publishers"": [""Mayday Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 189968, ""name"": ""English MYY4311 edition"", ""year"": 2013, ""publishers"": [""Mayday Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 483159, ""name"": ""Russian edition"", ""year"": 2013, ""publishers"": [""Cosmodrome Games"", ""Mayday Games""], ""languages"": [""Russian""], ""score"": 2}]",IMG_4517.jpeg;IMG_4549.jpeg,, Walk the Plank!,35505,Walk the Plank!,2013,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 21834, ""name"": ""Walk the Plank"", ""year"": 2002, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 18, ""rank"": null}, {""bgg_id"": 29010, ""name"": ""Walk the Plank"", ""year"": 2007, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 179, ""rank"": 28080}, {""bgg_id"": 152950, ""name"": ""Walk the Plank"", ""year"": null, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 5, ""rank"": null}, {""bgg_id"": 35505, ""name"": ""Walk the Plank!"", ""year"": 2013, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 3127, ""rank"": 3700}]","[{""version_id"": 781669, ""name"": ""English MDG-4311 edition"", ""year"": 2013, ""publishers"": [""Mayday Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 189968, ""name"": ""English MYY4311 edition"", ""year"": 2013, ""publishers"": [""Mayday Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 483159, ""name"": ""Russian edition"", ""year"": 2013, ""publishers"": [""Cosmodrome Games"", ""Mayday Games""], ""languages"": [""Russian""], ""score"": 2}]",IMG_4517.jpeg;IMG_4549.jpeg,,
AGRICOLA,31260,Agricola,2007,boardgame,auto,297589,Z-Man Games English fifth edition,version_auto,"[{""bgg_id"": 31260, ""name"": ""Agricola"", ""year"": 2007, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 297589, ""name"": ""Z-Man Games English fifth edition"", ""year"": 2013, ""publishers"": [""Filosofia Éditions"", ""Lookout Games"", ""Z-Man Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 165308, ""name"": ""999 Games Dutch second edition 2011"", ""year"": 2011, ""publishers"": [""999 Games"", ""Lookout Games""], ""languages"": [""Dutch""], ""score"": 2}, {""version_id"": 266028, ""name"": ""Hobby World Russian first edition"", ""year"": 2013, ""publishers"": [""Hobby World"", ""Lookout Games""], ""languages"": [""Russian""], ""score"": 2}, {""version_id"": 22673, ""name"": ""Lookout Games German first edition"", ""year"": 2007, ""publishers"": [""Lookout Games""], ""languages"": [""German""], ""score"": 2}, {""version_id"": 64715, ""name"": ""Lookout Games German second edition 2008 with animeeples"", ""year"": 2008, ""publishers"": [""Lookout Games""], ""languages"": [""German""], ""score"": 2}, {""version_id"": 219203, ""name"": ""Lookout Games German seventh edition 2012 with DSP and SdJ"", ""year"": 2012, ""publishers"": [""Lookout Games""], ""languages"": [""German""], ""score"": 2}, {""version_id"": 64718, ""name"": ""Lookout Games German third edition 2009 with DSP and SdJ"", ""year"": 2009, ""publishers"": [""Lookout Games""], ""languages"": [""German""], ""score"": 2}, {""version_id"": 113858, ""name"": ""Lookout Games Russian first edition 2011"", ""year"": 2011, ""publishers"": [""Lookout Games"", ""Smart Ltd""], ""languages"": [""Russian""], ""score"": 2}]",IMG_4518.jpeg,, AGRICOLA,31260,Agricola,2007,boardgame,auto,297589,Z-Man Games English fifth edition,version_auto,"[{""bgg_id"": 31260, ""name"": ""Agricola"", ""year"": 2007, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 297589, ""name"": ""Z-Man Games English fifth edition"", ""year"": 2013, ""publishers"": [""Filosofia Éditions"", ""Lookout Games"", ""Z-Man Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 165308, ""name"": ""999 Games Dutch second edition 2011"", ""year"": 2011, ""publishers"": [""999 Games"", ""Lookout Games""], ""languages"": [""Dutch""], ""score"": 2}, {""version_id"": 266028, ""name"": ""Hobby World Russian first edition"", ""year"": 2013, ""publishers"": [""Hobby World"", ""Lookout Games""], ""languages"": [""Russian""], ""score"": 2}, {""version_id"": 22673, ""name"": ""Lookout Games German first edition"", ""year"": 2007, ""publishers"": [""Lookout Games""], ""languages"": [""German""], ""score"": 2}, {""version_id"": 64715, ""name"": ""Lookout Games German second edition 2008 with animeeples"", ""year"": 2008, ""publishers"": [""Lookout Games""], ""languages"": [""German""], ""score"": 2}, {""version_id"": 219203, ""name"": ""Lookout Games German seventh edition 2012 with DSP and SdJ"", ""year"": 2012, ""publishers"": [""Lookout Games""], ""languages"": [""German""], ""score"": 2}, {""version_id"": 64718, ""name"": ""Lookout Games German third edition 2009 with DSP and SdJ"", ""year"": 2009, ""publishers"": [""Lookout Games""], ""languages"": [""German""], ""score"": 2}, {""version_id"": 113858, ""name"": ""Lookout Games Russian first edition 2011"", ""year"": 2011, ""publishers"": [""Lookout Games"", ""Smart Ltd""], ""languages"": [""Russian""], ""score"": 2}]",IMG_4518.jpeg,,
METEOR,166109,Meteor,2014,boardgame,auto,,,version_unknown,"[{""bgg_id"": 25172, ""name"": ""Meteor"", ""year"": 1975, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 56, ""rank"": null}, {""bgg_id"": 166109, ""name"": ""Meteor"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 1396, ""rank"": 10371}, {""bgg_id"": 246740, ""name"": ""Meteors"", ""year"": 2018, ""type"": ""boardgame"", ""exact"": false, ""fuzzy"": 92.3, ""owned"": 71, ""rank"": 25541}]",[],IMG_4518.jpeg,, METEOR,166109,Meteor,2014,boardgame,auto,,,version_unknown,"[{""bgg_id"": 25172, ""name"": ""Meteor"", ""year"": 1975, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 56, ""rank"": null}, {""bgg_id"": 166109, ""name"": ""Meteor"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 1396, ""rank"": 10371}, {""bgg_id"": 246740, ""name"": ""Meteors"", ""year"": 2018, ""type"": ""boardgame"", ""exact"": false, ""fuzzy"": 92.3, ""owned"": 71, ""rank"": 25541}]",[],IMG_4518.jpeg,,
MAGE WARS ACADEMY CORE SET,,,,,unmatched,,,,[],[],IMG_4518.jpeg,,
DOCTOR WHO THE card GAME,125675,Doctor Who: The Card Game,2012,boardgame,auto,,,version_unknown,"[{""bgg_id"": 125675, ""name"": ""Doctor Who: The Card Game"", ""year"": 2012, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4518.jpeg,, DOCTOR WHO THE card GAME,125675,Doctor Who: The Card Game,2012,boardgame,auto,,,version_unknown,"[{""bgg_id"": 125675, ""name"": ""Doctor Who: The Card Game"", ""year"": 2012, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4518.jpeg,,
DUNGEONS & DRAGONS,140509,Dungeons & Dragons,1985,boardgame,auto,,,version_unknown,"[{""bgg_id"": 140509, ""name"": ""Dungeons & Dragons"", ""year"": 1985, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4519.jpeg,, DUNGEONS & DRAGONS,140509,Dungeons & Dragons,1985,boardgame,auto,,,version_unknown,"[{""bgg_id"": 140509, ""name"": ""Dungeons & Dragons"", ""year"": 1985, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4519.jpeg,,
DUNGEONS & DRAGONS FANTASY GAME RULES,,,,,unmatched,,,,[],[],IMG_4519.jpeg,, DUNGEONS & DRAGONS FANTASY GAME RULES,,,,,unmatched,,,,[],[],IMG_4519.jpeg,,
@@ -110,7 +106,7 @@ Greed Quest,9666,Greed Quest,2004,boardgame,auto,215137,English edition,version_
GLOOMHAVEN - Jaws of the Lion,291457,Gloomhaven: Jaws of the Lion,2020,boardgame,auto,,,version_unknown,"[{""bgg_id"": 291457, ""name"": ""Gloomhaven: Jaws of the Lion"", ""year"": 2020, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4538.jpeg,, GLOOMHAVEN - Jaws of the Lion,291457,Gloomhaven: Jaws of the Lion,2020,boardgame,auto,,,version_unknown,"[{""bgg_id"": 291457, ""name"": ""Gloomhaven: Jaws of the Lion"", ""year"": 2020, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4538.jpeg,,
SCRABBLE SLAM!,40508,Scrabble Slam!,2008,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 40508, ""name"": ""Scrabble Slam!"", ""year"": 2008, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 39335, ""name"": ""English edition"", ""year"": 2009, ""publishers"": [""Hasbro""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 644326, ""name"": ""English edition with Hasbro logo"", ""year"": 2008, ""publishers"": [""Hasbro"", ""Parker Brothers""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 25061, ""name"": ""English edition with Parker Brothers logo"", ""year"": 2008, ""publishers"": [""Hasbro"", ""Parker Brothers""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 294332, ""name"": ""English/French edition"", ""year"": 2008, ""publishers"": [""Hasbro"", ""Parker Brothers""], ""languages"": [""English"", ""French""], ""score"": 3}, {""version_id"": 480950, ""name"": ""English/French edition"", ""year"": 2014, ""publishers"": [""Hasbro""], ""languages"": [""English"", ""French""], ""score"": 3}, {""version_id"": 116530, ""name"": ""Dutch edition"", ""year"": 2009, ""publishers"": [""Hasbro""], ""languages"": [""Dutch""], ""score"": 2}, {""version_id"": 345823, ""name"": ""French edition"", ""year"": 2009, ""publishers"": [""Hasbro""], ""languages"": [""French""], ""score"": 2}, {""version_id"": 111167, ""name"": ""German edition"", ""year"": 2010, ""publishers"": [""Hasbro""], ""languages"": [""German""], ""score"": 2}]",IMG_4543.jpeg,, SCRABBLE SLAM!,40508,Scrabble Slam!,2008,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 40508, ""name"": ""Scrabble Slam!"", ""year"": 2008, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 39335, ""name"": ""English edition"", ""year"": 2009, ""publishers"": [""Hasbro""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 644326, ""name"": ""English edition with Hasbro logo"", ""year"": 2008, ""publishers"": [""Hasbro"", ""Parker Brothers""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 25061, ""name"": ""English edition with Parker Brothers logo"", ""year"": 2008, ""publishers"": [""Hasbro"", ""Parker Brothers""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 294332, ""name"": ""English/French edition"", ""year"": 2008, ""publishers"": [""Hasbro"", ""Parker Brothers""], ""languages"": [""English"", ""French""], ""score"": 3}, {""version_id"": 480950, ""name"": ""English/French edition"", ""year"": 2014, ""publishers"": [""Hasbro""], ""languages"": [""English"", ""French""], ""score"": 3}, {""version_id"": 116530, ""name"": ""Dutch edition"", ""year"": 2009, ""publishers"": [""Hasbro""], ""languages"": [""Dutch""], ""score"": 2}, {""version_id"": 345823, ""name"": ""French edition"", ""year"": 2009, ""publishers"": [""Hasbro""], ""languages"": [""French""], ""score"": 2}, {""version_id"": 111167, ""name"": ""German edition"", ""year"": 2010, ""publishers"": [""Hasbro""], ""languages"": [""German""], ""score"": 2}]",IMG_4543.jpeg,,
CAT STAX,,,,,unmatched,,,,[],[],IMG_4544.jpeg,, CAT STAX,,,,,unmatched,,,,[],[],IMG_4544.jpeg,,
POCKET FARKEL,,,,,unmatched,,,,[],[],IMG_4545.jpeg,, POCKET FARKEL,3181,Farkle,1930,boardgame,approved,,,version_ambiguous,[],"[{""version_id"": 374844, ""name"": ""Legendary Games black flat pack edition"", ""year"": 1996, ""publishers"": [""Legendary Games, Inc.""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 390425, ""name"": ""Legendary Games blister pack editon"", ""year"": 0, ""publishers"": [""Cardinal"", ""Legendary Games, Inc.""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 87931, ""name"": ""Legendary Games cannister edition"", ""year"": 0, ""publishers"": [""Legendary Games, Inc."", ""Reveal Entertainment, Inc.""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 295385, ""name"": ""Legendary Games dice rolling tray edition"", ""year"": 2006, ""publishers"": [""Legendary Games, Inc.""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 658688, ""name"": ""Legendary Games English/Spanish \""Alaska: The Last Frontier\"" edition"", ""year"": 1999, ""publishers"": [""Legendary Games, Inc.""], ""languages"": [""English"", ""Spanish""], ""score"": 3}, {""version_id"": 374847, ""name"": ""Legendary Games flat pack edition"", ""year"": 0, ""publishers"": [""Legendary Games, Inc.""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 247184, ""name"": ""Legendary Games flat pack edition 1996"", ""year"": 1996, ""publishers"": [""Legendary Games, Inc.""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 374842, ""name"": ""Legendary Games flat pack edition 1996"", ""year"": 1996, ""publishers"": [""Legendary Games, Inc.""], ""languages"": [""English""], ""score"": 3}]",IMG_4545.jpeg,,
Go Fish Yourself,173057,Go Fish Yourself,2015,boardgame,auto,,,version_unknown,"[{""bgg_id"": 173057, ""name"": ""Go Fish Yourself"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4546.jpeg,, Go Fish Yourself,173057,Go Fish Yourself,2015,boardgame,auto,,,version_unknown,"[{""bgg_id"": 173057, ""name"": ""Go Fish Yourself"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4546.jpeg,,
Hold your Breath!,150925,Hold Your Breath!,2014,boardgame,auto,,,version_unknown,"[{""bgg_id"": 150925, ""name"": ""Hold Your Breath!"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4547.jpeg,, Hold your Breath!,150925,Hold Your Breath!,2014,boardgame,auto,,,version_unknown,"[{""bgg_id"": 150925, ""name"": ""Hold Your Breath!"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4547.jpeg,,
Dastardly Dirigibles,193485,Dastardly Dirigibles,2016,boardgame,auto,300446,English edition,version_auto,"[{""bgg_id"": 193485, ""name"": ""Dastardly Dirigibles"", ""year"": 2016, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 300446, ""name"": ""English edition"", ""year"": 2016, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4548.jpeg,, Dastardly Dirigibles,193485,Dastardly Dirigibles,2016,boardgame,auto,300446,English edition,version_auto,"[{""bgg_id"": 193485, ""name"": ""Dastardly Dirigibles"", ""year"": 2016, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 300446, ""name"": ""English edition"", ""year"": 2016, ""publishers"": [""Fireside Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4548.jpeg,,
@@ -131,3 +127,4 @@ The Simpsons Trivia Game,2980,The Simpsons Trivia Game,2000,boardgame,auto,,,ver
THE CAT GAME,231302,The Cat Game,2017,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 231302, ""name"": ""The Cat Game"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 361880, ""name"": ""English/French edition"", ""year"": 2017, ""publishers"": [""Spin Master Ltd.""], ""languages"": [""English"", ""French""], ""score"": 2}, {""version_id"": 433263, ""name"": ""German edition"", ""year"": 0, ""publishers"": [""Spin Master Ltd.""], ""languages"": [""German""], ""score"": 2}]",shelf-20260803-213756.jpg,, THE CAT GAME,231302,The Cat Game,2017,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 231302, ""name"": ""The Cat Game"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 361880, ""name"": ""English/French edition"", ""year"": 2017, ""publishers"": [""Spin Master Ltd.""], ""languages"": [""English"", ""French""], ""score"": 2}, {""version_id"": 433263, ""name"": ""German edition"", ""year"": 0, ""publishers"": [""Spin Master Ltd.""], ""languages"": [""German""], ""score"": 2}]",shelf-20260803-213756.jpg,,
BLINK,1197,Blink,1995,boardgame,auto,,,version_unknown,"[{""bgg_id"": 1197, ""name"": ""Blink"", ""year"": 1995, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 6143, ""rank"": 6149}, {""bgg_id"": 185341, ""name"": ""Blink!"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 111, ""rank"": null}, {""bgg_id"": 15852, ""name"": ""Blinki"", ""year"": 1910, ""type"": ""boardgame"", ""exact"": false, ""fuzzy"": 90.9, ""owned"": 106, ""rank"": 30046}]",[],shelf-20260803-215650.jpg,, BLINK,1197,Blink,1995,boardgame,auto,,,version_unknown,"[{""bgg_id"": 1197, ""name"": ""Blink"", ""year"": 1995, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 6143, ""rank"": 6149}, {""bgg_id"": 185341, ""name"": ""Blink!"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 111, ""rank"": null}, {""bgg_id"": 15852, ""name"": ""Blinki"", ""year"": 1910, ""type"": ""boardgame"", ""exact"": false, ""fuzzy"": 90.9, ""owned"": 106, ""rank"": 30046}]",[],shelf-20260803-215650.jpg,,
The Office Trivia Game,352819,The Office Trivia Game,2019,boardgame,approved,,,version_error,"[{""bgg_id"": 352819, ""name"": ""The Office Trivia Game"", ""year"": 2019, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 159, ""rank"": null}, {""bgg_id"": 36895, ""name"": ""The Office Trivia Game"", ""year"": 2008, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 432, ""rank"": 30024}]",[],shelf-20260803-215703.jpg,, The Office Trivia Game,352819,The Office Trivia Game,2019,boardgame,approved,,,version_error,"[{""bgg_id"": 352819, ""name"": ""The Office Trivia Game"", ""year"": 2019, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 159, ""rank"": null}, {""bgg_id"": 36895, ""name"": ""The Office Trivia Game"", ""year"": 2008, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 432, ""rank"": 30024}]",[],shelf-20260803-215703.jpg,,
Patchwork,163412,Patchwork,2014,boardgame,auto,,,version_unknown,"[{""bgg_id"": 163412, ""name"": ""Patchwork"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 124114, ""rank"": 148}, {""bgg_id"": 41585, ""name"": ""Patchwork"", ""year"": 2009, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 473, ""rank"": 14984}]",[],IMG_4501.jpeg,,
1 title_raw bgg_id bgg_name year type match_status version_id version_name version_status candidates_json version_candidates_json source_photos merged_into dedupe_veto
29 CATAN 13 Catan 1995 boardgame auto version_ambiguous [{"bgg_id": 13, "name": "Catan", "year": 1995, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 243104, "rank": 626}, {"bgg_id": 66056, "name": "Catan Двубоят", "year": 2010, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 21829, "rank": 1187}] [{"version_id": 347121, "name": "English edition 2016", "year": 2016, "publishers": ["Catan Studio"], "languages": ["English"], "score": 3}, {"version_id": 495726, "name": "English edition 2019", "year": 2019, "publishers": ["Catan Studio"], "languages": ["English"], "score": 3}, {"version_id": 568647, "name": "English edition 2020", "year": 2020, "publishers": ["Catan Studio"], "languages": ["English"], "score": 3}, {"version_id": 733924, "name": "English edition 2023", "year": 2023, "publishers": ["Catan Studio"], "languages": ["English"], "score": 3}, {"version_id": 416798, "name": "Afrikaans edition", "year": 0, "publishers": ["Catan Studio"], "languages": ["Afrikaans"], "score": 2}] IMG_4504.jpeg;IMG_4542.jpeg
30 RAMPAGE 97903 Rampage 2013 boardgame auto version_unknown [{"bgg_id": 97903, "name": "Rampage", "year": 2013, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [] IMG_4504.jpeg
31 Onitama 160477 Onitama 2014 boardgame auto version_unknown [{"bgg_id": 160477, "name": "Onitama", "year": 2014, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [] IMG_4505.jpeg
MANTIS FALLS (a game of trust) unmatched [] [] IMG_4505.jpeg
32 Herbaceous 195314 Herbaceous 2017 boardgame auto version_unknown [{"bgg_id": 195314, "name": "Herbaceous", "year": 2017, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [] IMG_4507.jpeg
33 LABYRINTH 1219 Labyrinth 1986 boardgame auto version_ambiguous [{"bgg_id": 1219, "name": "Labyrinth", "year": 1986, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 35117, "rank": 2116}, {"bgg_id": 6546, "name": "Labyrinth", "year": 1940, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 1078, "rank": 31020}, {"bgg_id": 23074, "name": "Labyrinth", "year": 2000, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 63, "rank": null}, {"bgg_id": 24422, "name": "Labyrinth", "year": 2001, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 121, "rank": null}, {"bgg_id": 65696, "name": "Labyrinth", "year": null, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 46, "rank": null}] [{"version_id": 488621, "name": "English edition 2017", "year": 2017, "publishers": ["Ravensburger AG"], "languages": ["English"], "score": 3}, {"version_id": 591493, "name": "English/German edition", "year": 2018, "publishers": ["Ravensburger AG"], "languages": ["English", "German"], "score": 3}, {"version_id": 361899, "name": "Multilingual 30th anniversary edition", "year": 2016, "publishers": ["Ravensburger AG"], "languages": ["English", "French", "German", "Italian"], "score": 3}, {"version_id": 338520, "name": "Multilingual edition", "year": 2017, "publishers": ["Ravensburger AG"], "languages": ["English", "French", "German", "Italian", "Portuguese", "Spanish"], "score": 3}, {"version_id": 440013, "name": "Multilingual edition", "year": 2019, "publishers": ["Ravensburger AG"], "languages": ["Dutch", "English", "French", "German", "Italian"], "score": 3}, {"version_id": 487001, "name": "Multilingual edition", "year": 2019, "publishers": ["Ravensburger AG"], "languages": ["Dutch", "English", "French", "German", "Italian", "Spanish"], "score": 3}, {"version_id": 571233, "name": "Multilingual edition", "year": 2021, "publishers": ["Ravensburger AG"], "languages": ["Dutch", "English", "French", "German", "Italian", "Portuguese", "Spanish"], "score": 3}, {"version_id": 630855, "name": "Multilingual edition", "year": 2022, "publishers": ["Ravensburger AG"], "languages": ["Dutch", "English", "French", "German", "Italian", "Portuguese", "Spanish"], "score": 3}] IMG_4507.jpeg;IMG_4508.jpeg
34 Super Mario Checkers Collector's Edition unmatched [] [] IMG_4508.jpeg
48 Kensington 2197 Kensington 1979 boardgame auto version_unknown [{"bgg_id": 2197, "name": "Kensington", "year": 1979, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 1207, "rank": 29937}, {"bgg_id": 276459, "name": "Kensington", "year": 2019, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 99, "rank": 20091}] [] IMG_4512.jpeg
49 Guillotine 116 Guillotine 1998 boardgame auto version_unknown [{"bgg_id": 116, "name": "Guillotine", "year": 1998, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 34783, "rank": 1630}, {"bgg_id": 11629, "name": "Guillotine", "year": 1983, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 87, "rank": 18073}, {"bgg_id": 27373, "name": "Guillotine", "year": 1987, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 163, "rank": 19576}] [] IMG_4512.jpeg
50 THE DOOM THAT CAME TO ATLANTIC CITY 124548 The Doom That Came to Atlantic City 2013 boardgame auto version_unknown [{"bgg_id": 124548, "name": "The Doom That Came to Atlantic City", "year": 2013, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [] IMG_4513.jpeg
SMALLWORLD unmatched [] [] IMG_4513.jpeg
51 Flick'em UP! 169124 Flick 'em Up! 2015 boardgame auto version_unknown [{"bgg_id": 169124, "name": "Flick 'em Up!", "year": 2015, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [] IMG_4513.jpeg
52 Castle Panic 43443 Castle Panic 2009 boardgame auto version_ambiguous [{"bgg_id": 43443, "name": "Castle Panic", "year": 2009, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [{"version_id": 468123, "name": "English edition, eighteenth printing", "year": 2018, "publishers": ["Fireside Games"], "languages": ["English"], "score": 3}, {"version_id": 222131, "name": "English edition, eighth printing", "year": 2013, "publishers": ["Fireside Games"], "languages": ["English"], "score": 3}, {"version_id": 265959, "name": "English edition, eleventh printing", "year": 2014, "publishers": ["Fireside Games"], "languages": ["English"], "score": 3}, {"version_id": 343185, "name": "English edition, fifteenth printing", "year": 2016, "publishers": ["Fireside Games"], "languages": ["English"], "score": 3}, {"version_id": 169933, "name": "English edition, fifth printing", "year": 2012, "publishers": ["Fireside Games"], "languages": ["English"], "score": 3}, {"version_id": 24624, "name": "English edition, first printing", "year": 2009, "publishers": ["Fireside Games"], "languages": ["English"], "score": 3}, {"version_id": 337551, "name": "English edition, fourteenth printing", "year": 2016, "publishers": ["Fireside Games"], "languages": ["English"], "score": 3}, {"version_id": 103068, "name": "English edition, fourth printing", "year": 2010, "publishers": ["Fireside Games"], "languages": ["English"], "score": 3}] IMG_4513.jpeg
53 The Red Dragon Inn 24310 The Red Dragon Inn 2007 boardgame auto version_ambiguous [{"bgg_id": 24310, "name": "The Red Dragon Inn", "year": 2007, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 16870, "rank": 2164}, {"bgg_id": 33451, "name": "The Red Dragon Inn 2", "year": 2008, "type": "boardgame", "exact": false, "fuzzy": 93.3, "owned": 8355, "rank": 2056}, {"bgg_id": 66510, "name": "The Red Dragon Inn 3", "year": 2011, "type": "boardgame", "exact": false, "fuzzy": 93.3, "owned": 6246, "rank": 2363}, {"bgg_id": 142402, "name": "The Red Dragon Inn 4", "year": 2013, "type": "boardgame", "exact": false, "fuzzy": 93.3, "owned": 4107, "rank": 3367}, {"bgg_id": 455834, "name": "The Red Dragon Inn 10", "year": 2026, "type": "boardgame", "exact": false, "fuzzy": 90.3, "owned": 162, "rank": null}] [{"version_id": 400341, "name": "English eighth edition", "year": 2017, "publishers": ["SlugFest Games"], "languages": ["English"], "score": 3}, {"version_id": 620786, "name": "English eleventh edition", "year": 2021, "publishers": ["SlugFest Games"], "languages": ["English"], "score": 3}, {"version_id": 762888, "name": "English fifteenth edition", "year": 2024, "publishers": ["SlugFest Games"], "languages": ["English"], "score": 3}, {"version_id": 374258, "name": "English fifth edition", "year": 2012, "publishers": ["SlugFest Games"], "languages": ["English"], "score": 3}, {"version_id": 33932, "name": "English first edition", "year": 2007, "publishers": ["SlugFest Games"], "languages": ["English"], "score": 3}, {"version_id": 728027, "name": "English fourteenth edition", "year": 2023, "publishers": ["SlugFest Games"], "languages": ["English"], "score": 3}, {"version_id": 312374, "name": "English seventh edition", "year": 2015, "publishers": ["SlugFest Games"], "languages": ["English"], "score": 3}, {"version_id": 389768, "name": "English sixth edition", "year": 2014, "publishers": ["SlugFest Games"], "languages": ["English"], "score": 3}] IMG_4514.jpeg
54 Dixit 39856 Dixit 2008 boardgame auto version_ambiguous [{"bgg_id": 39856, "name": "Dixit", "year": 2008, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [{"version_id": 482308, "name": "Afrikaans/English edition", "year": 2019, "publishers": ["Libellud"], "languages": ["Afrikaans", "English"], "score": 3}, {"version_id": 488774, "name": "Arabic/English edition", "year": 2019, "publishers": ["Boardgame Space", "Libellud"], "languages": ["Arabic", "English"], "score": 3}, {"version_id": 296529, "name": "Asmodee/Libellud English edition 2015", "year": 2015, "publishers": ["Libellud"], "languages": ["English"], "score": 3}, {"version_id": 160064, "name": "English edition 2011", "year": 2011, "publishers": ["Libellud"], "languages": ["English"], "score": 3}, {"version_id": 193872, "name": "English edition 2012", "year": 2012, "publishers": ["Libellud"], "languages": ["English"], "score": 3}, {"version_id": 241272, "name": "English edition 2013", "year": 2013, "publishers": ["Libellud"], "languages": ["English"], "score": 3}, {"version_id": 256721, "name": "English edition 2014", "year": 2014, "publishers": ["Libellud"], "languages": ["English"], "score": 3}, {"version_id": 273210, "name": "English edition 2014", "year": 2014, "publishers": ["Libellud"], "languages": ["English"], "score": 3}] IMG_4514.jpeg
55 Huggermugger 2387 Huggermugger 1989 boardgame auto version_unknown [{"bgg_id": 2387, "name": "Huggermugger", "year": 1989, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [] IMG_4514.jpeg
56 The Rocky Horror Trivia Game 20866 The Rocky Horror Trivia Game 2005 boardgame auto version_unknown [{"bgg_id": 20866, "name": "The Rocky Horror Trivia Game", "year": 2005, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [] IMG_4514.jpeg
ART NOUVEAU ARTISTRY unmatched [] [] IMG_4515.jpeg
57 SPEECHLESS 196105 Speechless 2016 boardgame approved version_unknown [{"bgg_id": 14370, "name": "Speechless", "year": 1985, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 94, "rank": null}, {"bgg_id": 196105, "name": "Speechless", "year": 2016, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 493, "rank": 10370}] [] IMG_4516.jpeg
58 CATAN JUNIOR 125921 Catan: Junior 2012 boardgame auto version_ambiguous [{"bgg_id": 184842, "name": "Catan Junior", "year": 2014, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 3961, "rank": 4440}, {"bgg_id": 5824, "name": "Catan Junior", "year": 2003, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 1225, "rank": 30649}, {"bgg_id": 125921, "name": "Catan: Junior", "year": 2012, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 9917, "rank": 2616}] [{"version_id": 791740, "name": "English edition 2025", "year": 2025, "publishers": ["Catan GmbH", "Catan Studio"], "languages": ["English"], "score": 3}, {"version_id": 558911, "name": "English fourth edition", "year": 2018, "publishers": ["Catan GmbH", "Catan Studio"], "languages": ["English"], "score": 3}, {"version_id": 414618, "name": "English second edition", "year": 2016, "publishers": ["Catan GmbH", "Catan Studio"], "languages": ["English"], "score": 3}, {"version_id": 476263, "name": "English third edition", "year": 2016, "publishers": ["Catan Studio"], "languages": ["English"], "score": 3}] IMG_4516.jpeg
59 SKYLANDERS PORTAL MASTER 156994 Skylanders Portal Master 2013 boardgame auto version_unknown [{"bgg_id": 156994, "name": "Skylanders Portal Master", "year": 2013, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [] IMG_4516.jpeg
63 Walk the Plank! 35505 Walk the Plank! 2013 boardgame auto version_ambiguous [{"bgg_id": 21834, "name": "Walk the Plank", "year": 2002, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 18, "rank": null}, {"bgg_id": 29010, "name": "Walk the Plank", "year": 2007, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 179, "rank": 28080}, {"bgg_id": 152950, "name": "Walk the Plank", "year": null, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 5, "rank": null}, {"bgg_id": 35505, "name": "Walk the Plank!", "year": 2013, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 3127, "rank": 3700}] [{"version_id": 781669, "name": "English MDG-4311 edition", "year": 2013, "publishers": ["Mayday Games"], "languages": ["English"], "score": 3}, {"version_id": 189968, "name": "English MYY4311 edition", "year": 2013, "publishers": ["Mayday Games"], "languages": ["English"], "score": 3}, {"version_id": 483159, "name": "Russian edition", "year": 2013, "publishers": ["Cosmodrome Games", "Mayday Games"], "languages": ["Russian"], "score": 2}] IMG_4517.jpeg;IMG_4549.jpeg
64 AGRICOLA 31260 Agricola 2007 boardgame auto 297589 Z-Man Games English fifth edition version_auto [{"bgg_id": 31260, "name": "Agricola", "year": 2007, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [{"version_id": 297589, "name": "Z-Man Games English fifth edition", "year": 2013, "publishers": ["Filosofia Éditions", "Lookout Games", "Z-Man Games"], "languages": ["English"], "score": 3}, {"version_id": 165308, "name": "999 Games Dutch second edition 2011", "year": 2011, "publishers": ["999 Games", "Lookout Games"], "languages": ["Dutch"], "score": 2}, {"version_id": 266028, "name": "Hobby World Russian first edition", "year": 2013, "publishers": ["Hobby World", "Lookout Games"], "languages": ["Russian"], "score": 2}, {"version_id": 22673, "name": "Lookout Games German first edition", "year": 2007, "publishers": ["Lookout Games"], "languages": ["German"], "score": 2}, {"version_id": 64715, "name": "Lookout Games German second edition 2008 with animeeples", "year": 2008, "publishers": ["Lookout Games"], "languages": ["German"], "score": 2}, {"version_id": 219203, "name": "Lookout Games German seventh edition 2012 with DSP and SdJ", "year": 2012, "publishers": ["Lookout Games"], "languages": ["German"], "score": 2}, {"version_id": 64718, "name": "Lookout Games German third edition 2009 with DSP and SdJ", "year": 2009, "publishers": ["Lookout Games"], "languages": ["German"], "score": 2}, {"version_id": 113858, "name": "Lookout Games Russian first edition 2011", "year": 2011, "publishers": ["Lookout Games", "Smart Ltd"], "languages": ["Russian"], "score": 2}] IMG_4518.jpeg
65 METEOR 166109 Meteor 2014 boardgame auto version_unknown [{"bgg_id": 25172, "name": "Meteor", "year": 1975, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 56, "rank": null}, {"bgg_id": 166109, "name": "Meteor", "year": 2014, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 1396, "rank": 10371}, {"bgg_id": 246740, "name": "Meteors", "year": 2018, "type": "boardgame", "exact": false, "fuzzy": 92.3, "owned": 71, "rank": 25541}] [] IMG_4518.jpeg
MAGE WARS ACADEMY CORE SET unmatched [] [] IMG_4518.jpeg
66 DOCTOR WHO THE card GAME 125675 Doctor Who: The Card Game 2012 boardgame auto version_unknown [{"bgg_id": 125675, "name": "Doctor Who: The Card Game", "year": 2012, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [] IMG_4518.jpeg
67 DUNGEONS & DRAGONS 140509 Dungeons & Dragons 1985 boardgame auto version_unknown [{"bgg_id": 140509, "name": "Dungeons & Dragons", "year": 1985, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [] IMG_4519.jpeg
68 DUNGEONS & DRAGONS FANTASY GAME RULES unmatched [] [] IMG_4519.jpeg
106 GLOOMHAVEN - Jaws of the Lion 291457 Gloomhaven: Jaws of the Lion 2020 boardgame auto version_unknown [{"bgg_id": 291457, "name": "Gloomhaven: Jaws of the Lion", "year": 2020, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [] IMG_4538.jpeg
107 SCRABBLE SLAM! 40508 Scrabble Slam! 2008 boardgame auto version_ambiguous [{"bgg_id": 40508, "name": "Scrabble Slam!", "year": 2008, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [{"version_id": 39335, "name": "English edition", "year": 2009, "publishers": ["Hasbro"], "languages": ["English"], "score": 3}, {"version_id": 644326, "name": "English edition with Hasbro logo", "year": 2008, "publishers": ["Hasbro", "Parker Brothers"], "languages": ["English"], "score": 3}, {"version_id": 25061, "name": "English edition with Parker Brothers logo", "year": 2008, "publishers": ["Hasbro", "Parker Brothers"], "languages": ["English"], "score": 3}, {"version_id": 294332, "name": "English/French edition", "year": 2008, "publishers": ["Hasbro", "Parker Brothers"], "languages": ["English", "French"], "score": 3}, {"version_id": 480950, "name": "English/French edition", "year": 2014, "publishers": ["Hasbro"], "languages": ["English", "French"], "score": 3}, {"version_id": 116530, "name": "Dutch edition", "year": 2009, "publishers": ["Hasbro"], "languages": ["Dutch"], "score": 2}, {"version_id": 345823, "name": "French edition", "year": 2009, "publishers": ["Hasbro"], "languages": ["French"], "score": 2}, {"version_id": 111167, "name": "German edition", "year": 2010, "publishers": ["Hasbro"], "languages": ["German"], "score": 2}] IMG_4543.jpeg
108 CAT STAX unmatched [] [] IMG_4544.jpeg
109 POCKET FARKEL 3181 Farkle 1930 boardgame unmatched approved version_ambiguous [] [] [{"version_id": 374844, "name": "Legendary Games black flat pack edition", "year": 1996, "publishers": ["Legendary Games, Inc."], "languages": ["English"], "score": 3}, {"version_id": 390425, "name": "Legendary Games blister pack editon", "year": 0, "publishers": ["Cardinal", "Legendary Games, Inc."], "languages": ["English"], "score": 3}, {"version_id": 87931, "name": "Legendary Games cannister edition", "year": 0, "publishers": ["Legendary Games, Inc.", "Reveal Entertainment, Inc."], "languages": ["English"], "score": 3}, {"version_id": 295385, "name": "Legendary Games dice rolling tray edition", "year": 2006, "publishers": ["Legendary Games, Inc."], "languages": ["English"], "score": 3}, {"version_id": 658688, "name": "Legendary Games English/Spanish \"Alaska: The Last Frontier\" edition", "year": 1999, "publishers": ["Legendary Games, Inc."], "languages": ["English", "Spanish"], "score": 3}, {"version_id": 374847, "name": "Legendary Games flat pack edition", "year": 0, "publishers": ["Legendary Games, Inc."], "languages": ["English"], "score": 3}, {"version_id": 247184, "name": "Legendary Games flat pack edition 1996", "year": 1996, "publishers": ["Legendary Games, Inc."], "languages": ["English"], "score": 3}, {"version_id": 374842, "name": "Legendary Games flat pack edition 1996", "year": 1996, "publishers": ["Legendary Games, Inc."], "languages": ["English"], "score": 3}] IMG_4545.jpeg
110 Go Fish Yourself 173057 Go Fish Yourself 2015 boardgame auto version_unknown [{"bgg_id": 173057, "name": "Go Fish Yourself", "year": 2015, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [] IMG_4546.jpeg
111 Hold your Breath! 150925 Hold Your Breath! 2014 boardgame auto version_unknown [{"bgg_id": 150925, "name": "Hold Your Breath!", "year": 2014, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [] IMG_4547.jpeg
112 Dastardly Dirigibles 193485 Dastardly Dirigibles 2016 boardgame auto 300446 English edition version_auto [{"bgg_id": 193485, "name": "Dastardly Dirigibles", "year": 2016, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [{"version_id": 300446, "name": "English edition", "year": 2016, "publishers": ["Fireside Games"], "languages": ["English"], "score": 3}] IMG_4548.jpeg
127 THE CAT GAME 231302 The Cat Game 2017 boardgame auto version_ambiguous [{"bgg_id": 231302, "name": "The Cat Game", "year": 2017, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": null, "rank": null}] [{"version_id": 361880, "name": "English/French edition", "year": 2017, "publishers": ["Spin Master Ltd."], "languages": ["English", "French"], "score": 2}, {"version_id": 433263, "name": "German edition", "year": 0, "publishers": ["Spin Master Ltd."], "languages": ["German"], "score": 2}] shelf-20260803-213756.jpg
128 BLINK 1197 Blink 1995 boardgame auto version_unknown [{"bgg_id": 1197, "name": "Blink", "year": 1995, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 6143, "rank": 6149}, {"bgg_id": 185341, "name": "Blink!", "year": 2015, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 111, "rank": null}, {"bgg_id": 15852, "name": "Blinki", "year": 1910, "type": "boardgame", "exact": false, "fuzzy": 90.9, "owned": 106, "rank": 30046}] [] shelf-20260803-215650.jpg
129 The Office Trivia Game 352819 The Office Trivia Game 2019 boardgame approved version_error [{"bgg_id": 352819, "name": "The Office Trivia Game", "year": 2019, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 159, "rank": null}, {"bgg_id": 36895, "name": "The Office Trivia Game", "year": 2008, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 432, "rank": 30024}] [] shelf-20260803-215703.jpg
130 Patchwork 163412 Patchwork 2014 boardgame auto version_unknown [{"bgg_id": 163412, "name": "Patchwork", "year": 2014, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 124114, "rank": 148}, {"bgg_id": 41585, "name": "Patchwork", "year": 2009, "type": "boardgame", "exact": true, "fuzzy": 100.0, "owned": 473, "rank": 14984}] [] IMG_4501.jpeg
+20
View File
@@ -57,5 +57,25 @@
"match": "Patch Work", "match": "Patch Work",
"title_raw": "Patchwork", "title_raw": "Patchwork",
"confidence": "high" "confidence": "high"
},
{
"match": "SMALLWORLD",
"title_raw": "SMALL WORLD",
"confidence": "high"
},
{
"match": "ART NOUVEAU ARTISTRY",
"title_raw": "ARTISTRY",
"confidence": "high"
},
{
"match": "MAGE WARS ACADEMY CORE SET",
"title_raw": "MAGE WARS ACADEMY",
"confidence": "high"
},
{
"match": "MANTIS FALLS (a game of trust)",
"title_raw": "MANTIS FALLS",
"confidence": "high"
} }
] ]
+8 -8
View File
@@ -417,7 +417,7 @@
"title_normalized": "onitama" "title_normalized": "onitama"
}, },
{ {
"title_raw": "MANTIS FALLS (a game of trust)", "title_raw": "MANTIS FALLS",
"confidence": "high", "confidence": "high",
"publisher_hint": "Diligent Games", "publisher_hint": "Diligent Games",
"edition_hint": "", "edition_hint": "",
@@ -427,7 +427,7 @@
"source_photos": [ "source_photos": [
"IMG_4505.jpeg" "IMG_4505.jpeg"
], ],
"title_normalized": "mantis falls game of trust" "title_normalized": "mantis falls"
}, },
{ {
"title_raw": "Herbaceous", "title_raw": "Herbaceous",
@@ -680,7 +680,7 @@
"title_normalized": "doom that came to atlantic city" "title_normalized": "doom that came to atlantic city"
}, },
{ {
"title_raw": "SMALLWORLD", "title_raw": "SMALL WORLD",
"confidence": "high", "confidence": "high",
"publisher_hint": "Days of Wonder", "publisher_hint": "Days of Wonder",
"edition_hint": "", "edition_hint": "",
@@ -690,7 +690,7 @@
"source_photos": [ "source_photos": [
"IMG_4513.jpeg" "IMG_4513.jpeg"
], ],
"title_normalized": "smallworld" "title_normalized": "small world"
}, },
{ {
"title_raw": "Flick'em UP!", "title_raw": "Flick'em UP!",
@@ -771,7 +771,7 @@
"title_normalized": "rocky horror trivia game" "title_normalized": "rocky horror trivia game"
}, },
{ {
"title_raw": "ART NOUVEAU ARTISTRY", "title_raw": "ARTISTRY",
"confidence": "high", "confidence": "high",
"publisher_hint": "Dux Somnium", "publisher_hint": "Dux Somnium",
"edition_hint": "", "edition_hint": "",
@@ -781,7 +781,7 @@
"source_photos": [ "source_photos": [
"IMG_4515.jpeg" "IMG_4515.jpeg"
], ],
"title_normalized": "art nouveau artistry" "title_normalized": "artistry"
}, },
{ {
"title_raw": "SPEECHLESS", "title_raw": "SPEECHLESS",
@@ -902,7 +902,7 @@
"title_normalized": "meteor" "title_normalized": "meteor"
}, },
{ {
"title_raw": "MAGE WARS ACADEMY CORE SET", "title_raw": "MAGE WARS ACADEMY",
"confidence": "high", "confidence": "high",
"publisher_hint": "Arcane Wonders", "publisher_hint": "Arcane Wonders",
"edition_hint": "Core Set", "edition_hint": "Core Set",
@@ -912,7 +912,7 @@
"source_photos": [ "source_photos": [
"IMG_4518.jpeg" "IMG_4518.jpeg"
], ],
"title_normalized": "mage wars academy core set" "title_normalized": "mage wars academy"
}, },
{ {
"title_raw": "DOCTOR WHO THE card GAME", "title_raw": "DOCTOR WHO THE card GAME",
+8 -1
View File
@@ -135,6 +135,11 @@ def compute_diff(rows: list[dict], collection: list[CollectionItem]) -> DiffResu
if status == "merged": if status == "merged":
result.merged += 1 # represented by its survivor row result.merged += 1 # represented by its survivor row
continue continue
if status == "local":
# the human's call: a real game BGG doesn't have — a library
# citizen only, never queued
result.local_only.append(row["title_raw"])
continue
if status not in RECOGNIZED_MATCH_STATUSES or not row["bgg_id"]: if status not in RECOGNIZED_MATCH_STATUSES or not row["bgg_id"]:
result.pending.append(row["title_raw"]) result.pending.append(row["title_raw"])
continue continue
@@ -316,7 +321,9 @@ def run_diff(cfg: Config, *, client: BGGClient | None = None) -> DiffResult:
merged_note = f" · {result.merged} merged duplicate(s)" if result.merged else "" merged_note = f" · {result.merged} merged duplicate(s)" if result.merged else ""
local_note = ( local_note = (
f" · {len(result.local_only)} local-only (RPGs)" if result.local_only else "" f" · {len(result.local_only)} local-only (RPGs, off-BGG games)"
if result.local_only
else ""
) )
typer.echo( typer.echo(
f"\n{result.recognized} recognized · {len(result.already_owned)} already " f"\n{result.recognized} recognized · {len(result.already_owned)} already "
+31 -2
View File
@@ -28,7 +28,8 @@ from bggpipe.bgg_client import (
from bggpipe.config import Config from bggpipe.config import Config
from bggpipe.fsio import atomic_write_text from bggpipe.fsio import atomic_write_text
from bggpipe.models import is_confident_version, is_recognized from bggpipe.models import is_confident_version, is_recognized
from bggpipe.resolve import read_matches from bggpipe.normalize import normalize_title
from bggpipe.resolve import load_titles, read_matches
BATCH_SIZE = 20 BATCH_SIZE = 20
@@ -95,12 +96,40 @@ def run_enrich(
games[key] = {**fetched[bgg_id], "version": version} games[key] = {**fetched[bgg_id], "version": version}
updated += 1 updated += 1
# games the human ruled off-BGG: library entries built from their own
# photo reads — no API involved, so a blocked run still lands them
local_keys: set[str] = set()
local_rows = [r for r in rows if r["match_status"] == "local"]
if local_rows:
try:
cues = {
(e.title_raw, ";".join(e.source_photos)): e
for e in load_titles(cfg.titles_path)
}
except FileNotFoundError:
cues = {}
for row in local_rows:
key = f"local:{normalize_title(row['title_raw'])}:{row['source_photos']}"
local_keys.add(key)
entry = cues.get((row["title_raw"], row["source_photos"]))
games[key] = {
"bgg_id": None,
"name": row["title_raw"],
"year": entry.year_hint if entry else None,
"type": "localgame",
"publishers": [entry.publisher_hint]
if entry and entry.publisher_hint
else [],
"source_photos": [p for p in row["source_photos"].split(";") if p],
}
updated += 1
# prune keys no current target claims: a row whose version was approved # prune keys no current target claims: a row whose version was approved
# after a bare-key run (or was later rejected) must not leave an orphan # after a bare-key run (or was later rejected) must not leave an orphan
# entry in the frontend seed data. Only safe when nothing was blocked — # entry in the frontend seed data. Only safe when nothing was blocked —
# a token-less run knows too little to declare anything stale. # a token-less run knows too little to declare anything stale.
if not blocked: if not blocked:
current = {key for key, _, _ in targets} current = {key for key, _, _ in targets} | local_keys
stale = [k for k in games if k not in current] stale = [k for k in games if k not in current]
for k in stale: for k in stale:
del games[k] del games[k]
+17 -3
View File
@@ -357,6 +357,17 @@ class ReviewSession:
row["match_status"] = "rejected" row["match_status"] = "rejected"
self._save(row) self._save(row)
def decide_local(self, row: dict) -> None:
"""A real game BGG simply doesn't have: a local library citizen —
listed and enriched from its own photo reads, never uploaded."""
row["match_status"] = "local"
row["bgg_id"] = ""
row["bgg_name"] = ""
row["version_status"] = ""
row["version_id"] = ""
row["version_name"] = ""
self._save(row)
def open_version_ballot(self, row: dict) -> int: def open_version_ballot(self, row: dict) -> int:
"""The human knows which printing a box is even when the photo """The human knows which printing a box is even when the photo
showed no cues: put EVERY published version on the row's ballot showed no cues: put EVERY published version on the row's ballot
@@ -443,10 +454,10 @@ class ReviewSession:
while True: while True:
self._show_item(row, candidates) self._show_item(row, candidates)
prompt = ( prompt = (
"[1-N] pick (s)kip (r)eject (m <id>) manual BGG id " "[1-N] pick (s)kip (r)eject (l)ocal — not on BGG "
"(f <text>) re-search (q)uit > " "(m <id>) manual BGG id (f <text>) re-search (q)uit > "
if row["match_status"] == "unmatched" if row["match_status"] == "unmatched"
else "[1-N] pick (s)kip (r)eject (q)uit > " else "[1-N] pick (s)kip (r)eject (l)ocal — not on BGG (q)uit > "
) )
answer = self._ask(prompt) answer = self._ask(prompt)
lowered = answer.lower() lowered = answer.lower()
@@ -455,6 +466,9 @@ class ReviewSession:
if lowered == "r": if lowered == "r":
self.decide_reject(row) self.decide_reject(row)
return return
if lowered == "l":
self.decide_local(row)
return
if answer.isdigit() and 1 <= int(answer) <= len(candidates): if answer.isdigit() and 1 <= int(answer) <= len(candidates):
self.decide_pick(row, candidates[int(answer) - 1]) self.decide_pick(row, candidates[int(answer) - 1])
return return
+1
View File
@@ -155,6 +155,7 @@ function statusChip(c) {
if (c.status === "awaiting_resolve") return `<span class="chip wait">awaiting BGG</span>`; if (c.status === "awaiting_resolve") return `<span class="chip wait">awaiting BGG</span>`;
if (c.status === "auto" || c.status === "approved") return `<span class="chip ok">${esc(c.status)}</span>`; if (c.status === "auto" || c.status === "approved") return `<span class="chip ok">${esc(c.status)}</span>`;
if (c.status === "rejected") return `<span class="chip no">rejected</span>`; if (c.status === "rejected") return `<span class="chip no">rejected</span>`;
if (c.status === "local") return `<span class="chip open">local — not on BGG</span>`;
if (c.status === "merged") return `<span class="chip merged" title="merged into ${esc(c.merged_into)}">merged → ${esc(c.merged_into)}</span>`; if (c.status === "merged") return `<span class="chip merged" title="merged into ${esc(c.merged_into)}">merged → ${esc(c.merged_into)}</span>`;
return `<span class="chip open">${esc(c.status)}</span>`; return `<span class="chip open">${esc(c.status)}</span>`;
} }
+4 -3
View File
@@ -28,7 +28,7 @@
<p><b><a href="/">Pipeline</a></b> — run stages one at a time and watch their live output. Shows what's blocking (missing keys, stub data) and the counts at every step.</p> <p><b><a href="/">Pipeline</a></b> — run stages one at a time and watch their live output. Shows what's blocking (missing keys, stub data) and the counts at every step.</p>
<p><b><a href="/photos">Photos</a></b> — drag photos in, drop them in the <code>photos/</code> folder, or (on a paired phone) tap the drop zone and shoot straight from the camera; camera captures get unique <code>shelf-…</code> names so they can never overwrite each other. Each photo has its own page listing every title read from it and any reshoot tickets — boxes seen but not identified. Photograph those up close, drop the new shot in, and extract again. Re-uploading a photo under the same <i>file name</i> deliberately replaces it, and the next extract run re-reads it.</p> <p><b><a href="/photos">Photos</a></b> — drag photos in, drop them in the <code>photos/</code> folder, or (on a paired phone) tap the drop zone and shoot straight from the camera; camera captures get unique <code>shelf-…</code> names so they can never overwrite each other. Each photo has its own page listing every title read from it and any reshoot tickets — boxes seen but not identified. Photograph those up close, drop the new shot in, and extract again. Re-uploading a photo under the same <i>file name</i> deliberately replaces it, and the next extract run re-reads it.</p>
<p><b><a href="/titles">Titles</a></b> — every read off your shelves, alphabetized, with its status and photos. This is the proofread checkpoint: <a href="#curation">edit, split, remove</a>. Its badge counts <span class="chip shaky">shaky read</span> lines — the model wasn't sure and nothing has verified them; filter to them, then press <b>✓ looks right</b> or edit each one.</p> <p><b><a href="/titles">Titles</a></b> — every read off your shelves, alphabetized, with its status and photos. This is the proofread checkpoint: <a href="#curation">edit, split, remove</a>. Its badge counts <span class="chip shaky">shaky read</span> lines — the model wasn't sure and nothing has verified them; filter to them, then press <b>✓ looks right</b> or edit each one.</p>
<p><b><a href="/review">Review</a></b> — the decisions only you can make: which game a title is, which edition a copy is, and whether two same-game reads are really one box (merges show a veto). Keyboard-first; see <a href="#keys">shortcuts</a>.</p> <p><b><a href="/review">Review</a></b> — the decisions only you can make: which game a title is, which edition a copy is, whether two same-game reads are really one box (merges show a veto), and whether an unmatched title is a real game BGG simply doesn't have (<b>keep locally</b>: it joins the Library, never uploads). Keyboard-first; see <a href="#keys">shortcuts</a>.</p>
<p><b><a href="/queue">Queue</a></b> — exactly what upload will do (new entries and version upgrades) and the log of everything it has done. Nothing reaches BGG that isn't visible here first.</p> <p><b><a href="/queue">Queue</a></b> — exactly what upload will do (new entries and version upgrades) and the log of everything it has done. Nothing reaches BGG that isn't visible here first.</p>
<p><b><a href="/library">Library</a></b> — your enriched collection: filter by board games or RPGs. RPG matches are identified and enriched but never uploaded — BGG collections can't hold them, so they stay local citizens.</p> <p><b><a href="/library">Library</a></b> — your enriched collection: filter by board games or RPGs. RPG matches are identified and enriched but never uploaded — BGG collections can't hold them, so they stay local citizens.</p>
</div> </div>
@@ -49,13 +49,14 @@
<p><span class="chip ok">auto</span> matched confidently, no review needed. <span class="chip ok">approved</span> you picked the match yourself.</p> <p><span class="chip ok">auto</span> matched confidently, no review needed. <span class="chip ok">approved</span> you picked the match yourself.</p>
<p><span class="chip open">ambiguous</span> several plausible games — needs your pick on Review. <span class="chip open">unmatched</span> nothing plausible found — enter a BGG id or re-search on Review.</p> <p><span class="chip open">ambiguous</span> several plausible games — needs your pick on Review. <span class="chip open">unmatched</span> nothing plausible found — enter a BGG id or re-search on Review.</p>
<p><span class="chip merged">merged</span> two reads judged to be the same physical box; the merge is veto-able on Review. <span class="chip merged">copy</span> one copy of a title you split.</p> <p><span class="chip merged">merged</span> two reads judged to be the same physical box; the merge is veto-able on Review. <span class="chip merged">copy</span> one copy of a title you split.</p>
<p><span class="chip no">rejected</span> you ruled it's not on BGG (or not a game worth matching); it stays listed but goes no further.</p> <p><span class="chip open">local — not on BGG</span> you ruled it's a real game BGG doesn't have: it joins the Library from its own photo reads, and never uploads.</p>
<p><span class="chip no">rejected</span> you ruled it's a bad read or not worth matching; it stays listed but goes no further.</p>
<p><span class="chip shaky">shaky read</span> the vision model wasn't sure of this transcription and nothing has verified it yet — these are what the Titles badge counts. Clear one by pressing its <b>✓ looks right</b> (the read is fine as-is) or by editing it (you fixed it). A BGG match also clears it: a wrong read wouldn't have matched.</p> <p><span class="chip shaky">shaky read</span> the vision model wasn't sure of this transcription and nothing has verified it yet — these are what the Titles badge counts. Clear one by pressing its <b>✓ looks right</b> (the read is fine as-is) or by editing it (you fixed it). A BGG match also clears it: a wrong read wouldn't have matched.</p>
</div> </div>
<h2 id="keys">Keyboard shortcuts</h2> <h2 id="keys">Keyboard shortcuts</h2>
<div class="card prose"> <div class="card prose">
<p><b>Review:</b> <kbd>j</kbd>/<kbd>k</kbd> move between cards · <kbd>1</kbd><kbd>9</kbd> pick a candidate · <kbd>r</kbd> reject · <kbd>m</kbd> manual BGG id · <kbd>u</kbd> edition unknown · <kbd>v</kbd> veto a merge.</p> <p><b>Review:</b> <kbd>j</kbd>/<kbd>k</kbd> move between cards · <kbd>1</kbd><kbd>9</kbd> pick a candidate · <kbd>r</kbd> reject · <kbd>l</kbd> keep local (not on BGG) · <kbd>m</kbd> manual BGG id · <kbd>u</kbd> edition unknown · <kbd>v</kbd> veto a merge.</p>
<p><b>Photo pages:</b> <kbd></kbd>/<kbd></kbd> move between photos.</p> <p><b>Photo pages:</b> <kbd></kbd>/<kbd></kbd> move between photos.</p>
</div> </div>
+2 -1
View File
@@ -27,7 +27,8 @@ function gameCard(g) {
: `<div class="noart" aria-hidden="true">${esc((g.name || "?").charAt(0).toUpperCase())}</div>`} : `<div class="noart" aria-hidden="true">${esc((g.name || "?").charAt(0).toUpperCase())}</div>`}
<div class="info"> <div class="info">
<div class="gname">${esc(g.name)}${g.year ? ` <span class="meta">(${esc(g.year)})</span>` : ""} <div class="gname">${esc(g.name)}${g.year ? ` <span class="meta">(${esc(g.year)})</span>` : ""}
${g.type === "rpgitem" ? `<span class="chip open">RPG · local only</span>` : ""}</div> ${g.type === "rpgitem" ? `<span class="chip open">RPG · local only</span>` : ""}
${g.type === "localgame" ? `<span class="chip open">not on BGG · local</span>` : ""}</div>
<div class="gmeta">${[players, time, weight, rank].filter(Boolean).map(esc).join(" · ")}</div> <div class="gmeta">${[players, time, weight, rank].filter(Boolean).map(esc).join(" · ")}</div>
${g.version ? `<div class="gmeta">${esc(g.version.name || "")}</div>` : ""} ${g.version ? `<div class="gmeta">${esc(g.version.name || "")}</div>` : ""}
</div> </div>
+5 -1
View File
@@ -3,7 +3,7 @@
<span id="sectionlinks"></span> <span id="sectionlinks"></span>
<span class="keyhelp"> <span class="keyhelp">
<kbd>j</kbd>/<kbd>k</kbd> move · <kbd>1</kbd><kbd>9</kbd> pick · <kbd>j</kbd>/<kbd>k</kbd> move · <kbd>1</kbd><kbd>9</kbd> pick ·
<kbd>r</kbd> reject · <kbd>m</kbd> manual id · <kbd>u</kbd> unknown · <kbd>r</kbd> reject · <kbd>l</kbd> keep local · <kbd>m</kbd> manual id · <kbd>u</kbd> unknown ·
<kbd>v</kbd> veto merge <kbd>v</kbd> veto merge
</span> </span>
</div> </div>
@@ -76,6 +76,7 @@ function matchCard(row, idx) {
<span><kbd>m</kbd> <input type="text" inputmode="numeric" placeholder="BGG id, then ⏎" <span><kbd>m</kbd> <input type="text" inputmode="numeric" placeholder="BGG id, then ⏎"
aria-label="manual BGG id"></span> aria-label="manual BGG id"></span>
<button class="reject" title="press r"><kbd>r</kbd> reject — not a game / bad read</button> <button class="reject" title="press r"><kbd>r</kbd> reject — not a game / bad read</button>
<button class="golocal" title="press l"><kbd>l</kbd> not on BGG — keep locally</button>
</div> </div>
</div> </div>
</section>`; </section>`;
@@ -179,6 +180,8 @@ function render() {
}); });
m.querySelectorAll(".reject").forEach(b => b.onclick = () => m.querySelectorAll(".reject").forEach(b => b.onclick = () =>
decide(b.closest(".card"), "reject")); decide(b.closest(".card"), "reject"));
m.querySelectorAll(".golocal").forEach(b => b.onclick = () =>
decide(b.closest(".card"), "local"));
m.querySelectorAll(".unknown").forEach(b => b.onclick = () => m.querySelectorAll(".unknown").forEach(b => b.onclick = () =>
version(b.closest(".card"), "unknown")); version(b.closest(".card"), "unknown"));
m.querySelectorAll(".veto").forEach(b => b.onclick = () => m.querySelectorAll(".veto").forEach(b => b.onclick = () =>
@@ -238,6 +241,7 @@ document.addEventListener("keydown", e => {
if (li) version(card, "pick", Number(li.dataset.pickver)); if (li) version(card, "pick", Number(li.dataset.pickver));
} }
else if (e.key === "r" && kind === "match") decide(card, "reject"); else if (e.key === "r" && kind === "match") decide(card, "reject");
else if (e.key === "l" && kind === "match") decide(card, "local");
else if (e.key === "u" && kind === "version") version(card, "unknown"); else if (e.key === "u" && kind === "version") version(card, "unknown");
else if (e.key === "v" && kind === "merge") vetoMerge(card); else if (e.key === "v" && kind === "merge") vetoMerge(card);
else if (e.key === "m" && kind === "match") { card.querySelector("input")?.focus(); e.preventDefault(); } else if (e.key === "m" && kind === "match") { card.querySelector("input")?.focus(); e.preventDefault(); }
+3
View File
@@ -618,6 +618,7 @@ def create_app(
"ambiguous": counts.get("ambiguous", 0), "ambiguous": counts.get("ambiguous", 0),
"unmatched": counts.get("unmatched", 0), "unmatched": counts.get("unmatched", 0),
"rejected": counts.get("rejected", 0), "rejected": counts.get("rejected", 0),
"local": counts.get("local", 0),
"version_updates": version_updates, "version_updates": version_updates,
"total": len(session.rows), "total": len(session.rows),
"extracted": len(session.titles), "extracted": len(session.titles),
@@ -898,6 +899,8 @@ def create_app(
session.decide_manual(row, body.bgg_id) session.decide_manual(row, body.bgg_id)
elif body.action == "reject": elif body.action == "reject":
session.decide_reject(row) session.decide_reject(row)
elif body.action == "local":
session.decide_local(row)
else: else:
raise HTTPException(400, f"unknown action {body.action!r}") raise HTTPException(400, f"unknown action {body.action!r}")
return state() return state()
+8
View File
@@ -407,6 +407,14 @@ def test_rpgitem_rows_are_local_only_never_queued():
assert result.recognized == 1 # RPGs aren't counted against the queue assert result.recognized == 1 # RPGs aren't counted against the queue
def test_local_rows_are_library_citizens_never_queued():
local = {**_match("Obscure Homebrew", ""), "match_status": "local"}
result = compute_diff([local, _match("Catan", "13")], [])
assert result.local_only == ["Obscure Homebrew"]
assert [r["bgg_id"] for r in result.to_add] == ["13"]
assert result.pending == [] # decided, not waiting
def test_split_copies_become_extra_collection_entries(): def test_split_copies_become_extra_collection_entries():
# three vetoed per-photo copies, one owned: one claims the copy, the # three vetoed per-photo copies, one owned: one claims the copy, the
# other two are queued as new entries # other two are queued as new entries
+52
View File
@@ -226,3 +226,55 @@ def test_enrich_degrades_without_token(tmp_path, capsys):
assert games == {} assert games == {}
assert "waiting on the BGG API" in capsys.readouterr().out assert "waiting on the BGG API" in capsys.readouterr().out
assert json.loads((cfg.data_dir / "games.json").read_text()) == {} assert json.loads((cfg.data_dir / "games.json").read_text()) == {}
def test_local_rows_enrich_from_their_own_reads(tmp_path):
cfg = Config(data_dir=tmp_path / "data")
cfg.data_dir.mkdir(parents=True)
write_matches(
cfg.matches_path,
[
{
"title_raw": "Obscure Homebrew",
"bgg_id": "",
"bgg_name": "",
"year": "",
"type": "",
"match_status": "local",
"version_id": "",
"version_name": "",
"version_status": "",
"candidates_json": "[]",
"version_candidates_json": "[]",
"source_photos": "shelf.jpg",
}
],
)
cfg.titles_path.write_text(
json.dumps(
[
{
"title_raw": "Obscure Homebrew",
"publisher_hint": "Basement Press",
"year_hint": 1998,
"source_photos": ["shelf.jpg"],
}
]
)
)
from bggpipe.enrich import run_enrich
client = BGGClient(
cache_dir=cfg.data_dir / "cache",
transport=httpx.MockTransport(_no_network),
)
games = run_enrich(cfg, client=client)
(key,) = [k for k in games if k.startswith("local:")]
entry = games[key]
assert entry["name"] == "Obscure Homebrew"
assert entry["year"] == 1998
assert entry["publishers"] == ["Basement Press"]
assert entry["type"] == "localgame"
# idempotent: a second run keeps the entry (no prune, no dupe)
games2 = run_enrich(cfg, client=client)
assert key in games2
+12
View File
@@ -593,3 +593,15 @@ def test_open_version_ballot_puts_every_edition_up_for_review(tmp_path):
) )
with pytest.raises(ValueError, match="no BGG match"): with pytest.raises(ValueError, match="no BGG match"):
session2.open_version_ballot(session2.rows[0]) session2.open_version_ballot(session2.rows[0])
def test_local_decision_keeps_a_real_game_off_bgg(tmp_path):
cfg = _setup(
tmp_path, [_row(title_raw="Obscure Homebrew", match_status="unmatched")]
)
run_review(
cfg, console=quiet_console(), input_fn=scripted("l"), client=fixture_client()
)
(row,) = read_matches(cfg.matches_path)
assert row["match_status"] == "local"
assert row["bgg_id"] == "" # a local citizen carries no BGG identity
+11
View File
@@ -1128,3 +1128,14 @@ def test_open_versions_endpoint_routes_row_into_review(tmp_path):
json={"title_raw": "Dungeon!", "source_photos": "shelf.jpg"}, json={"title_raw": "Dungeon!", "source_photos": "shelf.jpg"},
) )
assert res.status_code == 502 assert res.status_code == 502
def test_local_decision_via_web(tmp_path):
web, cfg = make_client(tmp_path)
state = web.post(
"/api/decision",
json={"title_raw": "Mystery", "source_photos": "shelf.jpg", "action": "local"},
).json()
saved = {r["title_raw"]: r for r in read_matches(cfg.matches_path)}
assert saved["Mystery"]["match_status"] == "local"
assert state["summary"]["local"] == 1