Add a game by hand: the fourth curation store

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
This commit is contained in:
Eric Wagoner
2026-08-05 19:13:49 -04:00
co-authored by Claude Fable 5
parent 7b6ea90934
commit 90e75daf98
9 changed files with 216 additions and 16 deletions
+56
View File
@@ -260,6 +260,62 @@ def test_removals_filter_rebuilds_photo_scoped():
]
def test_hand_added_titles_survive_rebuilds_and_merge_with_sightings(tmp_path):
from bggpipe.extract import record_title_addition
cfg = Config(data_dir=tmp_path / "data", photos_dir=tmp_path / "photos")
raw = cfg.extract_raw_dir
raw.mkdir(parents=True)
(raw / "a.jpg.json").write_text(
json.dumps({"titles": [_entry("Catan", "a.jpg")], "unidentified": []})
)
record_title_addition(
cfg.title_additions_path,
{
"title_raw": "Wingspan: European Expansion",
"confidence": "high",
"publisher_hint": "Stonemaier Games",
"edition_hint": "",
"year_hint": None,
"language_hint": "",
"art_notes": "",
"source_photos": [],
},
)
replay_titles(cfg)
titles = {e["title_raw"] for e in json.loads(cfg.titles_path.read_text())}
assert titles == {"Catan", "Wingspan: European Expansion"}
# a later photo SEES the expansion: the sighting merges, no duplicate
(raw / "b.jpg.json").write_text(
json.dumps(
{
"titles": [
_entry(
"Wingspan: European Expansion",
"b.jpg",
publisher_hint="Stonemaier Games",
)
],
"unidentified": [],
}
)
)
replay_titles(cfg)
expansion = [
e
for e in json.loads(cfg.titles_path.read_text())
if e["title_raw"] == "Wingspan: European Expansion"
]
assert len(expansion) == 1
assert expansion[0]["source_photos"] == ["b.jpg"] # photo provenance won
# re-recording the same title is a no-op
record_title_addition(
cfg.title_additions_path,
{"title_raw": "WINGSPAN EUROPEAN EXPANSION", "source_photos": []},
)
assert len(json.loads(cfg.title_additions_path.read_text())) == 1
def test_stores_roundtrip_and_replay_from_raw(tmp_path):
cfg = Config(data_dir=tmp_path / "data", photos_dir=tmp_path / "photos")
raw = cfg.extract_raw_dir