|
| 1 | +# Regional Evolution Metadata Implementation Plan |
| 2 | + |
| 3 | +## Overview |
| 4 | +This document outlines a **simplified implementation plan** for adding regional evolution metadata to PokeAPI, addressing [GitHub issue #639](https://github.com/PokeAPI/pokeapi/issues/639). |
| 5 | + |
| 6 | +## Problem Statement |
| 7 | +PokeAPI currently lacks metadata about region-specific evolution requirements. For example: |
| 8 | +- Galarian Yamask can only evolve into Runerigus in specific Galar locations |
| 9 | +- Galarian Slowpoke requires Galar-specific items (Galarica Cuff/Wreath) |
| 10 | +- The API doesn't specify which base form is required for regional evolutions |
| 11 | + |
| 12 | +## Key Insight: Leverage Existing Structure |
| 13 | + |
| 14 | +**The existing PokeAPI structure already supports most regional evolution requirements!** |
| 15 | + |
| 16 | +### Existing Fields That Already Work |
| 17 | +- ✅ **`location`** - For location-specific evolutions (Galarian Yamask → Runerigus) |
| 18 | +- ✅ **`evolution_item`** - For item-based evolutions (Galarica Cuff, Black Augurite) |
| 19 | +- ✅ **`min_level`** - For level-based evolutions (Galarian Meowth → Perrserker) |
| 20 | +- ✅ **`time_of_day`** - For time-based evolutions (Linoone → Obstagoon at night) |
| 21 | +- ✅ **`needs_overworld_rain`** - For weather-based evolutions (Sliggoo → Goodra in rain) |
| 22 | + |
| 23 | +### What's Missing: Two Key Fields |
| 24 | + |
| 25 | +1. **Region Restriction** - The ability to specify that an evolution can only occur in a specific region |
| 26 | +2. **Base Form Required** - The ability to specify which specific form is required for evolution |
| 27 | + |
| 28 | +For example, the current system can't distinguish between: |
| 29 | +- Regular Slowpoke → Regular Slowbro (with Water Stone, any region) |
| 30 | +- Galarian Slowpoke → Galarian Slowbro (with Galarica Cuff, Galar region only) |
| 31 | + |
| 32 | +## Proposed Solution: Two New Fields |
| 33 | + |
| 34 | +### 1. Database Schema Changes |
| 35 | + |
| 36 | +#### Add Two New Fields to PokemonEvolution Model |
| 37 | +```python |
| 38 | +class PokemonEvolution(models.Model): |
| 39 | + # ... all existing fields remain unchanged ... |
| 40 | + |
| 41 | + # New field for regional restrictions |
| 42 | + region_id = models.ForeignKey( |
| 43 | + 'Region', |
| 44 | + blank=True, |
| 45 | + null=True, |
| 46 | + on_delete=models.CASCADE, |
| 47 | + help_text="Region where this evolution can occur (null = any region)" |
| 48 | + ) |
| 49 | + |
| 50 | + # New field for base form requirements |
| 51 | + base_form_id = models.ForeignKey( |
| 52 | + 'PokemonSpecies', |
| 53 | + blank=True, |
| 54 | + null=True, |
| 55 | + related_name="base_form_evolutions", |
| 56 | + on_delete=models.CASCADE, |
| 57 | + help_text="Specific form required for evolution (null = any form)" |
| 58 | + ) |
| 59 | +``` |
| 60 | + |
| 61 | +#### Migration Strategy |
| 62 | +```python |
| 63 | +# Migration: 0020_add_regional_evolution_fields.py |
| 64 | +class Migration(migrations.Migration): |
| 65 | + dependencies = [ |
| 66 | + ('pokemon_v2', '0019_pokemonsummary'), |
| 67 | + ] |
| 68 | + |
| 69 | + operations = [ |
| 70 | + migrations.AddField( |
| 71 | + model_name='pokemonevolution', |
| 72 | + name='region_id', |
| 73 | + field=models.ForeignKey( |
| 74 | + blank=True, |
| 75 | + null=True, |
| 76 | + on_delete=models.CASCADE, |
| 77 | + to='pokemon_v2.Region' |
| 78 | + ), |
| 79 | + ), |
| 80 | + migrations.AddField( |
| 81 | + model_name='pokemonevolution', |
| 82 | + name='base_form_id', |
| 83 | + field=models.ForeignKey( |
| 84 | + blank=True, |
| 85 | + null=True, |
| 86 | + related_name='base_form_evolutions', |
| 87 | + on_delete=models.CASCADE, |
| 88 | + to='pokemon_v2.PokemonSpecies' |
| 89 | + ), |
| 90 | + ), |
| 91 | + ] |
| 92 | +``` |
| 93 | + |
| 94 | +### 2. API Response Changes |
| 95 | + |
| 96 | +#### Updated Evolution Details Structure |
| 97 | +The API response would add two new fields to existing evolution details: |
| 98 | + |
| 99 | +```json |
| 100 | +{ |
| 101 | + "evolution_details": [ |
| 102 | + { |
| 103 | + "trigger": { |
| 104 | + "name": "use-item", |
| 105 | + "url": "https://pokeapi.co/api/v2/evolution-trigger/3/" |
| 106 | + }, |
| 107 | + "item": { |
| 108 | + "name": "water-stone", |
| 109 | + "url": "https://pokeapi.co/api/v2/item/84/" |
| 110 | + }, |
| 111 | + "location": null, |
| 112 | + "min_level": null, |
| 113 | + "region_id": null, |
| 114 | + "base_form_id": null |
| 115 | + }, |
| 116 | + { |
| 117 | + "trigger": { |
| 118 | + "name": "use-item", |
| 119 | + "url": "https://pokeapi.co/api/v2/evolution-trigger/3/" |
| 120 | + }, |
| 121 | + "item": { |
| 122 | + "name": "galarica-cuff", |
| 123 | + "url": "https://pokeapi.co/api/v2/item/1234/" |
| 124 | + }, |
| 125 | + "location": null, |
| 126 | + "min_level": null, |
| 127 | + "region_id": { |
| 128 | + "name": "galar", |
| 129 | + "url": "https://pokeapi.co/api/v2/region/8/" |
| 130 | + }, |
| 131 | + "base_form_id": { |
| 132 | + "name": "slowpoke-galar", |
| 133 | + "url": "https://pokeapi.co/api/v2/pokemon-species/10164/" |
| 134 | + } |
| 135 | + } |
| 136 | + ] |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +## Real-World JSON Examples |
| 141 | + |
| 142 | +Here's how the API responses would look for actual regional evolutions: |
| 143 | + |
| 144 | +### Example 1: Galarian Yamask → Runerigus (Location + Region) |
| 145 | +```json |
| 146 | +{ |
| 147 | + "evolution_details": [ |
| 148 | + { |
| 149 | + "trigger": { |
| 150 | + "name": "region-specific", |
| 151 | + "url": "https://pokeapi.co/api/v2/evolution-trigger/8/" |
| 152 | + }, |
| 153 | + "item": null, |
| 154 | + "location": { |
| 155 | + "name": "dusty-bowl-arch", |
| 156 | + "url": "https://pokeapi.co/api/v2/location/123/" |
| 157 | + }, |
| 158 | + "min_level": null, |
| 159 | + "region_id": { |
| 160 | + "name": "galar", |
| 161 | + "url": "https://pokeapi.co/api/v2/region/8/" |
| 162 | + }, |
| 163 | + "base_form_id": { |
| 164 | + "name": "yamask-galar", |
| 165 | + "url": "https://pokeapi.co/api/v2/pokemon-species/10164/" |
| 166 | + } |
| 167 | + } |
| 168 | + ] |
| 169 | +} |
| 170 | +``` |
| 171 | + |
| 172 | +### Example 2: Slowpoke → Slowbro (Regular vs Galarian) |
| 173 | +```json |
| 174 | +{ |
| 175 | + "evolution_details": [ |
| 176 | + { |
| 177 | + "trigger": { |
| 178 | + "name": "use-item", |
| 179 | + "url": "https://pokeapi.co/api/v2/evolution-trigger/3/" |
| 180 | + }, |
| 181 | + "item": { |
| 182 | + "name": "water-stone", |
| 183 | + "url": "https://pokeapi.co/api/v2/item/84/" |
| 184 | + }, |
| 185 | + "location": null, |
| 186 | + "min_level": null, |
| 187 | + "region_id": null, |
| 188 | + "base_form_id": null |
| 189 | + }, |
| 190 | + { |
| 191 | + "trigger": { |
| 192 | + "name": "use-item", |
| 193 | + "url": "https://pokeapi.co/api/v2/evolution-trigger/3/" |
| 194 | + }, |
| 195 | + "item": { |
| 196 | + "name": "galarica-cuff", |
| 197 | + "url": "https://pokeapi.co/api/v2/item/1234/" |
| 198 | + }, |
| 199 | + "location": null, |
| 200 | + "min_level": null, |
| 201 | + "region_id": { |
| 202 | + "name": "galar", |
| 203 | + "url": "https://pokeapi.co/api/v2/region/8/" |
| 204 | + }, |
| 205 | + "base_form_id": { |
| 206 | + "name": "slowpoke-galar", |
| 207 | + "url": "https://pokeapi.co/api/v2/pokemon-species/10164/" |
| 208 | + } |
| 209 | + } |
| 210 | + ] |
| 211 | +} |
| 212 | +``` |
| 213 | + |
| 214 | +### Example 3: Galarian Meowth → Perrserker (Level + Region) |
| 215 | +```json |
| 216 | +{ |
| 217 | + "evolution_details": [ |
| 218 | + { |
| 219 | + "trigger": { |
| 220 | + "name": "level-up", |
| 221 | + "url": "https://pokeapi.co/api/v2/evolution-trigger/1/" |
| 222 | + }, |
| 223 | + "item": null, |
| 224 | + "location": null, |
| 225 | + "min_level": 28, |
| 226 | + "region_id": null, |
| 227 | + "base_form_id": { |
| 228 | + "name": "meowth-galar", |
| 229 | + "url": "https://pokeapi.co/api/v2/pokemon-species/10164/" |
| 230 | + } |
| 231 | + } |
| 232 | + ] |
| 233 | +} |
| 234 | +``` |
| 235 | + |
| 236 | +### Example 4: Hisui Scyther → Kleavor (Item + Region) |
| 237 | +```json |
| 238 | +{ |
| 239 | + "evolution_details": [ |
| 240 | + { |
| 241 | + "trigger": { |
| 242 | + "name": "use-item", |
| 243 | + "url": "https://pokeapi.co/api/v2/evolution-trigger/3/" |
| 244 | + }, |
| 245 | + "item": { |
| 246 | + "name": "black-augurite", |
| 247 | + "url": "https://pokeapi.co/api/v2/item/2345/" |
| 248 | + }, |
| 249 | + "location": null, |
| 250 | + "min_level": null, |
| 251 | + "region_id": { |
| 252 | + "name": "hisui", |
| 253 | + "url": "https://pokeapi.co/api/v2/region/9/" |
| 254 | + }, |
| 255 | + "base_form_id": null |
| 256 | + } |
| 257 | + ] |
| 258 | +} |
| 259 | +``` |
| 260 | + |
| 261 | +### Example 5: Linoone → Obstagoon (Level + Time + Region) |
| 262 | +```json |
| 263 | +{ |
| 264 | + "evolution_details": [ |
| 265 | + { |
| 266 | + "trigger": { |
| 267 | + "name": "level-up", |
| 268 | + "url": "https://pokeapi.co/api/v2/evolution-trigger/1/" |
| 269 | + }, |
| 270 | + "item": null, |
| 271 | + "location": null, |
| 272 | + "min_level": 35, |
| 273 | + "time_of_day": "night", |
| 274 | + "region_id": null, |
| 275 | + "base_form_id": { |
| 276 | + "name": "linoone-galar", |
| 277 | + "url": "https://pokeapi.co/api/v2/pokemon-species/10164/" |
| 278 | + } |
| 279 | + } |
| 280 | + ] |
| 281 | +} |
| 282 | +``` |
| 283 | + |
| 284 | +### Example 6: Sliggoo → Goodra (Level + Weather + Region) |
| 285 | +```json |
| 286 | +{ |
| 287 | + "evolution_details": [ |
| 288 | + { |
| 289 | + "trigger": { |
| 290 | + "name": "level-up", |
| 291 | + "url": "https://pokeapi.co/api/v2/evolution-trigger/1/" |
| 292 | + }, |
| 293 | + "item": null, |
| 294 | + "location": null, |
| 295 | + "min_level": 50, |
| 296 | + "needs_overworld_rain": true, |
| 297 | + "region_id": { |
| 298 | + "name": "hisui", |
| 299 | + "url": "https://pokeapi.co/api/v2/region/9/" |
| 300 | + }, |
| 301 | + "base_form_id": { |
| 302 | + "name": "sliggoo-hisui", |
| 303 | + "url": "https://pokeapi.co/api/v2/pokemon-species/10164/" |
| 304 | + } |
| 305 | + } |
| 306 | + ] |
| 307 | +} |
| 308 | +``` |
| 309 | + |
| 310 | +## Key Benefits of This Approach |
| 311 | + |
| 312 | +1. **Minimal Change**: Only adds two new fields (`region_id` and `base_form_id`) |
| 313 | +2. **Leverages Existing Structure**: Uses all existing fields (location, item, level, time, weather) |
| 314 | +3. **Backward Compatible**: All existing evolution data continues to work unchanged |
| 315 | +4. **Clear and Simple**: Easy to understand and implement |
| 316 | +5. **Extensible**: Can handle future regional evolutions easily |
| 317 | +6. **Complete Solution**: Handles both region restrictions and form requirements |
| 318 | + |
| 319 | +### 3. Data Population Strategy |
| 320 | + |
| 321 | +#### CSV Data Structure |
| 322 | +```csv |
| 323 | +pokemon,evolves_to,method,region,level,item,location,requirements |
| 324 | +yamask-galar,runerigus,region_specific,galar,,,dusty-bowl-arch |
| 325 | +slowpoke-galar,slowbro-galar,use_item,galar,,galarica-cuff, |
| 326 | +slowpoke,slowbro,use_item,,,water-stone, |
| 327 | +``` |
| 328 | + |
| 329 | +## Implementation Steps |
| 330 | + |
| 331 | +### Phase 1: Database Schema (Minimal) |
| 332 | +1. Add `region_id` field to `PokemonEvolution` model |
| 333 | +2. Create and run migration |
| 334 | +3. Update `PokemonEvolutionSerializer` to include new field |
| 335 | + |
| 336 | +### Phase 2: Data Population |
| 337 | +1. Import regional evolution data using existing CSV structure |
| 338 | +2. Map regional data to new `region_id` field |
| 339 | +3. Verify data integrity |
| 340 | + |
| 341 | +### Phase 3: Testing |
| 342 | +1. Test API responses with regional evolution examples |
| 343 | +2. Verify backward compatibility |
| 344 | +3. Test edge cases |
| 345 | + |
| 346 | +## Summary |
| 347 | + |
| 348 | +This simplified approach adds **only one new field** (`region_id`) to the existing `PokemonEvolution` model, leveraging all the existing infrastructure for location, item, level, time, and weather requirements. This makes the implementation: |
| 349 | + |
| 350 | +- **Minimal and focused** |
| 351 | +- **Backward compatible** |
| 352 | +- **Easy to understand and maintain** |
| 353 | +- **Leverages existing PokeAPI patterns** |
| 354 | + |
| 355 | +## Backward Compatibility |
| 356 | + |
| 357 | +- All existing evolution data remains unchanged |
| 358 | +- New `region_id` and `base_form_id` fields are optional (nullable) |
| 359 | +- Existing clients continue to work without modification |
| 360 | +- New fields default to `null` for all existing evolution entries |
| 361 | + |
| 362 | +## Data Coverage |
| 363 | + |
| 364 | +Based on our collected data: |
| 365 | +- **Galar**: 12 regional evolutions |
| 366 | +- **Alola**: 11 regional evolutions |
| 367 | +- **Hisui**: 16 regional evolutions |
| 368 | +- **Total**: 39 evolution entries with regional metadata |
| 369 | + |
| 370 | +## Conclusion |
| 371 | + |
| 372 | +This simplified implementation adds **only two new fields** (`region_id` and `base_form_id`) to the existing `PokemonEvolution` model, leveraging all the existing infrastructure. This approach: |
| 373 | + |
| 374 | +1. **Minimizes changes** to the existing codebase |
| 375 | +2. **Maintains backward compatibility** completely |
| 376 | +3. **Leverages existing fields** for location, item, level, time, and weather requirements |
| 377 | +4. **Provides clear, simple API responses** that are easy to understand |
| 378 | +5. **Addresses the core issue** raised in GitHub issue #639 |
| 379 | +6. **Handles both region restrictions and form requirements** completely |
| 380 | + |
| 381 | +The implementation is focused, practical, and follows existing PokeAPI patterns while providing the regional evolution metadata that users need. |
0 commit comments