Add genlocke cumulative graveyard with backend endpoint and UI
Aggregates all fainted encounters across every leg of a genlocke into a
unified graveyard view. Backend serves GET /genlockes/{id}/graveyard with
per-entry leg/game context and summary stats (total deaths, deaths per
leg, deadliest leg). Frontend adds a toggle button on the genlocke detail
page that reveals a filterable/sortable grid of grayscale Pokemon cards.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,13 +15,17 @@ from app.schemas.genlocke import (
|
||||
AddLegRequest,
|
||||
GenlockeCreate,
|
||||
GenlockeDetailResponse,
|
||||
GenlockeGraveyardResponse,
|
||||
GenlockeLegDetailResponse,
|
||||
GenlockeListItem,
|
||||
GenlockeResponse,
|
||||
GenlockeStatsResponse,
|
||||
GenlockeUpdate,
|
||||
GraveyardEntryResponse,
|
||||
GraveyardLegSummary,
|
||||
RetiredPokemonResponse,
|
||||
)
|
||||
from app.schemas.pokemon import PokemonResponse
|
||||
from app.services.families import build_families
|
||||
|
||||
router = APIRouter()
|
||||
@@ -154,6 +158,105 @@ async def get_genlocke(
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{genlocke_id}/graveyard",
|
||||
response_model=GenlockeGraveyardResponse,
|
||||
)
|
||||
async def get_genlocke_graveyard(
|
||||
genlocke_id: int, session: AsyncSession = Depends(get_session)
|
||||
):
|
||||
# Load genlocke with legs + game
|
||||
result = await session.execute(
|
||||
select(Genlocke)
|
||||
.where(Genlocke.id == genlocke_id)
|
||||
.options(
|
||||
selectinload(Genlocke.legs).selectinload(GenlockeLeg.game),
|
||||
)
|
||||
)
|
||||
genlocke = result.scalar_one_or_none()
|
||||
if genlocke is None:
|
||||
raise HTTPException(status_code=404, detail="Genlocke not found")
|
||||
|
||||
# Build run_id → (leg_order, game_name) lookup
|
||||
run_ids = [leg.run_id for leg in genlocke.legs if leg.run_id is not None]
|
||||
run_lookup: dict[int, tuple[int, str]] = {}
|
||||
for leg in genlocke.legs:
|
||||
if leg.run_id is not None:
|
||||
run_lookup[leg.run_id] = (leg.leg_order, leg.game.name)
|
||||
|
||||
if not run_ids:
|
||||
return GenlockeGraveyardResponse(
|
||||
entries=[], total_deaths=0, deaths_per_leg=[], deadliest_leg=None
|
||||
)
|
||||
|
||||
# Query all fainted encounters across all legs
|
||||
enc_result = await session.execute(
|
||||
select(Encounter)
|
||||
.where(
|
||||
Encounter.run_id.in_(run_ids),
|
||||
Encounter.faint_level.isnot(None),
|
||||
Encounter.status == "caught",
|
||||
)
|
||||
.options(
|
||||
selectinload(Encounter.pokemon),
|
||||
selectinload(Encounter.current_pokemon),
|
||||
selectinload(Encounter.route),
|
||||
)
|
||||
)
|
||||
encounters = enc_result.scalars().all()
|
||||
|
||||
# Map to response entries and compute stats
|
||||
entries: list[GraveyardEntryResponse] = []
|
||||
deaths_count: dict[int, int] = {} # run_id → count
|
||||
|
||||
for enc in encounters:
|
||||
leg_order, game_name = run_lookup[enc.run_id]
|
||||
deaths_count[enc.run_id] = deaths_count.get(enc.run_id, 0) + 1
|
||||
|
||||
entries.append(
|
||||
GraveyardEntryResponse(
|
||||
id=enc.id,
|
||||
pokemon=PokemonResponse.model_validate(enc.pokemon),
|
||||
current_pokemon=(
|
||||
PokemonResponse.model_validate(enc.current_pokemon)
|
||||
if enc.current_pokemon
|
||||
else None
|
||||
),
|
||||
nickname=enc.nickname,
|
||||
catch_level=enc.catch_level,
|
||||
faint_level=enc.faint_level,
|
||||
death_cause=enc.death_cause,
|
||||
is_shiny=enc.is_shiny,
|
||||
route_name=enc.route.name,
|
||||
leg_order=leg_order,
|
||||
game_name=game_name,
|
||||
)
|
||||
)
|
||||
|
||||
# Build per-leg summaries
|
||||
deaths_per_leg: list[GraveyardLegSummary] = []
|
||||
for leg in genlocke.legs:
|
||||
if leg.run_id is not None:
|
||||
count = deaths_count.get(leg.run_id, 0)
|
||||
if count > 0:
|
||||
deaths_per_leg.append(
|
||||
GraveyardLegSummary(
|
||||
leg_order=leg.leg_order,
|
||||
game_name=leg.game.name,
|
||||
death_count=count,
|
||||
)
|
||||
)
|
||||
|
||||
deadliest = max(deaths_per_leg, key=lambda s: s.death_count) if deaths_per_leg else None
|
||||
|
||||
return GenlockeGraveyardResponse(
|
||||
entries=entries,
|
||||
total_deaths=len(entries),
|
||||
deaths_per_leg=deaths_per_leg,
|
||||
deadliest_leg=deadliest,
|
||||
)
|
||||
|
||||
|
||||
@router.post("", response_model=GenlockeResponse, status_code=201)
|
||||
async def create_genlocke(
|
||||
data: GenlockeCreate, session: AsyncSession = Depends(get_session)
|
||||
|
||||
Reference in New Issue
Block a user