Fix linting errors across backend and frontend
Backend: auto-fix and format all ruff issues, manually fix B904/B023/ SIM117/B007/E741/F841 errors, suppress B008 (FastAPI Depends) and F821 (SQLAlchemy forward refs) in config. Frontend: allow constant exports, disable React compiler-specific rules (set-state-in-effect, preserve-manual-memoization). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ Usage:
|
||||
|
||||
import asyncio
|
||||
import random
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
from sqlalchemy import delete, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -16,18 +16,52 @@ from app.models.encounter import Encounter
|
||||
from app.models.evolution import Evolution
|
||||
from app.models.game import Game
|
||||
from app.models.nuzlocke_run import NuzlockeRun
|
||||
from app.models.pokemon import Pokemon
|
||||
from app.models.route import Route
|
||||
|
||||
random.seed(42) # reproducible data
|
||||
|
||||
# --- Nicknames pool ---
|
||||
NICKNAMES = [
|
||||
"Blaze", "Thunder", "Shadow", "Luna", "Spike", "Rex", "Cinder", "Misty",
|
||||
"Rocky", "Breeze", "Fang", "Nova", "Scout", "Atlas", "Pepper", "Storm",
|
||||
"Bandit", "Echo", "Maple", "Titan", "Ziggy", "Bolt", "Rusty", "Pearl",
|
||||
"Ivy", "Ghost", "Sunny", "Dash", "Ember", "Frost", "Jade", "Onyx",
|
||||
"Willow", "Tank", "Pip", "Mochi", "Salem", "Patches", "Bean", "Rocket",
|
||||
"Blaze",
|
||||
"Thunder",
|
||||
"Shadow",
|
||||
"Luna",
|
||||
"Spike",
|
||||
"Rex",
|
||||
"Cinder",
|
||||
"Misty",
|
||||
"Rocky",
|
||||
"Breeze",
|
||||
"Fang",
|
||||
"Nova",
|
||||
"Scout",
|
||||
"Atlas",
|
||||
"Pepper",
|
||||
"Storm",
|
||||
"Bandit",
|
||||
"Echo",
|
||||
"Maple",
|
||||
"Titan",
|
||||
"Ziggy",
|
||||
"Bolt",
|
||||
"Rusty",
|
||||
"Pearl",
|
||||
"Ivy",
|
||||
"Ghost",
|
||||
"Sunny",
|
||||
"Dash",
|
||||
"Ember",
|
||||
"Frost",
|
||||
"Jade",
|
||||
"Onyx",
|
||||
"Willow",
|
||||
"Tank",
|
||||
"Pip",
|
||||
"Mochi",
|
||||
"Salem",
|
||||
"Patches",
|
||||
"Bean",
|
||||
"Rocket",
|
||||
]
|
||||
|
||||
DEATH_CAUSES = [
|
||||
@@ -129,20 +163,18 @@ async def get_leaf_routes(session: AsyncSession, game_id: int) -> list[Route]:
|
||||
"""Get routes that can have encounters (no children)."""
|
||||
# Get all routes for the game
|
||||
result = await session.execute(
|
||||
select(Route)
|
||||
.where(Route.game_id == game_id)
|
||||
.order_by(Route.order)
|
||||
select(Route).where(Route.game_id == game_id).order_by(Route.order)
|
||||
)
|
||||
all_routes = result.scalars().all()
|
||||
|
||||
parent_ids = {r.parent_route_id for r in all_routes if r.parent_route_id is not None}
|
||||
parent_ids = {
|
||||
r.parent_route_id for r in all_routes if r.parent_route_id is not None
|
||||
}
|
||||
leaf_routes = [r for r in all_routes if r.id not in parent_ids]
|
||||
return leaf_routes
|
||||
|
||||
|
||||
async def get_encounterables(
|
||||
session: AsyncSession, game_id: int
|
||||
) -> list[int]:
|
||||
async def get_encounterables(session: AsyncSession, game_id: int) -> list[int]:
|
||||
"""Get pokemon IDs that appear in route encounters for this game."""
|
||||
from app.models.route_encounter import RouteEncounter
|
||||
|
||||
@@ -157,16 +189,16 @@ async def get_encounterables(
|
||||
|
||||
async def get_evolution_map(session: AsyncSession) -> dict[int, list[int]]:
|
||||
"""Return {from_pokemon_id: [to_pokemon_id, ...]} for all evolutions."""
|
||||
result = await session.execute(select(Evolution.from_pokemon_id, Evolution.to_pokemon_id))
|
||||
result = await session.execute(
|
||||
select(Evolution.from_pokemon_id, Evolution.to_pokemon_id)
|
||||
)
|
||||
evo_map: dict[int, list[int]] = {}
|
||||
for from_id, to_id in result:
|
||||
evo_map.setdefault(from_id, []).append(to_id)
|
||||
return evo_map
|
||||
|
||||
|
||||
def pick_routes_for_run(
|
||||
leaf_routes: list[Route], progress: float
|
||||
) -> list[Route]:
|
||||
def pick_routes_for_run(leaf_routes: list[Route], progress: float) -> list[Route]:
|
||||
"""Pick a subset of leaf routes respecting one-per-group.
|
||||
|
||||
For routes with a parent, only one sibling per parent_route_id is chosen.
|
||||
@@ -257,74 +289,73 @@ async def inject():
|
||||
"""Clear existing runs and inject test data."""
|
||||
print("Injecting test data...")
|
||||
|
||||
async with async_session() as session:
|
||||
async with session.begin():
|
||||
# Clear existing runs and encounters
|
||||
await session.execute(delete(Encounter))
|
||||
await session.execute(delete(NuzlockeRun))
|
||||
print("Cleared existing runs and encounters")
|
||||
async with async_session() as session, session.begin():
|
||||
# Clear existing runs and encounters
|
||||
await session.execute(delete(Encounter))
|
||||
await session.execute(delete(NuzlockeRun))
|
||||
print("Cleared existing runs and encounters")
|
||||
|
||||
evo_map = await get_evolution_map(session)
|
||||
now = datetime.now(timezone.utc)
|
||||
evo_map = await get_evolution_map(session)
|
||||
now = datetime.now(UTC)
|
||||
|
||||
total_runs = 0
|
||||
total_encounters = 0
|
||||
total_runs = 0
|
||||
total_encounters = 0
|
||||
|
||||
for run_def in RUN_DEFS:
|
||||
game = await get_game_by_slug(session, run_def["game_slug"])
|
||||
if game is None:
|
||||
print(f" Warning: game '{run_def['game_slug']}' not found, skipping")
|
||||
continue
|
||||
for run_def in RUN_DEFS:
|
||||
game = await get_game_by_slug(session, run_def["game_slug"])
|
||||
if game is None:
|
||||
print(f" Warning: game '{run_def['game_slug']}' not found, skipping")
|
||||
continue
|
||||
|
||||
# Build rules
|
||||
rules = {**DEFAULT_RULES, **run_def["rules"]}
|
||||
# Build rules
|
||||
rules = {**DEFAULT_RULES, **run_def["rules"]}
|
||||
|
||||
# Compute dates
|
||||
started_at = now - timedelta(days=run_def["started_days_ago"])
|
||||
completed_at = None
|
||||
if run_def["ended_days_ago"] is not None:
|
||||
completed_at = now - timedelta(days=run_def["ended_days_ago"])
|
||||
# Compute dates
|
||||
started_at = now - timedelta(days=run_def["started_days_ago"])
|
||||
completed_at = None
|
||||
if run_def["ended_days_ago"] is not None:
|
||||
completed_at = now - timedelta(days=run_def["ended_days_ago"])
|
||||
|
||||
run = NuzlockeRun(
|
||||
game_id=game.id,
|
||||
name=run_def["name"],
|
||||
status=run_def["status"],
|
||||
rules=rules,
|
||||
started_at=started_at,
|
||||
completed_at=completed_at,
|
||||
)
|
||||
session.add(run)
|
||||
await session.flush() # get run.id
|
||||
run = NuzlockeRun(
|
||||
game_id=game.id,
|
||||
name=run_def["name"],
|
||||
status=run_def["status"],
|
||||
rules=rules,
|
||||
started_at=started_at,
|
||||
completed_at=completed_at,
|
||||
)
|
||||
session.add(run)
|
||||
await session.flush() # get run.id
|
||||
|
||||
# Get routes and pokemon for this game
|
||||
leaf_routes = await get_leaf_routes(session, game.id)
|
||||
pokemon_ids = await get_encounterables(session, game.id)
|
||||
|
||||
if not leaf_routes or not pokemon_ids:
|
||||
print(f" {run_def['name']}: no routes or pokemon, skipping encounters")
|
||||
total_runs += 1
|
||||
continue
|
||||
|
||||
chosen_routes = pick_routes_for_run(leaf_routes, run_def["progress"])
|
||||
used_pokemon: set[int] = set()
|
||||
|
||||
run_encounters = 0
|
||||
for i, route in enumerate(chosen_routes):
|
||||
enc = generate_encounter(
|
||||
run.id, route, pokemon_ids, evo_map, used_pokemon, i
|
||||
)
|
||||
session.add(enc)
|
||||
run_encounters += 1
|
||||
# Get routes and pokemon for this game
|
||||
leaf_routes = await get_leaf_routes(session, game.id)
|
||||
pokemon_ids = await get_encounterables(session, game.id)
|
||||
|
||||
if not leaf_routes or not pokemon_ids:
|
||||
print(f" {run_def['name']}: no routes or pokemon, skipping encounters")
|
||||
total_runs += 1
|
||||
total_encounters += run_encounters
|
||||
continue
|
||||
|
||||
print(
|
||||
f" {run_def['name']} ({game.name}, {run_def['status']}): "
|
||||
f"{run_encounters} encounters across {len(chosen_routes)} routes"
|
||||
chosen_routes = pick_routes_for_run(leaf_routes, run_def["progress"])
|
||||
used_pokemon: set[int] = set()
|
||||
|
||||
run_encounters = 0
|
||||
for i, route in enumerate(chosen_routes):
|
||||
enc = generate_encounter(
|
||||
run.id, route, pokemon_ids, evo_map, used_pokemon, i
|
||||
)
|
||||
session.add(enc)
|
||||
run_encounters += 1
|
||||
|
||||
print(f"\nCreated {total_runs} runs with {total_encounters} total encounters")
|
||||
total_runs += 1
|
||||
total_encounters += run_encounters
|
||||
|
||||
print(
|
||||
f" {run_def['name']} ({game.name}, {run_def['status']}): "
|
||||
f"{run_encounters} encounters across {len(chosen_routes)} routes"
|
||||
)
|
||||
|
||||
print(f"\nCreated {total_runs} runs with {total_encounters} total encounters")
|
||||
|
||||
print("Test data injection complete!")
|
||||
|
||||
|
||||
@@ -21,15 +21,18 @@ async def upsert_version_groups(
|
||||
"""Upsert version group records, return {slug: id} mapping."""
|
||||
for vg_slug, vg_info in vg_data.items():
|
||||
vg_name = " / ".join(
|
||||
g["name"].replace("Pokemon ", "")
|
||||
for g in vg_info["games"].values()
|
||||
g["name"].replace("Pokemon ", "") for g in vg_info["games"].values()
|
||||
)
|
||||
stmt = insert(VersionGroup).values(
|
||||
name=vg_name,
|
||||
slug=vg_slug,
|
||||
).on_conflict_do_update(
|
||||
index_elements=["slug"],
|
||||
set_={"name": vg_name},
|
||||
stmt = (
|
||||
insert(VersionGroup)
|
||||
.values(
|
||||
name=vg_name,
|
||||
slug=vg_slug,
|
||||
)
|
||||
.on_conflict_do_update(
|
||||
index_elements=["slug"],
|
||||
set_={"name": vg_name},
|
||||
)
|
||||
)
|
||||
await session.execute(stmt)
|
||||
|
||||
@@ -69,9 +72,13 @@ async def upsert_games(
|
||||
values["version_group_id"] = vg_id
|
||||
update_set["version_group_id"] = vg_id
|
||||
|
||||
stmt = insert(Game).values(**values).on_conflict_do_update(
|
||||
index_elements=["slug"],
|
||||
set_=update_set,
|
||||
stmt = (
|
||||
insert(Game)
|
||||
.values(**values)
|
||||
.on_conflict_do_update(
|
||||
index_elements=["slug"],
|
||||
set_=update_set,
|
||||
)
|
||||
)
|
||||
await session.execute(stmt)
|
||||
|
||||
@@ -81,23 +88,29 @@ async def upsert_games(
|
||||
return {row.slug: row.id for row in result}
|
||||
|
||||
|
||||
async def upsert_pokemon(session: AsyncSession, pokemon_list: list[dict]) -> dict[int, int]:
|
||||
async def upsert_pokemon(
|
||||
session: AsyncSession, pokemon_list: list[dict]
|
||||
) -> dict[int, int]:
|
||||
"""Upsert pokemon records, return {pokeapi_id: id} mapping."""
|
||||
for poke in pokemon_list:
|
||||
stmt = insert(Pokemon).values(
|
||||
pokeapi_id=poke["pokeapi_id"],
|
||||
national_dex=poke["national_dex"],
|
||||
name=poke["name"],
|
||||
types=poke["types"],
|
||||
sprite_url=poke.get("sprite_url"),
|
||||
).on_conflict_do_update(
|
||||
index_elements=["pokeapi_id"],
|
||||
set_={
|
||||
"national_dex": poke["national_dex"],
|
||||
"name": poke["name"],
|
||||
"types": poke["types"],
|
||||
"sprite_url": poke.get("sprite_url"),
|
||||
},
|
||||
stmt = (
|
||||
insert(Pokemon)
|
||||
.values(
|
||||
pokeapi_id=poke["pokeapi_id"],
|
||||
national_dex=poke["national_dex"],
|
||||
name=poke["name"],
|
||||
types=poke["types"],
|
||||
sprite_url=poke.get("sprite_url"),
|
||||
)
|
||||
.on_conflict_do_update(
|
||||
index_elements=["pokeapi_id"],
|
||||
set_={
|
||||
"national_dex": poke["national_dex"],
|
||||
"name": poke["name"],
|
||||
"types": poke["types"],
|
||||
"sprite_url": poke.get("sprite_url"),
|
||||
},
|
||||
)
|
||||
)
|
||||
await session.execute(stmt)
|
||||
|
||||
@@ -119,14 +132,18 @@ async def upsert_routes(
|
||||
"""
|
||||
# First pass: upsert all parent routes (without parent_route_id)
|
||||
for route in routes:
|
||||
stmt = insert(Route).values(
|
||||
name=route["name"],
|
||||
version_group_id=version_group_id,
|
||||
order=route["order"],
|
||||
parent_route_id=None, # Parent routes have no parent
|
||||
).on_conflict_do_update(
|
||||
constraint="uq_routes_version_group_name",
|
||||
set_={"order": route["order"], "parent_route_id": None},
|
||||
stmt = (
|
||||
insert(Route)
|
||||
.values(
|
||||
name=route["name"],
|
||||
version_group_id=version_group_id,
|
||||
order=route["order"],
|
||||
parent_route_id=None, # Parent routes have no parent
|
||||
)
|
||||
.on_conflict_do_update(
|
||||
constraint="uq_routes_version_group_name",
|
||||
set_={"order": route["order"], "parent_route_id": None},
|
||||
)
|
||||
)
|
||||
await session.execute(stmt)
|
||||
|
||||
@@ -146,19 +163,23 @@ async def upsert_routes(
|
||||
|
||||
parent_id = name_to_id[route["name"]]
|
||||
for child in children:
|
||||
stmt = insert(Route).values(
|
||||
name=child["name"],
|
||||
version_group_id=version_group_id,
|
||||
order=child["order"],
|
||||
parent_route_id=parent_id,
|
||||
pinwheel_zone=child.get("pinwheel_zone"),
|
||||
).on_conflict_do_update(
|
||||
constraint="uq_routes_version_group_name",
|
||||
set_={
|
||||
"order": child["order"],
|
||||
"parent_route_id": parent_id,
|
||||
"pinwheel_zone": child.get("pinwheel_zone"),
|
||||
},
|
||||
stmt = (
|
||||
insert(Route)
|
||||
.values(
|
||||
name=child["name"],
|
||||
version_group_id=version_group_id,
|
||||
order=child["order"],
|
||||
parent_route_id=parent_id,
|
||||
pinwheel_zone=child.get("pinwheel_zone"),
|
||||
)
|
||||
.on_conflict_do_update(
|
||||
constraint="uq_routes_version_group_name",
|
||||
set_={
|
||||
"order": child["order"],
|
||||
"parent_route_id": parent_id,
|
||||
"pinwheel_zone": child.get("pinwheel_zone"),
|
||||
},
|
||||
)
|
||||
)
|
||||
await session.execute(stmt)
|
||||
|
||||
@@ -186,21 +207,25 @@ async def upsert_route_encounters(
|
||||
print(f" Warning: no pokemon_id for pokeapi_id {enc['pokeapi_id']}")
|
||||
continue
|
||||
|
||||
stmt = insert(RouteEncounter).values(
|
||||
route_id=route_id,
|
||||
pokemon_id=pokemon_id,
|
||||
game_id=game_id,
|
||||
encounter_method=enc["method"],
|
||||
encounter_rate=enc["encounter_rate"],
|
||||
min_level=enc["min_level"],
|
||||
max_level=enc["max_level"],
|
||||
).on_conflict_do_update(
|
||||
constraint="uq_route_pokemon_method_game",
|
||||
set_={
|
||||
"encounter_rate": enc["encounter_rate"],
|
||||
"min_level": enc["min_level"],
|
||||
"max_level": enc["max_level"],
|
||||
},
|
||||
stmt = (
|
||||
insert(RouteEncounter)
|
||||
.values(
|
||||
route_id=route_id,
|
||||
pokemon_id=pokemon_id,
|
||||
game_id=game_id,
|
||||
encounter_method=enc["method"],
|
||||
encounter_rate=enc["encounter_rate"],
|
||||
min_level=enc["min_level"],
|
||||
max_level=enc["max_level"],
|
||||
)
|
||||
.on_conflict_do_update(
|
||||
constraint="uq_route_pokemon_method_game",
|
||||
set_={
|
||||
"encounter_rate": enc["encounter_rate"],
|
||||
"min_level": enc["min_level"],
|
||||
"max_level": enc["max_level"],
|
||||
},
|
||||
)
|
||||
)
|
||||
await session.execute(stmt)
|
||||
count += 1
|
||||
@@ -224,37 +249,44 @@ async def upsert_bosses(
|
||||
if after_route_name and route_name_to_id:
|
||||
after_route_id = route_name_to_id.get(after_route_name)
|
||||
if after_route_id is None:
|
||||
print(f" Warning: route '{after_route_name}' not found for boss '{boss['name']}'")
|
||||
print(
|
||||
f" Warning: route '{after_route_name}' not found for boss '{boss['name']}'"
|
||||
)
|
||||
|
||||
# Upsert the boss battle on (version_group_id, order) conflict
|
||||
stmt = insert(BossBattle).values(
|
||||
version_group_id=version_group_id,
|
||||
name=boss["name"],
|
||||
boss_type=boss["boss_type"],
|
||||
specialty_type=boss.get("specialty_type"),
|
||||
badge_name=boss.get("badge_name"),
|
||||
badge_image_url=boss.get("badge_image_url"),
|
||||
level_cap=boss["level_cap"],
|
||||
order=boss["order"],
|
||||
after_route_id=after_route_id,
|
||||
location=boss["location"],
|
||||
section=boss.get("section"),
|
||||
sprite_url=boss.get("sprite_url"),
|
||||
).on_conflict_do_update(
|
||||
constraint="uq_boss_battles_version_group_order",
|
||||
set_={
|
||||
"name": boss["name"],
|
||||
"boss_type": boss["boss_type"],
|
||||
"specialty_type": boss.get("specialty_type"),
|
||||
"badge_name": boss.get("badge_name"),
|
||||
"badge_image_url": boss.get("badge_image_url"),
|
||||
"level_cap": boss["level_cap"],
|
||||
"after_route_id": after_route_id,
|
||||
"location": boss["location"],
|
||||
"section": boss.get("section"),
|
||||
"sprite_url": boss.get("sprite_url"),
|
||||
},
|
||||
).returning(BossBattle.id)
|
||||
stmt = (
|
||||
insert(BossBattle)
|
||||
.values(
|
||||
version_group_id=version_group_id,
|
||||
name=boss["name"],
|
||||
boss_type=boss["boss_type"],
|
||||
specialty_type=boss.get("specialty_type"),
|
||||
badge_name=boss.get("badge_name"),
|
||||
badge_image_url=boss.get("badge_image_url"),
|
||||
level_cap=boss["level_cap"],
|
||||
order=boss["order"],
|
||||
after_route_id=after_route_id,
|
||||
location=boss["location"],
|
||||
section=boss.get("section"),
|
||||
sprite_url=boss.get("sprite_url"),
|
||||
)
|
||||
.on_conflict_do_update(
|
||||
constraint="uq_boss_battles_version_group_order",
|
||||
set_={
|
||||
"name": boss["name"],
|
||||
"boss_type": boss["boss_type"],
|
||||
"specialty_type": boss.get("specialty_type"),
|
||||
"badge_name": boss.get("badge_name"),
|
||||
"badge_image_url": boss.get("badge_image_url"),
|
||||
"level_cap": boss["level_cap"],
|
||||
"after_route_id": after_route_id,
|
||||
"location": boss["location"],
|
||||
"section": boss.get("section"),
|
||||
"sprite_url": boss.get("sprite_url"),
|
||||
},
|
||||
)
|
||||
.returning(BossBattle.id)
|
||||
)
|
||||
result = await session.execute(stmt)
|
||||
boss_id = result.scalar_one()
|
||||
|
||||
@@ -267,13 +299,15 @@ async def upsert_bosses(
|
||||
if pokemon_id is None:
|
||||
print(f" Warning: no pokemon_id for pokeapi_id {bp['pokeapi_id']}")
|
||||
continue
|
||||
session.add(BossPokemon(
|
||||
boss_battle_id=boss_id,
|
||||
pokemon_id=pokemon_id,
|
||||
level=bp["level"],
|
||||
order=bp["order"],
|
||||
condition_label=bp.get("condition_label"),
|
||||
))
|
||||
session.add(
|
||||
BossPokemon(
|
||||
boss_battle_id=boss_id,
|
||||
pokemon_id=pokemon_id,
|
||||
level=bp["level"],
|
||||
order=bp["order"],
|
||||
condition_label=bp.get("condition_label"),
|
||||
)
|
||||
)
|
||||
|
||||
count += 1
|
||||
|
||||
|
||||
@@ -42,130 +42,139 @@ async def seed():
|
||||
"""Run the full seed process."""
|
||||
print("Starting seed...")
|
||||
|
||||
async with async_session() as session:
|
||||
async with session.begin():
|
||||
# 1. Upsert version groups
|
||||
with open(VG_JSON) as f:
|
||||
vg_data = json.load(f)
|
||||
vg_slug_to_id = await upsert_version_groups(session, vg_data)
|
||||
print(f"Version Groups: {len(vg_slug_to_id)} upserted")
|
||||
async with async_session() as session, session.begin():
|
||||
# 1. Upsert version groups
|
||||
with open(VG_JSON) as f:
|
||||
vg_data = json.load(f)
|
||||
vg_slug_to_id = await upsert_version_groups(session, vg_data)
|
||||
print(f"Version Groups: {len(vg_slug_to_id)} upserted")
|
||||
|
||||
# Build game_slug -> vg_id mapping
|
||||
game_slug_to_vg_id: dict[str, int] = {}
|
||||
for vg_slug, vg_info in vg_data.items():
|
||||
vg_id = vg_slug_to_id[vg_slug]
|
||||
for game_slug in vg_info["games"]:
|
||||
game_slug_to_vg_id[game_slug] = vg_id
|
||||
# Build game_slug -> vg_id mapping
|
||||
game_slug_to_vg_id: dict[str, int] = {}
|
||||
for vg_slug, vg_info in vg_data.items():
|
||||
vg_id = vg_slug_to_id[vg_slug]
|
||||
for game_slug in vg_info["games"]:
|
||||
game_slug_to_vg_id[game_slug] = vg_id
|
||||
|
||||
# 2. Upsert games (with version_group_id)
|
||||
games_data = load_json("games.json")
|
||||
slug_to_id = await upsert_games(session, games_data, game_slug_to_vg_id)
|
||||
print(f"Games: {len(slug_to_id)} upserted")
|
||||
# 2. Upsert games (with version_group_id)
|
||||
games_data = load_json("games.json")
|
||||
slug_to_id = await upsert_games(session, games_data, game_slug_to_vg_id)
|
||||
print(f"Games: {len(slug_to_id)} upserted")
|
||||
|
||||
# 3. Upsert Pokemon
|
||||
pokemon_data = load_json("pokemon.json")
|
||||
dex_to_id = await upsert_pokemon(session, pokemon_data)
|
||||
print(f"Pokemon: {len(dex_to_id)} upserted")
|
||||
# 3. Upsert Pokemon
|
||||
pokemon_data = load_json("pokemon.json")
|
||||
dex_to_id = await upsert_pokemon(session, pokemon_data)
|
||||
print(f"Pokemon: {len(dex_to_id)} upserted")
|
||||
|
||||
# 4. Per version group: upsert routes once, then encounters per game
|
||||
total_routes = 0
|
||||
total_encounters = 0
|
||||
route_maps_by_vg: dict[int, dict[str, int]] = {}
|
||||
# 4. Per version group: upsert routes once, then encounters per game
|
||||
total_routes = 0
|
||||
total_encounters = 0
|
||||
route_maps_by_vg: dict[int, dict[str, int]] = {}
|
||||
|
||||
for vg_slug, vg_info in vg_data.items():
|
||||
vg_id = vg_slug_to_id[vg_slug]
|
||||
game_slugs = list(vg_info["games"].keys())
|
||||
for vg_slug, vg_info in vg_data.items():
|
||||
vg_id = vg_slug_to_id[vg_slug]
|
||||
game_slugs = list(vg_info["games"].keys())
|
||||
|
||||
# Use the first game's route JSON for the shared route structure
|
||||
first_game_slug = game_slugs[0]
|
||||
routes_file = DATA_DIR / f"{first_game_slug}.json"
|
||||
if not routes_file.exists():
|
||||
print(f" {vg_slug}: no route data ({first_game_slug}.json), skipping")
|
||||
# Use the first game's route JSON for the shared route structure
|
||||
first_game_slug = game_slugs[0]
|
||||
routes_file = DATA_DIR / f"{first_game_slug}.json"
|
||||
if not routes_file.exists():
|
||||
print(f" {vg_slug}: no route data ({first_game_slug}.json), skipping")
|
||||
continue
|
||||
|
||||
routes_data = load_json(f"{first_game_slug}.json")
|
||||
if not routes_data:
|
||||
print(f" {vg_slug}: empty route data, skipping")
|
||||
continue
|
||||
|
||||
# Upsert routes once per version group
|
||||
route_map = await upsert_routes(session, vg_id, routes_data)
|
||||
route_maps_by_vg[vg_id] = route_map
|
||||
total_routes += len(route_map)
|
||||
print(f" {vg_slug}: {len(route_map)} routes")
|
||||
|
||||
# Upsert encounters per game (each game may have different encounters)
|
||||
for game_slug in game_slugs:
|
||||
game_id = slug_to_id.get(game_slug)
|
||||
if game_id is None:
|
||||
print(f" Warning: game '{game_slug}' not found, skipping")
|
||||
continue
|
||||
|
||||
routes_data = load_json(f"{first_game_slug}.json")
|
||||
if not routes_data:
|
||||
print(f" {vg_slug}: empty route data, skipping")
|
||||
game_routes_file = DATA_DIR / f"{game_slug}.json"
|
||||
if not game_routes_file.exists():
|
||||
continue
|
||||
|
||||
# Upsert routes once per version group
|
||||
route_map = await upsert_routes(session, vg_id, routes_data)
|
||||
route_maps_by_vg[vg_id] = route_map
|
||||
total_routes += len(route_map)
|
||||
print(f" {vg_slug}: {len(route_map)} routes")
|
||||
|
||||
# Upsert encounters per game (each game may have different encounters)
|
||||
for game_slug in game_slugs:
|
||||
game_id = slug_to_id.get(game_slug)
|
||||
if game_id is None:
|
||||
print(f" Warning: game '{game_slug}' not found, skipping")
|
||||
game_routes_data = load_json(f"{game_slug}.json")
|
||||
for route in game_routes_data:
|
||||
route_id = route_map.get(route["name"])
|
||||
if route_id is None:
|
||||
print(f" Warning: route '{route['name']}' not found")
|
||||
continue
|
||||
|
||||
game_routes_file = DATA_DIR / f"{game_slug}.json"
|
||||
if not game_routes_file.exists():
|
||||
continue
|
||||
# Parent routes may have empty encounters
|
||||
if route["encounters"]:
|
||||
enc_count = await upsert_route_encounters(
|
||||
session,
|
||||
route_id,
|
||||
route["encounters"],
|
||||
dex_to_id,
|
||||
game_id,
|
||||
)
|
||||
total_encounters += enc_count
|
||||
|
||||
game_routes_data = load_json(f"{game_slug}.json")
|
||||
for route in game_routes_data:
|
||||
route_id = route_map.get(route["name"])
|
||||
if route_id is None:
|
||||
print(f" Warning: route '{route['name']}' not found")
|
||||
# Handle child routes
|
||||
for child in route.get("children", []):
|
||||
child_id = route_map.get(child["name"])
|
||||
if child_id is None:
|
||||
print(
|
||||
f" Warning: child route '{child['name']}' not found"
|
||||
)
|
||||
continue
|
||||
|
||||
# Parent routes may have empty encounters
|
||||
if route["encounters"]:
|
||||
enc_count = await upsert_route_encounters(
|
||||
session, route_id, route["encounters"],
|
||||
dex_to_id, game_id,
|
||||
)
|
||||
total_encounters += enc_count
|
||||
enc_count = await upsert_route_encounters(
|
||||
session,
|
||||
child_id,
|
||||
child["encounters"],
|
||||
dex_to_id,
|
||||
game_id,
|
||||
)
|
||||
total_encounters += enc_count
|
||||
|
||||
# Handle child routes
|
||||
for child in route.get("children", []):
|
||||
child_id = route_map.get(child["name"])
|
||||
if child_id is None:
|
||||
print(f" Warning: child route '{child['name']}' not found")
|
||||
continue
|
||||
print(f" {game_slug}: encounters loaded")
|
||||
|
||||
enc_count = await upsert_route_encounters(
|
||||
session, child_id, child["encounters"],
|
||||
dex_to_id, game_id,
|
||||
)
|
||||
total_encounters += enc_count
|
||||
print(f"\nTotal routes: {total_routes}")
|
||||
print(f"Total encounters: {total_encounters}")
|
||||
|
||||
print(f" {game_slug}: encounters loaded")
|
||||
# 5. Per version group: upsert bosses
|
||||
total_bosses = 0
|
||||
for vg_slug, vg_info in vg_data.items():
|
||||
vg_id = vg_slug_to_id[vg_slug]
|
||||
first_game_slug = list(vg_info["games"].keys())[0]
|
||||
bosses_file = DATA_DIR / f"{first_game_slug}-bosses.json"
|
||||
if not bosses_file.exists():
|
||||
continue
|
||||
|
||||
print(f"\nTotal routes: {total_routes}")
|
||||
print(f"Total encounters: {total_encounters}")
|
||||
bosses_data = load_json(f"{first_game_slug}-bosses.json")
|
||||
if not bosses_data:
|
||||
continue
|
||||
|
||||
# 5. Per version group: upsert bosses
|
||||
total_bosses = 0
|
||||
for vg_slug, vg_info in vg_data.items():
|
||||
vg_id = vg_slug_to_id[vg_slug]
|
||||
first_game_slug = list(vg_info["games"].keys())[0]
|
||||
bosses_file = DATA_DIR / f"{first_game_slug}-bosses.json"
|
||||
if not bosses_file.exists():
|
||||
continue
|
||||
route_name_to_id = route_maps_by_vg.get(vg_id, {})
|
||||
boss_count = await upsert_bosses(
|
||||
session, vg_id, bosses_data, dex_to_id, route_name_to_id
|
||||
)
|
||||
total_bosses += boss_count
|
||||
print(f" {vg_slug}: {boss_count} bosses")
|
||||
|
||||
bosses_data = load_json(f"{first_game_slug}-bosses.json")
|
||||
if not bosses_data:
|
||||
continue
|
||||
print(f"Total bosses: {total_bosses}")
|
||||
|
||||
route_name_to_id = route_maps_by_vg.get(vg_id, {})
|
||||
boss_count = await upsert_bosses(session, vg_id, bosses_data, dex_to_id, route_name_to_id)
|
||||
total_bosses += boss_count
|
||||
print(f" {vg_slug}: {boss_count} bosses")
|
||||
|
||||
print(f"Total bosses: {total_bosses}")
|
||||
|
||||
# 6. Upsert evolutions
|
||||
evolutions_path = DATA_DIR / "evolutions.json"
|
||||
if evolutions_path.exists():
|
||||
evolutions_data = load_json("evolutions.json")
|
||||
evo_count = await upsert_evolutions(session, evolutions_data, dex_to_id)
|
||||
print(f"Evolutions: {evo_count} upserted")
|
||||
else:
|
||||
print("No evolutions.json found, skipping evolutions")
|
||||
# 6. Upsert evolutions
|
||||
evolutions_path = DATA_DIR / "evolutions.json"
|
||||
if evolutions_path.exists():
|
||||
evolutions_data = load_json("evolutions.json")
|
||||
evo_count = await upsert_evolutions(session, evolutions_data, dex_to_id)
|
||||
print(f"Evolutions: {evo_count} upserted")
|
||||
else:
|
||||
print("No evolutions.json found, skipping evolutions")
|
||||
|
||||
print("Seed complete!")
|
||||
|
||||
@@ -180,7 +189,9 @@ async def verify():
|
||||
games_count = (await session.execute(select(func.count(Game.id)))).scalar()
|
||||
pokemon_count = (await session.execute(select(func.count(Pokemon.id)))).scalar()
|
||||
routes_count = (await session.execute(select(func.count(Route.id)))).scalar()
|
||||
enc_count = (await session.execute(select(func.count(RouteEncounter.id)))).scalar()
|
||||
enc_count = (
|
||||
await session.execute(select(func.count(RouteEncounter.id)))
|
||||
).scalar()
|
||||
boss_count = (await session.execute(select(func.count(BossBattle.id)))).scalar()
|
||||
|
||||
print(f"Version Groups: {vg_count}")
|
||||
@@ -328,7 +339,7 @@ async def _export_routes(session: AsyncSession, vg_data: dict):
|
||||
games_by_slug = {g.slug: g for g in game_result.scalars().all()}
|
||||
|
||||
exported = 0
|
||||
for vg_slug, vg_info in vg_data.items():
|
||||
for _vg_slug, vg_info in vg_data.items():
|
||||
for game_slug in vg_info["games"]:
|
||||
game = games_by_slug.get(game_slug)
|
||||
if game is None or game.version_group_id is None:
|
||||
@@ -356,11 +367,9 @@ async def _export_routes(session: AsyncSession, vg_data: dict):
|
||||
if r.parent_route_id is not None:
|
||||
children_by_parent.setdefault(r.parent_route_id, []).append(r)
|
||||
|
||||
def format_encounters(route: Route) -> list[dict]:
|
||||
def format_encounters(route: Route, _game: Game = game) -> list[dict]:
|
||||
game_encounters = [
|
||||
enc
|
||||
for enc in route.route_encounters
|
||||
if enc.game_id == game.id
|
||||
enc for enc in route.route_encounters if enc.game_id == _game.id
|
||||
]
|
||||
return [
|
||||
{
|
||||
@@ -384,17 +393,20 @@ async def _export_routes(session: AsyncSession, vg_data: dict):
|
||||
data["pinwheel_zone"] = route.pinwheel_zone
|
||||
return data
|
||||
|
||||
def format_route(route: Route) -> dict:
|
||||
def format_route(
|
||||
route: Route,
|
||||
_children_by_parent: dict[int, list[Route]] = children_by_parent,
|
||||
) -> dict:
|
||||
data: dict = {
|
||||
"name": route.name,
|
||||
"order": route.order,
|
||||
"encounters": format_encounters(route),
|
||||
}
|
||||
children = children_by_parent.get(route.id, [])
|
||||
children = _children_by_parent.get(route.id, [])
|
||||
if children:
|
||||
data["children"] = [
|
||||
format_child(c)
|
||||
for c in sorted(children, key=lambda r: r.order)
|
||||
for c in sorted(children, key=lambda route: route.order)
|
||||
]
|
||||
return data
|
||||
|
||||
@@ -444,7 +456,9 @@ def _download_image(
|
||||
|
||||
if filename not in downloaded:
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "nuzlocke-tracker/1.0"})
|
||||
req = urllib.request.Request(
|
||||
url, headers={"User-Agent": "nuzlocke-tracker/1.0"}
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
dest.write_bytes(resp.read())
|
||||
@@ -496,37 +510,45 @@ async def _export_bosses(session: AsyncSession, vg_data: dict):
|
||||
if badge_image_url and b.badge_name:
|
||||
badge_slug = _slugify(b.badge_name)
|
||||
badge_image_url = _download_image(
|
||||
badge_image_url, badge_dir, badge_slug, downloaded_badges,
|
||||
badge_image_url,
|
||||
badge_dir,
|
||||
badge_slug,
|
||||
downloaded_badges,
|
||||
)
|
||||
|
||||
if sprite_url:
|
||||
sprite_slug = _slugify(b.name)
|
||||
sprite_url = _download_image(
|
||||
sprite_url, sprite_dir, sprite_slug, downloaded_sprites,
|
||||
sprite_url,
|
||||
sprite_dir,
|
||||
sprite_slug,
|
||||
downloaded_sprites,
|
||||
)
|
||||
|
||||
data.append({
|
||||
"name": b.name,
|
||||
"boss_type": b.boss_type,
|
||||
"specialty_type": b.specialty_type,
|
||||
"badge_name": b.badge_name,
|
||||
"badge_image_url": badge_image_url,
|
||||
"level_cap": b.level_cap,
|
||||
"order": b.order,
|
||||
"after_route_name": b.after_route.name if b.after_route else None,
|
||||
"location": b.location,
|
||||
"section": b.section,
|
||||
"sprite_url": sprite_url,
|
||||
"pokemon": [
|
||||
{
|
||||
"pokeapi_id": bp.pokemon.pokeapi_id,
|
||||
"pokemon_name": bp.pokemon.name,
|
||||
"level": bp.level,
|
||||
"order": bp.order,
|
||||
}
|
||||
for bp in sorted(b.pokemon, key=lambda p: p.order)
|
||||
],
|
||||
})
|
||||
data.append(
|
||||
{
|
||||
"name": b.name,
|
||||
"boss_type": b.boss_type,
|
||||
"specialty_type": b.specialty_type,
|
||||
"badge_name": b.badge_name,
|
||||
"badge_image_url": badge_image_url,
|
||||
"level_cap": b.level_cap,
|
||||
"order": b.order,
|
||||
"after_route_name": b.after_route.name if b.after_route else None,
|
||||
"location": b.location,
|
||||
"section": b.section,
|
||||
"sprite_url": sprite_url,
|
||||
"pokemon": [
|
||||
{
|
||||
"pokeapi_id": bp.pokemon.pokeapi_id,
|
||||
"pokemon_name": bp.pokemon.name,
|
||||
"level": bp.level,
|
||||
"order": bp.order,
|
||||
}
|
||||
for bp in sorted(b.pokemon, key=lambda p: p.order)
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
_write_json(f"{first_game_slug}-bosses.json", data)
|
||||
exported += 1
|
||||
|
||||
Reference in New Issue
Block a user