"""Stub BGG XML fixtures for the 27 titles extracted from the real shelf photos, aligned with the objectids in the real collection snapshots. Like write_stub_fixtures.py these are hand-written approximations (BGG's API is locked until the registration is approved). They cover: the plain search per title, empty results for the long Avalon Hill/SPI box titles (what real search does with them), the truncated-head retry searches, tie-break stats (Cosmic Encounter, Wiz-War, Sorcerer), and version lists for every title with publisher/edition/year cues. Once BGG_API_TOKEN exists: delete the cache dirs and re-run resolve/record_fixtures to replace all of this with real recordings. Usage: uv run python scripts/write_photo_fixtures.py """ from __future__ import annotations from pathlib import Path from bggpipe.bgg_client import cache_key TARGETS = (Path("tests/fixtures/bgg_cache"), Path("data/bgg_cache")) SEARCH_TYPES = "boardgame,boardgameexpansion" BG, EXP = "boardgame", "boardgameexpansion" # (bgg_id, name, year, type, name_type) SEARCHES: dict[str, list[tuple]] = { "ROYALS": [(165986, "Royals", 2014, BG, "primary")], "CAT CRIMES": [(235096, "Cat Crimes", 2017, BG, "primary")], "COSMIC ENCOUNTER": [ (39, "Cosmic Encounter", 1977, BG, "primary"), (40529, "Cosmic Encounter", 2008, BG, "primary"), ], "SHERIFF OF NOTTINGHAM": [ (157969, "Sheriff of Nottingham", 2014, BG, "primary"), (289411, "Sheriff of Nottingham (2nd Edition)", 2020, BG, "primary"), ], "5 MINUTE DUNGEON": [(207830, "5-Minute Dungeon", 2017, BG, "primary")], "Britannia": [(240, "Britannia", 1986, BG, "primary")], # BGG search finds nothing for the full transcribed box titles "CIVILIZATION Game of the Heroic Age - The Dawn of History 8000 BC to 250 BC": [], "CIVILIZATION Game of the Heroic Age": [], "CIVILIZATION": [ (71, "Civilization", 1980, BG, "primary"), (170416, "Sid Meier's Civilization: The Board Game", 2002, BG, "primary"), (233247, "Civilization: A New Dawn", 2017, BG, "primary"), ], "ADVANCED CIVILIZATION Game Expansion of the Heroic Age - " "Featuring New Civilization, Commodity, and Calamity Cards": [], "ADVANCED CIVILIZATION Game Expansion of the Heroic Age": [], "ADVANCED CIVILIZATION": [(177, "Advanced Civilization", 1991, EXP, "primary")], "DOCTOR WHO The Game of Time & Space": [ (3090, "Doctor Who: The Game of Time & Space", 1980, BG, "primary"), (125675, "Doctor Who: The Card Game", 2012, BG, "primary"), ], "SORCERER The Game of Magical Conflict": [], "SORCERER": [ (3585, "Sorcerer", 1975, BG, "primary"), (244115, "Sorcerer", 2019, BG, "primary"), ], "STARFORCE ALPHA CENTAURI Interstellar Conflict in the 25th Century": [], "STARFORCE ALPHA": [ ( 2524, "StarForce 'Alpha Centauri': Interstellar Conflict in the 25th Century", 1974, BG, "primary", ), ], "SKIP-BO": [(1078, "Skip-Bo", 1967, BG, "primary")], "a Gentle Rain": [(268163, "A Gentle Rain", 2019, BG, "primary")], "PATCH WORK": [(163412, "Patchwork", 2014, BG, "primary")], "SANTORINI": [ (194655, "Santorini", 2016, BG, "primary"), (255563, "Santorini: New York", 2020, BG, "primary"), ], "Utter Nonsense!": [(181254, "Utter Nonsense", 2015, BG, "primary")], "Cards Against Humanity": [(50381, "Cards Against Humanity", 2009, BG, "primary")], "Joking Hazard": [(193621, "Joking Hazard", 2016, BG, "primary")], "EXPLODING KITTENS": [ (172225, "Exploding Kittens", 2015, BG, "primary"), (172242, "Exploding Kittens: NSFW Edition", 2015, BG, "primary"), ], "Herbaceous": [(195314, "Herbaceous", 2017, BG, "primary")], # vision misread of Herbaceous on one run — search finds nothing; # review's free-text re-search ("f Herbaceous") rescues it "Hebarceos": [], "Wiz-War": [ (1218, "Wiz-War", 1983, BG, "primary"), (104710, "Wiz-War", 2012, BG, "alternate"), # primary: Wiz-War (Eighth Edition) ], "SCRAWL": [(218866, "Scrawl", 2017, BG, "primary")], "FLAT TOP": [(2529, "Flat Top", 1977, BG, "primary")], "Diplomacy": [(483, "Diplomacy", 1959, BG, "primary")], "Age of Renaissance": [(26, "Age of Renaissance", 1996, BG, "primary")], "History of the World": [(224, "History of the World", 1991, BG, "primary")], "Dungeon!": [(1339, "Dungeon!", 1975, BG, "primary")], "DUNGEON!": [(1339, "Dungeon!", 1975, BG, "primary")], # spelling drift } # ids-param -> [(bgg_id, name, year, type, owned, rank, [publishers])] THING_STATS: dict[str, list[tuple]] = { "39,40529": [ (39, "Cosmic Encounter", 1977, BG, 7000, 800, ["Eon"]), (40529, "Cosmic Encounter", 2008, BG, 75000, 150, ["Fantasy Flight Games"]), ], "1218,104710": [ (1218, "Wiz-War", 1983, BG, 4000, 2500, ["Chessex"]), ( 104710, "Wiz-War (Eighth Edition)", 2012, BG, 8000, 1200, ["Fantasy Flight Games"], ), ], "3585,244115": [ ( 3585, "Sorcerer", 1975, BG, 900, 5000, ["Simulations Publications, Inc. (SPI)"], ), (244115, "Sorcerer", 2019, BG, 6000, 1500, ["White Wizard Games"]), ], } # bgg_id -> [(version_id, name, year, [publishers], [languages])] VERSIONS: dict[int, list[tuple]] = { 235096: [(360982, "English edition", 2017, ["ThinkFun"], ["English"])], 240: [ ( 25668, "Avalon Hill English edition", 1986, ["The Avalon Hill Game Co"], ["English"], ), (90000, "Fantasy Flight edition", 2006, ["Fantasy Flight Games"], ["English"]), ( 90001, "Welt der Spiele German edition", 1986, ["Welt der Spiele"], ["German"], ), ], 71: [ ( 71001, "Avalon Hill English edition", 1981, ["The Avalon Hill Game Co"], ["English"], ), ( 71002, "Hartland Trefoil first edition", 1980, ["Hartland Trefoil Ltd."], ["English"], ), (71003, "Gibsons Games edition", 1988, ["Gibsons Games"], ["English"]), ], 177: [ ( 177001, "Avalon Hill English edition", 1991, ["The Avalon Hill Game Co"], ["English"], ) ], 3090: [ ( 309001, "Games Workshop English edition", 1980, ["Games Workshop Ltd."], ["English"], ), ], 3585: [ ( 358501, "SPI Designer's Edition", 1975, ["Simulations Publications, Inc. (SPI)"], ["English"], ), ( 358502, "SPI folio edition", 1975, ["Simulations Publications, Inc. (SPI)"], ["English"], ), ], 2524: [ ( 252401, "SPI Designer's Edition", 1974, ["Simulations Publications, Inc. (SPI)"], ["English"], ), ( 252402, "SPI folio edition", 1974, ["Simulations Publications, Inc. (SPI)"], ["English"], ), ], 268163: [ (268164, "Bloom Edition", 2021, ["Mondo Games"], ["English"]), (268165, "First edition", 2019, ["Mondo Games"], ["English"]), ], 193621: [(19362101, "First edition", 2016, ["Cyanide & Happiness"], ["English"])], 2529: [ (252901, "Avalon Hill edition", 1981, ["The Avalon Hill Game Co"], ["English"]), (252902, "Battleline first edition", 1977, ["Battleline"], ["English"]), ], 483: [ ( 48301, "Avalon Hill 1976 edition", 1976, ["The Avalon Hill Game Co"], ["English"], ), ( 48302, "Avalon Hill 1999 edition", 1999, ["The Avalon Hill Game Co"], ["English"], ), (48303, "Gibsons Games edition", 1963, ["Gibsons Games"], ["English"]), ], 26: [ ( 2601, "Avalon Hill English edition", 1996, ["The Avalon Hill Game Co"], ["English"], ) ], 224: [ ( 22401, "Avalon Hill English edition", 1993, ["The Avalon Hill Game Co"], ["English"], ), ( 22402, "Ragnar Brothers first edition", 1991, ["Ragnar Brothers"], ["English"], ), ], 1339: [ (133901, "TSR first edition", 1975, ["TSR"], ["English"]), (133902, "TSR second edition", 1980, ["TSR"], ["English"]), (133903, "TSR New Dungeon edition", 1989, ["TSR"], ["English"]), ], } def search_xml(results: list[tuple]) -> str: items = "".join( f'' f'' f'' for bgg_id, name, year, type_, name_type in results ) return f'{items}' def stats_xml(things: list[tuple]) -> str: items = "" for bgg_id, name, year, type_, owned, rank, publishers in things: links = "".join( f'' for p in publishers ) items += ( f'' f'' f'{links}' f"" f'' f'' f"" ) return f"{items}" def versions_xml(bgg_id: int, versions: list[tuple]) -> str: version_items = "" for vid, name, year, publishers, languages in versions: links = "".join( f'' for p in publishers ) + "".join( f'' for lang in languages ) version_items += ( f'' f'' f'{links}' ) return ( f'' f'' f"{version_items}" ) def _esc(text: str) -> str: return text.replace("&", "&").replace("<", "<").replace('"', """) def main() -> None: files: dict[str, str] = {} for query, results in SEARCHES.items(): key = cache_key("search", {"query": query, "type": SEARCH_TYPES}) files[key] = search_xml(results) for ids, things in THING_STATS.items(): files[cache_key("thing", {"id": ids, "stats": "1"})] = stats_xml(things) for bgg_id, versions in VERSIONS.items(): key = cache_key("thing", {"id": str(bgg_id), "versions": "1"}) files[key] = versions_xml(bgg_id, versions) for target in TARGETS: target.mkdir(parents=True, exist_ok=True) for name, xml in files.items(): (target / name).write_text(xml) # provenance marker: anything resolved from this cache is stub-derived # and NOT upload-ready; re-recording real fixtures removes the marker (target / "STUB_FIXTURES.marker").write_text( "This cache contains hand-written stub XML, not real BGG " "responses. Data resolved from it must not be uploaded.\n" ) print(f"Wrote {len(files)} fixture file(s) to {' and '.join(map(str, TARGETS))}") if __name__ == "__main__": main()