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:
co-authored by
Claude Fable 5
parent
59f4b8c43c
commit
e187ed4f3f
@@ -407,6 +407,14 @@ def test_rpgitem_rows_are_local_only_never_queued():
|
||||
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():
|
||||
# three vetoed per-photo copies, one owned: one claims the copy, the
|
||||
# other two are queued as new entries
|
||||
|
||||
@@ -226,3 +226,55 @@ def test_enrich_degrades_without_token(tmp_path, capsys):
|
||||
assert games == {}
|
||||
assert "waiting on the BGG API" in capsys.readouterr().out
|
||||
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
|
||||
|
||||
@@ -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"):
|
||||
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
|
||||
|
||||
@@ -1128,3 +1128,14 @@ def test_open_versions_endpoint_routes_row_into_review(tmp_path):
|
||||
json={"title_raw": "Dungeon!", "source_photos": "shelf.jpg"},
|
||||
)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user