Skip to content

Commit 2b66da2

Browse files
committed
Refactor Pokemon evolution fields to improve clarity and consistency
- Renamed `region_restriction` to `region` and `base_form_required` to `base_form` in the PokemonEvolution model and related files. - Updated corresponding CSV and serializer fields to reflect the new naming conventions. - Adjusted foreign key constraints in the metadata YAML files to align with the updated field names.
1 parent 9014375 commit 2b66da2

12 files changed

Lines changed: 934 additions & 21 deletions
Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
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.

data/v2/build.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2073,8 +2073,8 @@ def csv_record_to_objects(info):
20732073
trade_species_id=int(info[17]) if info[17] != "" else None,
20742074
needs_overworld_rain=bool(int(info[18])),
20752075
turn_upside_down=bool(int(info[19])),
2076-
region_restriction_id=int(info[20]) if info[20] != "" else None,
2077-
base_form_required_id=int(info[21]) if info[21] != "" else None,
2076+
region_id=int(info[20]) if info[20] != "" else None,
2077+
base_form_id=int(info[21]) if info[21] != "" else None,
20782078
)
20792079

20802080
build_generic((PokemonEvolution,), "pokemon_evolution.csv", csv_record_to_objects)

data/v2/csv/pokemon_evolution.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
id,evolved_species_id,evolution_trigger_id,trigger_item_id,minimum_level,gender_id,location_id,held_item_id,time_of_day,known_move_id,known_move_type_id,minimum_happiness,minimum_beauty,minimum_affection,relative_physical_stats,party_species_id,party_type_id,trade_species_id,needs_overworld_rain,turn_upside_down,region_restriction,base_form_required
1+
id,evolved_species_id,evolution_trigger_id,trigger_item_id,minimum_level,gender_id,location_id,held_item_id,time_of_day,known_move_id,known_move_type_id,minimum_happiness,minimum_beauty,minimum_affection,relative_physical_stats,party_species_id,party_type_id,trade_species_id,needs_overworld_rain,turn_upside_down,region_id,base_form_id
22
1,2,1,,16,,,,,,,,,,,,,,0,0,,
33
2,3,1,,32,,,,,,,,,,,,,,0,0,,
44
3,5,1,,16,,,,,,,,,,,,,,0,0,,

0 commit comments

Comments
 (0)