"""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, filter_den_routes from .sprites import download_all_sprites, download_sprites SEEDS_DIR_CANDIDATES = [ Path("backend/src/app/seeds"), # from repo root Path("../../backend/src/app/seeds"), # from tools/import-pokedb/ ] SPRITES_DIR_CANDIDATES = [ Path("frontend/public/sprites"), # from repo root Path("../../frontend/public/sprites"), # 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 find_sprites_dir() -> Path: """Locate the frontend sprites directory.""" for candidate in SPRITES_DIR_CANDIDATES: if candidate.parent.exists(): return candidate.resolve() # Fallback return Path("frontend/public/sprites").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) # Filter out Max Raid den child routes (Sword/Shield only) if vg_key == "sword-shield": routes = filter_den_routes(routes) # 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 to frontend/public/sprites sprites_dir = find_sprites_dir() print(f"\nDownloading sprites to {sprites_dir}...") sprite_map = download_sprites(pokemon_mapper, all_encountered_form_ids, sprites_dir) print(f" Sprite map covers {len(sprite_map)} forms") # Download sprites for ALL pokemon (including non-encountered evolutions etc.) all_sprite_ids = download_all_sprites(pokemon_mapper, sprites_dir) # 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()