Files
bggpipe/scripts/write_stub_fixtures.py
T
Eric Wagoner 4a466f2a68 Progressive title truncation, publisher tiebreak, thematic-year fix
Long transcribed box titles that defeat search now retry with shorter
heads (pre-separator, pre-"Game ..." descriptor, first-two-words from
the pre-subtitle part) matched exact-only against the head — fuzzy
thresholds stay untouched. Tie-breaks gain a publisher pick: when the
box showed a publisher and exactly one exact-named candidate is from
that publisher, it wins (SPI's Sorcerer 1975 now beats the more-owned
White Wizard Sorcerer 2019). The extract prompt excludes thematic/
subject years from year_hint; re-extracting Flat Top's photo drops the
bogus 1942, and a regression test pins that a wrong year can never
drive version selection. Re-extraction also drifted two transcriptions
(DUNGEON!, and Herbaceous misread as "Hebarceos") — fixtures added; the
misread demos review's re-search rescue in the end-to-end run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 14:14:15 -04:00

120 lines
4.5 KiB
Python

"""Write hand-crafted stub XML fixtures into tests/fixtures/bgg_cache/.
BGG's XML API now requires a registered application token, which we don't
have yet, so these fixtures are realistic approximations of real responses
(shapes verified against the API docs) rather than recordings. Once a
BGG_API_TOKEN exists: delete tests/fixtures/bgg_cache/ and run
scripts/record_fixtures.py — it writes real recordings under the SAME
filenames, and the test suite must still pass.
Usage: uv run python scripts/write_stub_fixtures.py
"""
from __future__ import annotations
from pathlib import Path
from bggpipe.bgg_client import cache_key
FIXTURE_CACHE = Path("tests/fixtures/bgg_cache")
SEARCH_TYPES = "boardgame,boardgameexpansion"
def search_item(bgg_id: int, name: str, year: int | None, type_: str) -> str:
year_xml = f'<yearpublished value="{year}"/>' if year else ""
return (
f'<item type="{type_}" id="{bgg_id}">'
f'<name type="primary" value="{name}"/>{year_xml}</item>'
)
WINGSPAN_FAMILY = (
search_item(266192, "Wingspan", 2019, "boardgame")
+ search_item(290448, "Wingspan: European Expansion", 2019, "boardgameexpansion")
+ search_item(300580, "Wingspan: Oceania Expansion", 2020, "boardgameexpansion")
+ search_item(366161, "Wingspan Asia", 2022, "boardgame")
)
SEARCHES = {
"Catan": (
search_item(13, "CATAN", 1995, "boardgame")
+ search_item(926, "CATAN: Seafarers", 1997, "boardgameexpansion")
+ search_item(27760, "Catan Dice Game", 2007, "boardgame")
+ search_item(133038, "CATAN 3D Collector's Edition", 2005, "boardgame")
),
"Wingspan": WINGSPAN_FAMILY,
"Wingspan: European Expansion": WINGSPAN_FAMILY,
"Wingspan Europe": WINGSPAN_FAMILY,
"Café International": (
search_item(373, "Café International", 1989, "boardgame")
+ search_item(8103, "Café International: Das Kartenspiel", 2001, "boardgame")
),
"Citadels": (
search_item(478, "Citadels", 2000, "boardgame")
+ search_item(205398, "Citadels", 2016, "boardgame")
),
"Blorvath: Quest of the Zzyzx": "",
"Blorvath": "", # truncation-retry head of the nonsense title
}
THINGS = {
# Citadels tie-break: neither edition dominates 10x -> stays ambiguous.
("478,205398", "stats"): """<items>
<item type="boardgame" id="478">
<name type="primary" value="Citadels"/><yearpublished value="2000"/>
<statistics><ratings><owned value="85000"/>
<ranks><rank type="subtype" id="1" name="boardgame" value="250"/></ranks>
</ratings></statistics>
</item>
<item type="boardgame" id="205398">
<name type="primary" value="Citadels"/><yearpublished value="2016"/>
<statistics><ratings><owned value="24000"/>
<ranks><rank type="subtype" id="1" name="boardgame" value="400"/></ranks>
</ratings></statistics>
</item>
</items>""",
# Wingspan versions: 2019 Stonemaier English must beat the 2020 printing.
("266192", "versions"): """<items>
<item type="boardgame" id="266192">
<name type="primary" value="Wingspan"/><yearpublished value="2019"/>
<versions>
<item type="boardgameversion" id="465063">
<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="521212">
<name type="primary" value="English fourth printing"/>
<yearpublished value="2020"/>
<link type="boardgamepublisher" id="23202" value="Stonemaier Games"/>
<link type="language" id="2184" value="English"/>
</item>
<item type="boardgameversion" id="472430">
<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>""",
}
def main() -> None:
FIXTURE_CACHE.mkdir(parents=True, exist_ok=True)
for query, items in SEARCHES.items():
key = cache_key("search", {"query": query, "type": SEARCH_TYPES})
total = items.count("<item ")
(FIXTURE_CACHE / key).write_text(f'<items total="{total}">{items}</items>')
for (ids, flavor), xml in THINGS.items():
key = cache_key("thing", {"id": ids, flavor: "1"})
(FIXTURE_CACHE / key).write_text(xml)
for f in sorted(FIXTURE_CACHE.iterdir()):
print(f" {f.name}")
if __name__ == "__main__":
main()