---
# nuzlocke-tracker-g8zi
title: 'Fix Pokemon form identification: separate PokeAPI ID from national dex'
status: completed
type: bug
priority: high
created_at: 2026-02-07T13:44:25Z
updated_at: 2026-02-07T13:54:29Z
blocking:
    - 6aje
---

## Problem

Pokemon forms (Alolan, Galarian, etc.) share a national dex number with their base species (e.g., Alolan Rattata is still national dex #19, same as regular Rattata). However, the current data model uses the PokeAPI internal ID (e.g., 10091 for Alolan Rattata) as the `national_dex` field. This is semantically wrong and causes:

1. **Wrong display**: Frontend shows "#10091" next to Alolan Rattata instead of "#19"
2. **Broken sorting**: Forms sort at the end of the Pokemon list instead of next to their base species
3. **Misleading data**: The field name implies a national Pokedex number but contains an internal API ID

## Current architecture

The `national_dex` field is deeply embedded as the unique Pokemon identifier:

- **Database**: `SmallInteger` column with `UNIQUE` constraint (`alembic/versions/03e5f186a9d5`)
- **Models**: `pokemon.py` — `mapped_column(SmallInteger, unique=True)`
- **Seeder**: `loader.py` — upsert conflict resolution on `national_dex`, builds `{national_dex: id}` mapping for linking encounters and evolutions
- **Seed data**: `fetch_pokeapi.py` — uses PokeAPI pokemon ID as `national_dex` for both base species and forms
- **API**: All CRUD operations key on `national_dex`, returned as `nationalDex` in JSON
- **Frontend**: Displayed as `#nationalDex` in Pokemon selector, admin table, encounter modals

## Proposed fix

Add a `pokemon_id` (or similar) field as the true unique identifier (the PokeAPI pokemon ID), and keep `national_dex` as the real national dex number (shared between forms). This requires changes across every layer.

## Checklist

- [ ] Add new migration: add `pokemon_id` column (unique, not null), change `national_dex` unique constraint to non-unique
- [ ] Update Pokemon model to add `pokemon_id` field
- [ ] Update seed data: `pokemon.json` entries get both `pokemon_id` (PokeAPI ID) and `national_dex` (real dex number, from species endpoint)
- [ ] Update `fetch_pokeapi.py`: for forms, look up the species to get the real national dex, store PokeAPI ID separately
- [ ] Update `loader.py`: upsert on `pokemon_id` instead of `national_dex`, update encounter/evolution linking
- [ ] Update API schemas and endpoints to expose both fields
- [ ] Update frontend to display real `national_dex` but use `pokemon_id` internally for uniqueness
- [ ] Update encounter seed data to reference `pokemon_id` instead of `national_dex`

## Impact

Touches almost every layer: migration, model, seeder, API, frontend. Should be done before more forms are added (bean 6aje) to avoid migrating bad data.
