From 6f10308cf6f7061c478e69219319e7cc8bd48857 Mon Sep 17 00:00:00 2001 From: Anh Thang Bui Date: Sat, 7 Mar 2026 14:08:31 +0700 Subject: [PATCH 01/12] feat(data): add ItemPrice model and serializer for item pricing data --- data/v2/build.py | 10 ++++++++++ data/v2/csv/item_prices.csv | 29 +++++++++++++++++++++++++++++ pokemon_v2/models.py | 5 +++++ pokemon_v2/serializers.py | 10 ++++++++++ 4 files changed, 54 insertions(+) create mode 100644 data/v2/csv/item_prices.csv diff --git a/data/v2/build.py b/data/v2/build.py index c0e845477..cc520d4ba 100644 --- a/data/v2/build.py +++ b/data/v2/build.py @@ -544,6 +544,16 @@ def csv_record_to_objects(info): build_generic((ItemGameIndex,), "item_game_indices.csv", csv_record_to_objects) + def csv_record_to_objects(info): + yield ItemPrice( + item_id=int(info[0]), + version_group_id=int(info[1]), + purchase_price=int(info[2]), + sell_price=int(info[3]), + ) + + build_generic((ItemPrice,), "item_prices.csv", csv_record_to_objects) + def csv_record_to_objects(info): yield ItemFlavorText( item_id=int(info[0]), diff --git a/data/v2/csv/item_prices.csv b/data/v2/csv/item_prices.csv new file mode 100644 index 000000000..eb20ab0a4 --- /dev/null +++ b/data/v2/csv/item_prices.csv @@ -0,0 +1,29 @@ +item_id,version_group_id,purchase_price,sell_price +305,1,3000,1500 +305,2,3000,1500 +305,3,3000,1500 +305,4,3000,1500 +305,5,3000,1500 +305,6,3000,1500 +305,7,3000,1500 +305,8,10000,5000 +305,9,10000,5000 +305,10,10000,5000 +305,11,10000,5000 +305,12,3000,1500 +305,13,3000,1500 +305,14,10000,5000 +305,15,10000,5000 +305,16,10000,5000 +305,17,10000,5000 +305,18,10000,5000 +305,19,0,0 +305,20,3000,1500 +305,21,3000,1500 +305,22,3000,1500 +305,23,10000,5000 +305,25,50000,25000 +305,26,50000,25000 +305,27,50000,25000 +305,28,3000,1500 +305,29,3000,1500 diff --git a/pokemon_v2/models.py b/pokemon_v2/models.py index 5fd7c2ae5..5508590c6 100644 --- a/pokemon_v2/models.py +++ b/pokemon_v2/models.py @@ -885,6 +885,11 @@ class ItemGameIndex(HasItem, HasGeneration, HasGameIndex): pass +class ItemPrice(HasItem, HasVersionGroup): + purchase_price = models.IntegerField() + sell_price = models.IntegerField() + + class ItemSprites(HasItem): sprites = models.JSONField() diff --git a/pokemon_v2/serializers.py b/pokemon_v2/serializers.py index 636cdb20f..fe5f098cd 100644 --- a/pokemon_v2/serializers.py +++ b/pokemon_v2/serializers.py @@ -1806,6 +1806,14 @@ class Meta: fields = ("game_index", "generation") +class ItemPriceSerializer(serializers.ModelSerializer): + version_group = VersionGroupSummarySerializer() + + class Meta: + model = ItemPrice + fields = ("purchase_price", "sell_price", "version_group") + + class ItemNameSerializer(serializers.ModelSerializer): language = LanguageSummarySerializer() @@ -1825,6 +1833,7 @@ class ItemDetailSerializer(serializers.ModelSerializer): game_indices = ItemGameIndexSerializer( many=True, read_only=True, source="itemgameindex" ) + prices = ItemPriceSerializer(many=True, read_only=True, source="itemprice") effect_entries = ItemEffectTextSerializer( many=True, read_only=True, source="itemeffecttext" ) @@ -1852,6 +1861,7 @@ class Meta: "effect_entries", "flavor_text_entries", "game_indices", + "prices", "names", "held_by_pokemon", "sprites", From 8db52919251f7c143edc1836ef1d85e809fb261c Mon Sep 17 00:00:00 2001 From: Anh Thang Bui Date: Sat, 7 Mar 2026 21:21:21 +0700 Subject: [PATCH 02/12] add is_purchasable --- data/v2/build.py | 5 ++-- data/v2/csv/item_prices.csv | 59 +++++++++++++++++++------------------ pokemon_v2/models.py | 1 + pokemon_v2/serializers.py | 2 +- 4 files changed, 35 insertions(+), 32 deletions(-) diff --git a/data/v2/build.py b/data/v2/build.py index cc520d4ba..fff333671 100644 --- a/data/v2/build.py +++ b/data/v2/build.py @@ -548,8 +548,9 @@ def csv_record_to_objects(info): yield ItemPrice( item_id=int(info[0]), version_group_id=int(info[1]), - purchase_price=int(info[2]), - sell_price=int(info[3]), + is_purchasable=bool(int(info[2])), + purchase_price=int(info[3]), + sell_price=int(info[4]), ) build_generic((ItemPrice,), "item_prices.csv", csv_record_to_objects) diff --git a/data/v2/csv/item_prices.csv b/data/v2/csv/item_prices.csv index eb20ab0a4..a4b5176c3 100644 --- a/data/v2/csv/item_prices.csv +++ b/data/v2/csv/item_prices.csv @@ -1,29 +1,30 @@ -item_id,version_group_id,purchase_price,sell_price -305,1,3000,1500 -305,2,3000,1500 -305,3,3000,1500 -305,4,3000,1500 -305,5,3000,1500 -305,6,3000,1500 -305,7,3000,1500 -305,8,10000,5000 -305,9,10000,5000 -305,10,10000,5000 -305,11,10000,5000 -305,12,3000,1500 -305,13,3000,1500 -305,14,10000,5000 -305,15,10000,5000 -305,16,10000,5000 -305,17,10000,5000 -305,18,10000,5000 -305,19,0,0 -305,20,3000,1500 -305,21,3000,1500 -305,22,3000,1500 -305,23,10000,5000 -305,25,50000,25000 -305,26,50000,25000 -305,27,50000,25000 -305,28,3000,1500 -305,29,3000,1500 +item_id,version_group_id,is_purchasable,purchase_price,sell_price +305,1,1,3000,1500 +305,2,1,3000,1500 +305,16,1,10000,5000 +305,20,1,3000,1500 +305,21,1,3000,1500 +305,22,1,3000,1500 +305,25,1,50000,25000 +305,26,1,50000,25000 +305,27,1,50000,25000 +305,28,1,3000,1500 +305,29,1,3000,1500 +305,3,0,0,1500 +305,4,0,0,1500 +305,5,0,0,1500 +305,6,0,0,1500 +305,7,0,0,1500 +305,8,0,0,5000 +305,9,0,0,5000 +305,10,0,0,5000 +305,11,0,0,5000 +305,12,0,0,1500 +305,13,0,0,1500 +305,14,0,0,5000 +305,15,0,0,5000 +305,17,0,0,5000 +305,18,0,0,5000 +305,19,0,0,0 +305,23,0,0,5000 +305,24,0,0,5000 diff --git a/pokemon_v2/models.py b/pokemon_v2/models.py index 5508590c6..bbf365ebf 100644 --- a/pokemon_v2/models.py +++ b/pokemon_v2/models.py @@ -886,6 +886,7 @@ class ItemGameIndex(HasItem, HasGeneration, HasGameIndex): class ItemPrice(HasItem, HasVersionGroup): + is_purchasable = models.BooleanField() purchase_price = models.IntegerField() sell_price = models.IntegerField() diff --git a/pokemon_v2/serializers.py b/pokemon_v2/serializers.py index fe5f098cd..863ada2f5 100644 --- a/pokemon_v2/serializers.py +++ b/pokemon_v2/serializers.py @@ -1811,7 +1811,7 @@ class ItemPriceSerializer(serializers.ModelSerializer): class Meta: model = ItemPrice - fields = ("purchase_price", "sell_price", "version_group") + fields = ("is_purchasable", "purchase_price", "sell_price", "version_group") class ItemNameSerializer(serializers.ModelSerializer): From a47fbb12d4dac018cf057fb4b69f9dfbf71af987 Mon Sep 17 00:00:00 2001 From: Anh Thang Bui Date: Sat, 7 Mar 2026 22:54:09 +0700 Subject: [PATCH 03/12] feat(api): add ItemPrice resource and add item prices, remove item cost --- README.md | 2 +- .../tables/public_pokemon_v2_item.yaml | 7 +++ .../tables/public_pokemon_v2_itemprice.yaml | 17 ++++++ .../public_pokemon_v2_versiongroup.yaml | 7 +++ .../databases/default/tables/tables.yaml | 1 + .../tables/public_pokemon_v2_item.yaml | 7 +++ .../tables/public_pokemon_v2_itemprice.yaml | 22 ++++++++ .../public_pokemon_v2_versiongroup.yaml | 7 +++ .../databases/default/tables/tables.yaml | 1 + pokemon_v2/migrations/0026_itemprice.py | 54 +++++++++++++++++++ pokemon_v2/models.py | 2 - pokemon_v2/serializers.py | 1 - 12 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml create mode 100644 graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml create mode 100644 pokemon_v2/migrations/0026_itemprice.py diff --git a/README.md b/README.md index 41a113475..21dd6e7c5 100755 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ make docker-migrate When you start PokéAPI with the above Docker Compose setup, an [Hasura Engine](https://github.com/hasura/graphql-engine) server is started as well. It's possible to track all the PokeAPI tables and foreign keys by simply ```sh -# hasura cli needs to be installed and available in your $PATH: https://hasura.io/docs/latest/graphql/core/hasura-cli/install-hasura-cli.html +# hasura cli needs to be installed and available in your $PATH: https://hasura.io/docs/2.0/hasura-cli/install-hasura-cli # hasura cli's version has to greater than v2.48.1 make hasura-apply ``` diff --git a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_item.yaml b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_item.yaml index 7e9fbc70d..13c8feeae 100644 --- a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_item.yaml +++ b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_item.yaml @@ -65,6 +65,13 @@ array_relationships: table: name: pokemon_v2_itemname schema: public + - name: pokemon_v2_itemprices + using: + foreign_key_constraint_on: + column: item_id + table: + name: pokemon_v2_itemprice + schema: public - name: pokemon_v2_itemsprites using: foreign_key_constraint_on: diff --git a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml new file mode 100644 index 000000000..d9c38bdff --- /dev/null +++ b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml @@ -0,0 +1,17 @@ +table: + name: pokemon_v2_itemprice + schema: public +object_relationships: + - name: pokemon_v2_item + using: + foreign_key_constraint_on: item_id + - name: pokemon_v2_versiongroup + using: + foreign_key_constraint_on: version_group_id +select_permissions: + - role: anon + permission: + columns: '*' + filter: {} + limit: 100000 + allow_aggregations: true diff --git a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml index bead609fe..2e5f0990b 100644 --- a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml +++ b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml @@ -34,6 +34,13 @@ array_relationships: table: name: pokemon_v2_itemflavortext schema: public + - name: pokemon_v2_itemprices + using: + foreign_key_constraint_on: + column: version_group_id + table: + name: pokemon_v2_itemprice + schema: public - name: pokemon_v2_machines using: foreign_key_constraint_on: diff --git a/graphql/v1beta/metadata/databases/default/tables/tables.yaml b/graphql/v1beta/metadata/databases/default/tables/tables.yaml index 85ecc995e..31c3ca3a8 100644 --- a/graphql/v1beta/metadata/databases/default/tables/tables.yaml +++ b/graphql/v1beta/metadata/databases/default/tables/tables.yaml @@ -51,6 +51,7 @@ - "!include public_pokemon_v2_itemflingeffecteffecttext.yaml" - "!include public_pokemon_v2_itemgameindex.yaml" - "!include public_pokemon_v2_itemname.yaml" +- "!include public_pokemon_v2_itemprice.yaml" - "!include public_pokemon_v2_itempocket.yaml" - "!include public_pokemon_v2_itempocketname.yaml" - "!include public_pokemon_v2_itemsprites.yaml" diff --git a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_item.yaml b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_item.yaml index f8589db37..1e174fc7d 100644 --- a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_item.yaml +++ b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_item.yaml @@ -70,6 +70,13 @@ array_relationships: table: name: pokemon_v2_itemname schema: public + - name: itemprices + using: + foreign_key_constraint_on: + column: item_id + table: + name: pokemon_v2_itemprice + schema: public - name: itemsprites using: foreign_key_constraint_on: diff --git a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml new file mode 100644 index 000000000..3082e5ffd --- /dev/null +++ b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml @@ -0,0 +1,22 @@ +table: + name: pokemon_v2_itemprice + schema: public +configuration: + column_config: {} + custom_column_names: {} + custom_name: itemprice + custom_root_fields: {} +object_relationships: + - name: item + using: + foreign_key_constraint_on: item_id + - name: versiongroup + using: + foreign_key_constraint_on: version_group_id +select_permissions: + - role: anon + permission: + columns: '*' + filter: {} + limit: 100000 + allow_aggregations: true diff --git a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml index 6bede0498..1c454f142 100644 --- a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml +++ b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_versiongroup.yaml @@ -39,6 +39,13 @@ array_relationships: table: name: pokemon_v2_itemflavortext schema: public + - name: itemprices + using: + foreign_key_constraint_on: + column: version_group_id + table: + name: pokemon_v2_itemprice + schema: public - name: machines using: foreign_key_constraint_on: diff --git a/graphql/v1beta2/metadata/databases/default/tables/tables.yaml b/graphql/v1beta2/metadata/databases/default/tables/tables.yaml index 78b95c7c8..0021c9c22 100644 --- a/graphql/v1beta2/metadata/databases/default/tables/tables.yaml +++ b/graphql/v1beta2/metadata/databases/default/tables/tables.yaml @@ -51,6 +51,7 @@ - "!include public_pokemon_v2_itemflingeffecteffecttext.yaml" - "!include public_pokemon_v2_itemgameindex.yaml" - "!include public_pokemon_v2_itemname.yaml" +- "!include public_pokemon_v2_itemprice.yaml" - "!include public_pokemon_v2_itempocket.yaml" - "!include public_pokemon_v2_itempocketname.yaml" - "!include public_pokemon_v2_itemsprites.yaml" diff --git a/pokemon_v2/migrations/0026_itemprice.py b/pokemon_v2/migrations/0026_itemprice.py new file mode 100644 index 000000000..516cab66e --- /dev/null +++ b/pokemon_v2/migrations/0026_itemprice.py @@ -0,0 +1,54 @@ +# Generated by Django 5.2.10 on 2026-03-07 00:00 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("pokemon_v2", "0025_pokemonstatpast"), + ] + + operations = [ + migrations.CreateModel( + name="ItemPrice", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("is_purchasable", models.BooleanField()), + ("purchase_price", models.IntegerField()), + ("sell_price", models.IntegerField()), + ( + "item", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s", + to="pokemon_v2.item", + ), + ), + ( + "version_group", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s", + to="pokemon_v2.versiongroup", + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/pokemon_v2/models.py b/pokemon_v2/models.py index bbf365ebf..4fdc56e8b 100644 --- a/pokemon_v2/models.py +++ b/pokemon_v2/models.py @@ -848,8 +848,6 @@ class ItemFlingEffectEffectText(HasLanguage, HasEffect, HasFlingEffect): class Item(HasName, HasItemCategory, HasFlingEffect): - cost = models.IntegerField(blank=True, null=True) - fling_power = models.IntegerField(blank=True, null=True) diff --git a/pokemon_v2/serializers.py b/pokemon_v2/serializers.py index 863ada2f5..3ed7f80f3 100644 --- a/pokemon_v2/serializers.py +++ b/pokemon_v2/serializers.py @@ -1853,7 +1853,6 @@ class Meta: fields = ( "id", "name", - "cost", "fling_power", "fling_effect", "attributes", From e127186835e66b6553022502af9371e6cf5f4c3f Mon Sep 17 00:00:00 2001 From: Anh Thang Bui Date: Mon, 9 Mar 2026 13:56:40 +0700 Subject: [PATCH 04/12] remove cost from build, test and migration --- data/v2/build.py | 1 - graphql/v1beta/examples/go/pokemon.go | 1 - graphql/v1beta/examples/node/pokemon.js | 1 - graphql/v1beta2/examples/go/pokemon.go | 1 - graphql/v1beta2/examples/node/pokemon.js | 1 - openapi.yml | 4 ---- .../migrations/0001_squashed_0002_auto_20160301_1408.py | 1 - pokemon_v2/tests.py | 3 --- 8 files changed, 13 deletions(-) diff --git a/data/v2/build.py b/data/v2/build.py index fff333671..19c50edfa 100644 --- a/data/v2/build.py +++ b/data/v2/build.py @@ -499,7 +499,6 @@ def csv_record_to_objects(info): id=int(info[0]), name=info[1], item_category_id=int(info[2]), - cost=int(info[3]), fling_power=int(info[4]) if info[4] != "" else None, item_fling_effect_id=int(info[5]) if info[5] != "" else None, ) diff --git a/graphql/v1beta/examples/go/pokemon.go b/graphql/v1beta/examples/go/pokemon.go index 7a1abf1f6..bd877ea90 100644 --- a/graphql/v1beta/examples/go/pokemon.go +++ b/graphql/v1beta/examples/go/pokemon.go @@ -75,7 +75,6 @@ query pokemon_details($name: String) { fireRedItems: pokemon_v2_pokemonitems(where: {pokemon_v2_version: {name: {_eq: "firered"}}}) { pokemon_v2_item { name - cost } rarity } diff --git a/graphql/v1beta/examples/node/pokemon.js b/graphql/v1beta/examples/node/pokemon.js index b11a8f598..a3fc772a5 100644 --- a/graphql/v1beta/examples/node/pokemon.js +++ b/graphql/v1beta/examples/node/pokemon.js @@ -93,7 +93,6 @@ function fetchPokemon_details(name="starmie") { fireRedItems: pokemon_v2_pokemonitems(where: {pokemon_v2_version: {name: {_eq: "firered"}}}) { pokemon_v2_item { name - cost } rarity } diff --git a/graphql/v1beta2/examples/go/pokemon.go b/graphql/v1beta2/examples/go/pokemon.go index c3be49641..ef19a9ddc 100644 --- a/graphql/v1beta2/examples/go/pokemon.go +++ b/graphql/v1beta2/examples/go/pokemon.go @@ -75,7 +75,6 @@ query pokemon_details($name: String) { fireRedItems: pokemonitems(where: {version: {name: {_eq: "firered"}}}) { item { name - cost } rarity } diff --git a/graphql/v1beta2/examples/node/pokemon.js b/graphql/v1beta2/examples/node/pokemon.js index 7597f02f9..64d6f3844 100644 --- a/graphql/v1beta2/examples/node/pokemon.js +++ b/graphql/v1beta2/examples/node/pokemon.js @@ -93,7 +93,6 @@ function fetchPokemon_details(name="starmie") { fireRedItems: pokemonitems(where: {version: {name: {_eq: "firered"}}}) { item { name - cost } rarity } diff --git a/openapi.yml b/openapi.yml index 1b600bd4e..54a2918c2 100644 --- a/openapi.yml +++ b/openapi.yml @@ -4659,10 +4659,6 @@ components: name: type: string maxLength: 200 - cost: - type: - - integer - - 'null' fling_power: type: - integer diff --git a/pokemon_v2/migrations/0001_squashed_0002_auto_20160301_1408.py b/pokemon_v2/migrations/0001_squashed_0002_auto_20160301_1408.py index 15c60c610..698ae037b 100644 --- a/pokemon_v2/migrations/0001_squashed_0002_auto_20160301_1408.py +++ b/pokemon_v2/migrations/0001_squashed_0002_auto_20160301_1408.py @@ -3497,7 +3497,6 @@ class Migration(migrations.Migration): ), ), ("name", models.CharField(max_length=100)), - ("cost", models.IntegerField(null=True, blank=True)), ("fling_power", models.IntegerField(null=True, blank=True)), ], options={ diff --git a/pokemon_v2/tests.py b/pokemon_v2/tests.py index 6bed79fcd..887056323 100644 --- a/pokemon_v2/tests.py +++ b/pokemon_v2/tests.py @@ -331,13 +331,11 @@ def setup_item_data( item_category=None, item_fling_effect=None, name="itm", - cost=100, fling_power=100, ): item = Item.objects.create( name=name, item_category=item_category, - cost=cost, fling_power=fling_power, item_fling_effect=item_fling_effect, ) @@ -2714,7 +2712,6 @@ def test_item_api(self): # base params self.assertEqual(response.data["id"], item.pk) self.assertEqual(response.data["name"], item.name) - self.assertEqual(response.data["cost"], item.cost) self.assertEqual(response.data["fling_power"], item.fling_power) # name params self.assertEqual(response.data["names"][0]["name"], item_name.name) From ca81ac48b4bc602d8a698d52b77601945863563c Mon Sep 17 00:00:00 2001 From: Anh Thang Bui Date: Mon, 9 Mar 2026 21:14:40 +0700 Subject: [PATCH 05/12] correct tm01, tm02 --- data/v2/csv/item_prices.csv | 48 ++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/data/v2/csv/item_prices.csv b/data/v2/csv/item_prices.csv index a4b5176c3..92c148005 100644 --- a/data/v2/csv/item_prices.csv +++ b/data/v2/csv/item_prices.csv @@ -1,30 +1,28 @@ item_id,version_group_id,is_purchasable,purchase_price,sell_price 305,1,1,3000,1500 305,2,1,3000,1500 -305,16,1,10000,5000 -305,20,1,3000,1500 -305,21,1,3000,1500 -305,22,1,3000,1500 -305,25,1,50000,25000 -305,26,1,50000,25000 -305,27,1,50000,25000 +305,3,0,,1500 +305,4,0,,1500 +305,5,0,,1500 +305,6,0,,1500 +305,7,0,,1500 +305,16,1,5000, +305,20,1,40000, +305,23,1,,1500 +305,25,1,,400 305,28,1,3000,1500 305,29,1,3000,1500 -305,3,0,0,1500 -305,4,0,0,1500 -305,5,0,0,1500 -305,6,0,0,1500 -305,7,0,0,1500 -305,8,0,0,5000 -305,9,0,0,5000 -305,10,0,0,5000 -305,11,0,0,5000 -305,12,0,0,1500 -305,13,0,0,1500 -305,14,0,0,5000 -305,15,0,0,5000 -305,17,0,0,5000 -305,18,0,0,5000 -305,19,0,0,0 -305,23,0,0,5000 -305,24,0,0,5000 +306,1,1,2000,1000 +306,2,1,2000,1000 +306,3,1,2000,1000 +306,4,1,2000,1000 +306,5,0,,1500 +306,6,0,,1500 +306,7,0,,1500 +306,8,0,,1500 +306,9,0,,1500 +306,10,0,,1500 +306,12,0,,1500 +306,13,0,,1500 +306,23,1,,1500 +306,25,1,,400 From bd8de1ff3c3b4da5a24b224b88da543cba59258e Mon Sep 17 00:00:00 2001 From: Anh Thang Bui Date: Mon, 9 Mar 2026 22:38:44 +0700 Subject: [PATCH 06/12] fix: purchase_price and sell_price should be nullable --- data/v2/build.py | 4 ++-- pokemon_v2/migrations/0026_itemprice.py | 4 ++-- pokemon_v2/models.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/v2/build.py b/data/v2/build.py index 19c50edfa..f7a7e864e 100644 --- a/data/v2/build.py +++ b/data/v2/build.py @@ -548,8 +548,8 @@ def csv_record_to_objects(info): item_id=int(info[0]), version_group_id=int(info[1]), is_purchasable=bool(int(info[2])), - purchase_price=int(info[3]), - sell_price=int(info[4]), + purchase_price=int(info[3]) if info[3] else None, + sell_price=int(info[4]) if info[4] else None, ) build_generic((ItemPrice,), "item_prices.csv", csv_record_to_objects) diff --git a/pokemon_v2/migrations/0026_itemprice.py b/pokemon_v2/migrations/0026_itemprice.py index 516cab66e..dcb6ec070 100644 --- a/pokemon_v2/migrations/0026_itemprice.py +++ b/pokemon_v2/migrations/0026_itemprice.py @@ -24,8 +24,8 @@ class Migration(migrations.Migration): ), ), ("is_purchasable", models.BooleanField()), - ("purchase_price", models.IntegerField()), - ("sell_price", models.IntegerField()), + ("purchase_price", models.IntegerField(blank=True, null=True)), + ("sell_price", models.IntegerField(blank=True, null=True)), ( "item", models.ForeignKey( diff --git a/pokemon_v2/models.py b/pokemon_v2/models.py index 4fdc56e8b..59b50d202 100644 --- a/pokemon_v2/models.py +++ b/pokemon_v2/models.py @@ -885,8 +885,8 @@ class ItemGameIndex(HasItem, HasGeneration, HasGameIndex): class ItemPrice(HasItem, HasVersionGroup): is_purchasable = models.BooleanField() - purchase_price = models.IntegerField() - sell_price = models.IntegerField() + purchase_price = models.IntegerField(blank=True, null=True) + sell_price = models.IntegerField(blank=True, null=True) class ItemSprites(HasItem): From 3b8693837c80b77db0330cc336cd5a6cb820e2b2 Mon Sep 17 00:00:00 2001 From: Anh Thang Bui Date: Tue, 10 Mar 2026 22:43:34 +0700 Subject: [PATCH 07/12] update openapi specs, fix Location.region and Region.main_generation is nullable --- openapi.yml | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/openapi.yml b/openapi.yml index 54a2918c2..d5e02d28d 100644 --- a/openapi.yml +++ b/openapi.yml @@ -4700,6 +4700,11 @@ components: items: $ref: '#/components/schemas/ItemGameIndex' readOnly: true + prices: + type: array + items: + $ref: '#/components/schemas/ItemPrice' + readOnly: true names: type: array items: @@ -4919,6 +4924,24 @@ components: required: - language - name + ItemPrice: + type: object + properties: + is_purchasable: + type: boolean + purchase_price: + type: + - integer + - 'null' + sell_price: + type: + - integer + - 'null' + version_group: + $ref: '#/components/schemas/VersionGroupSummary' + required: + - is_purchasable + - version_group ItemPocketDetail: type: object properties: @@ -7526,7 +7549,6 @@ components: - name - names - pokemon_entries - - region - version_groups PokedexName: type: object @@ -8634,7 +8656,6 @@ components: required: - id - locations - - main_generation - name - names - pokedexes From af5b7d32659c607b2e16419100be49dbfb92a181 Mon Sep 17 00:00:00 2001 From: Anh Thang Bui Date: Wed, 11 Mar 2026 10:02:05 +0700 Subject: [PATCH 08/12] remove is_purchasable --- data/v2/build.py | 5 +- data/v2/csv/item_prices.csv | 56 +++++++++---------- openapi.yml | 3 - .../0001_squashed_0002_auto_20160301_1408.py | 1 + pokemon_v2/migrations/0026_itemprice.py | 1 - pokemon_v2/models.py | 1 - pokemon_v2/serializers.py | 2 +- 7 files changed, 32 insertions(+), 37 deletions(-) diff --git a/data/v2/build.py b/data/v2/build.py index f7a7e864e..5306845bd 100644 --- a/data/v2/build.py +++ b/data/v2/build.py @@ -547,9 +547,8 @@ def csv_record_to_objects(info): yield ItemPrice( item_id=int(info[0]), version_group_id=int(info[1]), - is_purchasable=bool(int(info[2])), - purchase_price=int(info[3]) if info[3] else None, - sell_price=int(info[4]) if info[4] else None, + purchase_price=int(info[2]) if info[2] else None, + sell_price=int(info[3]) if info[3] else None, ) build_generic((ItemPrice,), "item_prices.csv", csv_record_to_objects) diff --git a/data/v2/csv/item_prices.csv b/data/v2/csv/item_prices.csv index 92c148005..08689dbb8 100644 --- a/data/v2/csv/item_prices.csv +++ b/data/v2/csv/item_prices.csv @@ -1,28 +1,28 @@ -item_id,version_group_id,is_purchasable,purchase_price,sell_price -305,1,1,3000,1500 -305,2,1,3000,1500 -305,3,0,,1500 -305,4,0,,1500 -305,5,0,,1500 -305,6,0,,1500 -305,7,0,,1500 -305,16,1,5000, -305,20,1,40000, -305,23,1,,1500 -305,25,1,,400 -305,28,1,3000,1500 -305,29,1,3000,1500 -306,1,1,2000,1000 -306,2,1,2000,1000 -306,3,1,2000,1000 -306,4,1,2000,1000 -306,5,0,,1500 -306,6,0,,1500 -306,7,0,,1500 -306,8,0,,1500 -306,9,0,,1500 -306,10,0,,1500 -306,12,0,,1500 -306,13,0,,1500 -306,23,1,,1500 -306,25,1,,400 +item_id,version_group_id,purchase_price,sell_price +305,1,3000,1500 +305,2,3000,1500 +305,3,,1500 +305,4,,1500 +305,5,,1500 +305,6,,1500 +305,7,,1500 +305,16,5000, +305,20,40000, +305,23,,1500 +305,25,,400 +305,28,3000,1500 +305,29,3000,1500 +306,1,2000,1000 +306,2,2000,1000 +306,3,2000,1000 +306,4,2000,1000 +306,5,,1500 +306,6,,1500 +306,7,,1500 +306,8,,1500 +306,9,,1500 +306,10,,1500 +306,12,,1500 +306,13,,1500 +306,23,,1500 +306,25,,400 diff --git a/openapi.yml b/openapi.yml index d5e02d28d..4175e13a0 100644 --- a/openapi.yml +++ b/openapi.yml @@ -4927,8 +4927,6 @@ components: ItemPrice: type: object properties: - is_purchasable: - type: boolean purchase_price: type: - integer @@ -4940,7 +4938,6 @@ components: version_group: $ref: '#/components/schemas/VersionGroupSummary' required: - - is_purchasable - version_group ItemPocketDetail: type: object diff --git a/pokemon_v2/migrations/0001_squashed_0002_auto_20160301_1408.py b/pokemon_v2/migrations/0001_squashed_0002_auto_20160301_1408.py index 698ae037b..15c60c610 100644 --- a/pokemon_v2/migrations/0001_squashed_0002_auto_20160301_1408.py +++ b/pokemon_v2/migrations/0001_squashed_0002_auto_20160301_1408.py @@ -3497,6 +3497,7 @@ class Migration(migrations.Migration): ), ), ("name", models.CharField(max_length=100)), + ("cost", models.IntegerField(null=True, blank=True)), ("fling_power", models.IntegerField(null=True, blank=True)), ], options={ diff --git a/pokemon_v2/migrations/0026_itemprice.py b/pokemon_v2/migrations/0026_itemprice.py index dcb6ec070..14ed75d9e 100644 --- a/pokemon_v2/migrations/0026_itemprice.py +++ b/pokemon_v2/migrations/0026_itemprice.py @@ -23,7 +23,6 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("is_purchasable", models.BooleanField()), ("purchase_price", models.IntegerField(blank=True, null=True)), ("sell_price", models.IntegerField(blank=True, null=True)), ( diff --git a/pokemon_v2/models.py b/pokemon_v2/models.py index 59b50d202..4c883021c 100644 --- a/pokemon_v2/models.py +++ b/pokemon_v2/models.py @@ -884,7 +884,6 @@ class ItemGameIndex(HasItem, HasGeneration, HasGameIndex): class ItemPrice(HasItem, HasVersionGroup): - is_purchasable = models.BooleanField() purchase_price = models.IntegerField(blank=True, null=True) sell_price = models.IntegerField(blank=True, null=True) diff --git a/pokemon_v2/serializers.py b/pokemon_v2/serializers.py index 3ed7f80f3..2535d0d33 100644 --- a/pokemon_v2/serializers.py +++ b/pokemon_v2/serializers.py @@ -1811,7 +1811,7 @@ class ItemPriceSerializer(serializers.ModelSerializer): class Meta: model = ItemPrice - fields = ("is_purchasable", "purchase_price", "sell_price", "version_group") + fields = ("purchase_price", "sell_price", "version_group") class ItemNameSerializer(serializers.ModelSerializer): From b49c15e3fd065860958ea8dc80d87b913b92f22b Mon Sep 17 00:00:00 2001 From: Anh Thang Bui Date: Wed, 11 Mar 2026 22:27:19 +0700 Subject: [PATCH 09/12] add script to scraping pokeball price --- data/v2/csv/item_prices.csv | 318 ++++++++++++++++++++++++++--- scripts/scraping_pokeball_price.py | 222 ++++++++++++++++++++ 2 files changed, 513 insertions(+), 27 deletions(-) create mode 100644 scripts/scraping_pokeball_price.py diff --git a/data/v2/csv/item_prices.csv b/data/v2/csv/item_prices.csv index 08689dbb8..ca9d547b5 100644 --- a/data/v2/csv/item_prices.csv +++ b/data/v2/csv/item_prices.csv @@ -1,28 +1,292 @@ item_id,version_group_id,purchase_price,sell_price -305,1,3000,1500 -305,2,3000,1500 -305,3,,1500 -305,4,,1500 -305,5,,1500 -305,6,,1500 -305,7,,1500 -305,16,5000, -305,20,40000, -305,23,,1500 -305,25,,400 -305,28,3000,1500 -305,29,3000,1500 -306,1,2000,1000 -306,2,2000,1000 -306,3,2000,1000 -306,4,2000,1000 -306,5,,1500 -306,6,,1500 -306,7,,1500 -306,8,,1500 -306,9,,1500 -306,10,,1500 -306,12,,1500 -306,13,,1500 -306,23,,1500 -306,25,,400 +1,1,,0 +1,2,,0 +1,3,,0 +1,4,,0 +1,28,,0 +1,29,,0 +2,1,1200,600 +2,2,1200,600 +2,3,1200,600 +2,4,1200,600 +2,5,1200,600 +2,6,1200,600 +2,7,1200,600 +2,8,1200,600 +2,9,1200,600 +2,10,1200,600 +2,11,1200,600 +2,14,1200,600 +2,15,1200,600 +2,16,1200,600 +2,17,800,400 +2,18,800,400 +2,19,500,250 +2,20,800,400 +2,23,1200,600 +2,24,600,150 +2,25,800,200 +2,28,1200,600 +2,29,1200,600 +2,30,600,150 +3,1,600,300 +3,2,600,300 +3,3,600,300 +3,4,600,300 +3,5,600,300 +3,6,600,300 +3,7,600,300 +3,8,600,300 +3,9,600,300 +3,10,600,300 +3,11,600,300 +3,14,600,300 +3,15,600,300 +3,16,600,300 +3,17,600,300 +3,18,600,300 +3,19,300,150 +3,20,600,300 +3,23,600,300 +3,24,300,75 +3,25,600,150 +3,28,600,300 +3,29,600,300 +3,30,300,75 +6,5,1000,500 +6,6,1000,500 +6,7,1000,500 +6,8,1000,500 +6,9,1000,500 +6,10,1000,500 +6,11,1000,500 +6,14,1000,500 +6,15,1000,500 +6,16,1000,500 +6,17,1000,500 +6,18,1000,500 +6,20,1000,500 +6,23,1000,500 +6,25,1000,250 +6,30,1000,250 +7,5,1000,500 +7,6,1000,500 +7,7,,500 +7,8,,500 +7,9,,500 +7,10,1000,500 +7,11,1000,500 +7,14,1000,500 +7,15,1000,500 +7,16,1000,500 +7,17,1000,500 +7,18,1000,500 +7,20,1000,500 +7,23,,500 +7,25,1000,250 +7,30,1000,250 +8,5,1000,500 +8,6,1000,500 +8,7,1000,500 +8,8,1000,500 +8,9,1000,500 +8,10,1000,500 +8,11,1000,500 +8,14,1000,500 +8,15,1000,500 +8,16,1000,500 +8,17,1000,500 +8,18,1000,500 +8,20,1000,500 +8,23,1000,500 +8,25,1000,250 +8,30,1000,250 +9,5,1000,500 +9,6,1000,500 +9,7,1000,500 +9,8,1000,500 +9,9,1000,500 +9,10,1000,500 +9,11,1000,500 +9,14,1000,500 +9,15,1000,500 +9,16,1000,500 +9,17,1000,500 +9,18,1000,500 +9,20,1000,500 +9,23,1000,500 +9,25,1000,250 +9,30,1000,250 +10,5,1000,500 +10,6,1000,500 +10,7,1000,500 +10,8,1000,500 +10,9,1000,500 +10,10,1000,500 +10,11,1000,500 +10,14,1000,500 +10,15,1000,500 +10,16,1000,500 +10,17,1000,500 +10,18,1000,500 +10,20,1000,500 +10,23,1000,500 +10,25,1000,250 +10,30,1000,250 +11,5,1000,500 +11,6,1000,500 +11,7,1000,500 +11,8,1000,500 +11,9,1000,500 +11,10,1000,500 +11,11,1000,500 +11,14,1000,500 +11,15,1000,500 +11,16,1000,500 +11,17,1000,500 +11,18,1000,500 +11,20,3000,1500 +11,23,1000,500 +11,25,3000,750 +11,30,3000,750 +12,5,,100 +12,6,,100 +12,7,,100 +12,8,,100 +12,9,,100 +12,10,,100 +12,15,200,100 +12,16,,100 +12,17,,10 +12,18,,10 +12,19,,50 +12,20,,10 +12,23,,100 +12,25,,5 +12,30,200,50 +13,8,1000,500 +13,9,1000,500 +13,10,1000,500 +13,11,1000,500 +13,14,1000,500 +13,15,1000,500 +13,16,1000,500 +13,17,1000,500 +13,18,1000,500 +13,20,1000,500 +13,23,1000,500 +13,25,1000,250 +13,30,1000,250 +14,8,300,150 +14,9,300,150 +14,10,300,150 +14,11,300,150 +14,14,300,150 +14,15,300,150 +14,16,300,150 +14,17,300,150 +14,18,300,150 +14,20,300,150 +14,23,300,150 +14,25,300,75 +14,30,300,75 +15,8,1000,500 +15,9,1000,500 +15,10,1000,500 +15,11,1000,500 +15,14,1000,500 +15,15,1000,500 +15,16,1000,500 +15,17,1000,500 +15,18,1000,500 +15,20,1000,500 +15,23,1000,500 +15,25,1000,250 +15,30,1000,250 +305,1,3000.0,1500.0 +305,2,3000.0,1500.0 +305,3,,1500.0 +305,4,,1500.0 +305,5,,1500.0 +305,6,,1500.0 +305,7,,1500.0 +305,16,5000.0, +305,20,40000.0, +305,23,,1500.0 +305,25,,400.0 +305,28,3000.0,1500.0 +305,29,3000.0,1500.0 +306,1,2000.0,1000.0 +306,2,2000.0,1000.0 +306,3,2000.0,1000.0 +306,4,2000.0,1000.0 +306,5,,1500.0 +306,6,,1500.0 +306,7,,1500.0 +306,8,,1500.0 +306,9,,1500.0 +306,10,,1500.0 +306,12,,1500.0 +306,13,,1500.0 +306,23,,1500.0 +306,25,,400.0 +449,3,,150 +449,4,,150 +449,10,,150 +449,11,,150 +449,14,,150 +449,15,,150 +449,16,,150 +450,3,,150 +450,4,,150 +450,10,,150 +450,11,,150 +450,14,,150 +450,15,,150 +450,16,,150 +451,3,,150 +451,4,,150 +451,10,,150 +451,11,,150 +451,14,,150 +451,15,,150 +451,16,,150 +452,3,,150 +452,4,,150 +452,10,,150 +452,11,,150 +452,14,,150 +452,15,,150 +452,16,,150 +452,24,120,30 +453,3,,150 +453,4,,150 +453,10,,150 +453,11,,150 +453,14,,150 +453,15,,150 +453,16,,150 +454,3,,150 +454,4,,150 +454,10,,150 +454,11,,150 +454,14,,150 +454,15,,150 +454,16,,150 +455,3,,150 +455,4,,150 +455,10,,150 +455,11,,150 +455,14,,150 +455,15,,150 +455,16,,150 +457,10,,150 +457,11,,150 +457,14,,150 +457,15,,150 +457,16,,150 +457,17,,150 +457,18,,150 +457,19,,150 +457,20,,150 +887,18,1000, diff --git a/scripts/scraping_pokeball_price.py b/scripts/scraping_pokeball_price.py new file mode 100644 index 000000000..01bbbe540 --- /dev/null +++ b/scripts/scraping_pokeball_price.py @@ -0,0 +1,222 @@ +import requests +from bs4 import BeautifulSoup +import pandas as pd +import time +import re +from pathlib import Path + +# 1. Object Map for Item IDs (from PokeAPI's items.csv) +ITEM_MAP = { + "master-ball": 1, + "ultra-ball": 2, + "great-ball": 3, + "poke-ball": 4, + "safari-ball": 5, + "net-ball": 6, + "dive-ball": 7, + "nest-ball": 8, + "repeat-ball": 9, + "timer-ball": 10, + "luxury-ball": 11, + "premier-ball": 12, + "dusk-ball": 13, + "heal-ball": 14, + "quick-ball": 15, + "cherish-ball": 16, + "smoke-ball": 205, + "light-ball": 213, + "iron-ball": 255, + "lure-ball": 449, + "level-ball": 450, + "moon-ball": 451, + "heavy-ball": 452, + "fast-ball": 453, + "friend-ball": 454, + "love-ball": 455, + "park-ball": 456, + "sport-ball": 457, + "air-balloon": 584, + "dream-ball": 617, + "beast-ball": 887, + "left-poke-ball": 994, + "polished-mud-ball": 1031, + "strange-ball": 1663, + "koraidons-poke-ball": 1667, + "miraidons-poke-ball": 1668, + "academy-ball": 2031, + "marill-ball": 2033, + "yarn-ball": 2034, + "cyber-ball": 2035, + "blue-poke-ball-pick": 2044, + "exercise-ball": 2064, + "green-poke-ball-pick": 2084, + "red-poke-ball-pick": 2085, + "lastrange-ball": 2219, + "lapoke-ball": 2220, + "lagreat-ball": 2221, + "laultra-ball": 2222, + "laheavy-ball": 2223, + "laleaden-ball": 2224, + "lagigaton-ball": 2225, + "lafeather-ball": 2226, + "lawing-ball": 2227, + "lajet-ball": 2228, + "laorigin-ball": 2229, +} + +# 2. Version Group Mapping (from PokeAPI's version_groups.csv) +GAME_MAP = { + "RGBY": [1, 2, 28, 29], + "RBY": [1, 2], + "GSC": [3, 4], + "RSE": [5, 6], + "FRLG": [7], + "DPPt": [8, 9], + "HGSS": [10], + "BWB2W2": [11, 14], + "XY": [15], + "ORAS": [16], + "SM": [17], + "USUM": [18], + "PE": [19], + "SwSh": [20], + "BDSP": [23], + "LA": [24], + "SV": [25], + "ZA": [30], + "ColoXD": [12, 13], +} + +BASE_URL = "https://bulbapedia.bulbagarden.net" +START_URL = f"{BASE_URL}/wiki/Pok%C3%A9_Ball" + + +def normalize_name(name): + """Maps Bulbapedia names to our dictionary keys.""" + raw = name.lower().strip() + # Handle Hisui variants: 'Heavy Ball (Hisui)' -> 'laheavy-ball' + if "(hisui)" in raw: + clean = re.sub(r"[^a-z0-9\s]", "", raw.replace("(hisui)", "")) + return "la" + clean.strip().replace(" ", "-") + # Standard: 'Ultra Ball' -> 'ultra-ball' + clean = re.sub(r"[^a-z0-9\s-]", "", raw) + return clean.replace(" ", "-") + + +def get_poke_ball_links(): + print("Fetching Poké Ball list...") + res = requests.get(START_URL) + soup = BeautifulSoup(res.text, "html.parser") + balls = [] + + header = soup.find("span", id="Types_of_Pok.C3.A9_Balls") + table = header.find_next("table") + for row in table.find_all("tr")[1:]: + cols = row.find_all("td") + if len(cols) > 1: + a = cols[1].find("a") + if a: + name = a.text.strip() + identifier = normalize_name(name) + item_id = ITEM_MAP.get(identifier) + if item_id: + balls.append( + {"name": name, "item_id": item_id, "url": BASE_URL + a["href"]} + ) + return balls + + +def get_ball_prices(ball_url): + res = requests.get(ball_url) + soup = BeautifulSoup(res.text, "html.parser") + price_data = [] + + # Locate the Price section + header = soup.find("span", id="Price") + if not header: + return [] + + table = header.find_next("table") + for row in table.find_all("tr")[1:]: + cols = row.find_all("td") + if len(cols) >= 3: + game_text = cols[0].get_text(strip=True) + + # Clean prices: remove symbols, convert N/A to empty + buy = cols[1].get_text(strip=True).replace("$", "").replace(",", "") + sell = cols[2].get_text(strip=True).replace("$", "").replace(",", "") + + buy = "" if buy.upper() == "N/A" else buy + sell = "" if sell.upper() == "N/A" else sell + + # Skip if both are empty + if not buy and not sell: + continue + + # Cross-reference with GAME_MAP keys + v_ids = [] + for key, ids in GAME_MAP.items(): + if key in game_text: + v_ids.extend(ids) + + for v_id in set(v_ids): + price_data.append({"vg_id": v_id, "buy": buy, "sell": sell}) + + return price_data + + +def main(): + print("Starting Poké Ball price scraping...") + + output_path = Path(__file__).parent.parent / "data/v2/csv/item_prices.csv" + output_path.parent.mkdir(parents=True, exist_ok=True) + + results = [] + balls = get_poke_ball_links() + + for b in balls: + print(f"Scraping: {b['name']}...") + try: + prices = get_ball_prices(b["url"]) + for p in prices: + results.append( + { + # "name": b["name"], # enable this if you wanna keep the name in the output for easier debugging + "item_id": b["item_id"], + "version_group_id": p["vg_id"], + "purchase_price": p["buy"], + "sell_price": p["sell"], + } + ) + time.sleep(0.3) + except Exception as e: + print(f"Error on {b['name']}: {e}") + + if not results: + print("No new data found.") + return + + df_new = pd.DataFrame(results) + + # If file exists, merge with existing data + if output_path.exists(): + df_old = pd.read_csv(output_path) + df_combined = pd.concat([df_old, df_new], ignore_index=True) + else: + df_combined = df_new + + # 1. Deduplicate (prevents repeating data if script is run twice) + # 2. Sort by item_id then version_group_id + df_final = df_combined.drop_duplicates( + subset=["item_id", "version_group_id"], keep="last" + ) + df_final = df_final.sort_values(by=["item_id", "version_group_id"]) + + # Write back to CSV + df_final.to_csv(output_path, index=False) + + print(f"\nSuccess! File updated and sorted at: {output_path}") + + +if __name__ == "__main__": + main() From 0798ac02273e296b9e04e9dfac8ea06b27478762 Mon Sep 17 00:00:00 2001 From: Anh Thang Bui Date: Wed, 11 Mar 2026 22:48:47 +0700 Subject: [PATCH 10/12] fix: update item prices and handle N/A values in scraping script --- data/v2/csv/item_prices.csv | 54 +++++++++++++++--------------- scripts/scraping_pokeball_price.py | 27 ++++++++++----- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/data/v2/csv/item_prices.csv b/data/v2/csv/item_prices.csv index ca9d547b5..706e2476f 100644 --- a/data/v2/csv/item_prices.csv +++ b/data/v2/csv/item_prices.csv @@ -203,33 +203,33 @@ item_id,version_group_id,purchase_price,sell_price 15,23,1000,500 15,25,1000,250 15,30,1000,250 -305,1,3000.0,1500.0 -305,2,3000.0,1500.0 -305,3,,1500.0 -305,4,,1500.0 -305,5,,1500.0 -305,6,,1500.0 -305,7,,1500.0 -305,16,5000.0, -305,20,40000.0, -305,23,,1500.0 -305,25,,400.0 -305,28,3000.0,1500.0 -305,29,3000.0,1500.0 -306,1,2000.0,1000.0 -306,2,2000.0,1000.0 -306,3,2000.0,1000.0 -306,4,2000.0,1000.0 -306,5,,1500.0 -306,6,,1500.0 -306,7,,1500.0 -306,8,,1500.0 -306,9,,1500.0 -306,10,,1500.0 -306,12,,1500.0 -306,13,,1500.0 -306,23,,1500.0 -306,25,,400.0 +305,1,3000,1500 +305,2,3000,1500 +305,3,,1500 +305,4,,1500 +305,5,,1500 +305,6,,1500 +305,7,,1500 +305,16,5000, +305,20,40000, +305,23,,1500 +305,25,,400 +305,28,3000,1500 +305,29,3000,1500 +306,1,2000,1000 +306,2,2000,1000 +306,3,2000,1000 +306,4,2000,1000 +306,5,,1500 +306,6,,1500 +306,7,,1500 +306,8,,1500 +306,9,,1500 +306,10,,1500 +306,12,,1500 +306,13,,1500 +306,23,,1500 +306,25,,400 449,3,,150 449,4,,150 449,10,,150 diff --git a/scripts/scraping_pokeball_price.py b/scripts/scraping_pokeball_price.py index 01bbbe540..514e5fed7 100644 --- a/scripts/scraping_pokeball_price.py +++ b/scripts/scraping_pokeball_price.py @@ -146,11 +146,10 @@ def get_ball_prices(ball_url): buy = cols[1].get_text(strip=True).replace("$", "").replace(",", "") sell = cols[2].get_text(strip=True).replace("$", "").replace(",", "") - buy = "" if buy.upper() == "N/A" else buy - sell = "" if sell.upper() == "N/A" else sell + buy = None if buy.upper() == "N/A" or not buy else buy + sell = None if sell.upper() == "N/A" or not sell else sell - # Skip if both are empty - if not buy and not sell: + if buy is None and sell is None: continue # Cross-reference with GAME_MAP keys @@ -205,17 +204,27 @@ def main(): else: df_combined = df_new - # 1. Deduplicate (prevents repeating data if script is run twice) - # 2. Sort by item_id then version_group_id + df_combined["purchase_price"] = pd.to_numeric( + df_combined["purchase_price"], errors="coerce" + ) + df_combined["sell_price"] = pd.to_numeric( + df_combined["sell_price"], errors="coerce" + ) + + # Deduplicate and Sort df_final = df_combined.drop_duplicates( subset=["item_id", "version_group_id"], keep="last" ) df_final = df_final.sort_values(by=["item_id", "version_group_id"]) - # Write back to CSV - df_final.to_csv(output_path, index=False) + df_final["purchase_price"] = df_final["purchase_price"].astype("Int64") + df_final["sell_price"] = df_final["sell_price"].astype("Int64") + + df_final["item_id"] = df_final["item_id"].astype(int) + df_final["version_group_id"] = df_final["version_group_id"].astype(int) - print(f"\nSuccess! File updated and sorted at: {output_path}") + df_final.to_csv(output_path, index=False) + print(f"\nSuccess! File sorted and updated at: {output_path}") if __name__ == "__main__": From b3577b0d56414b3a3cc05eca25e8e441750d8f44 Mon Sep 17 00:00:00 2001 From: Anh Thang Bui Date: Sat, 14 Mar 2026 22:14:55 +0700 Subject: [PATCH 11/12] add currency --- data/v2/build.py | 24 +- data/v2/csv/currencies.csv | 20 + data/v2/csv/currency_names.csv | 20 + data/v2/csv/item_prices.csv | 584 +++++++++--------- .../tables/public_pokemon_v2_currency.yaml | 25 + .../public_pokemon_v2_currencyname.yaml | 17 + .../tables/public_pokemon_v2_itemprice.yaml | 3 + .../databases/default/tables/tables.yaml | 2 + .../tables/public_pokemon_v2_currency.yaml | 34 + .../public_pokemon_v2_currencyname.yaml | 26 + .../tables/public_pokemon_v2_itemprice.yaml | 3 + .../databases/default/tables/tables.yaml | 2 + pokemon_v2/api.py | 16 + pokemon_v2/migrations/0026_itemprice.py | 67 +- pokemon_v2/models.py | 23 +- pokemon_v2/serializers.py | 35 +- pokemon_v2/urls.py | 1 + 17 files changed, 605 insertions(+), 297 deletions(-) create mode 100644 data/v2/csv/currencies.csv create mode 100644 data/v2/csv/currency_names.csv create mode 100644 graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_currency.yaml create mode 100644 graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_currencyname.yaml create mode 100644 graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_currency.yaml create mode 100644 graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_currencyname.yaml diff --git a/data/v2/build.py b/data/v2/build.py index 5306845bd..0644afbe7 100644 --- a/data/v2/build.py +++ b/data/v2/build.py @@ -452,6 +452,18 @@ def csv_record_to_objects(info): def _build_items(): + def csv_record_to_objects(info): + yield Currency(id=int(info[0]), name=info[1]) + + build_generic((Currency,), "currencies.csv", csv_record_to_objects) + + def csv_record_to_objects(info): + yield CurrencyName( + currency_id=int(info[0]), language_id=int(info[1]), name=info[2] + ) + + build_generic((CurrencyName,), "currency_names.csv", csv_record_to_objects) + def csv_record_to_objects(info): yield ItemPocket(id=int(info[0]), name=info[1]) @@ -544,11 +556,19 @@ def csv_record_to_objects(info): build_generic((ItemGameIndex,), "item_game_indices.csv", csv_record_to_objects) def csv_record_to_objects(info): + # Keep backward compatibility with 4-column files (no currency_id). + has_currency_id = len(info) >= 5 + purchase_price_index = 3 if has_currency_id else 2 + sell_price_index = 4 if has_currency_id else 3 + yield ItemPrice( item_id=int(info[0]), version_group_id=int(info[1]), - purchase_price=int(info[2]) if info[2] else None, - sell_price=int(info[3]) if info[3] else None, + currency_id=int(info[2]) if has_currency_id and info[2] else 1, + purchase_price=( + int(info[purchase_price_index]) if info[purchase_price_index] else None + ), + sell_price=int(info[sell_price_index]) if info[sell_price_index] else None, ) build_generic((ItemPrice,), "item_prices.csv", csv_record_to_objects) diff --git a/data/v2/csv/currencies.csv b/data/v2/csv/currencies.csv new file mode 100644 index 000000000..4ce4ea25f --- /dev/null +++ b/data/v2/csv/currencies.csv @@ -0,0 +1,20 @@ +id,identifier +1,poke-dollar +2,coin +3,volcanic-ash +4,poke-coupon +5,berry-powder +6,battle-point +7,sphere +8,castle-point +9,watt +10,athlete-point +11,dream-point +12,dream-world-berry +13,poke-mile +14,festival-coin +15,poke-bean +16,home-point +17,merit-point +18,league-point +19,blueberry-point diff --git a/data/v2/csv/currency_names.csv b/data/v2/csv/currency_names.csv new file mode 100644 index 000000000..5bd477917 --- /dev/null +++ b/data/v2/csv/currency_names.csv @@ -0,0 +1,20 @@ +id,language_id,name +1,9,Pokémon Dollar +2,9,Coin +3,9,Volcanic Ash +4,9,Poké Coupon +5,9,Berry Powder +6,9,Battle Point +7,9,Sphere +8,9,Castle Point +9,9,Watt +10,9,Athlete Point +11,9,Dream Point +12,9,Berry (Dream World) +13,9,Poké Mile +14,9,Festival Coin +15,9,Poké Bean +16,9,Pokémon HOME Point +17,9,Merit Point +18,9,League Point +19,9,Blueberry Point diff --git a/data/v2/csv/item_prices.csv b/data/v2/csv/item_prices.csv index 706e2476f..b43ac17af 100644 --- a/data/v2/csv/item_prices.csv +++ b/data/v2/csv/item_prices.csv @@ -1,292 +1,292 @@ -item_id,version_group_id,purchase_price,sell_price -1,1,,0 -1,2,,0 -1,3,,0 -1,4,,0 -1,28,,0 -1,29,,0 -2,1,1200,600 -2,2,1200,600 -2,3,1200,600 -2,4,1200,600 -2,5,1200,600 -2,6,1200,600 -2,7,1200,600 -2,8,1200,600 -2,9,1200,600 -2,10,1200,600 -2,11,1200,600 -2,14,1200,600 -2,15,1200,600 -2,16,1200,600 -2,17,800,400 -2,18,800,400 -2,19,500,250 -2,20,800,400 -2,23,1200,600 -2,24,600,150 -2,25,800,200 -2,28,1200,600 -2,29,1200,600 -2,30,600,150 -3,1,600,300 -3,2,600,300 -3,3,600,300 -3,4,600,300 -3,5,600,300 -3,6,600,300 -3,7,600,300 -3,8,600,300 -3,9,600,300 -3,10,600,300 -3,11,600,300 -3,14,600,300 -3,15,600,300 -3,16,600,300 -3,17,600,300 -3,18,600,300 -3,19,300,150 -3,20,600,300 -3,23,600,300 -3,24,300,75 -3,25,600,150 -3,28,600,300 -3,29,600,300 -3,30,300,75 -6,5,1000,500 -6,6,1000,500 -6,7,1000,500 -6,8,1000,500 -6,9,1000,500 -6,10,1000,500 -6,11,1000,500 -6,14,1000,500 -6,15,1000,500 -6,16,1000,500 -6,17,1000,500 -6,18,1000,500 -6,20,1000,500 -6,23,1000,500 -6,25,1000,250 -6,30,1000,250 -7,5,1000,500 -7,6,1000,500 -7,7,,500 -7,8,,500 -7,9,,500 -7,10,1000,500 -7,11,1000,500 -7,14,1000,500 -7,15,1000,500 -7,16,1000,500 -7,17,1000,500 -7,18,1000,500 -7,20,1000,500 -7,23,,500 -7,25,1000,250 -7,30,1000,250 -8,5,1000,500 -8,6,1000,500 -8,7,1000,500 -8,8,1000,500 -8,9,1000,500 -8,10,1000,500 -8,11,1000,500 -8,14,1000,500 -8,15,1000,500 -8,16,1000,500 -8,17,1000,500 -8,18,1000,500 -8,20,1000,500 -8,23,1000,500 -8,25,1000,250 -8,30,1000,250 -9,5,1000,500 -9,6,1000,500 -9,7,1000,500 -9,8,1000,500 -9,9,1000,500 -9,10,1000,500 -9,11,1000,500 -9,14,1000,500 -9,15,1000,500 -9,16,1000,500 -9,17,1000,500 -9,18,1000,500 -9,20,1000,500 -9,23,1000,500 -9,25,1000,250 -9,30,1000,250 -10,5,1000,500 -10,6,1000,500 -10,7,1000,500 -10,8,1000,500 -10,9,1000,500 -10,10,1000,500 -10,11,1000,500 -10,14,1000,500 -10,15,1000,500 -10,16,1000,500 -10,17,1000,500 -10,18,1000,500 -10,20,1000,500 -10,23,1000,500 -10,25,1000,250 -10,30,1000,250 -11,5,1000,500 -11,6,1000,500 -11,7,1000,500 -11,8,1000,500 -11,9,1000,500 -11,10,1000,500 -11,11,1000,500 -11,14,1000,500 -11,15,1000,500 -11,16,1000,500 -11,17,1000,500 -11,18,1000,500 -11,20,3000,1500 -11,23,1000,500 -11,25,3000,750 -11,30,3000,750 -12,5,,100 -12,6,,100 -12,7,,100 -12,8,,100 -12,9,,100 -12,10,,100 -12,15,200,100 -12,16,,100 -12,17,,10 -12,18,,10 -12,19,,50 -12,20,,10 -12,23,,100 -12,25,,5 -12,30,200,50 -13,8,1000,500 -13,9,1000,500 -13,10,1000,500 -13,11,1000,500 -13,14,1000,500 -13,15,1000,500 -13,16,1000,500 -13,17,1000,500 -13,18,1000,500 -13,20,1000,500 -13,23,1000,500 -13,25,1000,250 -13,30,1000,250 -14,8,300,150 -14,9,300,150 -14,10,300,150 -14,11,300,150 -14,14,300,150 -14,15,300,150 -14,16,300,150 -14,17,300,150 -14,18,300,150 -14,20,300,150 -14,23,300,150 -14,25,300,75 -14,30,300,75 -15,8,1000,500 -15,9,1000,500 -15,10,1000,500 -15,11,1000,500 -15,14,1000,500 -15,15,1000,500 -15,16,1000,500 -15,17,1000,500 -15,18,1000,500 -15,20,1000,500 -15,23,1000,500 -15,25,1000,250 -15,30,1000,250 -305,1,3000,1500 -305,2,3000,1500 -305,3,,1500 -305,4,,1500 -305,5,,1500 -305,6,,1500 -305,7,,1500 -305,16,5000, -305,20,40000, -305,23,,1500 -305,25,,400 -305,28,3000,1500 -305,29,3000,1500 -306,1,2000,1000 -306,2,2000,1000 -306,3,2000,1000 -306,4,2000,1000 -306,5,,1500 -306,6,,1500 -306,7,,1500 -306,8,,1500 -306,9,,1500 -306,10,,1500 -306,12,,1500 -306,13,,1500 -306,23,,1500 -306,25,,400 -449,3,,150 -449,4,,150 -449,10,,150 -449,11,,150 -449,14,,150 -449,15,,150 -449,16,,150 -450,3,,150 -450,4,,150 -450,10,,150 -450,11,,150 -450,14,,150 -450,15,,150 -450,16,,150 -451,3,,150 -451,4,,150 -451,10,,150 -451,11,,150 -451,14,,150 -451,15,,150 -451,16,,150 -452,3,,150 -452,4,,150 -452,10,,150 -452,11,,150 -452,14,,150 -452,15,,150 -452,16,,150 -452,24,120,30 -453,3,,150 -453,4,,150 -453,10,,150 -453,11,,150 -453,14,,150 -453,15,,150 -453,16,,150 -454,3,,150 -454,4,,150 -454,10,,150 -454,11,,150 -454,14,,150 -454,15,,150 -454,16,,150 -455,3,,150 -455,4,,150 -455,10,,150 -455,11,,150 -455,14,,150 -455,15,,150 -455,16,,150 -457,10,,150 -457,11,,150 -457,14,,150 -457,15,,150 -457,16,,150 -457,17,,150 -457,18,,150 -457,19,,150 -457,20,,150 -887,18,1000, +item_id,version_group_id,currency_id,purchase_price,sell_price +1,1,1,,0 +1,2,1,,0 +1,3,1,,0 +1,4,1,,0 +1,28,1,,0 +1,29,1,,0 +2,1,1,1200,600 +2,2,1,1200,600 +2,3,1,1200,600 +2,4,1,1200,600 +2,5,1,1200,600 +2,6,1,1200,600 +2,7,1,1200,600 +2,8,1,1200,600 +2,9,1,1200,600 +2,10,1,1200,600 +2,11,1,1200,600 +2,14,1,1200,600 +2,15,1,1200,600 +2,16,1,1200,600 +2,17,1,800,400 +2,18,1,800,400 +2,19,1,500,250 +2,20,1,800,400 +2,23,1,1200,600 +2,24,1,600,150 +2,25,1,800,200 +2,28,1,1200,600 +2,29,1,1200,600 +2,30,1,600,150 +3,1,1,600,300 +3,2,1,600,300 +3,3,1,600,300 +3,4,1,600,300 +3,5,1,600,300 +3,6,1,600,300 +3,7,1,600,300 +3,8,1,600,300 +3,9,1,600,300 +3,10,1,600,300 +3,11,1,600,300 +3,14,1,600,300 +3,15,1,600,300 +3,16,1,600,300 +3,17,1,600,300 +3,18,1,600,300 +3,19,1,300,150 +3,20,1,600,300 +3,23,1,600,300 +3,24,1,300,75 +3,25,1,600,150 +3,28,1,600,300 +3,29,1,600,300 +3,30,1,300,75 +6,5,1,1000,500 +6,6,1,1000,500 +6,7,1,1000,500 +6,8,1,1000,500 +6,9,1,1000,500 +6,10,1,1000,500 +6,11,1,1000,500 +6,14,1,1000,500 +6,15,1,1000,500 +6,16,1,1000,500 +6,17,1,1000,500 +6,18,1,1000,500 +6,20,1,1000,500 +6,23,1,1000,500 +6,25,1,1000,250 +6,30,1,1000,250 +7,5,1,1000,500 +7,6,1,1000,500 +7,7,1,,500 +7,8,1,,500 +7,9,1,,500 +7,10,1,1000,500 +7,11,1,1000,500 +7,14,1,1000,500 +7,15,1,1000,500 +7,16,1,1000,500 +7,17,1,1000,500 +7,18,1,1000,500 +7,20,1,1000,500 +7,23,1,,500 +7,25,1,1000,250 +7,30,1,1000,250 +8,5,1,1000,500 +8,6,1,1000,500 +8,7,1,1000,500 +8,8,1,1000,500 +8,9,1,1000,500 +8,10,1,1000,500 +8,11,1,1000,500 +8,14,1,1000,500 +8,15,1,1000,500 +8,16,1,1000,500 +8,17,1,1000,500 +8,18,1,1000,500 +8,20,1,1000,500 +8,23,1,1000,500 +8,25,1,1000,250 +8,30,1,1000,250 +9,5,1,1000,500 +9,6,1,1000,500 +9,7,1,1000,500 +9,8,1,1000,500 +9,9,1,1000,500 +9,10,1,1000,500 +9,11,1,1000,500 +9,14,1,1000,500 +9,15,1,1000,500 +9,16,1,1000,500 +9,17,1,1000,500 +9,18,1,1000,500 +9,20,1,1000,500 +9,23,1,1000,500 +9,25,1,1000,250 +9,30,1,1000,250 +10,5,1,1000,500 +10,6,1,1000,500 +10,7,1,1000,500 +10,8,1,1000,500 +10,9,1,1000,500 +10,10,1,1000,500 +10,11,1,1000,500 +10,14,1,1000,500 +10,15,1,1000,500 +10,16,1,1000,500 +10,17,1,1000,500 +10,18,1,1000,500 +10,20,1,1000,500 +10,23,1,1000,500 +10,25,1,1000,250 +10,30,1,1000,250 +11,5,1,1000,500 +11,6,1,1000,500 +11,7,1,1000,500 +11,8,1,1000,500 +11,9,1,1000,500 +11,10,1,1000,500 +11,11,1,1000,500 +11,14,1,1000,500 +11,15,1,1000,500 +11,16,1,1000,500 +11,17,1,1000,500 +11,18,1,1000,500 +11,20,1,3000,1500 +11,23,1,1000,500 +11,25,1,3000,750 +11,30,1,3000,750 +12,5,1,,100 +12,6,1,,100 +12,7,1,,100 +12,8,1,,100 +12,9,1,,100 +12,10,1,,100 +12,15,1,200,100 +12,16,1,,100 +12,17,1,,10 +12,18,1,,10 +12,19,1,,50 +12,20,1,,10 +12,23,1,,100 +12,25,1,,5 +12,30,1,200,50 +13,8,1,1000,500 +13,9,1,1000,500 +13,10,1,1000,500 +13,11,1,1000,500 +13,14,1,1000,500 +13,15,1,1000,500 +13,16,1,1000,500 +13,17,1,1000,500 +13,18,1,1000,500 +13,20,1,1000,500 +13,23,1,1000,500 +13,25,1,1000,250 +13,30,1,1000,250 +14,8,1,300,150 +14,9,1,300,150 +14,10,1,300,150 +14,11,1,300,150 +14,14,1,300,150 +14,15,1,300,150 +14,16,1,300,150 +14,17,1,300,150 +14,18,1,300,150 +14,20,1,300,150 +14,23,1,300,150 +14,25,1,300,75 +14,30,1,300,75 +15,8,1,1000,500 +15,9,1,1000,500 +15,10,1,1000,500 +15,11,1,1000,500 +15,14,1,1000,500 +15,15,1,1000,500 +15,16,1,1000,500 +15,17,1,1000,500 +15,18,1,1000,500 +15,20,1,1000,500 +15,23,1,1000,500 +15,25,1,1000,250 +15,30,1,1000,250 +305,1,1,3000,1500 +305,2,1,3000,1500 +305,3,1,,1500 +305,4,1,,1500 +305,5,1,,1500 +305,6,1,,1500 +305,7,1,,1500 +305,16,1,5000, +305,20,1,40000, +305,23,1,,1500 +305,25,1,,400 +305,28,1,3000,1500 +305,29,1,3000,1500 +306,1,1,2000,1000 +306,2,1,2000,1000 +306,3,1,2000,1000 +306,4,1,2000,1000 +306,5,1,,1500 +306,6,1,,1500 +306,7,1,,1500 +306,8,1,,1500 +306,9,1,,1500 +306,10,1,,1500 +306,12,1,,1500 +306,13,1,,1500 +306,23,1,,1500 +306,25,1,,400 +449,3,1,,150 +449,4,1,,150 +449,10,1,,150 +449,11,1,,150 +449,14,1,,150 +449,15,1,,150 +449,16,1,,150 +450,3,1,,150 +450,4,1,,150 +450,10,1,,150 +450,11,1,,150 +450,14,1,,150 +450,15,1,,150 +450,16,1,,150 +451,3,1,,150 +451,4,1,,150 +451,10,1,,150 +451,11,1,,150 +451,14,1,,150 +451,15,1,,150 +451,16,1,,150 +452,3,1,,150 +452,4,1,,150 +452,10,1,,150 +452,11,1,,150 +452,14,1,,150 +452,15,1,,150 +452,16,1,,150 +452,24,1,120,30 +453,3,1,,150 +453,4,1,,150 +453,10,1,,150 +453,11,1,,150 +453,14,1,,150 +453,15,1,,150 +453,16,1,,150 +454,3,1,,150 +454,4,1,,150 +454,10,1,,150 +454,11,1,,150 +454,14,1,,150 +454,15,1,,150 +454,16,1,,150 +455,3,1,,150 +455,4,1,,150 +455,10,1,,150 +455,11,1,,150 +455,14,1,,150 +455,15,1,,150 +455,16,1,,150 +457,10,1,,150 +457,11,1,,150 +457,14,1,,150 +457,15,1,,150 +457,16,1,,150 +457,17,1,,150 +457,18,1,,150 +457,19,1,,150 +457,20,1,,150 +887,18,1,1000, diff --git a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_currency.yaml b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_currency.yaml new file mode 100644 index 000000000..b2b2688ca --- /dev/null +++ b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_currency.yaml @@ -0,0 +1,25 @@ +table: + name: pokemon_v2_currency + schema: public +array_relationships: + - name: pokemon_v2_currencynames + using: + foreign_key_constraint_on: + column: currency_id + table: + name: pokemon_v2_currencyname + schema: public + - name: pokemon_v2_itemprices + using: + foreign_key_constraint_on: + column: currency_id + table: + name: pokemon_v2_itemprice + schema: public +select_permissions: + - role: anon + permission: + columns: '*' + filter: {} + limit: 100000 + allow_aggregations: true diff --git a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_currencyname.yaml b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_currencyname.yaml new file mode 100644 index 000000000..5242b49bb --- /dev/null +++ b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_currencyname.yaml @@ -0,0 +1,17 @@ +table: + name: pokemon_v2_currencyname + schema: public +object_relationships: + - name: pokemon_v2_currency + using: + foreign_key_constraint_on: currency_id + - name: pokemon_v2_language + using: + foreign_key_constraint_on: language_id +select_permissions: + - role: anon + permission: + columns: '*' + filter: {} + limit: 100000 + allow_aggregations: true diff --git a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml index d9c38bdff..947622b24 100644 --- a/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml +++ b/graphql/v1beta/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml @@ -5,6 +5,9 @@ object_relationships: - name: pokemon_v2_item using: foreign_key_constraint_on: item_id + - name: pokemon_v2_currency + using: + foreign_key_constraint_on: currency_id - name: pokemon_v2_versiongroup using: foreign_key_constraint_on: version_group_id diff --git a/graphql/v1beta/metadata/databases/default/tables/tables.yaml b/graphql/v1beta/metadata/databases/default/tables/tables.yaml index 31c3ca3a8..4e0518181 100644 --- a/graphql/v1beta/metadata/databases/default/tables/tables.yaml +++ b/graphql/v1beta/metadata/databases/default/tables/tables.yaml @@ -18,6 +18,8 @@ - "!include public_pokemon_v2_contesteffectflavortext.yaml" - "!include public_pokemon_v2_contesttype.yaml" - "!include public_pokemon_v2_contesttypename.yaml" +- "!include public_pokemon_v2_currency.yaml" +- "!include public_pokemon_v2_currencyname.yaml" - "!include public_pokemon_v2_egggroup.yaml" - "!include public_pokemon_v2_egggroupname.yaml" - "!include public_pokemon_v2_encounter.yaml" diff --git a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_currency.yaml b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_currency.yaml new file mode 100644 index 000000000..b2b12e51c --- /dev/null +++ b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_currency.yaml @@ -0,0 +1,34 @@ +table: + name: pokemon_v2_currency + schema: public +configuration: + column_config: {} + custom_column_names: {} + custom_name: currency + custom_root_fields: {} +array_relationships: + - name: currencynames + using: + foreign_key_constraint_on: + column: currency_id + table: + name: pokemon_v2_currencyname + schema: public + - name: itemprices + using: + foreign_key_constraint_on: + column: currency_id + table: + name: pokemon_v2_itemprice + schema: public +select_permissions: + - role: anon + permission: + columns: '*' + filter: {} + limit: 100000 + allow_aggregations: true + query_root_fields: + - select + - select_aggregate + subscription_root_fields: [] diff --git a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_currencyname.yaml b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_currencyname.yaml new file mode 100644 index 000000000..60424fed0 --- /dev/null +++ b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_currencyname.yaml @@ -0,0 +1,26 @@ +table: + name: pokemon_v2_currencyname + schema: public +configuration: + column_config: {} + custom_column_names: {} + custom_name: currencyname + custom_root_fields: {} +object_relationships: + - name: currency + using: + foreign_key_constraint_on: currency_id + - name: language + using: + foreign_key_constraint_on: language_id +select_permissions: + - role: anon + permission: + columns: '*' + filter: {} + limit: 100000 + allow_aggregations: true + query_root_fields: + - select + - select_aggregate + subscription_root_fields: [] diff --git a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml index 3082e5ffd..613702252 100644 --- a/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml +++ b/graphql/v1beta2/metadata/databases/default/tables/public_pokemon_v2_itemprice.yaml @@ -10,6 +10,9 @@ object_relationships: - name: item using: foreign_key_constraint_on: item_id + - name: currency + using: + foreign_key_constraint_on: currency_id - name: versiongroup using: foreign_key_constraint_on: version_group_id diff --git a/graphql/v1beta2/metadata/databases/default/tables/tables.yaml b/graphql/v1beta2/metadata/databases/default/tables/tables.yaml index 0021c9c22..a166aed64 100644 --- a/graphql/v1beta2/metadata/databases/default/tables/tables.yaml +++ b/graphql/v1beta2/metadata/databases/default/tables/tables.yaml @@ -18,6 +18,8 @@ - "!include public_pokemon_v2_contesteffectflavortext.yaml" - "!include public_pokemon_v2_contesttype.yaml" - "!include public_pokemon_v2_contesttypename.yaml" +- "!include public_pokemon_v2_currency.yaml" +- "!include public_pokemon_v2_currencyname.yaml" - "!include public_pokemon_v2_egggroup.yaml" - "!include public_pokemon_v2_egggroupname.yaml" - "!include public_pokemon_v2_encounter.yaml" diff --git a/pokemon_v2/api.py b/pokemon_v2/api.py index 590721839..c895646cc 100644 --- a/pokemon_v2/api.py +++ b/pokemon_v2/api.py @@ -434,6 +434,22 @@ class ItemPocketResource(PokeapiCommonViewset): list_serializer_class = ItemPocketSummarySerializer +@extend_schema( + description="Currencies used to buy items.", + summary="Get currency", + tags=["items"], +) +@extend_schema_view( + list=extend_schema( + summary="List currencies", + ) +) +class CurrencyResource(PokeapiCommonViewset): + queryset = Currency.objects.all() + serializer_class = CurrencyDetailSerializer + list_serializer_class = CurrencySummarySerializer + + @extend_schema( description="Languages for translations of API resource information.", summary="Get language", diff --git a/pokemon_v2/migrations/0026_itemprice.py b/pokemon_v2/migrations/0026_itemprice.py index 14ed75d9e..c6f773ea8 100644 --- a/pokemon_v2/migrations/0026_itemprice.py +++ b/pokemon_v2/migrations/0026_itemprice.py @@ -5,12 +5,29 @@ class Migration(migrations.Migration): - dependencies = [ ("pokemon_v2", "0025_pokemonstatpast"), ] operations = [ + migrations.CreateModel( + name="Currency", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(db_index=True, max_length=200)), + ], + options={ + "abstract": False, + }, + ), migrations.CreateModel( name="ItemPrice", fields=[ @@ -25,6 +42,16 @@ class Migration(migrations.Migration): ), ("purchase_price", models.IntegerField(blank=True, null=True)), ("sell_price", models.IntegerField(blank=True, null=True)), + ( + "currency", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s", + to="pokemon_v2.currency", + ), + ), ( "item", models.ForeignKey( @@ -50,4 +77,42 @@ class Migration(migrations.Migration): "abstract": False, }, ), + migrations.CreateModel( + name="CurrencyName", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(db_index=True, max_length=200)), + ( + "currency", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s", + to="pokemon_v2.currency", + ), + ), + ( + "language", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_language", + to="pokemon_v2.language", + ), + ), + ], + options={ + "abstract": False, + }, + ), ] diff --git a/pokemon_v2/models.py b/pokemon_v2/models.py index 4c883021c..6038c51cf 100644 --- a/pokemon_v2/models.py +++ b/pokemon_v2/models.py @@ -57,6 +57,19 @@ class Meta: abstract = True +class HasCurrency(models.Model): + currency = models.ForeignKey( + "Currency", + blank=True, + null=True, + related_name="%(class)s", + on_delete=models.CASCADE, + ) + + class Meta: + abstract = True + + class HasSuperContestEffect(models.Model): super_contest_effect = models.ForeignKey( "SuperContestEffect", @@ -823,6 +836,14 @@ class EggGroupName(IsName, HasEggGroup): ################# +class Currency(HasName): + pass + + +class CurrencyName(IsName, HasCurrency, HasLanguage): + pass + + class ItemPocket(HasName): pass @@ -883,7 +904,7 @@ class ItemGameIndex(HasItem, HasGeneration, HasGameIndex): pass -class ItemPrice(HasItem, HasVersionGroup): +class ItemPrice(HasItem, HasVersionGroup, HasCurrency): purchase_price = models.IntegerField(blank=True, null=True) sell_price = models.IntegerField(blank=True, null=True) diff --git a/pokemon_v2/serializers.py b/pokemon_v2/serializers.py index 2535d0d33..6e9373114 100644 --- a/pokemon_v2/serializers.py +++ b/pokemon_v2/serializers.py @@ -116,6 +116,12 @@ class Meta: fields = ("name", "url") +class CurrencySummarySerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Currency + fields = ("name", "url") + + class ItemPocketSummarySerializer(serializers.HyperlinkedModelSerializer): class Meta: model = ItemPocket @@ -1755,6 +1761,27 @@ def get_attribute_items(self, obj): return items +########################### +# CURRENCY SERIALIZERS # +########################### + + +class CurrencyNameSerializer(serializers.ModelSerializer): + language = LanguageSummarySerializer() + + class Meta: + model = CurrencyName + fields = ("name", "language") + + +class CurrencyDetailSerializer(serializers.ModelSerializer): + names = CurrencyNameSerializer(many=True, read_only=True, source="currencyname") + + class Meta: + model = Currency + fields = ("id", "name", "names") + + ################################### # ITEM FLING EFFECT SERIALIZERS # ################################### @@ -1807,11 +1834,17 @@ class Meta: class ItemPriceSerializer(serializers.ModelSerializer): + currency = CurrencySummarySerializer() version_group = VersionGroupSummarySerializer() class Meta: model = ItemPrice - fields = ("purchase_price", "sell_price", "version_group") + fields = ( + "purchase_price", + "sell_price", + "currency", + "version_group", + ) class ItemNameSerializer(serializers.ModelSerializer): diff --git a/pokemon_v2/urls.py b/pokemon_v2/urls.py index fdd268419..08587665d 100644 --- a/pokemon_v2/urls.py +++ b/pokemon_v2/urls.py @@ -34,6 +34,7 @@ router.register(r"item-attribute", ItemAttributeResource) router.register(r"item-fling-effect", ItemFlingEffectResource) router.register(r"item-pocket", ItemPocketResource) +router.register(r"currency", CurrencyResource) router.register(r"language", LanguageResource) router.register(r"location", LocationResource) router.register(r"location-area", LocationAreaResource) From c22b2b15b6a41a5e5fd1bc4a5b65b0f3416086ad Mon Sep 17 00:00:00 2001 From: Anh Thang Bui Date: Sun, 15 Mar 2026 11:42:26 +0700 Subject: [PATCH 12/12] add currency to scrape script --- scripts/scraping_pokeball_price.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts/scraping_pokeball_price.py b/scripts/scraping_pokeball_price.py index 514e5fed7..7cbd7c0b2 100644 --- a/scripts/scraping_pokeball_price.py +++ b/scripts/scraping_pokeball_price.py @@ -5,6 +5,13 @@ import re from pathlib import Path +CURRENCY_MAP = { + "poke-dollar": 1, + # "battle-point": 6, + # "league-point": 18, + # "blueberry-point": 19, +} + # 1. Object Map for Item IDs (from PokeAPI's items.csv) ITEM_MAP = { "master-ball": 1, @@ -183,6 +190,7 @@ def main(): # "name": b["name"], # enable this if you wanna keep the name in the output for easier debugging "item_id": b["item_id"], "version_group_id": p["vg_id"], + "currency_id": CURRENCY_MAP["poke-dollar"], "purchase_price": p["buy"], "sell_price": p["sell"], } @@ -213,15 +221,16 @@ def main(): # Deduplicate and Sort df_final = df_combined.drop_duplicates( - subset=["item_id", "version_group_id"], keep="last" + subset=["item_id", "version_group_id", "currency_id"], keep="last" ) - df_final = df_final.sort_values(by=["item_id", "version_group_id"]) + df_final = df_final.sort_values(by=["item_id", "version_group_id", "currency_id"]) df_final["purchase_price"] = df_final["purchase_price"].astype("Int64") df_final["sell_price"] = df_final["sell_price"].astype("Int64") df_final["item_id"] = df_final["item_id"].astype(int) df_final["version_group_id"] = df_final["version_group_id"].astype(int) + df_final["currency_id"] = df_final["currency_id"].astype(int) df_final.to_csv(output_path, index=False) print(f"\nSuccess! File sorted and updated at: {output_path}")