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:
co-authored by
Claude Fable 5
parent
7b6ea90934
commit
90e75daf98
@@ -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
|
||||
|
||||
@@ -1139,3 +1139,24 @@ def test_local_decision_via_web(tmp_path):
|
||||
saved = {r["title_raw"]: r for r in read_matches(cfg.matches_path)}
|
||||
assert saved["Mystery"]["match_status"] == "local"
|
||||
assert state["summary"]["local"] == 1
|
||||
|
||||
|
||||
def test_add_title_by_hand(tmp_path):
|
||||
web, cfg = make_client(tmp_path)
|
||||
res = web.post(
|
||||
"/api/add-title",
|
||||
json={"title": "Wingspan: European Expansion", "publisher": "Stonemaier Games"},
|
||||
)
|
||||
assert res.status_code == 200
|
||||
line = next(
|
||||
c
|
||||
for c in res.json()["catalog"]
|
||||
if c["title_raw"] == "Wingspan: European Expansion"
|
||||
)
|
||||
assert line["status"] == "awaiting_resolve"
|
||||
assert line["photos"] == [] # no photo — shown as added by hand
|
||||
assert web.post("/api/add-title", json={"title": " "}).status_code == 400
|
||||
assert (
|
||||
web.post("/api/add-title", json={"title": "X", "year": "199Y"}).status_code
|
||||
== 400
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user