From 90e75daf980349e9d54216f2a278b0fbdf8c8158 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Wed, 5 Aug 2026 19:13:49 -0400 Subject: [PATCH] Add a game by hand: the fourth curation store MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BGG wants base game and expansion as separate collection entries, but a box that stores its expansion's bits shows one spine to the camera — the hidden half was unreachable. "add a game" on the Titles page records an entry in data/title_additions.json (committed, like every curation store), joined into every rebuild BEFORE edits and dedupe: so corrections apply to it, a later photo sighting of the same game merges instead of duplicating (photo provenance wins), and re-adding an existing title is a no-op. Photo-less lines show an "added by hand" chip where their photo links would be; from resolve onward they are ordinary titles. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g --- CLAUDE.md | 4 +- data/matches.csv | 26 +++++++----- src/bggpipe/config.py | 6 +++ src/bggpipe/extract.py | 31 +++++++++++++- src/bggpipe/templates/pages/help.html | 1 + src/bggpipe/templates/pages/titles.html | 50 ++++++++++++++++++++-- src/bggpipe/webreview.py | 37 ++++++++++++++++ tests/test_extract.py | 56 +++++++++++++++++++++++++ tests/test_webreview.py | 21 ++++++++++ 9 files changed, 216 insertions(+), 16 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2ad1a11..d5713b4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,11 +47,11 @@ Full design lives in `bgg-shelf-pipeline-spec.md` (read it before changing pipel - Base game vs. expansion vs. new edition is the top failure mode — bias matching toward `ambiguous` over auto-match ("Wingspan Europe" must not match base Wingspan). - Editions/versions matter: Eric owns multiple editions of some games — each is a separate collection entry (keyed by `collid` on BGG). Never guess a version: no legible cues → `version_unknown` and a version-less collection entry. - Normalize titles (casefold, strip punctuation/articles, special chars like é/&/:) identically on both sides of a match; dedupe across photos but keep `source_photos` provenance. -- **Human curation is durable**: `data/title_splits.json` (photo-scoped split-into-copies decisions, honored by extract's dedupe AND resolve's dedupe), `data/title_edits.json` (corrected reads/cues, applied before dedupe on every titles.json rebuild), and `data/title_removals.json` (lines removed from the catalog — filtered out of every rebuild; delete the record to undo) persist forever. Row-level decisions persist via the `dedupe_veto` column — edits never drop veto'd rows (a rename retitles them in place); removal drops them (explicitly discarding the line). +- **Human curation is durable**: `data/title_splits.json` (photo-scoped split-into-copies decisions, honored by extract's dedupe AND resolve's dedupe), `data/title_edits.json` (corrected reads/cues, applied before dedupe on every titles.json rebuild), `data/title_removals.json` (lines removed from the catalog — filtered out of every rebuild; delete the record to undo), and `data/title_additions.json` (games added without a photo — joined into every rebuild; a later photo sighting dedupe-merges with them) persist forever. Row-level decisions persist via the `dedupe_veto` column — edits never drop veto'd rows (a rename retitles them in place); removal drops them (explicitly discarding the line). - **RPGs are local-only citizens**: when the board-game search runs dry, resolve falls back to `type=rpgitem` (same geekdo API/token). Matched rpgitems enrich into the library but diff routes them to `local_only` — they must never reach `to_add.csv`/upload (their collection lives on RPGGeek, out of scope). - Detailed BGG API behavior (202 queueing, collection-endpoint quirks, endpoints): use the `bgg-api` skill. **If the spec's BGG behavior changes, update the `bgg-api` skill to match** — they must not drift. ## Git - Remote is self-hosted Gitea 1.26 (`git.kestrelsnest.social/eric/bggpipe`), **not GitHub** — `gh` CLI does not work here. -- Commit `data/matches.csv`, `data/to_add.csv`, `data/to_update.csv`, `data/upload_log.csv`, `data/titles.json`, `data/unidentified.json`, `data/unidentified_dismissed.json`, `data/title_splits.json`, `data/title_edits.json`, `data/title_removals.json`, `data/games.json`, `data/STUB_DATA.marker` (while it applies), and the collection snapshot XMLs. Never commit `data/bgg_cache/`, `data/extract_raw/`, `photos/`, `data/.lan_key`, Playwright storage state, or `.env`. +- Commit `data/matches.csv`, `data/to_add.csv`, `data/to_update.csv`, `data/upload_log.csv`, `data/titles.json`, `data/unidentified.json`, `data/unidentified_dismissed.json`, `data/title_splits.json`, `data/title_edits.json`, `data/title_removals.json`, `data/title_additions.json`, `data/games.json`, `data/STUB_DATA.marker` (while it applies), and the collection snapshot XMLs. Never commit `data/bgg_cache/`, `data/extract_raw/`, `photos/`, `data/.lan_key`, Playwright storage state, or `.env`. diff --git a/data/matches.csv b/data/matches.csv index effac18..e66ed38 100644 --- a/data/matches.csv +++ b/data/matches.csv @@ -1,6 +1,6 @@ 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 ROYALS,165986,Royals,2014,boardgame,auto,,,version_unknown,"[{""bgg_id"": 154495, ""name"": ""Royals"", ""year"": 2013, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 72, ""rank"": null}, {""bgg_id"": 165986, ""name"": ""Royals"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 4114, ""rank"": 1534}]",[],IMG_4499.jpeg,, -CAT CRIMES,,,,,unmatched,,,,[],[],IMG_4499.jpeg;IMG_4505.jpeg;shelf-20260803-215723.jpg,, +CAT CRIMES,,,,,local,,,,[],[],IMG_4499.jpeg;IMG_4505.jpeg;shelf-20260803-215723.jpg,, COSMIC ENCOUNTER,40529,Cosmic Encounter,1991,boardgame,approved,,,version_unknown,"[{""bgg_id"": 15, ""name"": ""Cosmic Encounter"", ""year"": 1977, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 4593, ""rank"": 1749}, {""bgg_id"": 39463, ""name"": ""Cosmic Encounter"", ""year"": 2008, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 43809, ""rank"": 232}, {""bgg_id"": 40529, ""name"": ""Cosmic Encounter"", ""year"": 1991, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 1728, ""rank"": 3075}, {""bgg_id"": 40531, ""name"": ""Cosmic Encounter"", ""year"": 2000, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 2609, ""rank"": 4543}]",[],IMG_4499.jpeg,, SHERIFF OF NOTTINGHAM,157969,Sheriff of Nottingham,2014,boardgame,auto,,,version_unknown,"[{""bgg_id"": 157969, ""name"": ""Sheriff of Nottingham"", ""year"": 2014, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4499.jpeg,, 5 MINUTE DUNGEON,207830,5-Minute Dungeon,2017,boardgame,auto,,,version_unknown,"[{""bgg_id"": 207830, ""name"": ""5-Minute Dungeon"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4499.jpeg,, @@ -31,7 +31,7 @@ RAMPAGE,97903,Rampage,2013,boardgame,auto,,,version_unknown,"[{""bgg_id"": 97903 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,, 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,, -Super Mario Checkers Collector's Edition,,,,,unmatched,,,,[],[],IMG_4508.jpeg,, +Super Mario Checkers Collector's Edition,,,,,local,,,,[],[],IMG_4508.jpeg,, Dragon Land,320202,Dragon Land,2020,boardgame,auto,,,version_unknown,"[{""bgg_id"": 320202, ""name"": ""Dragon Land"", ""year"": 2020, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4508.jpeg;IMG_4542.jpeg,, Bugs in the Kitchen,137909,Bugs in the Kitchen,2013,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 137909, ""name"": ""Bugs in the Kitchen"", ""year"": 2013, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 280025, ""name"": ""Chinese/English edition"", ""year"": 2014, ""publishers"": [""Ravensburger AG""], ""languages"": [""Chinese"", ""English""], ""score"": 3}, {""version_id"": 751514, ""name"": ""English edition 2024"", ""year"": 2024, ""publishers"": [""Ravensburger AG""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 216474, ""name"": ""English first edition"", ""year"": 2013, ""publishers"": [""Ravensburger AG""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 435117, ""name"": ""English second edition"", ""year"": 2018, ""publishers"": [""Ravensburger AG""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 212273, ""name"": ""Multilingual EN/FR/DE/IT/NE/SP"", ""year"": 2013, ""publishers"": [""Ravensburger AG""], ""languages"": [""Dutch"", ""English"", ""French"", ""German"", ""Italian"", ""Spanish""], ""score"": 3}, {""version_id"": 260936, ""name"": ""French first edition"", ""year"": 2013, ""publishers"": [""Ravensburger AG""], ""languages"": [""French""], ""score"": 2}, {""version_id"": 726152, ""name"": ""German edition 2024"", ""year"": 2024, ""publishers"": [""Ravensburger AG""], ""languages"": [""German""], ""score"": 2}, {""version_id"": 199002, ""name"": ""German first edition"", ""year"": 2013, ""publishers"": [""Ravensburger AG""], ""languages"": [""German""], ""score"": 2}]",IMG_4508.jpeg;IMG_4545.jpeg,, CATAN DICE GAME,27710,Catan Dice Game,2007,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 27710, ""name"": ""Catan Dice Game"", ""year"": 2007, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 606657, ""name"": ""English edition 2018"", ""year"": 2018, ""publishers"": [""Catan Studio""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 565571, ""name"": ""English edition 2020"", ""year"": 2020, ""publishers"": [""Catan Studio""], ""languages"": [""English""], ""score"": 3}]",IMG_4509.jpeg,, @@ -39,7 +39,7 @@ Castle Panic THE DARK TITAN,169784,Castle Panic: The Dark Titan,2015,boardgame,a Jenga,2452,Jenga,1983,boardgame,auto,,,version_unknown,"[{""bgg_id"": 2452, ""name"": ""Jenga"", ""year"": 1983, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4509.jpeg,, "NO THANK YOU, EVIL!",208428,"No Thank You, Evil!",2016,boardgame,auto,,,version_unknown,"[{""bgg_id"": 208428, ""name"": ""No Thank You, Evil!"", ""year"": 2016, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4510.jpeg,, CODE MASTER,42063,Code Master,1997,boardgame,auto,,,version_unknown,"[{""bgg_id"": 42063, ""name"": ""Code Master"", ""year"": 1997, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4510.jpeg,, -GRAVITY MAZE,,,,,unmatched,,,,[],[],IMG_4510.jpeg,, +GRAVITY MAZE,,,,,local,,,,[],[],IMG_4510.jpeg,, ROBOT TURTLES,147370,Robot Turtles,2013,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 147370, ""name"": ""Robot Turtles"", ""year"": 2013, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 231664, ""name"": ""English second edition"", ""year"": 2014, ""publishers"": [""ThinkFun""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 571231, ""name"": ""Multilingual edition"", ""year"": 2021, ""publishers"": [""ThinkFun""], ""languages"": [""Dutch"", ""English"", ""French"", ""German"", ""Italian"", ""Portuguese"", ""Spanish""], ""score"": 3}, {""version_id"": 498079, ""name"": ""Korean edition"", ""year"": 2017, ""publishers"": [""Korea Boardgames"", ""ThinkFun""], ""languages"": [""Korean""], ""score"": 2}]",IMG_4510.jpeg,, GOOGLY EYES,57215,Googly Eyes,2008,boardgame,auto,,,version_unknown,"[{""bgg_id"": 57215, ""name"": ""Googly Eyes"", ""year"": 2008, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4510.jpeg,, THROW THROW AVOCADO,340420,Throw Throw Avocado,2021,boardgame,auto,,,version_unknown,"[{""bgg_id"": 340420, ""name"": ""Throw Throw Avocado"", ""year"": 2021, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4511.jpeg,, @@ -57,7 +57,7 @@ The Rocky Horror Trivia Game,20866,The Rocky Horror Trivia Game,2005,boardgame,a 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,, 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,, -LASER MAZE BEAM-BENDING LOGIC GAME,,,,,unmatched,,,,[],[],IMG_4516.jpeg,, +LASER MAZE BEAM-BENDING LOGIC GAME,,,,,local,,,,[],[],IMG_4516.jpeg,, CAPTAIN MARVEL SECRET SKRULLS,269564,Captain Marvel: Secret Skrulls,2019,boardgame,auto,,,version_unknown,"[{""bgg_id"": 269564, ""name"": ""Captain Marvel: Secret Skrulls"", ""year"": 2019, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4516.jpeg,, Pie Face!,18755,Pie Face!,1964,boardgame,approved,,,version_unknown,"[{""bgg_id"": 16562, ""name"": ""Pie Face"", ""year"": 1993, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 274, ""rank"": 29603}, {""bgg_id"": 18755, ""name"": ""Pie Face!"", ""year"": 1964, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 1404, ""rank"": 30933}]",[],IMG_4517.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,, @@ -65,7 +65,7 @@ AGRICOLA,31260,Agricola,2007,boardgame,auto,297589,Z-Man Games English fifth edi 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,, 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 FANTASY GAME RULES,,,,,unmatched,,,,[],[],IMG_4519.jpeg,, +DUNGEONS & DRAGONS FANTASY GAME RULES,,,,,local,,,,[],[],IMG_4519.jpeg,, "Before I Kill You, Mister Bond...",164,"Before I Kill You, Mister Bond",1996,boardgame,auto,28465,"Cheapass black and white ""Better Edition"" edition",version_auto,"[{""bgg_id"": 164, ""name"": ""Before I Kill You, Mister Bond"", ""year"": 1996, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 28465, ""name"": ""Cheapass black and white \""Better Edition\"" edition"", ""year"": 2001, ""publishers"": [""Cheapass Games""], ""languages"": [""English""], ""score"": 5}, {""version_id"": 291316, ""name"": ""English edition 2016"", ""year"": 2016, ""publishers"": [""Cheapass Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 520361, ""name"": ""English second edition"", ""year"": 1997, ""publishers"": [""Cheapass Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 28459, ""name"": ""First \""Renamed\"" edition, First color edition"", ""year"": 2004, ""publishers"": [""Cheapass Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 180392, ""name"": ""First black and white envelope edition"", ""year"": 1996, ""publishers"": [""Cheapass Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 28462, ""name"": ""Third black and white edition"", ""year"": 1998, ""publishers"": [""Cheapass Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4519.jpeg;IMG_4554.jpeg,, MYSTERIUM,181304,Mysterium,2015,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 181304, ""name"": ""Mysterium"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 85034, ""rank"": 454}, {""bgg_id"": 113997, ""name"": ""Mysterium"", ""year"": 2013, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 5494, ""rank"": 1143}]","[{""version_id"": 781306, ""name"": ""English 2025 Refresh Edition"", ""year"": 2025, ""publishers"": [""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 280251, ""name"": ""English edition 2015-1"", ""year"": 2015, ""publishers"": [""Esdevium"", ""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 493543, ""name"": ""English edition 2015-2"", ""year"": 2015, ""publishers"": [""Esdevium"", ""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 475162, ""name"": ""English edition 2016-1"", ""year"": 2016, ""publishers"": [""Esdevium"", ""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 351093, ""name"": ""English edition 2017-1"", ""year"": 2017, ""publishers"": [""Esdevium"", ""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 467608, ""name"": ""English edition 2017-2"", ""year"": 2017, ""publishers"": [""Esdevium"", ""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 404443, ""name"": ""English edition 2017-3"", ""year"": 2017, ""publishers"": [""Esdevium"", ""Libellud""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 536289, ""name"": ""English edition 2018-2"", ""year"": 2018, ""publishers"": [""Libellud""], ""languages"": [""English""], ""score"": 3}]",IMG_4520.jpeg,, Völuspá,128554,Völuspá,2012,boardgame,auto,,,version_unknown,"[{""bgg_id"": 128554, ""name"": ""Völuspá"", ""year"": 2012, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4520.jpeg,, @@ -81,12 +81,12 @@ FORBIDDEN ISLAND,65244,Forbidden Island,2010,boardgame,auto,,,version_ambiguous, TEMPURRA,95386,Tempurra,2011,boardgame,auto,290572,English edition,version_auto,"[{""bgg_id"": 95386, ""name"": ""Tempurra"", ""year"": 2011, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 290572, ""name"": ""English edition"", ""year"": 2016, ""publishers"": [""IELLO""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 287427, ""name"": ""French edition"", ""year"": 2016, ""publishers"": [""IELLO""], ""languages"": [""French""], ""score"": 2}]",IMG_4524.jpeg,, FLUXX,258,Fluxx,1997,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 258, ""name"": ""Fluxx"", ""year"": 1997, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 33357, ""rank"": 12263}, {""bgg_id"": 314413, ""name"": ""Fluxx"", ""year"": 2012, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 2152, ""rank"": 15720}]","[{""version_id"": 20841, ""name"": ""Looney Labs edition v3.0"", ""year"": 2002, ""publishers"": [""Looney Labs""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 20843, ""name"": ""Looney Labs edition v3.1"", ""year"": 2005, ""publishers"": [""Looney Labs""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 20844, ""name"": ""Looney Labs edition v4.0"", ""year"": 2008, ""publishers"": [""Looney Labs""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 263858, ""name"": ""Looney Labs edition v5.0"", ""year"": 2014, ""publishers"": [""Looney Labs""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 20838, ""name"": ""Looney Labs English 1.0 edition"", ""year"": 1996, ""publishers"": [""Looney Labs""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 20840, ""name"": ""Looney Labs Version v2.1"", ""year"": 2000, ""publishers"": [""Looney Labs""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 488647, ""name"": ""Hobby World Russian 5.0 edition"", ""year"": 2017, ""publishers"": [""Hobby World"", ""Looney Labs""], ""languages"": [""Russian""], ""score"": 2}, {""version_id"": 290805, ""name"": ""Polish edition"", ""year"": 2015, ""publishers"": [""Black Monk"", ""Looney Labs""], ""languages"": [""Polish""], ""score"": 2}]",IMG_4525.jpeg,, Poetry for Neanderthals,312786,Poetry for Neanderthals,2020,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 312786, ""name"": ""Poetry for Neanderthals"", ""year"": 2020, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 514050, ""name"": ""English edition 2020"", ""year"": 2020, ""publishers"": [""Exploding Kittens""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 655388, ""name"": ""English edition 2022"", ""year"": 2022, ""publishers"": [""Exploding Kittens""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 782169, ""name"": ""English edition 2024"", ""year"": 2024, ""publishers"": [""Exploding Kittens""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 746180, ""name"": ""English Grab & Game edition 2023"", ""year"": 2023, ""publishers"": [""Exploding Kittens""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 786184, ""name"": ""English Grab & Game edition 2025"", ""year"": 2025, ""publishers"": [""Exploding Kittens""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 735751, ""name"": ""Dutch edition"", ""year"": 2024, ""publishers"": [""Exploding Kittens""], ""languages"": [""Dutch""], ""score"": 2}, {""version_id"": 783801, ""name"": ""French edition"", ""year"": 2023, ""publishers"": [""Exploding Kittens""], ""languages"": [""French""], ""score"": 2}, {""version_id"": 632084, ""name"": ""German edition"", ""year"": 2022, ""publishers"": [""Exploding Kittens""], ""languages"": [""German""], ""score"": 2}]",IMG_4526.jpeg,, -Elite Sportz Equipment Table Top Curling,,,,,unmatched,,,,[],[],IMG_4526.jpeg,, +Elite Sportz Equipment Table Top Curling,,,,,local,,,,[],[],IMG_4526.jpeg,, Simon's Cat Card Game,188614,Simon's Cat Card Game,2016,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 188614, ""name"": ""Simon's Cat Card Game"", ""year"": 2016, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": 1429, ""rank"": 12119}, {""bgg_id"": 330317, ""name"": ""NOM: Simon's Cat Card Game"", ""year"": 2021, ""type"": ""boardgame"", ""exact"": false, ""fuzzy"": 91.3, ""owned"": 70, ""rank"": null}]","[{""version_id"": 292307, ""name"": ""English edition, first printing"", ""year"": 2016, ""publishers"": [""Steve Jackson Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 779792, ""name"": ""English edition, second printing"", ""year"": 2016, ""publishers"": [""Steve Jackson Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4526.jpeg,, The Red Dragon Inn: Smorgasbox,273240,The Red Dragon Inn Smorgasbox,2019,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 273240, ""name"": ""The Red Dragon Inn Smorgasbox"", ""year"": 2019, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 446786, ""name"": ""Collector's edition"", ""year"": 2019, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 446788, ""name"": ""Eco-Collector's edition"", ""year"": 2019, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 446787, ""name"": ""Eco-Kickstarter edition"", ""year"": 2019, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 446069, ""name"": ""Kickstarter edition"", ""year"": 2019, ""publishers"": [""SlugFest Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4527.jpeg,, WIZ-WAR,589,Wiz-War,1983,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 589, ""name"": ""Wiz-War"", ""year"": 1983, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 26114, ""name"": ""Fifth edition"", ""year"": 1991, ""publishers"": [""Chessex""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 29214, ""name"": ""Fourth edition"", ""year"": 1987, ""publishers"": [""Jolly Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 360420, ""name"": ""Third edition"", ""year"": 1986, ""publishers"": [""Jolly Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4528.jpeg,, RISK THE LORD OF THE RINGS: The Middle-earth Conquest Game,4324,Risk: The Lord of the Rings,2002,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 4324, ""name"": ""Risk: The Lord of the Rings"", ""year"": 2002, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 57.1, ""owned"": null, ""rank"": null}]","[{""version_id"": 33264, ""name"": ""English (UK) edition 2002"", ""year"": 2002, ""publishers"": [""Hasbro"", ""Parker Brothers""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 26859, ""name"": ""English edition 2002"", ""year"": 2002, ""publishers"": [""Hasbro"", ""Parker Brothers""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 26863, ""name"": ""English/French edition"", ""year"": 2002, ""publishers"": [""Parker Brothers""], ""languages"": [""English"", ""French""], ""score"": 3}, {""version_id"": 26865, ""name"": ""Danish edition"", ""year"": 2002, ""publishers"": [""Parker Brothers""], ""languages"": [""Danish""], ""score"": 2}, {""version_id"": 26861, ""name"": ""Dutch edition"", ""year"": 2002, ""publishers"": [""Parker Brothers""], ""languages"": [""Dutch""], ""score"": 2}, {""version_id"": 77144, ""name"": ""French edition 2002"", ""year"": 2002, ""publishers"": [""Hasbro"", ""Parker Brothers""], ""languages"": [""French""], ""score"": 2}, {""version_id"": 26862, ""name"": ""German edition"", ""year"": 2002, ""publishers"": [""Parker Brothers""], ""languages"": [""German""], ""score"": 2}, {""version_id"": 75135, ""name"": ""Norwegian edition"", ""year"": 2002, ""publishers"": [""Parker Brothers""], ""languages"": [""Norwegian""], ""score"": 2}]",IMG_4529.jpeg,, -Ouija,,,,,unmatched,,,,[],[],IMG_4529.jpeg,, +Ouija,,,,,local,,,,[],[],IMG_4529.jpeg,, Munchkin Big Box,419687,Munchkin Big Box,2025,boardgame,auto,711400,English edition,version_auto,"[{""bgg_id"": 419687, ""name"": ""Munchkin Big Box"", ""year"": 2025, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 711400, ""name"": ""English edition"", ""year"": 2025, ""publishers"": [""Steve Jackson Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 777457, ""name"": ""Russian edition"", ""year"": 2025, ""publishers"": [""Hobby World"", ""Steve Jackson Games""], ""languages"": [""Russian""], ""score"": 2}]",IMG_4530.jpeg,, FROSTHAVEN,295770,Frosthaven,2022,boardgame,auto,,,version_unknown,"[{""bgg_id"": 295770, ""name"": ""Frosthaven"", ""year"": 2022, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4531.jpeg,, GLOOMHAVEN,174430,Gloomhaven,2017,boardgame,auto,,,version_unknown,"[{""bgg_id"": 174430, ""name"": ""Gloomhaven"", ""year"": 2017, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4532.jpeg,, @@ -105,7 +105,7 @@ SPANC Space Pirate Amazon Ninja Catgirls,14535,SPANC: Space Pirate Amazon Ninja Greed Quest,9666,Greed Quest,2004,boardgame,auto,215137,English edition,version_auto,"[{""bgg_id"": 9666, ""name"": ""Greed Quest"", ""year"": 2004, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 215137, ""name"": ""English edition"", ""year"": 2004, ""publishers"": [""Steve Jackson Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4537.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,, -CAT STAX,,,,,unmatched,,,,[],[],IMG_4544.jpeg,, +CAT STAX,,,,,local,,,,[],[],IMG_4544.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,, 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,, @@ -115,8 +115,8 @@ MACHI KORO,143884,Machi Koro,2012,boardgame,auto,,,version_ambiguous,"[{""bgg_id Dark Cults: The Horror Story Game,1784,Dark Cults,1983,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 1784, ""name"": ""Dark Cults"", ""year"": 1983, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 52.6, ""owned"": null, ""rank"": null}]","[{""version_id"": 110926, ""name"": ""First edition"", ""year"": 1983, ""publishers"": [""Dark House""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 110927, ""name"": ""Second edition"", ""year"": 1985, ""publishers"": [""Dark House""], ""languages"": [""English""], ""score"": 3}]",IMG_4552.jpeg,, Give Me The Brain,176,Give Me the Brain!,1996,boardgame,auto,27673,Czech edition,version_auto,"[{""bgg_id"": 176, ""name"": ""Give Me the Brain!"", ""year"": 1996, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 27673, ""name"": ""Czech edition"", ""year"": 1997, ""publishers"": [""Cheapass Games""], ""languages"": [""Czech""], ""score"": 4}, {""version_id"": 28103, ""name"": ""Cheapass English first paper envelope edition 1996"", ""year"": 1996, ""publishers"": [""Cheapass Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 795469, ""name"": ""Cheapass English paper envelope edition 1998"", ""year"": 1998, ""publishers"": [""Cheapass Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 24093, ""name"": ""Cheapass English special edition 2002"", ""year"": 2002, ""publishers"": [""Cheapass Games"", ""James Ernest Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 742802, ""name"": ""German zipper bag edition"", ""year"": 2000, ""publishers"": [""Cheapass Games"", ""Truant Spiele""], ""languages"": [""German""], ""score"": 2}]",IMG_4553.jpeg,, KILL DOCTOR LUCKY,257,Kill Doctor Lucky,1996,boardgame,auto,520364,"Cheapass envelope edition, second printing",version_auto,"[{""bgg_id"": 257, ""name"": ""Kill Doctor Lucky"", ""year"": 1996, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 520364, ""name"": ""Cheapass envelope edition, second printing"", ""year"": 1997, ""publishers"": [""Cheapass Games""], ""languages"": [""English""], ""score"": 5}, {""version_id"": 28629, ""name"": ""Cheapass boxed edition, sixth printing"", ""year"": 2000, ""publishers"": [""Cheapass Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 342532, ""name"": ""Cheapass English 19.5th Anniversary edition"", ""year"": 2016, ""publishers"": [""Cheapass Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 302015, ""name"": ""Cheapass English/German 19.5th Anniversary edition"", ""year"": 2016, ""publishers"": [""Cheapass Games""], ""languages"": [""English"", ""German""], ""score"": 3}, {""version_id"": 24741, ""name"": ""Cheapass envelope edition, first printing"", ""year"": 1996, ""publishers"": [""Cheapass Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 261344, ""name"": ""Cheapass envelope edition, fourth printing"", ""year"": 1999, ""publishers"": [""Cheapass Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 133486, ""name"": ""Cheapass print & play edition 2011"", ""year"": 2011, ""publishers"": [""Cheapass Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 24760, ""name"": ""Cheapass special edition, eighth printing"", ""year"": 2002, ""publishers"": [""Cheapass Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4555.jpeg,, -ALICE IS MISSING SILENT FALLS EXPANSION,,,,,unmatched,,,,[],[],IMG_4566.jpeg,, -ALICE IS MISSING A SILENT ROLE PLAYING GAME,,,,,unmatched,,,,[],[],IMG_4566.jpeg,, +ALICE IS MISSING SILENT FALLS EXPANSION,,,,,local,,,,[],[],IMG_4566.jpeg,, +ALICE IS MISSING A SILENT ROLE PLAYING GAME,,,,,local,,,,[],[],IMG_4566.jpeg,, ETHERFIELDS KITTENBURG EXPANSION,358880,Etherfields: Kittenburg Expansion,2022,boardgame,auto,603694,English edition,version_auto,"[{""bgg_id"": 358880, ""name"": ""Etherfields: Kittenburg Expansion"", ""year"": 2022, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 603694, ""name"": ""English edition"", ""year"": 2022, ""publishers"": [""Awaken Realms""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 709658, ""name"": ""Polish edition"", ""year"": 2022, ""publishers"": [""Awaken Realms""], ""languages"": [""Polish""], ""score"": 2}, {""version_id"": 699892, ""name"": ""Spanish edition"", ""year"": 2022, ""publishers"": [""Awaken Realms""], ""languages"": [""Spanish""], ""score"": 2}]",IMG_4570.jpeg,, Parsely,258849,Parsely,2018,rpgitem,auto,,,version_unknown,"[{""bgg_id"": 258849, ""name"": ""Parsely"", ""year"": 2018, ""type"": ""rpgitem"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4573.jpeg,, "...and then, we held hands.",153999,"...and then, we held hands.",2015,boardgame,auto,280845,English edition,version_auto,"[{""bgg_id"": 153999, ""name"": ""...and then, we held hands."", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 280845, ""name"": ""English edition"", ""year"": 2015, ""publishers"": [""LudiCreations""], ""languages"": [""English""], ""score"": 3}]",image.jpg,, @@ -126,3 +126,9 @@ THE CAT GAME,231302,The Cat Game,2017,boardgame,auto,,,version_ambiguous,"[{""bg 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,, 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,, +MANTIS FALLS,291847,Mantis Falls,2021,boardgame,auto,,,version_unknown,"[{""bgg_id"": 291847, ""name"": ""Mantis Falls"", ""year"": 2021, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]",[],IMG_4505.jpeg,, +SMALL WORLD,40692,Small World,2009,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 40692, ""name"": ""Small World"", ""year"": 2009, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 488854, ""name"": ""English edition 2019"", ""year"": 2019, ""publishers"": [""Days of Wonder""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 23936, ""name"": ""English first edition"", ""year"": 2009, ""publishers"": [""Days of Wonder""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 174280, ""name"": ""English second edition"", ""year"": 2010, ""publishers"": [""Days of Wonder""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 294178, ""name"": ""English third edition"", ""year"": 2012, ""publishers"": [""Days of Wonder""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 478262, ""name"": ""Chinese edition"", ""year"": 2018, ""publishers"": [""Days of Wonder""], ""languages"": [""Chinese""], ""score"": 2}, {""version_id"": 24053, ""name"": ""Dutch edition"", ""year"": 2009, ""publishers"": [""Days of Wonder""], ""languages"": [""Dutch""], ""score"": 2}, {""version_id"": 73633, ""name"": ""French first edition"", ""year"": 2009, ""publishers"": [""Days of Wonder""], ""languages"": [""French""], ""score"": 2}, {""version_id"": 287468, ""name"": ""French second edition"", ""year"": 2010, ""publishers"": [""Days of Wonder""], ""languages"": [""French""], ""score"": 2}]",IMG_4513.jpeg,, +ARTISTRY,432858,Artistry,2025,boardgame,auto,,,version_ambiguous,"[{""bgg_id"": 432858, ""name"": ""Artistry"", ""year"": 2025, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 762382, ""name"": ""English Deluxe edition"", ""year"": 2025, ""publishers"": [""Dux Somnium Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 734890, ""name"": ""English edition"", ""year"": 2025, ""publishers"": [""Dux Somnium Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 762383, ""name"": ""English Jewelry Box edition"", ""year"": 2025, ""publishers"": [""Dux Somnium Games""], ""languages"": [""English""], ""score"": 3}, {""version_id"": 777369, ""name"": ""Russian edition"", ""year"": 2026, ""publishers"": [""Choo Choo Games"", ""Dux Somnium Games""], ""languages"": [""Russian""], ""score"": 2}]",IMG_4515.jpeg,, +MAGE WARS ACADEMY,172503,Mage Wars Academy,2015,boardgame,auto,265107,English edition,version_auto,"[{""bgg_id"": 172503, ""name"": ""Mage Wars Academy"", ""year"": 2015, ""type"": ""boardgame"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 265107, ""name"": ""English edition"", ""year"": 2015, ""publishers"": [""Arcane Wonders""], ""languages"": [""English""], ""score"": 3}]",IMG_4518.jpeg,, +BOTANY: TANTALIZING TREES,417060,Botany: Tantalizing Trees,2024,boardgameexpansion,auto,706506,English edition,version_auto,"[{""bgg_id"": 417060, ""name"": ""Botany: Tantalizing Trees"", ""year"": 2024, ""type"": ""boardgameexpansion"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 706506, ""name"": ""English edition"", ""year"": 2024, ""publishers"": [""Dux Somnium Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4571.jpeg,, +BOTANY: PERILOUS PERFUMES,417064,Botany: Perilous Perfumes,2024,boardgameexpansion,auto,706510,English edition,version_auto,"[{""bgg_id"": 417064, ""name"": ""Botany: Perilous Perfumes"", ""year"": 2024, ""type"": ""boardgameexpansion"", ""exact"": true, ""fuzzy"": 100.0, ""owned"": null, ""rank"": null}]","[{""version_id"": 706510, ""name"": ""English edition"", ""year"": 2024, ""publishers"": [""Dux Somnium Games""], ""languages"": [""English""], ""score"": 3}]",IMG_4571.jpeg,, diff --git a/src/bggpipe/config.py b/src/bggpipe/config.py index 5f166e4..0b358a9 100644 --- a/src/bggpipe/config.py +++ b/src/bggpipe/config.py @@ -74,6 +74,12 @@ class Config: def games_path(self) -> Path: return self.data_dir / "games.json" + @property + def title_additions_path(self) -> Path: + # games the human added without a photo (expansions stored inside + # base boxes, unshelved games) — joined into every rebuild + return self.data_dir / "title_additions.json" + @property def title_removals_path(self) -> Path: # titles the human removed from the catalog (not a game, misread) — diff --git a/src/bggpipe/extract.py b/src/bggpipe/extract.py index 3710547..099d9b6 100644 --- a/src/bggpipe/extract.py +++ b/src/bggpipe/extract.py @@ -395,6 +395,24 @@ def _scoped_records(path: Path) -> list[dict]: return records +def load_title_additions(path: Path) -> list[dict]: + """Entries the human added without a photo — an expansion stored inside + a base box, a game away from the shelves. Shaped like raw reads and fed + into every rebuild BEFORE edits and dedupe, so corrections apply and a + later photo sighting of the same game merges instead of duplicating.""" + return _load_store(path) + + +def record_title_addition(path: Path, entry: dict) -> None: + existing = load_title_additions(path) + norm = normalize_title(entry["title_raw"]) + if any(normalize_title(e["title_raw"]) == norm for e in existing): + return # already on file; edit the existing line instead + atomic_write_text( + path, json.dumps([*existing, entry], indent=2, ensure_ascii=False) + "\n" + ) + + def load_title_splits(path: Path) -> list[dict]: """The human's split-into-copies decisions — durable: they must survive extract rebuilds and resolve --force.""" @@ -490,6 +508,7 @@ def rebuild_artifacts( splits: list[dict] | None = None, edits: list[dict] | None = None, removals: list[dict] | None = None, + additions: list[dict] | None = None, ) -> tuple[list[dict], dict[str, list[dict]]]: """Regenerate titles.json and unidentified.json from the per-photo raw cache. A raw file is either an object with titles/unidentified or a bare @@ -511,6 +530,7 @@ def rebuild_artifacts( photo = raw_file.name.removesuffix(".json") if data.get("unidentified"): unidentified[photo] = data["unidentified"] + entries = entries + [dict(e) for e in additions or []] entries = apply_title_removals( apply_title_edits(entries, edits or []), removals or [] ) @@ -534,6 +554,7 @@ def replay_titles(cfg: Config) -> None: splits = load_title_splits(cfg.title_splits_path) edits = load_title_edits(cfg.title_edits_path) removals = load_title_removals(cfg.title_removals_path) + additions = load_title_additions(cfg.title_additions_path) raw_dir = cfg.extract_raw_dir raw_photos = ( {f.name.removesuffix(".json") for f in raw_dir.glob("*.json")} @@ -549,7 +570,13 @@ def replay_titles(cfg: Config) -> None: # them would silently truncate the committed catalog if raw_photos and raw_photos >= known_photos: rebuild_artifacts( - raw_dir, cfg.titles_path, cfg.unidentified_path, splits, edits, removals + raw_dir, + cfg.titles_path, + cfg.unidentified_path, + splits, + edits, + removals, + additions, ) return if not cfg.titles_path.exists(): @@ -561,6 +588,7 @@ def replay_titles(cfg: Config) -> None: exploded.extend({**entry, "source_photos": [p]} for p in photos) else: exploded.append(dict(entry)) + exploded.extend(dict(e) for e in additions) deduped = dedupe_entries( apply_title_removals(apply_title_edits(exploded, edits), removals), splits ) @@ -655,6 +683,7 @@ def run_extract( load_title_splits(cfg.title_splits_path), load_title_edits(cfg.title_edits_path), load_title_removals(cfg.title_removals_path), + load_title_additions(cfg.title_additions_path), ) typer.echo(f"Wrote {len(deduped)} unique title(s) to {cfg.titles_path}.") if failed: diff --git a/src/bggpipe/templates/pages/help.html b/src/bggpipe/templates/pages/help.html index 955467e..7ea72ff 100644 --- a/src/bggpipe/templates/pages/help.html +++ b/src/bggpipe/templates/pages/help.html @@ -39,6 +39,7 @@

