The real-data era: token live, stubs retired, editions on demand
BGG application approved. The migration the stub markers guarded for weeks: both synthetic caches deleted; tests/fixtures/bgg_cache re-recorded from the live API (recording list extended to every scenario the suite exercises — Civilization truncation, the Sorcerer SPI tiebreak, StarForce, Flat Top's thematic year, Alice Is Missing's rpgitem fallback); resolve --force re-matched all 133 titles for real (109 auto, 6 ambiguous, 18 unmatched, 30 edition ballots); data/STUB_DATA.marker deleted with its exit condition met — the guard mechanism stays armed should stubs ever regenerate. Reality fixed one bug and taught one lesson. The bug: a multi-type search lists an expansion twice (once per matched type) and the parser kept the generic boardgame entry — parse_search now dedupes by id preferring the specific type, which is what keeps expansion tagging (the base-vs-expansion review guard) alive on real data. The lesson: hand-built ambiguity is tidier than the real thing — Wingspan has 46 versions with three plausible English Stonemaier printings, so the suite's synthetic version ids and version_auto expectations became real ballots (assertions updated to recorded reality; the cue-plumbing test keeps its crafted two-version scenario via an injected transport). New: pick edition. A cue-less matched row is version_unknown by design (never guess) — but the owner knows which printing the box is. open_version_ballot() fetches the game's complete version list, cue-scores it when cues exist, and marks the row version_ambiguous so the normal Review edition pass presents it; the Titles page grows the button (Eric's three Wiz-Wars: two cue-less copies can now each claim their edition). 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
15d3120029
commit
59f4b8c43c
+65
-6
@@ -201,15 +201,17 @@ def test_unmatched_research_from_fixture_cache_with_version(tmp_path):
|
||||
run_review(
|
||||
cfg,
|
||||
console=quiet_console(),
|
||||
input_fn=scripted("f Wingspan", "1"),
|
||||
# the real search lists a fan pack first; Wingspan proper is #2
|
||||
input_fn=scripted("f Wingspan", "2"),
|
||||
client=fixture_client(),
|
||||
)
|
||||
(row,) = read_matches(cfg.matches_path)
|
||||
assert row["match_status"] == "approved"
|
||||
assert row["bgg_id"] == "266192"
|
||||
# cues + fixture versions -> resolved to the 2019 Stonemaier English edition
|
||||
assert row["version_status"] == "version_auto"
|
||||
assert row["version_id"] == "465063"
|
||||
# cues + the real version list -> a handful of English Stonemaier
|
||||
# printings stay plausible; the edition pass gets the ballot
|
||||
assert row["version_status"] == "version_ambiguous"
|
||||
assert "433233" in row["version_candidates_json"]
|
||||
|
||||
|
||||
def test_resumable_quit_midway_then_continue(tmp_path):
|
||||
@@ -440,14 +442,36 @@ def test_fill_version_uses_the_approved_rows_own_cues(tmp_path):
|
||||
],
|
||||
)
|
||||
(cfg.data_dir / "titles.json").write_text(json.dumps([entry_good, entry_bad]))
|
||||
two_versions = """<items><item type="boardgame" id="266192">
|
||||
<name type="primary" value="Wingspan"/><yearpublished value="2019"/>
|
||||
<versions>
|
||||
<item type="boardgameversion" id="111">
|
||||
<name type="primary" value="English first edition"/>
|
||||
<yearpublished value="2019"/>
|
||||
<link type="boardgamepublisher" id="23202" value="Stonemaier Games"/>
|
||||
<link type="language" id="2184" value="English"/>
|
||||
</item>
|
||||
<item type="boardgameversion" id="222">
|
||||
<name type="primary" value="German edition"/>
|
||||
<yearpublished value="2019"/>
|
||||
<link type="boardgamepublisher" id="22160" value="Feuerland Spiele"/>
|
||||
<link type="language" id="2188" value="German"/>
|
||||
</item>
|
||||
</versions></item></items>"""
|
||||
crafted = BGGClient(
|
||||
cache_dir=tmp_path / "crafted_cache",
|
||||
transport=httpx.MockTransport(
|
||||
lambda req: httpx.Response(200, text=two_versions)
|
||||
),
|
||||
)
|
||||
session = ReviewSession(
|
||||
cfg, console=quiet_console(), input_fn=scripted(), client=fixture_client()
|
||||
cfg, console=quiet_console(), input_fn=scripted(), client=crafted
|
||||
)
|
||||
row = session.rows[0]
|
||||
session.decide_pick(row, {"bgg_id": 266192, "name": "Wingspan", "year": 2019})
|
||||
# the row's own photo (good.jpg) must select entry_good's cues
|
||||
assert row["version_status"] == "version_auto"
|
||||
assert row["version_id"] == "465063"
|
||||
assert row["version_id"] == "111"
|
||||
|
||||
|
||||
def test_dismiss_failure_keeps_ticket_visible(tmp_path, monkeypatch):
|
||||
@@ -534,3 +558,38 @@ def test_split_copies_survive_resolve_rerun(tmp_path):
|
||||
saved = read_matches(cfg.matches_path)
|
||||
assert len(saved) == 3 # not re-merged, not re-resolved
|
||||
assert [r["source_photos"] for r in saved] == ["a.jpg", "b.jpg", "c.jpg"]
|
||||
|
||||
|
||||
def test_open_version_ballot_puts_every_edition_up_for_review(tmp_path):
|
||||
# a cue-less row is version_unknown by design — but the human knows
|
||||
# which printing the box is; the ballot fetches the FULL version list
|
||||
cfg = _setup(
|
||||
tmp_path,
|
||||
[
|
||||
_row(
|
||||
title_raw="Wingspan",
|
||||
match_status="auto",
|
||||
bgg_id="266192",
|
||||
bgg_name="Wingspan",
|
||||
version_status="version_unknown",
|
||||
)
|
||||
],
|
||||
)
|
||||
session = ReviewSession(
|
||||
cfg, console=quiet_console(), input_fn=scripted(), client=fixture_client()
|
||||
)
|
||||
row = session.rows[0]
|
||||
count = session.open_version_ballot(row)
|
||||
assert count == 46 # the real recorded version list, complete
|
||||
assert row["version_status"] == "version_ambiguous"
|
||||
candidates = json.loads(row["version_candidates_json"])
|
||||
assert {c["version_id"] for c in candidates} >= {433233}
|
||||
saved = read_matches(cfg.matches_path)[0]
|
||||
assert saved["version_status"] == "version_ambiguous" # persisted
|
||||
|
||||
unmatched = _setup(tmp_path / "second", [_row(title_raw="Mystery")])
|
||||
session2 = ReviewSession(
|
||||
unmatched, console=quiet_console(), input_fn=scripted(), client=fixture_client()
|
||||
)
|
||||
with pytest.raises(ValueError, match="no BGG match"):
|
||||
session2.open_version_ballot(session2.rows[0])
|
||||
|
||||
Reference in New Issue
Block a user