Validate and regenerate all seed data from PokeDB
- Regenerate seed JSON for all 37 games with more complete PokeDB data
- Add category field to games.json (original/enhanced/remake/sequel/spinoff)
- Include all 1350 pokemon in pokemon.json with types and local sprites
- Build reverse index for PokeDB form lookups (types/sprites for evolutions)
- Move sprites to frontend/public/sprites, reference as /sprites/{id}.webp
- Truncate Sw/Sh den names to fit DB VARCHAR(100) limit
- Deduplicate route names and merge unnamed child areas into parent routes
- Populate 7 previously empty games (Sw/Sh, BDSP, PLA, Sc/Vi)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -302,6 +302,29 @@ _FORM_SUFFIX_MAP: dict[str, str] = {
|
||||
}
|
||||
|
||||
|
||||
# PokeDB type IDs → type names (from PokeDB's type system)
|
||||
TYPE_ID_MAP: dict[int, str] = {
|
||||
1: "normal",
|
||||
2: "fighting",
|
||||
3: "flying",
|
||||
4: "poison",
|
||||
5: "ground",
|
||||
6: "rock",
|
||||
7: "bug",
|
||||
8: "ghost",
|
||||
9: "steel",
|
||||
10: "fire",
|
||||
11: "water",
|
||||
12: "grass",
|
||||
13: "electric",
|
||||
14: "psychic",
|
||||
15: "ice",
|
||||
16: "dragon",
|
||||
17: "dark",
|
||||
18: "fairy",
|
||||
}
|
||||
|
||||
|
||||
def _normalize_slug(identifier: str) -> str:
|
||||
"""Normalize a PokeDB pokemon_form_identifier to a PokeAPI-style slug.
|
||||
|
||||
@@ -390,6 +413,7 @@ class PokemonMapper:
|
||||
# Build slug → (pokeapi_id, name) from existing pokemon.json
|
||||
self._slug_to_info: dict[str, tuple[int, str]] = {}
|
||||
self._id_to_info: dict[int, tuple[int, str]] = {} # pokeapi_id → (national_dex, name)
|
||||
self._existing_types: dict[int, list[str]] = {} # pokeapi_id → types (fallback)
|
||||
self._unmapped: set[str] = set()
|
||||
|
||||
if pokemon_json_path.exists():
|
||||
@@ -401,6 +425,8 @@ class PokemonMapper:
|
||||
name = p["name"]
|
||||
ndex = p["national_dex"]
|
||||
self._id_to_info[pid] = (ndex, name)
|
||||
if p.get("types"):
|
||||
self._existing_types[pid] = p["types"]
|
||||
|
||||
# Index by base slug (from pokeapi_id for base forms)
|
||||
slug = _name_to_slug(name)
|
||||
@@ -413,11 +439,37 @@ class PokemonMapper:
|
||||
|
||||
# Build index from PokeDB pokemon_forms.json if it has useful fields
|
||||
self._pokedb_form_index: dict[str, dict] = {}
|
||||
# Reverse index: pokeapi_id → PokeDB form record (for non-encountered lookups)
|
||||
self._id_to_pokedb_form: dict[int, dict] = {}
|
||||
for form in pokedb.pokemon_forms:
|
||||
identifier = form.get("identifier", "")
|
||||
if identifier:
|
||||
self._pokedb_form_index[identifier] = form
|
||||
|
||||
# Build reverse index from pokeapi_id → PokeDB form
|
||||
# First, for all encountered lookups that succeed, we cache the mapping.
|
||||
# Here we pre-build for default forms using ndex_id.
|
||||
for form in pokedb.pokemon_forms:
|
||||
ndex = form.get("ndex_id")
|
||||
if ndex and form.get("is_default_form"):
|
||||
# Default form matches the base species (ndex == pokeapi_id for base forms)
|
||||
if ndex in self._id_to_info:
|
||||
self._id_to_pokedb_form[ndex] = form
|
||||
# Also look for alternate-form pokeapi_ids that share the same ndex
|
||||
for pid, (p_ndex, _) in self._id_to_info.items():
|
||||
if p_ndex == ndex and pid not in self._id_to_pokedb_form:
|
||||
self._id_to_pokedb_form[pid] = form
|
||||
|
||||
# Map non-default forms to their specific pokeapi_ids where possible
|
||||
for form in pokedb.pokemon_forms:
|
||||
identifier = form.get("identifier", "")
|
||||
if not identifier or form.get("is_default_form"):
|
||||
continue
|
||||
slug = _normalize_slug(identifier)
|
||||
if slug in self._slug_to_info:
|
||||
pid, _ = self._slug_to_info[slug]
|
||||
self._id_to_pokedb_form[pid] = form
|
||||
|
||||
def lookup(self, pokemon_form_identifier: str | None) -> tuple[int, str] | None:
|
||||
"""Look up a PokeDB pokemon_form_identifier.
|
||||
|
||||
@@ -487,6 +539,64 @@ class PokemonMapper:
|
||||
return None
|
||||
return self._pokedb_form_index.get(pokemon_form_identifier)
|
||||
|
||||
def all_pokemon(self) -> list[tuple[int, tuple[int, str]]]:
|
||||
"""Return all known pokemon as [(pokeapi_id, (national_dex, name)), ...].
|
||||
|
||||
Sourced from the existing pokemon.json.
|
||||
"""
|
||||
return sorted(self._id_to_info.items())
|
||||
|
||||
def get_types_for_id(self, pokeapi_id: int) -> list[str]:
|
||||
"""Get types for a pokemon by pokeapi_id, looking up via PokeDB form data.
|
||||
|
||||
Falls back to existing types from pokemon.json if no PokeDB form found.
|
||||
"""
|
||||
form = self._find_form_for_id(pokeapi_id)
|
||||
if form:
|
||||
types = []
|
||||
t1 = form.get("type_1_id")
|
||||
t2 = form.get("type_2_id")
|
||||
if t1 and t1 in TYPE_ID_MAP:
|
||||
types.append(TYPE_ID_MAP[t1])
|
||||
if t2 and t2 in TYPE_ID_MAP:
|
||||
types.append(TYPE_ID_MAP[t2])
|
||||
if types:
|
||||
return types
|
||||
# Fallback to existing types from pokemon.json
|
||||
return self._existing_types.get(pokeapi_id, [])
|
||||
|
||||
def _find_form_for_id(self, pokeapi_id: int) -> dict | None:
|
||||
"""Find the PokeDB form record for a pokeapi_id."""
|
||||
# Check pre-built reverse index first
|
||||
if pokeapi_id in self._id_to_pokedb_form:
|
||||
return self._id_to_pokedb_form[pokeapi_id]
|
||||
|
||||
if pokeapi_id not in self._id_to_info:
|
||||
return None
|
||||
_, name = self._id_to_info[pokeapi_id]
|
||||
slug = _name_to_slug(name)
|
||||
for suffix in ["-default", ""]:
|
||||
candidate = slug + suffix
|
||||
if candidate in self._pokedb_form_index:
|
||||
return self._pokedb_form_index[candidate]
|
||||
form_slug = _name_to_form_slug(name)
|
||||
if form_slug:
|
||||
for suffix in ["-default", ""]:
|
||||
candidate = form_slug + suffix
|
||||
if candidate in self._pokedb_form_index:
|
||||
return self._pokedb_form_index[candidate]
|
||||
return None
|
||||
|
||||
def has_sprite_for_id(self, pokeapi_id: int) -> bool:
|
||||
"""Check if a sprite exists for a pokemon by pokeapi_id."""
|
||||
form = self._find_form_for_id(pokeapi_id)
|
||||
return bool(form and form.get("main_image_normal_path_medium"))
|
||||
|
||||
def get_sprite_url_for_id(self, pokeapi_id: int) -> str | None:
|
||||
"""Get the PokeDB CDN sprite URL for a pokemon by pokeapi_id."""
|
||||
form = self._find_form_for_id(pokeapi_id)
|
||||
return form.get("main_image_normal_path_medium") if form else None
|
||||
|
||||
def report_unmapped(self) -> None:
|
||||
"""Print warnings for any unmapped identifiers."""
|
||||
if self._unmapped:
|
||||
|
||||
Reference in New Issue
Block a user