edit — fix a misread title or add cues you already know (publisher, edition, year, language). A corrected misspelling automatically merges with a correctly-read sighting of the same game from another photo. If the line already had a BGG match, saving re-queues it so resolve searches again with the corrected data.

split into copies — one line, several physical boxes? Splitting makes each photo its own copy, and each copy picks its own edition afterward. Appears on any line whose title was seen in more than one photo. Splitting one game never affects a same-named different edition.

pick edition — a matched game with no legible edition cues stays version-less by design (never guess) — but you know which printing your box is. This fetches the game's complete version list into a Review ballot; pick yours there.

+

add a game (top of the Titles page) — a game no photo shows: an expansion stored inside a base box, a game away from the shelves. It joins the list like any read (BGG wants base game and expansion as separate collection entries, so boxes that hold both need this for the hidden half), and if a later photo shows it, the sighting merges instead of duplicating.

remove (inside the edit panel) — for lines that shouldn't exist at all: a book read as a game, box art misread as a title. The line and its matches are discarded and stay gone. This is different from reject on the Review page, which keeps the line visible as "no BGG match" — right for real games BGG doesn't know.

Undo: each decision is one record in data/title_edits.json, data/title_splits.json, or data/title_removals.json — delete the record and the next rebuild restores the old state.

diff --git a/src/bggpipe/templates/pages/titles.html b/src/bggpipe/templates/pages/titles.html index 9424c08..1f3892e 100644 --- a/src/bggpipe/templates/pages/titles.html +++ b/src/bggpipe/templates/pages/titles.html @@ -4,7 +4,21 @@ + +

Nothing extracted yet — start on the photos page.