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
+52
View File
@@ -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