Wire output module into CLI pipeline: route ordering, special encounter merging, and JSON writing for per-game encounters, global games list, and pokemon list with types and sprite paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
223 lines
7.7 KiB
Python
223 lines
7.7 KiB
Python
"""CLI entry point for the PokeDB import tool.
|
|
|
|
Usage:
|
|
# From tools/import-pokedb/ (auto-downloads PokeDB data on first run):
|
|
python -m import_pokedb
|
|
|
|
# With options:
|
|
python -m import_pokedb --game firered
|
|
python -m import_pokedb --pokedb-dir ~/my-pokedb-data/
|
|
python -m import_pokedb --output /tmp/seed-output/
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from .loader import load_pokedb_data, load_seed_config
|
|
from .mappings import PokemonMapper, LocationMapper, build_version_map, map_encounter_method
|
|
from .output import sort_routes, merge_special_encounters, write_game_json, write_games_json, write_pokemon_json
|
|
from .processing import filter_encounters_for_game, process_encounters, build_routes
|
|
from .sprites import download_sprites
|
|
|
|
SEEDS_DIR_CANDIDATES = [
|
|
Path("backend/src/app/seeds"), # from repo root
|
|
Path("../../backend/src/app/seeds"), # from tools/import-pokedb/
|
|
]
|
|
|
|
|
|
def find_seeds_dir() -> Path:
|
|
"""Locate the backend seeds directory."""
|
|
for candidate in SEEDS_DIR_CANDIDATES:
|
|
if (candidate / "version_groups.json").exists():
|
|
return candidate.resolve()
|
|
# Fallback
|
|
return Path("backend/src/app/seeds").resolve()
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
parser = argparse.ArgumentParser(
|
|
prog="import-pokedb",
|
|
description="Convert PokeDB.org JSON data exports into nuzlocke-tracker seed format.",
|
|
)
|
|
parser.add_argument(
|
|
"--pokedb-dir",
|
|
type=Path,
|
|
default=None,
|
|
help="Path to directory containing PokeDB JSON export files (default: .pokedb_cache/)",
|
|
)
|
|
parser.add_argument(
|
|
"--output",
|
|
type=Path,
|
|
default=None,
|
|
help="Output directory for seed JSON files (default: backend/src/app/seeds/data/)",
|
|
)
|
|
parser.add_argument(
|
|
"--game",
|
|
type=str,
|
|
default=None,
|
|
help="Generate data for a specific game slug only (default: all games)",
|
|
)
|
|
return parser
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> None:
|
|
parser = build_parser()
|
|
args = parser.parse_args(argv)
|
|
|
|
seeds_dir = find_seeds_dir()
|
|
pokedb_dir: Path = args.pokedb_dir or (seeds_dir / ".pokedb_cache")
|
|
output_dir: Path = args.output or (seeds_dir / "data")
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
print(f"PokeDB data: {pokedb_dir.resolve()}")
|
|
print(f"Seeds config: {seeds_dir}")
|
|
print(f"Output: {output_dir.resolve()}")
|
|
print()
|
|
|
|
# Load PokeDB export data
|
|
pokedb = load_pokedb_data(pokedb_dir)
|
|
print(pokedb.summary())
|
|
print()
|
|
|
|
# Load existing seed configuration
|
|
config = load_seed_config(seeds_dir)
|
|
print(f"Loaded {len(config.version_groups)} version groups")
|
|
print(f"Loaded route order for {len(config.route_order)} version groups")
|
|
if config.special_encounters:
|
|
se_count = len(config.special_encounters.get("encounters", {}))
|
|
print(f"Loaded special encounters for {se_count} version groups")
|
|
print()
|
|
|
|
# Determine which games to process
|
|
target_game = args.game
|
|
if target_game:
|
|
found = False
|
|
for vg_info in config.version_groups.values():
|
|
if target_game in vg_info.get("versions", []):
|
|
found = True
|
|
break
|
|
if not found:
|
|
print(f"Error: Game '{target_game}' not found in version_groups.json", file=sys.stderr)
|
|
sys.exit(1)
|
|
print(f"Target: {target_game}")
|
|
else:
|
|
total_games = sum(
|
|
len(vg.get("versions", []))
|
|
for vg in config.version_groups.values()
|
|
)
|
|
print(f"Target: all {total_games} games")
|
|
|
|
# Build mappings
|
|
print("\nBuilding mappings...")
|
|
|
|
pokemon_json = seeds_dir / "data" / "pokemon.json"
|
|
pokemon_mapper = PokemonMapper(pokemon_json, pokedb)
|
|
|
|
location_mapper = LocationMapper(pokedb)
|
|
|
|
version_map = build_version_map(pokedb, config.version_groups)
|
|
print(f" Mapped {len(version_map)} PokeDB versions to our game slugs")
|
|
|
|
# Report encounter method coverage
|
|
pokedb_methods = {e.get("encounter_method_identifier", "") for e in pokedb.encounters}
|
|
pokedb_methods.discard("")
|
|
mapped_methods = {m for m in pokedb_methods if map_encounter_method(m) is not None}
|
|
unmapped_methods = pokedb_methods - mapped_methods
|
|
print(f" Encounter methods: {len(mapped_methods)} mapped, {len(unmapped_methods)} unmapped")
|
|
if unmapped_methods:
|
|
print(" Unmapped methods:", file=sys.stderr)
|
|
for m in sorted(unmapped_methods):
|
|
print(f" - {m}", file=sys.stderr)
|
|
|
|
# Spot-check pokemon mapping on actual encounter data
|
|
form_ids_in_encounters: set[str] = set()
|
|
for e in pokedb.encounters:
|
|
fid = e.get("pokemon_form_identifier")
|
|
if fid:
|
|
form_ids_in_encounters.add(fid)
|
|
mapped_forms = 0
|
|
for fid in form_ids_in_encounters:
|
|
if pokemon_mapper.lookup(fid) is not None:
|
|
mapped_forms += 1
|
|
total_forms = len(form_ids_in_encounters)
|
|
print(f" Pokemon forms: {mapped_forms}/{total_forms} mapped from encounters")
|
|
|
|
pokemon_mapper.report_unmapped()
|
|
|
|
# Process encounters per game
|
|
print("\nProcessing encounters...")
|
|
|
|
games_to_process: list[tuple[str, str, int]] = [] # (vg_key, game_slug, generation)
|
|
for vg_key, vg_info in config.version_groups.items():
|
|
generation = vg_info.get("generation", 0)
|
|
for slug in vg_info.get("versions", []):
|
|
if target_game and slug != target_game:
|
|
continue
|
|
games_to_process.append((vg_key, slug, generation))
|
|
|
|
all_encountered_form_ids: set[str] = set()
|
|
|
|
for vg_key, game_slug, generation in games_to_process:
|
|
print(f"\n--- {game_slug} ---")
|
|
|
|
# Filter encounters for this game
|
|
game_encounters = filter_encounters_for_game(pokedb.encounters, game_slug)
|
|
if not game_encounters:
|
|
print(f" No encounters found in PokeDB data")
|
|
continue
|
|
|
|
print(f" Raw encounters: {len(game_encounters)}")
|
|
|
|
# Track encountered form IDs for pokemon.json
|
|
for e in game_encounters:
|
|
fid = e.get("pokemon_form_identifier")
|
|
if fid:
|
|
all_encountered_form_ids.add(fid)
|
|
|
|
# Process into grouped encounters
|
|
encounters_by_area = process_encounters(
|
|
game_encounters, generation, pokemon_mapper, location_mapper,
|
|
)
|
|
print(f" Location areas with encounters: {len(encounters_by_area)}")
|
|
|
|
# Build route hierarchy
|
|
routes = build_routes(encounters_by_area, location_mapper)
|
|
|
|
# Merge special encounters (starters, gifts, fossils)
|
|
routes = merge_special_encounters(routes, config, vg_key, pokemon_mapper)
|
|
|
|
# Sort routes by game progression order
|
|
routes = sort_routes(routes, config, vg_key)
|
|
|
|
# Stats
|
|
total_routes = sum(1 + len(r.children) for r in routes)
|
|
total_enc = sum(
|
|
len(r.encounters) + sum(len(c.encounters) for c in r.children)
|
|
for r in routes
|
|
)
|
|
print(f" Routes: {total_routes}")
|
|
print(f" Encounter entries: {total_enc}")
|
|
|
|
# Write per-game JSON
|
|
write_game_json(routes, output_dir, game_slug)
|
|
|
|
# Download sprites for all encountered pokemon
|
|
print("\nDownloading sprites...")
|
|
sprites_dir = output_dir / "sprites"
|
|
sprite_map = download_sprites(pokemon_mapper, all_encountered_form_ids, sprites_dir)
|
|
print(f" Sprite map covers {len(sprite_map)} forms")
|
|
|
|
# Write global JSON files
|
|
print("\nWriting global data files...")
|
|
write_games_json(config, output_dir)
|
|
write_pokemon_json(pokemon_mapper, all_encountered_form_ids, sprite_map, output_dir)
|
|
|
|
print("\nDone!")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|