From 0cf2f0d0c4d35c283c05509272da7467322acb22 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 30 Apr 2026 10:40:15 +0200 Subject: [PATCH 1/3] feat(planet): include production fees in response --- backend/gamedata/api/serializer.py | 31 ++++++++++++++++++++++++++ backend/gamedata/models/game_planet.py | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/backend/gamedata/api/serializer.py b/backend/gamedata/api/serializer.py index cf918db..e777ee9 100644 --- a/backend/gamedata/api/serializer.py +++ b/backend/gamedata/api/serializer.py @@ -17,6 +17,9 @@ ) from rest_framework import serializers +WORKFORCE_ORDER = ['PIONEER', 'SETTLER', 'TECHNICIAN', 'ENGINEER', 'SCIENTIST'] +WF_INDEX_MAP = {level: i for i, level in enumerate(WORKFORCE_ORDER)} + class GameMaterialSerializer(serializers.ModelSerializer): class Meta: @@ -100,6 +103,8 @@ class GamePlanetSerializer(serializers.ModelSerializer): active_cogc_program_type = serializers.CharField(read_only=True) + production_fees = serializers.SerializerMethodField() + class Meta: model = GamePlanet fields = [ @@ -123,8 +128,34 @@ class Meta: 'resources', 'cogc_programs', 'active_cogc_program_type', + 'production_fees', ] + def get_production_fees(self, obj): + + # prefetched + fees = obj.production_fees.all() + if not fees: + return None + + currency = fees[0].fee_currency + + fee_map = {} + for fee in fees: + cat = fee.category + if cat not in fee_map: + # init with 0.0 for all workforces + fee_map[cat] = [0.0] * len(WORKFORCE_ORDER) + + try: + # overwrite with the values from data + idx = WF_INDEX_MAP[fee.workforce_level] + fee_map[cat][idx] = fee.fee_amount + except KeyError: + continue + + return {'currency': currency, 'fees': fee_map} + class PlanetIdsSerializer(serializers.ListSerializer): child = serializers.CharField(min_length=7, max_length=7) diff --git a/backend/gamedata/models/game_planet.py b/backend/gamedata/models/game_planet.py index 0d0949f..c9d70b6 100644 --- a/backend/gamedata/models/game_planet.py +++ b/backend/gamedata/models/game_planet.py @@ -27,7 +27,7 @@ def queryset_gameplanet() -> QuerySet: .values('program_type')[:1] ) - return GamePlanet.objects.prefetch_related('cogc_programs', 'resources').annotate( + return GamePlanet.objects.prefetch_related('cogc_programs', 'resources', 'production_fees').annotate( active_cogc_program_type=Subquery(active_program_sub) ) From 66ae6babf2e372e12cfb72f4f6f7fdc39734ff34 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 30 Apr 2026 14:05:12 +0200 Subject: [PATCH 2/3] improve(drf): schema hints --- backend/analytics/api/serializer.py | 11 +++++++++++ backend/analytics/api/viewsets.py | 18 +++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/backend/analytics/api/serializer.py b/backend/analytics/api/serializer.py index 2ae8289..bd27a30 100644 --- a/backend/analytics/api/serializer.py +++ b/backend/analytics/api/serializer.py @@ -11,3 +11,14 @@ class Meta: def get_status(self, obj) -> str: return 'success' + + +class AnalyticsMarketInsightSerializer(serializers.Serializer): + data = serializers.ListField( + child=serializers.ListField( + child=serializers.CharField(), + min_length=1, + max_length=3, + help_text='Indices: 0: ticker, 1: production, 2: consumption, 3: delta', + ) + ) diff --git a/backend/analytics/api/viewsets.py b/backend/analytics/api/viewsets.py index 6a64d6e..aa8ffb2 100644 --- a/backend/analytics/api/viewsets.py +++ b/backend/analytics/api/viewsets.py @@ -1,12 +1,12 @@ from datetime import timedelta -from analytics.api.serializer import AnalyticsPlanAggregateSerializer +from analytics.api.serializer import AnalyticsMarketInsightSerializer, AnalyticsPlanAggregateSerializer from analytics.models import AnalyticsEmpireMaterialSnapshot, AnalyticsPlanAggregate from analytics.services.analytics_cache_manager import AnalyticsCacheManager from django.db.models import Sum from django.http import Http404 from django.utils import timezone -from drf_spectacular.utils import extend_schema +from drf_spectacular.utils import OpenApiExample, extend_schema from gamedata.models.game_planet import GamePlanet from rest_framework import viewsets from rest_framework.decorators import action @@ -45,7 +45,19 @@ def fetch_data(planet_natural_id: str): class AnalyticsMarketInsightViewSet(viewsets.ViewSet): - @extend_schema(auth=[], summary='Fetch planning insights for materials') + @extend_schema( + auth=[], + summary='Fetch planning insights for materials', + responses={200: AnalyticsMarketInsightSerializer}, + examples=[ + OpenApiExample( + 'Material Insights Example', + summary='Example for positional array response', + value=[['AAR', 30.3584, 12.8593, 17.4991], ['ABH', 120.7919, 0.0, 120.7919]], + response_only=True, + ) + ], + ) @action(detail=False, methods=['get'], url_path='get-global-tracker') def get_global_materials(self, request): From 05cfcce6fc8eca11ea6fc1c50d67a0b426bf78b7 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 30 Apr 2026 14:05:38 +0200 Subject: [PATCH 3/3] improve(drf): recipe + planet schema hints --- backend/gamedata/api/serializer.py | 28 ++++++++++++++++++++++++++++ backend/gamedata/models/__init__.py | 3 ++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/backend/gamedata/api/serializer.py b/backend/gamedata/api/serializer.py index e777ee9..dde7073 100644 --- a/backend/gamedata/api/serializer.py +++ b/backend/gamedata/api/serializer.py @@ -3,12 +3,14 @@ from gamedata.models import ( GameBuilding, GameBuildingCost, + GameBuildingExpertiseChoices, GameExchangeAnalytics, GameExchangeCXPC, GameMaterial, GamePlanet, GamePlanetCOGCProgram, GamePlanetCOGCProgramChoices, + GamePlanetCurrencyCodeChoices, GamePlanetInfrastructureReport, GamePlanetResource, GameRecipe, @@ -49,6 +51,11 @@ class Meta: model = GameRecipe exclude = ['standard_recipe_name'] + @extend_schema_field( + serializers.CharField( + help_text="Composite ID formatted as 'BUILDING_TICKER#RECIPE_NAME'. Example: 'BMP#100xPE 25xPG=>20xOVE'" + ) + ) def get_recipe_id(self, obj: GameRecipe): return f'{obj.building_ticker}#{obj.recipe_name}' @@ -131,6 +138,27 @@ class Meta: 'production_fees', ] + @extend_schema_field( + inline_serializer( + name='PlanetProductionFeeSchema', + fields={ + 'currency': serializers.ChoiceField(choices=GamePlanetCurrencyCodeChoices.choices), + 'fees': serializers.DictField( + child=serializers.ListField( + child=serializers.FloatField(), + min_length=5, + max_length=5, + help_text='[PIONEER, SETTLER, TECHNICIAN, ENGINEER, SCIENTIST]', + ), + help_text=( + 'Dictionary keys are Building Categories (GameBuildingExpertiseChoices). ' + 'Valid keys include: ' + + ', '.join([choice[0] for choice in GameBuildingExpertiseChoices.choices]) + ), + ), + }, + ) + ) def get_production_fees(self, obj): # prefetched diff --git a/backend/gamedata/models/__init__.py b/backend/gamedata/models/__init__.py index 3f53247..f291773 100644 --- a/backend/gamedata/models/__init__.py +++ b/backend/gamedata/models/__init__.py @@ -1,4 +1,4 @@ -from .game_building import GameBuilding, GameBuildingCost +from .game_building import GameBuilding, GameBuildingCost, GameBuildingExpertiseChoices from .game_exchange import GameExchange, GameExchangeAnalytics, GameExchangeCXPC from .game_material import GameMaterial from .game_planet import ( @@ -11,6 +11,7 @@ GamePlanetProductionFee, GamePlanetResource, GamePlanetResourceTypeChoices, + GamePlanetCurrencyCodeChoices, queryset_gameplanet, ) from .game_playerdata import GameFIOPlayerData